Workflow, Node, Connection, Execution: 네 단어로 n8n의 세계를 명확히 설명
캔버스 위에는 다섯 개의 블록과 네 개의 선이 있다. 이걸 JSON으로 내보내면 두 개의 키만 남는다:nodes 和 connections. 실행 기록은 다른 곳에 있으며, 하나씩 나열되어 있고 각 항목에는 번호가 붙어 있다.
세 곳이 말하는 것은 같은一件事이며, 네 개의 단어로 표현된다: workflow, node, connection, execution. 이 네 단어를 정리하면 이후 어떤 노드 튜토리얼을 읽든 수월해지고, 정리하지 못하면 계속 「나는 분명히 실행을 눌렀는데」와 「왜 안 돌아간 거지」 사이에서 부딪히게 될 것이다.
코드를 작성할 줄 몰라도 된다. 설치된 n8n을 열 수 있고 워크플로를 한 번 실행해 본 적이 있으면 충분하다 (설치하지 않았다면이 글을 보고, 인터페이스가 아직 익숙하지 않다면 먼저인터페이스 가이드를 참고하라). 내 것은 2.43.7이고,你那边的 minor version이 다르면 버튼 위치가 약간 다를 수 있다.
- 네 개의 단어, 네 가지 것
- Workflow: 하나의 컨테이너, 그리고 두 개의 상태
- Node: 이름이 곧 기본 키
- Connection: 두 계층의 배열, 첫 번째 계층은 출구 번호
- Execution: 한 번의 실행, 스냅샷 하나를 동반
- 연결해서 보기: 한 번의 트러블슈팅 순서
- 진짜 이해했는지 판단하는 방법
- 실제로 부딪히는 다섯 가지 오류
- 내보내기, 공유, 기록이 남는 리스크
네 개의 단어, 네 가지 것
먼저 대조표를 하나 제시하고, 뒤의 몇 절에서 모두 그것을 풀어 설명한다.
| 词 | 그것은 무엇인가 | 어디에 존재하는가 | 수량 관계 |
|---|---|---|---|
| Workflow | 한 그룹의 노드가 연결되어 한 가지 일을 처리한다 | 데이터베이스의 워크플로 테이블, 하나의 JSON | 1개의 워크플로에 N개의 노드가 있다 |
| Node | 하나의 실행 단위, 한 단계의 작업을 처리한다 | 워크플로 JSON의 nodes 배열 안에 |
1개의 노드는 1개의 워크플로에 속한다 |
| Connection | 이전 노드의 출력을 다음 노드의 입력에 연결한다 | 같은 JSON의 connections 객체 안에서 |
1개의 워크플로에는 N개의 연결선이 있다 |
| Execution | 이 워크플로가 한 번 실행되었다 | 실행 테이블, 워크플로와 독립적이다 | 1개의 워크플로에는 N개의 실행이 있다 |
가장 혼동하기 쉬운 것은 마지막 줄이다. 워크플로는 도면이고, 실행은 시공이다. 도면을 열 번 고쳐도 이전에 시공한 것은 따라 변하지 않는다.

Workflow: 하나의 컨테이너, 그리고 두 개의 상태
공식 정의는 매우 짧다: 연결되어 자동으로 무언가를 완료하는 노드의 집합; 트리거가 울리거나, 직접 클릭하거나, 다른 프로세스가 호출하면 실행을 시작하고, 노드별로 하나씩 실행하며, 분기, 병합, 반복이 가능하다.
정의에는 문제될 것이 없다. 진짜 주의해야 할 것은 2.x 이후에 추가된 그 상태다:초안과 게시는 별개의 문제다。
캔버스에서 뭔가를 수정하면 n8n이 자체적으로 저장하며, 1초에서 5초 이내에 DB에 기록되고 저장 버튼은 없다. 하지만 이것은 초안에 저장되는 것일 뿐이다. 변경 사항을 실제로 반영하려면 오른쪽 상단의 「게시」를 눌러 특정 버전을 프로덕션에 고정해야 한다. 이후 타이머, Webhook, 앱 이벤트가 실행하는 것은 고정된 그 버전이지, 현재 화면에 보이는 버전이 아니다.
이 규칙은 오래된 문제(수정 도중 스케줄 작업이 충돌하는 것)를 해결했지만, 새로운 함정도 만들었다: 파라미터를 수정하고 게시를 잊으면, 온라인에서는 여전히 어제의 로직이 돌아가는데 캔버스에서는 모든 것이 정상으로 보인다. 처음 겪었을 때 두 버전의 파라미터를 10분 동안 비교하다가, 그제서야 위쪽 버튼이 아직 켜져 있다는 것을 떠올렸다.

