n8n에서 완벽하게 돌아가던 워크플로우가 갑자기 빨간 에러 메시지를 뿜어내며 멈춰버린 경험이 있는가?
나도 처음 n8n을 시작했을 때 가장 스트레스 받았던 순간이 바로 “어제까지 잘 돌아갔는데 갑자기 왜 안 돼?”라며 당황하던 때였다.
특히 밤늦게까지 공들여 만든 워크플로우가 아침에 확인해보니 “Connection timeout” 오류로 멈춰있을 때의 그 허탈감… 😅
더 답답한 건 에러 메시지만 봐서는 정확히 뭐가 문제인지 알 수 없어서 몇 시간씩 삽질했던 기억이 생생하다.
하지만 지금 돌이켜보면, n8n의 오류 처리 시스템은 생각보다 체계적이고 강력하다.
마치 의사가 환자를 진단하는 것처럼 올바른 진단 도구와 방법만 알고 있으면 어떤 문제든 해결할 수 있는 구조다.
오늘은 2025년 최신 n8n 버전을 기준으로 오류 처리의 모든 것부터 효과적인 디버깅 방법, 그리고 로그 분석을 통한 근본 원인 해결까지 실전 경험을 바탕으로 완전히 마스터해 보자!
🚨 n8n 오류, 왜 이렇게 자주 발생할까?
오류의 근본 원인 이해하기
n8n에서 오류가 발생하는 이유를 먼저 이해해 보자.
자동화는 본질적으로 외부 의존성이 높은 시스템이다.
2025년 n8n 오류의 주요 카테고리:
🔐 인증 관련 오류 (40%)
- API 키 만료
- OAuth 토큰 갱신 실패
- 권한 부족
🌐 네트워크 관련 오류 (30%)
- 연결 타임아웃
- 외부 서비스 다운타임
- Rate limiting 초과
📊 데이터 관련 오류 (20%)
- 예상과 다른 데이터 형식
- 필수 필드 누락
- 데이터 타입 불일치
⚙️ 설정 관련 오류 (10%)
- 잘못된 노드 설정
- 환경 변수 누락
- 버전 호환성 문제
왜 오류 처리가 중요한가?
나도 처음에는 “일단 돌아가게만 만들자”는 생각이었다.
하지만 실제로 운영해보니 오류 처리가 없는 자동화는 시한폭탄이라는 걸 깨달았다:
- 무음 실패: 오류가 발생해도 모르고 지나감
- 데이터 손실: 중요한 작업이 실패해도 복구 불가
- 신뢰성 저하: 팀에서 자동화 시스템을 불신하게 됨
- 유지보수 지옥: 문제 원인 파악에 몇 시간씩 소모
⚡ 2025년 n8n 오류 처리 완전 시스템
1. Error Workflow – 중앙집중식 오류 관리
가장 먼저 구축해야 할 기본 인프라
1단계: 중앙 Error Workflow 생성
워크플로우 이름: "[시스템] 중앙_오류_처리기_v1.0"
노드 구성:
├─ Error Trigger (오류 수집)
├─ 오류정보_분석_분류
├─ 심각도별_라우팅_분기
├─ 심각도별 처리 Code_준비(Code 작성)
├─ Slack_즉시알림 (Critical)
├─ 구글시트_로그데이터_통합(데이터 통합)
└─ 구글시트_로그기록 (모든 오류)
2단계: Error Trigger 노드 설정
핵심 데이터 구조 이해하기:
노드 타입: Error Trigger
설정: 기본값 사용 (모든 워크플로우 오류 수신)
3단계: 오류 분석 노드(Code 노드)
// 노드명: "오류_분석_및_분류"
// 안전한 데이터 추출 (undefined 방지)
const error = $json.error || {};
const workflow = $json.workflow || {};
const node = $json.node || {};
const execution = $json.execution || {};
console.log('🔍 오류 정보 수신:', {
workflowName: workflow.name || 'Unknown Workflow',
nodeName: node.name || 'Unknown Node',
errorMessage: error.message || 'No error message',
rawData: $json
});
// 심각도 판단 로직
let severity = 'INFO';
let action = 'LOG_ONLY';
let category = 'UNKNOWN';
// 안전한 에러 메시지 추출
const errorMessage = error.message || error.toString() || 'Unknown error';
// 인증 오류 (높은 심각도)
if (errorMessage.includes('401') ||
errorMessage.includes('Authorization failed') ||
errorMessage.includes('authentication')) {
severity = 'HIGH';
action = 'IMMEDIATE_ALERT';
category = 'AUTH_FAILURE';
}
// 네트워크 오류 (중간 심각도)
else if (errorMessage.includes('timeout') ||
errorMessage.includes('ETIMEDOUT') ||
errorMessage.includes('ECONNRESET') ||
errorMessage.includes('503') ||
errorMessage.includes('502')) {
severity = 'MEDIUM';
action = 'DELAYED_ALERT';
category = 'NETWORK_ISSUE';
}
// Rate Limiting (낮은 심각도)
else if (errorMessage.includes('rate limit') ||
errorMessage.includes('429') ||
errorMessage.includes('too many requests')) {
severity = 'LOW';
action = 'BATCH_ALERT';
category = 'RATE_LIMIT';
}
// 메모리/리소스 오류 (매우 높은 심각도)
else if (errorMessage.includes('out of memory') ||
errorMessage.includes('heap') ||
errorMessage.includes('memory')) {
severity = 'CRITICAL';
action = 'EMERGENCY_ALERT';
category = 'RESOURCE_EXHAUSTION';
}
// 업무시간 외에는 심각도 조정
const now = new Date();
const hour = now.getHours();
const isWorkingHours = hour >= 9 && hour <= 18;
if (!isWorkingHours && severity === 'MEDIUM') {
severity = 'LOW';
action = 'BATCH_ALERT';
}
// 결과 반환
return [{
json: {
// 원본 오류 정보 보존 (안전한 접근)
originalError: {
message: error.message || errorMessage || 'No error message',
stack: error.stack || 'No stack trace',
name: error.name || 'UnknownError'
},
// 워크플로우 정보 (안전한 접근)
workflow: {
id: workflow.id || 'unknown',
name: workflow.name || 'Unknown Workflow',
active: workflow.active !== undefined ? workflow.active : false
},
// 노드 정보 (안전한 접근)
node: {
name: node.name || 'Unknown Node',
type: node.type || 'unknown'
},
// 실행 정보 (안전한 접근)
execution: {
id: execution.id || 'unknown',
url: execution.url || '#',
retryOf: execution.retryOf || null
},
// 분석 결과
analysis: {
severity,
action,
category,
timestamp: now.toISOString(),
workingHours: isWorkingHours,
errorSummary: (errorMessage).substring(0, 100) + '...'
}
}
}];
4단계: 심각도별 분기 노드(Switch 노드)
Switch 노드 설정:
- 노드명:
심각도별_라우팅
- Mode:
Expression
- Value:
{{ $json.analysis.severity }}
분기 설정:
- 분기 1:
CRITICAL
- 분기 2:
HIGH
- 분기 3:
MEDIUM
- 분기 4:
LOW
- 분기 5: Fallback Output (체크박스 활성화)
5단계 : 분기별 처리 노드들
CRITICAL 분기(긴급 대응)
Code 노드:"CRITICAL_긴급_Slack_메시지_구성"
const data = $json; return [{ json: { channel: "#emergency", text: `🚨🚨 CRITICAL ERROR: ${data.workflow.name} - 즉시 대응 필요!`, blocks: [ { type: "header", text: { type: "plain_text", text: "🚨🚨 CRITICAL 오류 - 즉시 대응 필요" } }, { type: "section", fields: [ { type: "mrkdwn", text: `*워크플로우:*n${data.workflow.name}` }, { type: "mrkdwn", text: `*노드:*n${data.node.name}` }, { type: "mrkdwn", text: `*분류:*n${data.analysis.category}` }, { type: "mrkdwn", text: `*시간:*n${new Date(data.analysis.timestamp).toLocaleString('ko-KR')}` } ] }, { type: "section", text: { type: "mrkdwn", text: `*오류 내용:*n```${data.analysis.errorSummary}```` } }, { type: "section", text: { type: "mrkdwn", text: "⚠️ *이 오류는 즉시 대응이 필요합니다! 시스템 장애 가능성이 높습니다.*" } }, { type: "actions", elements: [ { type: "button", text: { type: "plain_text", text: "🔍 실행 로그 보기" }, url: data.execution.url, style: "danger" } ] } ] } }];HIGH 분기(즉시 알림)~LOW분기(로그만 기록), Google Sheets 노드 설정은 생략한다.
구체적인 코드 내용은 각자 검토할 워크플로우에 따라 달라지기 때문이다.오류 처리 워크플로우를 완성했다면, 실제 적용할 워크플로우에 가서 설정(Setting)에 Error Workflow를 설정하면 된다.
🔍 체계적인 디버깅 방법론(참고용)
1. 단계별 디버깅 프로세스
5단계 디버깅 프레임워크:
🔍 1단계: 빠른 상황 파악 (30초)
체크리스트: ✅ 워크플로우가 활성화되어 있는가? ✅ 마지막 실행이 언제였는가? ✅ 어떤 노드에서 멈췄는가? ✅ 에러 메시지가 명확한가?🧐 2단계: 데이터 흐름 추적 (2분)
// Debug Helper 노드 활용 // 각 주요 노드 사이에 삽입하여 데이터 확인 console.log('=== 디버그 포인트: Gmail 데이터 확인 ==='); console.log('입력 아이템 수:', $input.all().length); console.log('첫 번째 아이템:', JSON.stringify($json, null, 2)); // 중요한 필드만 추출해서 확인 const summary = $input.all().map(item => ({ from: item.json.from, subject: item.json.subject, hasAttachment: !!item.json.attachments })); console.log('데이터 요약:', summary); return $input.all();🔬 3단계: 개별 노드 실행 테스트 (5분)
“Execute Node” 기능 적극 활용:
- 문제가 된 노드 선택
- “Execute Node” 클릭
- 입력 데이터 확인
- 출력 결과 분석
- 설정 값 하나씩 검증
🕵️ 4단계: 로그 분석 (10분)
# Docker 환경에서 상세 로그 확인 docker logs n8n --tail 100 -f # 특정 워크플로우 로그만 필터링 docker logs n8n 2>&1 | grep "워크플로우_이름" # 에러 로그만 추출 docker logs n8n 2>&1 | grep -i "error|failed|exception"🎯 5단계: 근본 원인 해결 (시간 가변)
원인별 해결 전략 수립
2. 고급 디버깅 도구 활용
Browser Console.log 활용
// Code 노드에서 브라우저 콘솔로 디버그 정보 출력 console.log('🔍 워크플로우 디버그:', { currentNode: 'AI_분석_처리', inputCount: $input.all().length, firstItem: $json, timestamp: new Date().toISOString() }); // 복잡한 객체 구조 확인 console.table($input.all().map(item => ({ id: item.json.id, status: item.json.status, dataSize: JSON.stringify(item.json).length }))); return $input.all();조건부 디버깅
// 특정 조건에서만 디버그 정보 출력 const isDebugMode = $env.N8N_DEBUG_MODE === 'true'; const errorThreshold = 5; if (isDebugMode || $input.all().length > errorThreshold) { console.log('🚨 디버그 모드 활성화됨'); console.log('입력 데이터:', $input.all()); // 상세한 성능 정보 const startTime = Date.now(); // ... 처리 로직 ... const endTime = Date.now(); console.log(`⏱️ 처리 시간: ${endTime - startTime}ms`); }📊 로그 분석 마스터 가이드
1. n8n 로깅 시스템 완전 정복
환경 변수 설정
# 상세한 로그 레벨 설정 N8N_LOG_LEVEL=debug # 로그 파일로 저장 N8N_LOG_OUTPUT=file N8N_LOG_FILE_LOCATION=/var/log/n8n/n8n.log # 로그 파일 관리 N8N_LOG_FILE_SIZE_MAX=50 # 50MB N8N_LOG_FILE_COUNT_MAX=10 # 최대 10개 파일로그 레벨별 활용법
🔴 ERROR: 즉시 대응 필요 - 워크플로우 실행 실패 - 데이터베이스 연결 오류 - 인증 실패 🟡 WARN: 주의 깊게 모니터링 - 타임아웃 발생 - Rate limiting 경고 - 외부 서비스 응답 지연 🔵 INFO: 정상 운영 상태 확인 - 워크플로우 시작/완료 - 데이터 처리 현황 - 시스템 상태 정보 🟢 DEBUG: 개발/문제해결 시 - 노드별 상세 실행 정보 - 변수 값 추적 - 성능 측정 데이터2. 로그 분석 실전 기법
자주 사용하는 로그 분석 명령어
# 최근 30분간 에러 로그 확인 docker logs n8n --since 30m | grep -i error # 특정 워크플로우 실행 추적 docker logs n8n | grep "execution.*abc123" # 성능 문제 분석 (긴 실행 시간) docker logs n8n | grep "Workflow execution took" | awk '{print $NF}' | sort -n # 메모리 사용량 모니터링 docker logs n8n | grep -i "memory|heap" # API 호출 실패 패턴 분석 docker logs n8n | grep "HTTP.*[45][0-9][0-9]"로그 패턴별 문제 진단
// 로그 패턴 분석 도구 (Code 노드에서 활용) const logAnalyzer = { patterns: { authFailure: /401|unauthorized|authentication.*failed/i, timeout: /timeout|ETIMEDOUT|ECONNRESET/i, rateLimit: /rate.*limit|429|too.*many.*requests/i, serverError: /5[0-9][0-9]|internal.*server.*error/i, memoryIssue: /memory|heap.*out.*of.*space/i }, analyze(logMessage) { const results = {}; for (const [type, pattern] of Object.entries(this.patterns)) { results[type] = pattern.test(logMessage); } return results; }, getSolution(analysisResult) { if (analysisResult.authFailure) { return "인증 정보 확인 및 토큰 갱신 필요"; } if (analysisResult.timeout) { return "네트워크 연결 확인 및 타임아웃 설정 조정"; } if (analysisResult.rateLimit) { return "API 호출 빈도 조절 및 배치 처리 고려"; } // ... 추가 솔루션들 } };🛠️ 실전 프로젝트: 완벽한 오류 처리 시스템 구축
목표: 무중단 고객 문의 처리 시스템
완전한 오류 처리가 적용된 실제 워크플로우를 만들어 보자!
1단계: 메인 워크플로우 설계
워크플로우: "고객문의_완벽처리_오류방지_v2.0" 아키텍처: 📡 트리거 레이어 └─ Gmail_트리거 (5분 간격) 🛡️ 데이터 검증 레이어 ├─ 이메일_필수필드_검증 ├─ 스팸_필터링_확인 └─ 데이터_형식_정규화 🧠 처리 레이어 ├─ AI_문의유형_분류 (재시도 로직) ├─ 긴급도_판단_분기 └─ 담당자_자동_배정 📤 출력 레이어 ├─ Slack_담당팀_알림 ├─ 구글시트_이슈_등록 └─ 고객_자동응답_발송 ⚠️ 오류 처리 레이어 ├─ 단계별_오류_복구 ├─ 백업_프로세스_실행 └─ 관리자_에스컬레이션2단계: 노드별 오류 처리 구현
🔍 이메일 검증 노드 (방어적 프로그래밍)
// 노드명: "이메일_필수필드_검증_방어적" const emails = $input.all(); const validEmails = []; const errors = []; for (const [index, email] of emails.entries()) { try { // 필수 필드 검증 const requiredFields = ['from', 'subject', 'body']; const missing = requiredFields.filter(field => !email.json[field]); if (missing.length > 0) { errors.push({ index, type: 'MISSING_FIELDS', fields: missing, email: email.json.from || 'unknown' }); continue; } // 데이터 정규화 const normalizedEmail = { ...email.json, from: email.json.from.toLowerCase().trim(), subject: email.json.subject.trim(), receivedAt: new Date().toISOString() }; validEmails.push({ json: normalizedEmail }); } catch (error) { errors.push({ index, type: 'PROCESSING_ERROR', error: error.message, email: email.json?.from || 'unknown' }); } } // 오류가 있지만 처리 가능한 경우 if (errors.length > 0) { console.warn(`⚠️ ${errors.length}개 이메일 처리 중 오류 발생:`, errors); // 오류 정보를 별도 경로로 전송 (Error Trigger 대신) $execution.setWorkflowVariable('validationErrors', errors); } // 전체 실패가 아닌 부분 성공 허용 if (validEmails.length === 0) { throw new Error(`모든 이메일 검증 실패: ${JSON.stringify(errors)}`); } return validEmails;🤖 AI 분류 노드 (지능형 재시도)
// 노드명: "AI_문의유형_분류_스마트재시도" const MAX_RETRIES = 3; const RETRY_DELAYS = [1000, 3000, 5000]; // 점진적 지연 async function classifyWithRetry(emailContent, retryCount = 0) { try { // OpenAI API 호출 const response = await $http.request({ method: 'POST', url: 'https://api.openai.com/v1/chat/completions', headers: { 'Authorization': `Bearer ${$credentials.OpenAI.apiKey}`, 'Content-Type': 'application/json' }, data: { model: 'gpt-4o-mini', messages: [{ role: 'user', content: `다음 고객 문의를 분류해주세요: ${emailContent}` }], temperature: 0.1 }, timeout: 30000 // 30초 타임아웃 }); return response.data.choices[0].message.content; } catch (error) { console.log(`🔄 AI 분류 시도 ${retryCount + 1} 실패:`, error.message); // 재시도 가능한 오류인지 판단 const isRetryable = error.message.includes('timeout') || error.message.includes('503') || error.message.includes('502') || error.message.includes('rate limit'); if (isRetryable && retryCount < MAX_RETRIES) { // 점진적 백오프로 재시도 await new Promise(resolve => setTimeout(resolve, RETRY_DELAYS[retryCount]) ); return await classifyWithRetry(emailContent, retryCount + 1); } // 재시도 불가능하거나 한계 도달 시 백업 로직 console.warn('🚨 AI 분류 완전 실패, 백업 로직 사용'); return classifyWithFallback(emailContent); } } function classifyWithFallback(emailContent) { // 키워드 기반 백업 분류 const keywords = { urgent: ['긴급', '급함', 'urgent', '즉시'], technical: ['오류', '버그', '에러', '작동', '문제'], billing: ['결제', '요금', '환불', '청구'], general: ['문의', '질문', '안내'] }; const content = emailContent.toLowerCase(); for (const [category, words] of Object.entries(keywords)) { if (words.some(word => content.includes(word))) { return `백업분류: ${category}`; } } return '백업분류: general'; } // 실제 실행 const emails = $input.all(); const results = []; for (const email of emails) { const classification = await classifyWithRetry(email.json.body); results.push({ json: { ...email.json, classification, processedAt: new Date().toISOString() } }); } return results;3단계: 포괄적 Error Workflow
// 워크플로우: "[시스템] 고객문의_오류처리_v2.0" // Error Trigger 이후의 처리 로직 const error = $json.error; const workflow = $json.workflow; const node = $json.node; // 1. 오류 분석 및 분류 const errorAnalysis = { severity: 'UNKNOWN', category: 'UNKNOWN', autoRecoverable: false, requiresHumanIntervention: true }; // 오류 유형별 분석 if (error.message.includes('401') || error.message.includes('Authorization')) { errorAnalysis.severity = 'HIGH'; errorAnalysis.category = 'AUTH_FAILURE'; errorAnalysis.autoRecoverable = true; // 토큰 갱신 가능 } else if (error.message.includes('timeout') || error.message.includes('ETIMEDOUT')) { errorAnalysis.severity = 'MEDIUM'; errorAnalysis.category = 'NETWORK_ISSUE'; errorAnalysis.autoRecoverable = true; // 재시도 가능 } else if (error.message.includes('rate limit') || error.message.includes('429')) { errorAnalysis.severity = 'LOW'; errorAnalysis.category = 'RATE_LIMIT'; errorAnalysis.autoRecoverable = true; // 지연 후 재시도 } else if (error.message.includes('out of memory') || error.message.includes('heap')) { errorAnalysis.severity = 'CRITICAL'; errorAnalysis.category = 'RESOURCE_EXHAUSTION'; errorAnalysis.requiresHumanIntervention = true; } // 2. 자동 복구 시도 let recoveryAttempted = false; let recoverySuccess = false; if (errorAnalysis.autoRecoverable) { try { switch (errorAnalysis.category) { case 'AUTH_FAILURE': // 인증 토큰 갱신 시도 await refreshAuthTokens(); recoveryAttempted = true; break; case 'RATE_LIMIT': // 워크플로우 일시 정지 후 재시작 스케줄링 await scheduleDelayedRetry(30 * 60 * 1000); // 30분 후 recoveryAttempted = true; break; case 'NETWORK_ISSUE': // 네트워크 상태 확인 후 재시도 if (await checkNetworkHealth()) { await scheduleImmediateRetry(); recoveryAttempted = true; } break; } } catch (recoveryError) { console.error('자동 복구 실패:', recoveryError); } }// 3. 알림 발송
const alertData = { workflow: workflow.name, node: node.name, error: error.message, severity: errorAnalysis.severity, category: errorAnalysis.category, recoveryAttempted, recoverySuccess, timestamp: new Date().toISOString(), executionUrl: $json.execution.url }; // 심각도별 알림 전략 switch (errorAnalysis.severity) { case 'CRITICAL': // 즉시 전화 + Slack + 이메일 await sendSlackAlert(alertData, '#emergency'); await sendEmailAlert(alertData, ['admin@company.com']); break; case 'HIGH': // Slack 즉시 알림 await sendSlackAlert(alertData, '#n8n-alerts'); break; case 'MEDIUM': // 30분 배치 알림 await queueBatchAlert(alertData); break; case 'LOW': // 일일 요약에만 포함 await logToDailySummary(alertData); break; } // 4. 백업 프로세스 실행 if (workflow.name.includes('고객문의') && !recoverySuccess) { // 중요한 고객 문의 워크플로우는 백업 프로세스 실행 await triggerBackupWorkflow('고객문의_백업처리_수동모드'); } return [{ json: alertData }];4. 모니터링 대시보드 구축
실시간 상황 인식을 위한 모니터링 시스템:
Google Sheets 기반 오류 대시보드
// 노드명: "오류_대시보드_업데이트" const sheetData = { timestamp: new Date().toISOString(), workflow: $json.workflow, node: $json.node, severity: $json.analysis.severity, category: $json.analysis.category, errorMessage: $json.error.message.substring(0, 200), recoveryAttempted: $json.analysis.recoveryAttempted, status: $json.analysis.recoverySuccess ? 'RESOLVED' : 'OPEN' }; // 구글 시트에 실시간 업데이트 await $http.request({ method: 'POST', url: `https://sheets.googleapis.com/v4/spreadsheets/${DASHBOARD_SHEET_ID}/values/ErrorLog:append`, headers: { 'Authorization': `Bearer ${$credentials.GoogleSheets.accessToken}`, 'Content-Type': 'application/json' }, data: { values: [Object.values(sheetData)], valueInputOption: 'RAW' } });🎯 성능 최적화를 통한 오류 예방
1. 메모리 사용량 모니터링
// 노드명: "성능_모니터링_체크포인트" const usedMemory = process.memoryUsage(); const memoryLimit = 1024 * 1024 * 1024; // 1GB console.log('🔍 메모리 사용량:', { used: Math.round(usedMemory.heapUsed / 1024 / 1024) + 'MB', total: Math.round(usedMemory.heapTotal / 1024 / 1024) + 'MB', external: Math.round(usedMemory.external / 1024 / 1024) + 'MB', usage: Math.round((usedMemory.heapUsed / memoryLimit) * 100) + '%' }); // 메모리 사용량이 80% 초과 시 경고 if (usedMemory.heapUsed > memoryLimit * 0.8) { console.warn('⚠️ 메모리 사용량 임계점 도달'); // 가비지 컬렉션 강제 실행 if (global.gc) { global.gc(); console.log('🗑️ 가비지 컬렉션 실행됨'); } // 메모리 경고 알림 $execution.setWorkflowVariable('memoryWarning', true); } return $input.all();2. 배치 처리 최적화
// 노드명: "스마트_배치_처리" const items = $input.all(); const BATCH_SIZE = 50; // 한 번에 처리할 아이템 수 const DELAY_BETWEEN_BATCHES = 1000; // 배치 간 지연 시간 async function processBatch(batch, batchIndex) { console.log(`📦 배치 ${batchIndex + 1} 처리 중... (${batch.length}개 아이템)`); try { // 배치 처리 로직 const results = await Promise.allSettled( batch.map(async (item) => { // 개별 아이템 처리 return await processIndividualItem(item); }) ); // 성공/실패 분석 const successful = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; console.log(`✅ 배치 ${batchIndex + 1} 완료: 성공 ${successful}, 실패 ${failed}`); return results; } catch (error) { console.error(`❌ 배치 ${batchIndex + 1} 전체 실패:`, error); throw error; } } // 배치별 순차 처리 const allResults = []; for (let i = 0; i < items.length; i += BATCH_SIZE) { const batch = items.slice(i, i + BATCH_SIZE); const batchResults = await processBatch(batch, Math.floor(i / BATCH_SIZE)); allResults.push(...batchResults); // 마지막 배치가 아니면 잠시 대기 if (i + BATCH_SIZE < items.length) { await new Promise(resolve => setTimeout(resolve, DELAY_BETWEEN_BATCHES)); } } return allResults.map(result => ({ json: result }));🚀 고급 오류 처리 패턴
1. Circuit Breaker 패턴
// 노드명: "회로차단기_외부API호출" class CircuitBreaker { constructor(threshold = 5, timeout = 60000) { this.threshold = threshold; this.timeout = timeout; this.failureCount = 0; this.lastFailureTime = null; this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN } async execute(operation) { if (this.state === 'OPEN') { if (Date.now() - this.lastFailureTime < this.timeout) { throw new Error('Circuit Breaker is OPEN - operation blocked'); } else { this.state = 'HALF_OPEN'; } } try { const result = await operation(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } onSuccess() { this.failureCount = 0; this.state = 'CLOSED'; } onFailure() { this.failureCount++; this.lastFailureTime = Date.now(); if (this.failureCount >= this.threshold) { this.state = 'OPEN'; console.warn(`🔴 Circuit Breaker OPEN - ${this.failureCount} consecutive failures`); } } } // 사용 예시 const breaker = new CircuitBreaker(3, 30000); // 3번 실패 시 30초간 차단 try { const result = await breaker.execute(async () => { return await $http.request({ method: 'GET', url: 'https://external-api.com/data', timeout: 5000 }); }); console.log('✅ API 호출 성공:', result.data); return [{ json: result.data }]; } catch (error) { console.error('❌ API 호출 실패 (Circuit Breaker):', error.message); // 백업 데이터 사용 return [{ json: { error: 'Service temporarily unavailable', useCache: true } }]; }2. Bulkhead 패턴 (리소스 격리)
// 노드명: "리소스_격리_처리" class ResourcePool { constructor(maxConcurrent = 5) { this.maxConcurrent = maxConcurrent; this.activeRequests = 0; this.queue = []; } async execute(operation) { return new Promise((resolve, reject) => { this.queue.push({ operation, resolve, reject }); this.processQueue(); }); } async processQueue() { if (this.activeRequests >= this.maxConcurrent || this.queue.length === 0) { return; } const { operation, resolve, reject } = this.queue.shift(); this.activeRequests++; try { const result = await operation(); resolve(result); } catch (error) { reject(error); } finally { this.activeRequests--; this.processQueue(); // 다음 작업 처리 } } } // 중요도별 리소스 풀 분리 const criticalPool = new ResourcePool(3); // 중요한 작업용 const normalPool = new ResourcePool(2); // 일반 작업용 const items = $input.all(); const results = []; for (const item of items) { const pool = item.json.priority === 'high' ? criticalPool : normalPool; try { const result = await pool.execute(async () => { return await processItem(item.json); }); results.push({ json: result }); } catch (error) { console.error(`Processing failed for item ${item.json.id}:`, error); // 실패한 아이템은 재시도 큐에 추가 results.push({ json: { ...item.json, status: 'failed', error: error.message, retryQueued: true } }); } } return results;🎓 실전 경험에서 나온 골든 룰
1. 오류 처리 설계 원칙
🏗️ Fail Fast, Recover Gracefully
// ❌ 잘못된 예시 - 모든 오류를 숨김 try { const result = await riskyOperation(); return result; } catch (error) { return null; // 오류 정보 손실 } // ✅ 올바른 예시 - 명확한 오류 전달과 복구 try { const result = await riskyOperation(); return { success: true, data: result }; } catch (error) { console.error('Operation failed:', error); // 복구 가능한 오류인지 판단 if (isRecoverable(error)) { const fallbackResult = await fallbackOperation(); return { success: true, data: fallbackResult, source: 'fallback' }; } // 복구 불가능한 오류는 상위로 전달 throw new Error(`Critical failure: ${error.message}`); }🎯 단일 책임 원칙
// 각 노드는 하나의 명확한 책임만 가져야 함 // ❌ 하나의 노드에서 모든 것을 처리 async function doEverything(data) { const validated = validate(data); // 검증 const transformed = transform(validated); // 변환 const enriched = enrich(transformed); // 보강 const saved = save(enriched); // 저장 const notified = notify(saved); // 알림 return notified; } // ✅ 각 단계를 별도 노드로 분리 // 1. 데이터_검증 노드 // 2. 데이터_변환 노드 // 3. 데이터_보강 노드 // 4. 데이터_저장 노드 // 5. 알림_발송 노드2. 팀 워크플로우에서의 오류 처리
📋 오류 처리 체크리스트
워크플로우 배포 전 필수 체크: 🔍 예외 상황 처리 □ 필수 입력 데이터가 없을 때 □ 외부 API가 응답하지 않을 때 □ 인증이 실패했을 때 □ 데이터 형식이 예상과 다를 때 ⚡ 성능 및 안정성 □ 메모리 사용량 모니터링 □ 타임아웃 설정 □ 재시도 로직 □ 배치 처리 크기 제한 📊 모니터링 및 알림 □ Error Workflow 연결 □ 로그 레벨 적절히 설정 □ 알림 대상자 지정 □ 대시보드 업데이트 🔄 복구 메커니즘 □ 백업 프로세스 정의 □ 수동 복구 절차 문서화 □ 롤백 계획 수립3. 비용 최적화를 고려한 오류 처리
💰 스마트한 재시도 전략
// 노드명: "비용효율적_재시도_로직" class CostAwareRetry { constructor() { this.costs = { 'openai': { perToken: 0.002, maxRetries: 2 }, 'gmail': { perRequest: 0, maxRetries: 5 }, 'slack': { perRequest: 0, maxRetries: 3 } }; } async executeWithCostControl(service, operation, estimatedCost = 0) { const config = this.costs[service] || { maxRetries: 1 }; let attempt = 0; while (attempt <= config.maxRetries) { try { const result = await operation(); // 성공 시 비용 로깅 console.log(`💰 ${service} 호출 성공 (시도 ${attempt + 1}, 예상비용: $${estimatedCost})`); return result; } catch (error) { attempt++; // 비용이 높은 서비스는 더 신중하게 재시도 if (estimatedCost > 0.1 && attempt > 1) { console.warn(`💸 높은 비용 서비스 재시도 중단 (${service}, $${estimatedCost})`); throw error; } if (attempt <= config.maxRetries) { const delay = Math.min(1000 * Math.pow(2, attempt), 10000); console.log(`🔄 ${service} 재시도 ${attempt}/${config.maxRetries} (${delay}ms 대기)`); await new Promise(resolve => setTimeout(resolve, delay)); } else { throw error; } } } } } const costAwareRetry = new CostAwareRetry(); // 사용 예시 try { const result = await costAwareRetry.executeWithCostControl( 'openai', () => callOpenAIAPI($json.content), 0.05 // 예상 비용 $0.05 ); return [{ json: result }]; } catch (error) { // 비용 절약을 위한 백업 로직 console.log('💡 비용 절약 모드: 로컬 처리로 전환'); return [{ json: processLocally($json.content) }]; }🎉 마무리: 오류와 친구가 되기
"오류는 적이 아니라 더 나은 시스템을 만들기 위한 귀중한 피드백이다"
지금까지 n8n의 오류 처리와 디버깅에 대한 모든 것을 다뤄봤다.
처음에는 복잡해 보일 수 있지만, 체계적으로 접근하면 오류 처리는 단순한 문제 해결을 넘어서 시스템의 신뢰성과 사용자 경험을 크게 향상시키는 핵심 요소가 된다.💡 마지막 조언
"완벽한 오류 처리 시스템은 없다. 하지만 지속적으로 개선하는 시스템은 있다."
오류 처리는 한 번 설정하고 끝나는 게 아니라, 시스템이 성장하면서 함께 진화해야 하는 살아있는 프로세스다.
오늘 배운 내용을 바탕으로 여러분만의 강력하고 안정적인 자동화 시스템을 구축해보길 바란다! 🎯

