n8n 워크플로우 엔지니어
트리거 시점
- n8n 워크플로우 JSON 편집 또는 구축
- n8n 디버깅: "노드가 실행되지 않음", "페어 아이템을 사용할 수 없음", "정적 데이터가 유지되지 않음"
- n8n REST API를 통한 대량 작업 (이름 변경, 재시도, 삭제)
- Telegram, Gmail, HTTP Request, Code, Switch, Merge 노드 패턴
- n8n에서 예약 트리거 / 시간대 / cron 동작
- n8n v2.x UI 동작 (게시/게시 취소, 보관)
엄격한 규칙
- 수정 전 감사. 전체 워크플로우 JSON 읽기 → 결과 제시 → 확인 → 한 번에 적용. 맹목적으로 패치하지 마십시오.
- 예약 트리거를 통해 테스트. 수동 "실행"은
$workflow.staticData를 재설정합니다. 중복 제거 증명에는 실제 cron 실행이 필요합니다. - 실제 결과 확인. 실행 로그와 최종 상태(메시지 도착, 행 존재)를 확인하십시오. 노드가
ok: true를 반환하는 것은 좋은 신호이지만 증거는 아닙니다. UI 동기화 지연으로 인해 관찰자가 속을 수 있습니다. - Code 노드 모드를 반환 서명과 일치. 잘못된 모드 + 잘못된 반환 = 자동 부분 처리.
빠른 참조 — 가장 일반적인 버그
| 증상 | 근본 원인 | 수정 방법 |
|---|---|---|
localhost:4000에 대한 HTTP 요청이 refused/timeout 반환 | Docker 브리지 모드의 n8n | 호스트 게이트웨이 사용: 기본 브리지에서는 종종 172.17.0.1 (docker network inspect bridge로 확인) 또는 extra_hosts에 host.docker.internal:host-gateway 매핑 |
| Telegram 메시지가 도착하지 않고 오류 없음 | IPv6 DNS 확인 시간 초과 | docker-compose에 NODE_OPTIONS=--dns-result-order=ipv4first |
EST의 cron에도 불구하고 new Date()가 UTC 반환 | 컨테이너에 TZ 환경 변수 누락 | TZ와 GENERIC_TIMEZONE을 모두 시간대로 설정 |
parse_mode: Markdown이 자동 실패 또는 왜곡 | AI 출력에 일치하지 않는 * 또는 _ | parse_mode: HTML 사용 + 전송 전에 삭제 |
| LiteLLM HTTP Request 노드에서 401 | 인증 헤더 누락 | Authorization: Bearer {{ $env.LITELLM_API_KEY }} 추가 |
Claude 호출에서 ECONNABORTED: timeout of 15000ms | 기본 15초 시간 초과 | options.timeout: 90000 설정 |
/api/v1/workflows/:id에 PUT 시 400 반환 | 본문에 추가 필드 | name, nodes, connections, settings, staticData로만 제거 |
| 노드 이름 변경 시 워크플로우 자동 중단 | 연결 참조가 이전 이름을 참조 | nodes[].name과 connections{} 키 모두에서 이전 이름을 replace_all |
| Code 집계 후 페어 아이템을 사용할 수 없음 | N→1→M 항목 계통 손실 | executeOnce: true 설정, $('UpstreamNode').all()을 명시적으로 반복 |
| 수동 실행 시 정적 데이터 중복 제거 "작동하지 않음" | 수동 실행이 정적 데이터 재설정 | 예약된 cron을 통해서만 테스트 |
Code 노드 — 모드/반환 규칙
모든 Code 노드에는 mode 매개변수가 있습니다. 불일치 = 자동 버그.
| 모드 | 액세스 패턴 | 반환 |
|---|---|---|
runOnceForAllItems (기본값) | const items = $input.all(); | return items.map(...) — 배열이어야 함 |
runOnceForEachItem | $json이 현재 항목 | return { json: ... } — 단일 객체, 배열 아님 |
불일치 증상:
runOnceForAllItems+$json액세스 → 첫 번째 항목만 자동으로 처리됨runOnceForEachItem+ 배열 반환 →"'json' 속성이 객체가 아닙니다"검증 오류
중복 제거 패턴 (크로스 실행 상태)
const store = $getWorkflowStaticData('global');
store.jobIds = store.jobIds || [];
const newItems = items.filter(item => !store.jobIds.includes(item.json.id));
// 새 ID 유지, 무제한 증가를 방지하기 위해 최대 500개로 제한
store.jobIds = [...store.jobIds, ...newItems.map(i => i.json.id)].slice(-500);
return newItems;
중요: staticData는 예약된 실행에서만 유지됩니다. 수동 실행은 재설정합니다. Execute를 두 번 클릭하는 대신 두 번의 연속 예약 실행을 관찰하여 중복 제거를 확인하십시오.
실행당 캐시 정리
// 첫 번째 Code 노드 상단 — 임시 캐시 정리, 크로스 실행 상태는 유지
const store = $getWorkflowStaticData('global');
for (const k of Object.keys(store)) {
if (k.startsWith('cjob_') || k.startsWith('cprep_') || k === 'pendingJobs') {
delete store[k];
}
}
// 절대 jobIds 삭제하지 마십시오 — 크로스 실행 중복 제거가 이에 의존
파이프라인 관찰 가능성 패턴
각 주요 Code 노드에서 카운터를 설정합니다. 형식화 단계에서 읽습니다. 메시지에 추가합니다.
// 1단계 — 티커 구축
const store = $getWorkflowStaticData('global');
store.pipeline = { ticker_count: tickers.length, fixed_count: fixed.length };
// 2단계 — 신호 계산
store.pipeline = store.pipeline || {};
store.pipeline.signal_count = results.length;
// 최종 — 메시지 형식화
const pl = store.pipeline || {};
const pf = (n) => (n === undefined || n === null) ? '?' : String(n);
const pipelineBlock =
'\n\n📊 파이프라인\n' +
'티커 구축: ' + pf(pl.ticker_count) + '\n' +
'신호: ' + pf(pl.signal_count);
페어 아이템 문제 + 해결 방법
증상: 집계 노드(N 항목 → 1 프롬프트 → 1 LLM 응답 → M 항목으로 분할) 이후, 다운스트림 $('UpstreamNode').item.json이 'X' 노드의 항목에 대한 페어 아이템 데이터를 사용할 수 없습니다 오류를 발생시킵니다.
해결 방법 (선호도 순):
-
다운스트림 Code 노드에서
executeOnce: true설정, 명시적으로 반복:const upstream = $('UpstreamNode').all(); const outputs = []; for (let i = 0; i < parsed.length; i++) { outputs.push({ json: { ...parsed[i], original: upstream[i].json } }); } return outputs; -
정적 데이터를 통해 ID 전달, 다운스트림에서 명시적으로 조회.
-
pairedItem.sourceOverwrite— 취약, 최후의 수단.
Telegram 패턴
HTML 모드 — 7개 태그만 허용
<b>, <i>, <u>, <s>, <a>, <code>, <pre> — 이 외에는 400 Bad Request: Unsupported start tag 발생.
AI 생성 콘텐츠를 위한 3계층 방어
- 프롬프트: AI에 허용된 태그를 명시적으로 알림
- 삭제: 전송 전에 코드에서 지원되지 않는 태그 제거
- 이스케이프: 사용자/AI 콘텐츠를 HTML 이스케이프(
&→&,<→<,>→>)한 후 삽입
n8n 출처 표시 억제
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false,
"disable_web_page_preview": true
}
.env의 토큰 vs 자격 증명
내장 Telegram 노드 + 자격 증명보다 환경 변수 + HTTP Request 패턴을 선호:
- 마이그레이션 간 이식 가능
- 회전 용이 (
nano .env+ 컨테이너 재시작) - DB 암호화 키 잠금 위험 없음
- 패턴:
POST https://api.telegram.org/bot{{ $env.BOT_TOKEN }}/sendMessage - 절충: 토큰이 요청 URL에 있으므로 실행 데이터, 프록시 로그 및 오류 메시지에 나타날 수 있음. 이 워크플로우의 실행 데이터 저장을 끄거나, 다른 사람이 로그를 읽을 수 있는 경우 내장 Telegram 자격 증명 사용.
Gmail OAuth 패턴
| 문제 | 해결 방법 |
|---|---|
invalid_grant ... refresh token revoked | "테스트" 모드의 OAuth는 약 7일 후 만료됨. UI를 통해 다시 인증하거나 앱을 프로덕션에 게시. |
빈 payload.headers (보낸 사람/제목 비어 있음) | 기본 simple: true가 헤더를 제거함. options.simple: false 설정. |
| 보안 경고/영수증 누락 | -category:promotions -category:social -category:updates를 제외하지 마십시오 — Gmail이 관련 메일의 90%를 거기로 라우팅합니다. is:unread newer_than:Nd만 사용. |
| 여러 계정에서 읽음 표시 필요 | 계정별 자격 증명 필요. 하나의 Gmail 자격 증명으로 다른 계정의 메시지 ID를 조작할 수 없음. |
REST API 패턴 (대량 작업)
인증: X-N8N-API-KEY: <key> 헤더.
| 엔드포인트 | 참고 |
|---|---|
GET /api/v1/workflows | 모두 반환 (페이지 매김). 클라이언트 측 필터링. |
GET /api/v1/workflows/:id | 전체 워크플로우 JSON. |
PUT /api/v1/workflows/:id | 엄격: name, nodes, connections, settings, staticData만. settings 자체에는 허용 목록이 있음 (아래 참조). GET 후 PUT 왕복에서 다른 모든 것은 제거. |
DELETE /api/v1/workflows/:id | 실제로 삭제 (UI는 보관만 제공). 대량 정리에 사용. |
POST /api/v1/workflows/:id/activate | v2.x UI에서 게시와 동일. |
settings 필드 허용 목록 (PUT 본문)
executionOrder, timezone, saveExecutionProgress, saveDataErrorExecution, saveDataSuccessExecution, saveManualExecutions, callerPolicy, callerIds, errorWorkflow, executionTimeout
노드 type은 생성 후 변경 불가
node.type을 변경하는 PUT은 200을 반환하지만 유형은 그대로 유지됩니다. 변환하려면(예: telegram → httpRequest): 전체 노드 객체 교체 (id에 새 UUID, 동일한 name). 연결은 이름으로 라우팅되므로 이름을 유지하는 교체는 안전합니다.
n8n v2.x UI 세부 사항
| 이전 (v1.x) | 새 (v2.x) |
|---|---|
| 활성화 토글 | 게시 (녹색 버튼, 오른쪽 상단) / 게시 취소 |
| 워크플로우 삭제 | 보관만 (실제 삭제는 REST API 사용) |
HTTP Request 노드 — LLM 호출 필수 사항
{
"method": "POST",
"url": "http://172.17.0.1:4000/v1/chat/completions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{"name": "Content-Type", "value": "application/json"},
{"name": "Authorization", "value": "=Bearer {{ $env.LITELLM_API_KEY }}"}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({model:'<your-model-alias>',messages:[{role:'user',content:$json.prompt}],temperature:0.3}) }}",
"options": {
"timeout": 90000,
"retry": { "tries": 2, "waitBetweenTries": 5000 }
}
}
n8n 컨테이너에 curl 없음
컨테이너 내 HTTP 테스트를 위해 docker exec를 통해 Node의 내장 https 모듈 사용:
docker exec n8n node -e "const https=require('https'); /* request */"
SSH 사전 점검 (VPS에서 heredoc 전)
ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=10 <user>@<host>
# 프롬프트 확인: [<user>@<host> ...]$
# 그런 다음에만 heredoc 붙여넣기
SSH 드롭 후 PowerShell에 bash heredoc 붙여넣기 = 수백 개의 구문 분석 오류.
평가 스위트 패턴 (로직 전용)
n8n 평가 기능을 통한 모의 입력 테스트는 API 크레딧을 소모하지 않고 로직 회귀를 포착합니다. 필터 로직, 키워드 목록 또는 유효성 검사 규칙을 변경하기 전과 후에 다시 실행하십시오.
안티 패턴
| 안티 패턴 | 대신 수행할 작업 |
|---|---|
| "다시 실행해보고 작동하는지 확인" | 먼저 실행 로그를 처음부터 끝까지 읽기 |
| 여러 번의 왕복으로 한 번에 하나의 노드 패치 | 전체 파일 감사 → 번들 패치 스크립트 → 한 번에 확인 |
AI 출력에 parse_mode: Markdown 사용 | parse_mode: HTML + 삭제기 |
| 워크플로우 JSON에 API 키/채팅 ID 하드코딩 | {{ $env.KEY }} 참조, <secret-file>에 저장 (chmod 600) |
| 연결 업데이트 없이 노드 이름 변경 | nodes[].name 및 connections{} 키 모두에서 노드 이름 replace_all |
| 중복 제거 테스트에 수동 실행 사용 | 예약된 트리거 대기 |
| LLM 호출에 기본 httpRequest 시간 초과 | 최소 90000ms 설정 |
runOnceForAllItems와 $json 혼합 | 모드를 액세스 패턴과 일치 |