이것은 왜 어떤 사람들의 첫 워크플로가 도저히 「활성화」되지 않는지도 설명해준다. 수동 트리거만 있으면 그것은 태생적으로 프로덕션 형태가 없고, 게시 버튼은 영원히 회색이다. 스케줄링하려면 Schedule Trigger를 추가하고, 외부 요청을 받으려면 Webhook을 추가하면 된다. 이런 트리거가 있어야 버튼이 켜진다.
인터페이스에서 보면 더 명확하다: 내 이 인스턴스에서 새로 만든 워크플로는 active 是 false,activeVersionId 是 null, 단 하나만 versionId 이 매번 변경을 따라간다. 게시한 적이 없으면 온라인 버전도 없다.
Node: 이름이 곧 기본 키다
노드는 하나의 실행 단위다. 어떤 것은 시작을 담당하고(트리거), 어떤 것은 데이터를 가져오고, 보내고, 계산하며, 어떤 것은 흐름 제어만 하고, 한 줄 IF 도 하나의 노드가 될 수 있다.
JSON으로 내려가면, 하나의 노드는 일곱 가지로 구성된다:
{
"parameters": { "jsCode": "return [{ json: { amount: 620 } }];" },
"id": "n2",
"name": "造两条订单",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [-400, 0]
}
type: 어떤 종류의 노드인지.n8n-nodes-base.code는 Code 노드이고,-base.뒤쪽 부분은 노드 이름의 소문자다.typeVersion: 노드 정의의 버전. 같은 노드라도 버전이 다르면 파라미터 구조가 달라진다. 내这份에서 IF는2.2, Edit Fields는3.5, Code는2, Manual Trigger는1. 이 숫자는 네가 채운 게 아니라 생성 시점에 당시 정의에 따라 기록된 것이다.parameters: 오른쪽 패널에서 입력한 모든 것이 여기에 있다. 노드 유형을 바꾸면 이 블록 전체가 교체된다.position: 캔버스 좌표, 순수 표시용이며 실행 순서와 무관하다.id: 워크플로 내부에서 쓰는 짧은 식별자, 노드를 복사하면 새로 생성된다.
name 가장 눈에 안 띄지만 실제로는 이 네 단어 중 가장 손해 보기 쉬운 부분:이것이 기본 키다. 연결은 id가 아니라 이름을 기록한다.
캔버스에서 「小单归档」을 「小单归档(改)」로 바꿨더니 네 개 연결선이 하나도 끊기지 않았고, 저장 후 인터페이스의 connections 도 새 이름으로 바뀌었다 — 이건 UI가 대신 보정해 준 것이다. 다른 방식으로 JSON의 name만 직접 바꾸고 연결은 건드리지 않으면, 서버가 즉시 거부하며 오류를 한 번에 두 개나 뱉는다:
Workflow structure is invalid. nodes[4].name (duplicate_node_name):
Duplicate node name "大单跟进"; connections.判断大单.main[1][0].node
(unknown_connection_target): Connection target "小单归档" does not
reference an existing node
첫 문장은 중복 이름을, 두 번째 문장은 어떤 연결선이 존재하지 않는 노드를 가리킨다고 말한다. 두 문장은 사실 같은 사건의 양면이다: 네가 한 노드를 다른 노드의 이름으로 바꿨고, 밀려난 그 이름을 연결이 여전히 가리키고 있는 것이다.
그래서 노드 이름은 변수명처럼 짓는 걸 권한다: 「Set」「Code 1」 같은 걸 쓰지 말고, 이틀에 한 번씩 바꾸지도 마라. 이름에 공백, 괄호, 중국어가 들어가도 n8n은 인식하지만, 네가 표현식에서 쓸 $('小单归档(改)') 때 괄호 하나를 빠뜨리기 쉽다.
Connection: 2층 배열이고, 첫 번째 층은 출구 번호
연결은 한 노드의 출력을 다른 노드의 입력에 연결하는 것이다. JSON에서는 이렇게 생겼다:
"connections": {
"造两条订单": { "main": [[{ "node": "判断大单", "type": "main", "index": 0 }]] },
"判断大单": { "main": [
[{ "node": "大单跟进", "type": "main", "index": 0 }],
[{ "node": "小单归档", "type": "main", "index": 0 }]
]}
}
풀어서 읽으면: 가장 바깥쪽 키는소스 노드 이름;main 은 연결 타입이다 (일반 데이터 흐름; AI 쪽은 ai_tool、ai_languageModel 이런 것들); 안쪽 배열의인덱스가 몇 번째 출구인지이다; 그 안쪽이 대상 목록이고, 하나의 출구가 여러 다운스트림에 연결될 수 있다.
큰 건 판단에 두 그룹이 있으므로 [[...],[...]]이다. 첫 번째 그룹은 노드에서 true 표시된 원점에 대응하고, 두 번째 그룹은 false에 대응한다. 대충 보면 배열이 과하게 중첩된 것 같지만, 사실은 「몇 번째 출구 → 누구에게 연결」이다.

