20 KiB
76일차 개요 · 에러 처리와 예외 대응, 안정적 운영 체계
노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-30 30장 · 각 장은 가로줄(
---)로 나뉩니다. 1교시에는 1~6장을 보고, 나머지 장은 각 단계 강의(1 · 3 · 5교시) 때 함께 띄웁니다. 숫자는 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAIgpt-4.1-mini(분류), 76일차 판 데이터 API, 10/29 정답 워크플로우를 학생 순서대로 1회).
| 장 | 범위 | 띄우는 때 |
|---|---|---|
| 1~6 | 표지 · 목표 · 시간표 · 잃고 겹치는 접수 · 일하는 순서 · 실습 15개 | 1교시 시작 |
| 7~14 | 1단계 · 실패의 종류 | 1교시 강의 |
| 15~21 | 2단계 · 모으고 알리기 | 3교시 강의 |
| 22~28 | 3단계 · 재처리와 운영 체계 | 5교시 강의 |
| 29~30 | 바이브 코딩 · 정리와 예고 (8교시 35분 정리는 04-STAGE4-WRAPUP) | 7교시 · 8교시 |
1 / 30 · 표지
flowchart LR
F["실패"] --> S["나누기<br/>거절 · 재시도 · 키"] --> C["모으기<br/>DLQ"] --> A["알리기<br/>등급 · 한 번"] --> R["되돌리기<br/>재처리 · 런북"]
classDef d fill:#0F3D3E,stroke:#0F3D3E,color:#fff
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
class F d
class S,C,A,R o
76일차
에러 처리와 예외 대응, 안정적 운영 체계
실패해도 잃지 않고 · 다시 해도 겹치지 않고 · 사람이 알게
노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-30
2 / 30 · 오늘 할 수 있게 될 세 가지
| 목표 | 내용 | |
|---|---|---|
| 1 | 나눈다 | 실패를 다시 할 것 · 거절할 것으로 나누고, 항목마다 상태로 답하고, 재시도에 멱등 키를 붙이고, LLM이 멈춰도 접수하고, 입구에서 막는다 |
| 2 | 모으고 알린다 | 다시 할 실패는 원래 그대로 DLQ에, 예상 못 한 오류는 Error Workflow로, 그물 사이의 구멍을 막고, 등급을 나눠 같은 문제는 한 번만 알린다 |
| 3 | 되돌린다 | 고친 뒤 같은 입구로 재처리하고, 런북으로 짝 연습을 하고, 세 번 실패하면 사람에게 넘기고, 지금 괜찮은지 신호 하나로 |
3 / 30 · 오늘의 시간표
| 교시 | 시간 | 내용 |
|---|---|---|
| 1 | 09:00~09:50 | 개요 · 1단계 강의 · 실패의 종류 |
| 2 | 10:00~10:50 | Lab 01 실습 5개 · 망가뜨려 보기 · 항목 확인 · 재시도 · 멱등 키 · 고장 종류 · LLM 멈춤 · 요청 규칙 |
| 3 | 11:00~11:50 | 2단계 강의 · 모으고 알리기 |
| 4 | 12:00~12:50 | Lab 02 실습 5개 · DLQ · Error Workflow · 그물 지도 · 처리한 실패 알림 · 알림 폭주 |
| - | 13:00~14:00 | 점심 |
| 5 | 14:00~14:50 | 3단계 강의 · 재처리와 운영 체계 |
| 6 | 15:00~15:50 | Lab 03 실습 5개 · 재처리 · 런북 · 장애 연습 · 반쯤 처리된 요청 · parked · 운영 상태 |
| 7 | 16:00~16:50 | 바이브 코딩 tretry: 요청서 → TODO → SPEC → IMP-PLAN → 만들기 → 설치 |
| 8 | 17:00~17:50 | 발표 · 4단계 정리 (의미 · 효과 · 회사에서 쓰는 법) · 77일차 예고 |
4 / 30 · 오늘 고칠 것: 실패하면 잃고, 다시 보내면 겹치는 접수
base-intake: 문의 여러 건(requests[]) → 하나씩 LLM 분류 → 접수 건 만들기.
| 수업 준비 때 한 일 | 사용자가 받은 것 | 실제로 |
|---|---|---|
| 빈 글이 섞인 3건 | HTTP 500 | 2건은 이미 접수됨 |
잠깐 끊김 (flaky) |
500 | 0건 (재시도 없음) |
| 같은 3건을 다시 | 200 · 새 번호 3개 | VPN 문의의 접수 건이 둘 |
| API가 멈춘 동안 (고친 뒤에도 DLQ가 없으면) | failed: 2 |
화면을 닫으면 사라짐 |
| 보고서 작업의 버그 | 500 | 아무도 모름 |
5 / 30 · 일하는 순서 (오늘은 "망가뜨려 보기 → 고치기 → 다시 망가뜨려 확인")
flowchart LR
A["① 망가뜨려 보기<br/>fault · llm down · test(전)"] --> B["② 요청문<br/>실패마다 갈 곳"] --> C["③ 에이전트<br/>my-work · check"] --> D["④ 내가<br/>push · test -v · ops"]
D -->|✘| C
D -->|✔| E["⑤ RUNBOOK.md 한 줄"]
| 누가 | 무엇을 |
|---|---|
| 나 | 고장 스위치 · LLM 멈춤으로 실패를 일으키기, 요청문, 런북, 짝 연습 |
| CLI 에이전트 | AGENTS.md대로 워크플로우 JSON, check |
flow.py |
ops(접수 건 · DLQ · 알림 · 스위치), fault tickets|dlq flaky|slow|error|ok, test({"sleep": 3}) |
6 / 30 · 실습 15개 (Lab마다 예제 3 · 과제 2)
| 예제 1 | 예제 2 | 예제 3 | 과제 1 | 과제 2 | |
|---|---|---|---|---|---|
| Lab 01 | 망가뜨려 보기 (에이전트 없이) | 항목 확인 · 재시도 · 멱등 키 | 고장 종류마다 (에이전트 없이) | LLM이 멈춰도 미분류 | 같은 id · 10건 상한 |
| Lab 02 | DLQ | Error Workflow | 어느 실패가 어느 그물에 (에이전트 없이) | 처리한 실패 알림 · DLQ가 멈추면 | 알림 폭주 막기 |
| Lab 03 | 재처리 | 런북 · 장애 연습 (에이전트 없이) | 반쯤 처리된 요청 (에이전트 없이) | 세 번 실패하면 parked | 운영 상태 |
7 / 30 · 1단계 · 부분 실패: 500이 숨기는 것
생활 속 예: 마트 계산대에서 "결제 실패"라고만 들었는데, 영수증을 보니 앞의 8개는 이미 결제됨.
| 한 건 | 여러 건 (일괄) | |
|---|---|---|
| 결과 | 성공 / 실패 | 성공 / 실패 / 일부만 |
| 실패했을 때 서버에 | 대개 없음 | 이미 끝난 일 |
| 다시 보내면 | 대개 안전 | 끝난 일을 또 |
- 500을 받은 사용자는 다시 보냅니다 → 중복 (접수 · 결제 · 메일 두 통).
- 수업 준비 때: 빈 글이 섞인 3건 → 500, 접수 건 2개는 이미 생김.
8 / 30 · 실패의 종류
생활 속 예: 전화가 안 될 때: 통화 중(조금 뒤 다시) · 없는 번호(다시 걸어도 안 됨) · 전원 꺼짐(나중에).
| 종류 | 예 | 다시 하면? | 할 일 |
|---|---|---|---|
| 고칠 수 없음 | 빈 글 · 400 · 401 · 404 | 같음 | 그 항목만 거절 + 이유 |
| 잠깐 | 끊김 · 503 · 429 | 대부분 성공 | 재시도 |
| 오래감 | API가 한 시간 멈춤 | 나중에 성공 | 모아 두기 → 재처리 |
| 예상 못 함 | 버그 · 모르는 응답 | ? | 알림 |
- HTTP: 4xx = 내 요청 문제 (408 · 425 · 429 빼고), 5xx = 서버 문제, 응답 없음 = 모름.
9 / 30 · 부분 성공: 항목마다 상태
생활 속 예: 택배 배송 조회: "1~4번 배달 완료, 5번 주소 불명 반송" — 상자마다 상태.
flowchart LR
Q(["3건"]) --> V{"항목 확인"}
V -->|2건| P["분류 · 접수"] --> M["Merge"]
V -->|1건| R["거절 · 이유"] --> M
M --> O(["200 · created 2 · rejected 1"])
- 고친 뒤: 빈 글만
rejected(text is required), 1.7초 (빈 글을 LLM에 보내지 않음). - 업계: HTTP 207 Multi-Status, SQS batch item failures.
10 / 30 · 재시도: 몇 번 · 얼마나 기다리나
생활 속 예: 자판기 지폐는 펴서 몇 번 다시 넣지만, 콘서트 예매 새로고침을 모두가 연타하면 더 막힘 (재시도 폭풍).
기다림(i) = min(처음 간격 × 2^(i-1), 상한) × (1 - jitter × 무작위)
| 원칙 | 까닭 |
|---|---|
| 다시 할 실패만 | 400을 다시 보내면 부하만 |
| 지수 backoff · 상한 | 힘든 서버를 더 힘들게 하지 않게 |
| jitter | 모두 같은 순간에 다시 보내지 않게 |
| 한 곳에서 · 예산 안에서 | 층마다 3번이면 27배 (재시도 폭풍) |
- n8n:
retryOnFail·maxTries·waitBetweenTries(노드 바로 아래, 고정 간격, 노드의 항목을 모두 다시 보냄).
11 / 30 · 고장 종류마다 재시도가 하는 일 (Lab 01 예제 3)
생활 속 예: 통화 중이면 다시 걸면 되지만, 전화국이 고장이면 백 번 걸어도 시간만 감.
| 고장 | 시간 | 결과 | 재시도는 |
|---|---|---|---|
flaky |
2.4초 | created: 2 |
도움이 됨 |
error |
3.4초 | failed: 3 |
시간만 더함 |
slow |
12.4초 | failed: 3 |
사용자를 기다리게 함 |
- 그리고 8초 뒤: 실패라던 3건이 접수 건 목록에 있음. 시간 초과는 "안 됨"이 아니라 "모름".
12 / 30 · 멱등 키: 몇 번 보내도 한 번
생활 속 예: 엘리베이터 버튼을 열 번 눌러도 한 대만 옴 · 택배 송장 번호로 같은 상자를 두 번 배달하지 않음.
sequenceDiagram
participant C as my-intake
participant S as 데이터 API
C->>S: POST /tickets (key R-101)
S-->>S: T-2001 만들고 키 기억
C->>S: 다시 (key R-101)
S-->>C: 200 · T-2001 · duplicate: true
| 키 | 다시 보내면 |
|---|---|
$execution.id · 시각 |
매번 새 키 → 중복 |
요청 id R-101 |
같은 키 → 처음 번호 |
- 같은 3건을 두 번:
created: 3, duplicates: 3. 업계: StripeIdempotency-Key.
13 / 30 · 핵심 일과 덧붙인 일: 우아한 저하 (Lab 01 과제 1)
생활 속 예: 카드 단말기가 고장 난 편의점: 문을 닫지 않고 "오늘은 현금만".
| 핵심 일 (접수) | 덧붙인 일 (LLM 분류) | |
|---|---|---|
| 실패하면 | 일을 못 함 | 줄여서라도 핵심 일은 |
LLM 멈춤 (llm down) |
고치기 전 | 고친 뒤 |
|---|---|---|
| 결과 | 0.8초에 500 · 접수 0 | 0.9초 · created: 3, unclassified: 3 |
- 72일차 "간단 답변", 75일차 "예산을 넘으면 원문"과 같은 생각. 줄인 수를 숨기지 않기 (
unclassified).
14 / 30 · 입구에서 막기 (Lab 01 과제 2)
생활 속 예: 접수 창구 직원이 "방금 접수하셨는데요" 하고 같은 서류를 그 자리에서 돌려줌.
| 규칙 | 어기면 | 고치기 전 → 후 |
|---|---|---|
| 같은 요청 안의 같은 id | 두 번째만 거절 | created: 3, duplicates: 1 → created: 2, rejected: 1 |
| 한 번에 10건까지 | 전체 400 | 11.0초 · 13건 접수 → 0.2초 · 400 |
- 키는 피해를 막고, 입구 규칙은 일을 막습니다 (분류 세 번 · 11초 붙잡기).
15 / 30 · 2단계 · 결과에만 남은 실패는 사라진다
생활 속 예: 콜센터가 "장애라 접수가 안 돼요" 하고 끊으면, 고객 이름도 내용도 어디에도 안 남음.
flowchart LR
F["API 멈춤"] --> R(["200 · failed 2"]) --> X["화면을 닫으면<br/>아무 데도 없음"]
- 수업 준비 때
fault tickets error:failed: 2,ops의 DLQ 0 · 알림 0. - 실패한 일을 서버 쪽에 남겨야 고친 뒤 다시 할 수 있습니다.
16 / 30 · DLQ: 배달하지 못한 편지 보관함 (Lab 02 예제 1)
생활 속 예: 부재 시 택배는 버리지 않고 보관소에 — 상자 그대로 · 못 전한 까닭 · 시도 횟수와 함께.
| 넣는 것 | 까닭 |
|---|---|
| 원래 요청 그대로 | 재처리 때 같은 입구로 |
이유 · 어디서 · attempts |
원인 · 계속 실패하는 것 가려내기 |
| 넣지 않는 것 | 까닭 |
|---|---|
| 거절 (빈 글) | 다시 해도 같음 |
- 고친 뒤 DLQ:
D-1 R-301·D-2 R-302·attempts 1·500 - tickets service is failing. - 업계: Amazon SQS · RabbitMQ · Kafka · Azure Service Bus의 DLQ.
17 / 30 · 처리한 실패와 처리하지 못한 실패
생활 속 예: 떨어진 우유는 장보기 목록에 (예상한 일), 터진 수도관은 바로 관리실에 (예상 못 한 일).
| 처리한 | 처리하지 못한 | |
|---|---|---|
| 예 | API 멈춤 → DLQ | nightly-report의 버그 |
| 워크플로우 | 계속 · 200 | 멈춤 · 500 |
| 받는 곳 | 내가 만든 알림 | Error Workflow |
- 코드의
try/catch(노드onError)와 전역 오류 처리기(Error Workflow · Sentry)와 같은 구조.
18 / 30 · Error Workflow (Lab 02 예제 2)
생활 속 예: 자동차 경고등: 고장을 고치지는 않지만 "엔진 · 온도 높음"을 알려 정비소로 보냄.
flowchart LR
W["nightly-report<br/>settings.errorWorkflow"] -->|멈춤| E["Error Trigger"] --> A[("POST /alerts<br/>critical")]
[critical] 76 Nightly report (provided, has a bug) · unexpected · Count by category · rows is not iterable [line 4]
- 처리기 id 고정(
errHandler76001) · 게시해야 불림 (게시를 내리면 알림 0, 수업 준비 때 확인) · 응답 뒤 1~3초.
19 / 30 · 그물 지도와 구멍 (Lab 02 예제 3)
생활 속 예: 건전지가 다 된 화재경보기는 불이 나도 조용함 → 그래서 "건전지 부족" 삑 소리가 따로 있음.
| 실패 | 그물 | 수업 준비 때 |
|---|---|---|
| 빈 글 | rejected |
✔ |
| API 오류 | DLQ | ✔ DLQ 2 |
| 버그 | Error Workflow | ✔ critical |
| API 오류 + DLQ 오류 | 없음 | ✘ failed: 3, DLQ 그대로 2, 알림 없음 → 3건 사라짐 |
neverError덕분에 멈추지 않았고, 그래서 Error Workflow도 안 불렸습니다. 감시자를 누가 감시하나.
20 / 30 · 알림 등급 · 구멍 막기 (Lab 02 과제 1)
생활 속 예: 응급실 환자 분류: 심정지는 바로, 골절은 30분 안에, 감기는 몇 시간 뒤.
| 등급 | 오늘 | 받으면 |
|---|---|---|
critical |
버그, DLQ가 멈춤 | 바로 |
high |
DLQ에 쌓임, parked | 원인 확인 · 재처리 |
| 없음 | 거절, 미분류 | 보고서 |
[high] intake · dlq · count 2 · 2 request(s) could not be created and wait in the DLQ
[critical] intake · dlq_down · count 2 · DLQ save failed: … · lost ids R-201, R-203
- 요청 하나에 하나 ·
count. 결과에dlq_saved·alert.
21 / 30 · 알림 피로: 같은 문제는 한 번 (Lab 02 과제 2)
생활 속 예: 양치기 소년 · 매일 울리는 차 경보 · 알림을 꺼 버린 단톡방.
| 고치기 전 | 고친 뒤 | |
|---|---|---|
| 10분 안 두 번째 실패 | sent, dlq 알림 2 |
suppressed, dlq 알림 1 |
| DLQ | 5건 | 5건 모두 |
| critical | 보냄 | 늘 보냄 |
- 줄이는 것은 알림이지 기록이 아닙니다. 업계: Alertmanager · PagerDuty의 중복 없애기 · 묶기 · 반복 간격.
22 / 30 · 3단계 · 재처리: 같은 입구로 다시 (Lab 03 예제 1)
생활 속 예: 보관소의 택배를 다음 날 재배송하고, 전하면 목록에서 지움 — 같은 배송 절차로.
flowchart LR
F["원인 고침"] --> R["my-replay<br/>DLQ → my-intake 하나씩"] --> O{"created 1?"}
O -->|예| D[("지움")]
O -->|아니오| K["남김 · attempts +1"]
- 고친 뒤
replayed: 2, ok: 2(1.1초), DLQ 0, 접수 건 2 (중복 없음). - 직접 만들지 않고 같은 입구로 → 규칙이 하나. 업계: SQS DLQ redrive.
23 / 30 · 런북: 그때 할 일을 미리 (Lab 03 예제 2)
생활 속 예: 비행기 비상 체크리스트 · 엘리베이터 안의 "갇혔을 때" 안내문.
| 갖출 것 | 예 |
|---|---|
| 어느 알림 | "high · dlq를 받으면" |
| 명령 그대로 | uv run tools/flow.py ops |
| 볼 숫자 | "DLQ 0, 같은 key가 둘이 아닌지" |
| 갈림길 | "critical unexpected는 코드 담당에게" |
| 하지 말 것 | "DLQ를 비우지 말 것" |
| 누가 · 언제까지 | "parked는 데이터 담당이 다음 업무일까지" |
- 구글 SRE: 미리 쓴 플레이북은 회복 시간을 약 세 배 줄인다.
24 / 30 · 장애 연습: 써 둔 런북이 맞는지
생활 속 예: 소방 훈련: 불이 나기 전에 비상구 앞 상자를 발견.
sequenceDiagram
participant A as 장애를 내는 사람
participant B as 런북만 보는 사람
A->>A: fault tickets error · my-intake · nightly-report
B->>B: ops → fault ok → my-replay → ops
- 수업 준비 때: 알림 둘(high dlq · critical unexpected) — 재처리 뒤에도 critical은 남음 (버그는 재처리로 안 고쳐짐).
lab03-drill7/7, 접수 건 5 (중복 없음). 업계: game day · chaos engineering · blameless postmortem.
25 / 30 · 최소 한 번 + 멱등 = 결과는 한 번 (Lab 03 예제 3)
생활 속 예: 엘리베이터 버튼: 여러 번 눌려도(최소 한 번) 한 대만 옴(멱등).
| 순서 | 수업 준비 때 |
|---|---|
slow · 2건 |
11.6초 · failed: 2 → DLQ 2 |
| 8초 뒤 | 접수 건 이미 2개 |
| 재처리 | ok: 2, duplicates: 2, 접수 건 그대로 2 |
- DLQ의 요청은 반쯤 처리됐을 수 있습니다. 재처리가 안전한 것은 1단계의 키 덕분.
26 / 30 · 끝없이 돌리지 않기: parked (Lab 03 과제 1)
생활 속 예: 택배 재배송은 몇 번에서 멈추고 보낸 사람에게 연락 · 비밀번호 5번 틀리면 창구로.
| 고치기 전 | 고친 뒤 | |
|---|---|---|
재처리 3 (attempts 3) |
또 보냄 (2.0초) | 0.1초 · parked 2 · high 알림 |
| 고친 뒤 재처리 | 자동으로 보냄 | replayed: 0 (사람이 정함) |
- 간격 × 횟수 = 포기하기까지. 업계: SQS
maxReceiveCount, 독 메시지, human in the loop.
27 / 30 · 운영 상태: 신호 하나와 까닭 (Lab 03 과제 2)
생활 속 예: 미세먼지 앱의 "좋음 · 보통 · 나쁨"과 "마스크를 쓰세요".
| 신호 | 기준 |
|---|---|
critical |
parked · 10분 안 critical 알림 |
warn |
DLQ · 10분 안 high 알림 |
ok |
둘 다 아님 |
{"status": "critical", "dlq": 5, "parked": 0, "alerts": {"critical": 1, "high": 1},
"reasons": ["critical 알림 1건 (10분)", "DLQ 5건: 원인을 고친 뒤 재처리", "high 알림 1건 (10분)"]}
- 읽기만 (확인이 상태를 바꾸지 않게). 업계: health check · status page.
28 / 30 · 운영 체계 한 장
생활 속 예: 택배 회사에 오늘 만든 것이 모두 있음: 송장 번호 · 보관소 · 재배송 · 상황실 화면 (3단계 8절 표).
| 질문 | 답하는 것 |
|---|---|
| 잘못된 요청은? | 입구 규칙 · 거절 + 이유 |
| 잠깐 끊기면? | 재시도 + 멱등 키 |
| 오래 멈추면? | DLQ → 알림 → 재처리 |
| 예상 못 한 오류는? | Error Workflow → critical |
| 처리 장치가 멈추면? | critical dlq_down · 잃을 뻔한 id |
| 계속 실패하면? | parked → 사람 |
| 지금 괜찮은가? | 운영 상태 |
| 그때 누가 무엇을? | 런북 · 연습 |
29 / 30 · 7교시 바이브 코딩 · tretry
요청서(운영 담당의 말 ①~⑫) → Doc/TODO.md → SPEC.md → IMP-PLAN.md → 단계별로 → uv tool install.
| 오늘 n8n에서 | tretry로 n8n 밖 어떤 명령에든 |
|---|---|
retryOnFail · 고정 간격 |
지수 backoff · 상한 · jitter |
| 노드 timeout | 시도마다 --timeout, 전체 --deadline |
| 항목 거절 | 4xx는 1번 · 종료 코드 3 · 대체 안 함 |
key = 요청 id |
--idem-key · --body-key |
| 미분류 | --fallback |
| parked | 회로 차단 --breaker |
- 강사 본:
slow에서--timeout 3 --deadline 5→ 5.00초에 포기 (Lab 01 예제 3은 12.4초).
30 / 30 · 오늘의 정리 · 77일차 예고
| 단계 | 오늘 숫자 |
|---|---|
| 나누기 | 500 · 중복 → 부분 성공 · duplicates: 3, 11건 11.0초 → 0.2초 400 |
| 모으고 알리기 | 사라지던 2건 → DLQ · high 1, 버그 → critical, DLQ 멈춤 → lost ids |
| 되돌리기 | 재처리 ok: 2 · 중복 0, 반쯤 처리 duplicates: 2, parked 0.1초, 상태 ok → warn → critical → ok |
- 8교시 4단계 정리: 의미 · 효과 · 업계 이름 · 면접 · 최종 프로젝트 체크리스트.
- 77일차 (11/2): 실행 로그 분석과 운영 데이터 기반 Workflow 개선 — 오늘 남긴 실패 · 알림 · 실행 기록을 모아 어디가 자주 · 오래 실패하는지 찾고 고친 뒤 다시 잰다. 바이브 코딩
tlog.