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의 오류 처리와 디버깅에 대한 모든 것을 다뤄봤다.
처음에는 복잡해 보일 수 있지만, 체계적으로 접근하면 오류 처리는 단순한 문제 해결을 넘어서 시스템의 신뢰성과 사용자 경험을 크게 향상시키는 핵심 요소가 된다.
💡 마지막 조언
"완벽한 오류 처리 시스템은 없다. 하지만 지속적으로 개선하는 시스템은 있다."
오류 처리는 한 번 설정하고 끝나는 게 아니라, 시스템이 성장하면서 함께 진화해야 하는 살아있는 프로세스다.
오늘 배운 내용을 바탕으로 여러분만의 강력하고 안정적인 자동화 시스템을 구축해보길 바란다! 🎯
댓글 쓰기