여기에는 개념적인 오해가 하나 있어서 짚고 넘어갈 만하다. 많은 사람들이 IF를 '둘 중 하나'로 생각한다: 조건이 성립하면 이쪽으로, 성립하지 않으면 저쪽으로, 한 갈래만 간다고.
아니다. IF는항목별로 분기한다. 내가 두 개의 주문 620과 180을 받도록 했더니, 결과는 true 분기 1건, false 분기 1건으로 두 갈래 모두 켜졌다. 100개의 데이터를 넣으면 각자의 금액에 따라 양쪽으로 나뉘어, 왼쪽 30건 오른쪽 70건일 수도 있고 한쪽이 0건일 수도 있다.
따라서 "IF 뒤의 노드가 실행되지 않았다"는 말은, 먼저 그쪽에 데이터가 아예 없었던 건 아닌지 확인해야 한다. 이건 고장이 아니다.
Execution: 한 번의 실행, 하나의 snapshot을 동반
실행은 이 워크플로가 한 번 돌아간 것이다. 수동으로 한 번 클릭하면 한 번, 타이머가 한 번 울리면 한 번, Webhook에 요청이 하나 오면 한 번이다. 이는 워크플로 JSON에 속하지 않고 다른 테이블에 저장되며, workflowId 로 다시 연결된다.
하나의 실행 기록에서 내가 관심 있는 필드:
| 필드 | 이 기록에서의 값 | 용도 |
|---|---|---|
id |
35 | 전체 인스턴스 기준 하나의 자동 증가 시퀀스이며, 워크플로마다 1부터 시작하는 것이 아니다 |
workflowId |
9biNEw9D0DqWOVV1 | 어느 워크플로에 속하는지 |
mode |
manual | 수동으로 실행한 건지 프로덕션에서 실행한 건지 |
status |
success | 성공, 실패, 취소, 크래시, 대기 중 |
startedAt / stoppedAt |
01:17:42.352 / 01:17:42.513 | 161밀리초 차이, 바로 인터페이스에 있는 「161밀리초 이내」 |
workflowVersionId |
uuid 하나 | 이번에 실행된 건 어느 버전인지 |
마지막 줄은 놓치기 쉽지만 매우 유용하다: 실행은 버전을 기준으로 인식한다. 파라미터를 수정하고 발행하지 않으면 프로덕션에서는 여전히 이전 버전이 실행되고, 실행 기록에는 이전 버전의 id가 걸린다.

이번에야 비로소注意到한 세부 사항이 하나 더 있다: 각 실행에는 당시 워크플로의 snapshot이 하나 저장된다(workflowData). 「소액 주문 아카이브」를 「소액 주문 아카이브(수정)」로 바꾼 뒤, 돌아가서 09:17:42 실행을 열어보면 캔버스에 표시되는 것은 여전히 「소액 주문 아카이브」다 — 렌더링에 사용되는 것은 해당 실행에 내장된 snapshot이지, 현재의 캔버스가 아니기 때문이다.
이것은 두 가지를 설명한다. 좋은 면: 파라미터를 바꾸거나 노드를 삭제해도 이전 기록은 훼손되지 않으며, 돌아가서 문제를 확인할 때 보이는 것은 당시의 현장 그대로다. 나쁜 면: 이전 기록을 현재 캔버스와 대조하면 노드 이름이 맞지 않을 수 있으니, 자신이 잘못 기억했다고 생각하지 마라.

