아무도 테스트하지 않는 충돌 상황: n8n에서 '정확히 한 번'의 AI 워크플로우를 구축하며 배운 것
요약
본 글은 n8n 워크플로우의 실행 완료 여부와 외부 부작용(side effect) 발생 횟수 간의 괴리를 지적합니다. 단순히 '실행됨'을 넘어, 이메일 발송이나 청구 같은 외부 액션이 정확히 한 번만 발생하는지 검증하는 것이 중요하며, 이를 위해 충돌 상황 테스트 전략과 데이터 영속화 방식을 개선해야 함을 강조합니다.
핵심 포인트
- n8n은 실행 완료 여부만 알려줄 뿐, 외부 부작용 발생 횟수는 알 수 없다.
- 테스트는 모두가 실행하는 경로(happy path)에 국한되므로, 충돌 상황 테스트가 필수적이다.
- 지연 작업 데이터는 n8n 자체에 의존하지 않고 사용자 제어 저장소에 영속화해야 한다.
- 작업의 안정성을 위해 독립적인 작업 행을 정의하고 원자적으로 클레임하는 방식을 사용해야 한다.
이 글은 저의 build-in-public 시리즈 중 일부입니다. 저는 작은 n8n 워크플로우 템플릿 스토어(AI Automation Lab)와 GitHub에서 무료 오픈 소스 연구소를 배포하고 있습니다.
저는 예전에 n8n 워크플로우가 실행 목록이 초록색이면 신뢰할 수 있다고 믿었습니다. 그러다가 결제 알림 워크플로우가 같은 이메일을 두 번 보내는 바람에, 녹색 체크표시들은 여전히 녹색이었습니다.
n8n은 워크플로우가 실행되었다고 알려줄 뿐입니다. 외부 부작용(side effect)이 정확히 한 번 발생했는지 여부는 알려주지 못합니다.
초록색은 실행이 완료되었음을 의미할 뿐, 이메일, 청구, 행 등 외부 액션이 0번, 1번, 또는 2번 발생했는지에 대해서는 아무것도 말해주지 않습니다. 이러한 간극을 발견하면, '정확히 한 번(exactly-once)'은 단순히 체크박스를 뒤집는 것이 아니라 테스트해야 하는 경계가 됩니다.
제가 실제로 배운 것은 바로 이것이며, 대부분 이 경계를 어렵게 테스트하면서 알게 되었습니다.
두 가지 충돌 형태 중 n8n 내부에서 도달 가능한 것은 하나뿐
두 번째 요점이 이 글 전체를 한 문장으로 요약합니다. 모두가 실행하는 테스트는 클래스 1만 작동시키기 때문에, 실제로 이메일을 두 배로 만드는 버그인 클래스 2는 절대 테스트되지 않습니다.
아무도 실행하지 않는 충돌 테스트: n8n과 제공자 사이에 릴레이를 배치하기
n8n을 종료하고 그 종료가 올바른 시점에 발생하길 바라는 대신, 실패 자체를 스위치로 만드세요. 제공자 앞에 작은 로컬 HTTP 엔드포인트를 두거나 (여전히 수락을 기록하는 목업 사용), 헤더나 환경 변수를 사용하여 동작을 선택하세요:
- fail-before-forward: 제공자가 이를 전혀 보지 못합니다. 이는 재시도해도 안전해야 합니다.
- forward-then-drop-response: 제공자는 커밋하지만, 릴레이가 응답 없이 멈추거나 연결을 끊습니다. 이것이 모호한 케이스입니다. 여기서 스위퍼(sweeper)를 실행하세요.
- forward-and-respond: 정상적인 경로(happy path)입니다. 반드시
done으로 끝나야 하며, 스위퍼는 이를 건드려서는 안 됩니다.
forward-then-drop-response가
일단 실행이 중단된 경우 안전하게 '재개(resume)'할 수 없다는 점을 받아들이면, 설계가 바뀝니다. 실행을 재개하려고 시도하는 대신, 작업을 복구해야 합니다.
n8n의 실행 상태는 영속적인 큐(durable queue)가 아닙니다. 재시작 후에는 원래의 실행 기록이 사라지며, Wait 노드가 보유했던 모든 데이터는 대기 시간이 짧을 때만 안전합니다 (65초를 초과하는 대기는 DB로 오프로드되어 _대기 상태_가 재시작에도 살아남는다는 것을 알려줄 뿐, 충돌 전에 이미 커밋된 사이드 이펙트(side effect)에 대해서는 아무것도 말해주지 않습니다). Wait을 재개하는 것은 외부 상태의 증거가 아닙니다.
따라서 지연되는 작업은 사용자가 제어하는 저장소(Postgres가 기본이며, 코딩 없이 처리해야 하는 경우에만 Sheets/Airtable)에 보관하고, n8n 자체를 순수한 워커(worker)로 취급해야 합니다:
-
독립적인 작업 행을 영속화합니다. _소스 이벤트_에서 파생된 안정적인 중복 제거 키(dedup key) (절대
$execution.id,$now, 또는 새로운 uuid가 아닙니다), 그리고state,payload,attempts,claimed_at,updated_at,last_error,provider_ref를 포함합니다. -
확인 후 삽입(check-then-insert)이 아닌, 원자적으로 클레임(Claim atomically)합니다. 고전적인 버그는 조회(lookup) 후에 삽입하는 것입니다: 두 개의 동시 웹훅 전송이 모두
-
재시작이 아닌 스케줄에 따라 복구하세요.
state='claimed' AND claimed_at < now() - interval '10 minutes'필터링을 사용하는 스위퍼(sweeper)가 실제로 충돌 후 정리하는 역할을 합니다. -
상태는 두 개가 아닌 세 개를 유지하세요:
pending/done/review.pending상태는 단명해야 하며, 오래 지속되는pending상태야말로 사람이 개입하여 확인(page)해야 할 정확한 신호입니다.
Idempotency는 페이로드의 속성이 아니라 수신 시스템의 속성
메타데이터에 포함된 핵심 키는 **정산 핸들(reconciliation handle)**이지, 일회성 보장(idempotency guarantee)이 아닙니다. 일회성은 수신 시스템의 속성입니다. 즉, 페이로드는 수신자에게 어떤 논리적 작업인지 알려줄 뿐입니다. 이 때문에 제공업체는 두 가지 클래스로 나뉘며, 각각에 대해 다르게 설계해야 합니다:
- 서버 측에서 고유성을 강제하는 제공업체 (Stripe의
Idempotency-Key, DB의 고유 제약 조건 /ON CONFLICT, 클라이언트가 제공한 참조를 사용하는 주문 API). 이 경우, 무작정 재시도해도 안전하며 실제로 효과에 대해 정확히 한 번만 실행됨을 보장받습니다. - 키가 권고 사항인 제공업체 (SMTP, 일반 웹훅, 대부분의 추가(append) 스타일 API). 아무것도 두 번째 실행을 막지 못하므로, 호출자 측에서 정확히 한 번만 실행되는 것은 달성할 수 없습니다. 여기서 정직한 목표는 적어도 한 번 이상 실행되면서 가시성을 확보하는 것입니다.
클래스 2에 대한 불편한 진실은 이렇습니다. 워크플로우는
• 키 강제 제공자 (Key-enforced provider): 동일한 키로 재발행하거나 참조를 통해 조회합니다. 찾으면 done으로 표시하고, 찾지 못하면 재시도가 안전합니다. 이것이 유일하게 진정한 '정확히 한 번(exactly-once)' 복구 경로입니다.
• 키 권고 제공자 (Key-advisory provider): 제출되었으나 아직 확정되지 않은 것과 아예 제출되지 않은 것을 구분할 수 없습니다. 무작정 재전송해서는 안 됩니다. 해당 행을 review로 이동시키고 알림을 보내야 합니다. 사람, 또는 두 번째 워크플로우가 결정해야 합니다.
리뷰 기록의 경우, 부수 효과(side effect) 전에 마커를 작성하고 조정 핸들(reconciliation handle)과 실행 ID를 함께 전달하여, 사람이 추측하는 대신 제공자 측에서 조회할 수 있도록 해야 합니다. 여기서 조용한 재전송은 애초에 이중 이메일이 생성되는 방식입니다.
실제로 충돌 창 버그를 찾아내는 테스트
모든 녹색 체크 표시보다 두 가지 테스트가 더 가치가 있습니다:
- 동일한 배달을 동시에 두 번 실행하고, 정확히 하나의 행만 삽입되고 정확히 하나의 부수 효과가 발생하는지 단언합니다. 이것이 원자적(atomic) 클레임을 테스트합니다.
- 부수 효과와
mark-done사이에 실패를 주입합니다 (Stop 또는 throw 노드 사용). 그런 다음 스위퍼(sweeper)를 실행하고, 재전송되는 대신review에 기록되는지 단언합니다.
두 번째 테스트가 실제로 충돌 창 버그를 찾아내는 테스트이며, 거의 아무도 실행하지 않는 테스트입니다.
다음 워크플로우 구축 전에 제가 결정할 것
노드를 하나 작성하기 전에, 전송 주기(send cycle)에 대해 하나의 질문에 답하십시오: 제공자가 서버 측에서 고유성을 강제하는 곳인가요, 아니면 키가 단지 힌트에 불과한가요? 이 단 하나의 사실이 여러분의 복구가
이 내용이 유용했다면, 무료 실습 환경(workflows, MIT)은 여기입니다: github.com/zhp910318/n8n-ai-automation-lab - 그리고 저는 프로덕션 테스트를 거친 n8n 템플릿 몇 가지 세트를 AI Automation Lab에 보관하고 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기