얼마나 오래 보관할지는 현실적인 문제다. 기본적으로 자동 정리가 켜져 있다: 336시간(14일)을 초과한 것은 삭제되고, 총량이 10000건을 초과하면 오래된 것부터 삭제된다. 성공과 실패 모두 저장되고, 수동 실행도 저장된다. 두 규칙 중 먼저 닿는 쪽이 적용된다: 5분마다 한 번 실행되는 워크플로는 하루 288회, 14일이면 4032건이 쌓여 나이 조건에 먼저 닿는다; 하루 2000회인 것은第五天에 만 건이 차서 건수 조건에 먼저 닿는다. 따라서 「실행 목록에 그 실행이 없다」는 것이 반드시 실행되지 않았다는 뜻은 아니며, 이미 정리되었을 수 있다.
종합해서 보면: 트러블슈팅 순서
네 단어를 따로 떼어 놓으면 다 이해가 되지만, 실제로 쓸 때는 순서가 필요하다. 내 습관은 바깥에서 안으로 좁혀가는 것이다:
- 먼저 실행 기록의 상태를 본다. 기록이 없으면 애초에 트리거되지 않았다는 뜻이므로, 문제는 트리거나 게시 상태에 있지 노드 안에 있지 않다.
- 상태가 실패라면 들어가서 어느 노드가 빨갛게 표시되는지 본다. 노드 상세에서 입력을 보면, 대부분의 경우 입력 자체가 이미 잘못되어 있다.
- 상태는 성공인데 결과가 잘못되었다면 연결선 위의 항목 수를 본다. 어떤 선이 0이라면 뒤의 노드는 당연히 아무것도 하지 않는다.
- 숫자는 맞는데 필드가 틀렸다면, 그제서야 노드 파라미터를 의심하거나 업스트림에서 타입을 바꿨는지 본다.
이 순서의 장점은 각 단계에서 가능성의 큰 덩어리를 걸러낼 수 있다는 것이다. 반대로 처음부터 Code 노드의 코드만 들여다보면, 보통 30분을 보다가 업스트림이 0건만 보냈다는 걸 발견하게 된다.
진짜로 이해했다는 기준
정의를 외울 필요 없이 세 가지만 하면 되고, 각각 관찰 가능한 결과가 있다.
첫째, 세어 보기.캔버스에 사각형이 몇 개 있는지, 내보낸 JSON의 nodes 배열에도 그만큼 있어야 한다. 내 것은 5개이고 배열 길이는 5다. 연결선 수량도 마찬가지로, 4개의 선은 connections 안의 4개 소스 키에 대응한다(대형 주문 판단 하나의 키에 두 그룹이 들어 있지만 여전히 4개의 선이다).
둘째, 선 하나 바꾸기.false 쪽의 노드를 삭제하고 다시 내보내면,判断大单 의 두 번째 그룹 배열은 빈 배열이 되어야지, 그룹 전체가 사라지면 안 된다. 이 단계를 통해 "출구는 위치로 결정되지, 다운스트림이 있느냐로 결정되지 않는다"는 것을 기억하게 된다.
3. 두 번 실행한다.실행 후 「모든 실행」에 가면 한 건이 더 있어야 하고, ID는 이전 건보다 1 크며, 시간은 당신이 클릭한 그 순간이다. 다시 파라미터 하나를 바꾸고 발행하지 않은 채 한 번 실행한 다음, 온라인(스케줄 또는 Webhook)에서 여전히 예전 파라미터를 쓰는지 확인한다——이것이 당신의 실제 업무라면 이 단계는 테스트 워크플로에서 하는 것을 권장한다.
실제로 부딪히는 다섯 가지 오류
1. 두 노드의 이름이 같다.노드를 복사한 후 이름 바꾸는 것을 잊었거나, JSON을 수동으로 수정했다. 저장할 때 서버가 거부하며 다음과 같이 보고한다 Duplicate node name, 그리고 동시에 한 줄을 던진다 unknown_connection_target. 두 개를 보고 당황하지 말고, 고유한 이름으로 되돌리면 된다.
2. 노드 이름을 바꿨는데 표현식이 따라 바뀌지 않았다.연결은 인터페이스가 자동으로 보완하지만, 표현식은 그렇지 않다. 이전 노드 이름이 A였고, 다운스트림에서 $('A').item.json.amount 로 데이터를 가져왔다면, 이름을 B로 바꾼 후 이 문장은 아무것도 찾지 못하며, 오류는 이름 변경이 아니라 표현식 그 줄을 가리킨다.
3. 발행 버튼이 회색이다.워크플로에 수동 트리거만 있고 온라인에 올릴 수 있는 것이 없다. 마우스를 올리면 「이 워크플로에는 발행할 트리거 노드가 없습니다」라고 표시된다. Schedule Trigger나 Webhook을 추가해야 발행할 수 있다.
4. 파라미터를 바꾼 후에도 스케줄 작업이 예전 로직으로 실행된다.초안을 수정했지만 발행을 클릭하지 않았다. 이런 문제는 가장 찾기 어렵다. 캔버스, 파라미터, 테스트 실행이 모두 정상이고 온라인 동작만 잘못되었기 때문이다.
5. 실행 기록을 찾을 수 없다.세 가지 가능성: 보존 기간 초과로 정리됨, workflow가 삭제되어 기록도 함께 사라짐, 또는 「성공도 저장」 스위치를 꺼둔 경우(EXECUTIONS_DATA_SAVE_ON_SUCCESS=none). 앞의 두 가지가 가장 흔하다.
내보내기, 공유, 흔적 남기기의 리스크
workflow JSON은 통째로 내보내고 다른 사람에게 보낼 수 있다. 여기서 세 군데가 새어 나간다.
파라미터 안의 평문.credential 본체는 JSON 안에 없지만(이름 참조만 남음), 그러나 parameters 안에는 무엇이든 있을 수 있다: URL에 붙은 token, HTTP 요청 헤더에 직접 쓴 key, Webhook의 path.grep 한 번 key、token、Bearer 다시 보내면 2분이면 끝난다.
실행 기록 안에는 원본 데이터가 있다.node가 실행한 것은 무엇이든 기록에 원문 그대로 저장된다. 주문 번호, 고객명, 주소가 모두 있다. self-hosted는 보존 기간을 직접 제어할 수 있으니 EXECUTIONS_DATA_MAX_AGE 를 작게 조정하는 것이 가장 간편한 방법이다. 여러 명이 공유하는 인스턴스라면 처음부터 설정해 두는 것이 좋다.
다른 사람이 보낸 JSON을 그대로 붙여넣기.붙여넣는 순간 당신의 인스턴스에서 그가 작성한 코드가 실행된다. Code node 안의 JS는 실행 여부를 묻지 않는다. 출처가 불분명한 workflow는 빈 테스트 인스턴스에 넣고 확인한 뒤에 다루어라.
소결
워크플로우는 도면이고, 노드는 도면 위의 사각형이며, 연결은 사각형 사이의 화살표이고, 실행은 도면을 따라 시공하는 그 한 번이다. 네 단어는 네 가지 것에 대응하며, 위치가 다르고 변경의 결과도 다르다: 노드 파라미터를 바꾸면 이후의 모든 실행에 영향을 미치고, 실행 기록을 바꾸면 아무것도 바꿀 수 없으며(그것은 원래 기록이다), 이름을 바꾸면 연결은 따라가지만 표현식은 반드시 그렇지는 않다.
정말로 내가 가장 먼저 기억해야 할 하나를 고르라면, 「실행에 snapshot이 따른다」는 것이다. 그것은 왜 오래된 기록이 당신을 속이지 않는지, 왜 새로운 변경이 반드시 적용되지는 않는지, 왜 정리 기간이 지나면 아무것도 조회할 수 없는지를 동시에 설명한다.
자신이 이해했는지 판단하는 데는笨한 방법이 있다: 아무나 만든 워크플로우를 열어서, 캔버스는 보지 않고 내보낸 JSON만 보면서, 그것이 실행되면 노드가 몇 개, 경로가 몇 개, 어느 것이 빈 실행인지 말할 수 있는가. 말할 수 있다면, 이 네 단어는 통과다.
계속해서
아직 첫 번째 워크플로우를 실행해보지 못한 사람은 먼저《첫 번째 워크플로우: 수동 트리거 → 필드 편집 → 코드》를 보라, 그 글은 세 개의 노드를 어떻게 연결해서 실행하는지 설명한다. 시작 오류, port 점유 같은 진입 자체가 안 되는 문제는여기에 있다. 전체 과정의 목차는이 페이지에 있고, 노드 관련 내용은 모두이 분류아래에 있다.
트리거 중에서 가장 문제가 생기기 쉬운 정기 실행 유형은 따로 한 편을 썼다:워크플로우 정기 실행이 트리거되지 않을 때: 5가지 원인 하나씩 점검。
참고 자료
- Key concept glossary: workflow, node, execution, publish这几个词的官方定义,本文对齐的就是这里
- Understand workflows: 워크플로우란 무엇인지, 그리고 워크플로우 생성, 컴포넌트, 실행 이 네 부분의 분권
- Connect nodes together: 연결의 생성과 삭제, 한 node의 출력을 다른 node의 입력에 연결
- Save and publish workflows: 자동 저장의 시간 범위, publish가 버전을 온라인에 고정, 프로덕션 실행은 게시된 버전 사용
- Understand executions: 수동 실행과 프로덕션 실행의 차이, 어떤 실행이 쿼터에 포함되는지
- If: 조건의 데이터 타입과 비교 방식, 두 개의 출력
- Executions 환경 변수: 336시간 보관, 최대 10000건, 성공과 실패 모두 저장这些默认值
최종 업데이트: 2026년 11월 4일, n8n 2.43.7 사용.

