76일차 자료 추가 (답 포함): 실습 15개 · 의도 · 생활 속 예 · 실행 결과, LAB-A · 정답 워크플로우와 25단계 실행 기록, 교안 개요 30장 · 4단계 정리, 바이브 코딩 tretry
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3f36605043
commit
bbf17e4696
141 files changed
+34072
No files matched your search
@@ -0,0 +1,487 @@
|
||||
# 76일차 개요 · 에러 처리와 예외 대응, 안정적 운영 체계
|
||||
|
||||
> 노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-30
|
||||
> 30장 · 각 장은 가로줄(`---`)로 나뉩니다. 1교시에는 1~6장을 보고, 나머지 장은 각 단계 강의(1 · 3 · 5교시) 때 함께 띄웁니다.
|
||||
> 숫자는 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-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](04-STAGE4-WRAPUP.md)) | 7교시 · 8교시 |
|
||||
|
||||
---
|
||||
|
||||
## 1 / 30 · 표지
|
||||
|
||||
```mermaid
|
||||
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일차**
|
||||
|
||||
# 에러 처리와 예외 대응, 안정적 운영 체계
|
||||
|
||||
실패해도 잃지 않고 · 다시 해도 겹치지 않고 · 사람이 알게
|
||||
|
||||
<sub>노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-30</sub>
|
||||
|
||||
---
|
||||
|
||||
## 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 · 일하는 순서 (오늘은 "망가뜨려 보기 → 고치기 → 다시 망가뜨려 확인")
|
||||
|
||||
```mermaid
|
||||
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번 주소 불명 반송" — 상자마다 상태.
|
||||
|
||||
```mermaid
|
||||
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 · 재시도: 몇 번 · 얼마나 기다리나
|
||||
|
||||
> 생활 속 예: 자판기 지폐는 펴서 몇 번 다시 넣지만, 콘서트 예매 새로고침을 모두가 연타하면 더 막힘 (재시도 폭풍).
|
||||
|
||||
```text
|
||||
기다림(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 · 멱등 키: 몇 번 보내도 한 번
|
||||
|
||||
> 생활 속 예: 엘리베이터 버튼을 열 번 눌러도 한 대만 옴 · 택배 송장 번호로 같은 상자를 두 번 배달하지 않음.
|
||||
|
||||
```mermaid
|
||||
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`. 업계: Stripe `Idempotency-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단계 · 결과에만 남은 실패는 사라진다
|
||||
|
||||
> 생활 속 예: 콜센터가 "장애라 접수가 안 돼요" 하고 끊으면, 고객 이름도 내용도 어디에도 안 남음.
|
||||
|
||||
```mermaid
|
||||
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)
|
||||
|
||||
> 생활 속 예: 자동차 경고등: 고장을 고치지는 않지만 "엔진 · 온도 높음"을 알려 정비소로 보냄.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
W["nightly-report<br/>settings.errorWorkflow"] -->|멈춤| E["Error Trigger"] --> A[("POST /alerts<br/>critical")]
|
||||
```
|
||||
|
||||
```text
|
||||
[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 | 원인 확인 · 재처리 |
|
||||
| 없음 | 거절, 미분류 | 보고서 |
|
||||
|
||||
```text
|
||||
[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)
|
||||
|
||||
> 생활 속 예: 보관소의 택배를 다음 날 재배송하고, 전하면 목록에서 지움 — 같은 배송 절차로.
|
||||
|
||||
```mermaid
|
||||
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 · 장애 연습: 써 둔 런북이 맞는지
|
||||
|
||||
> 생활 속 예: 소방 훈련: 불이 나기 전에 비상구 앞 상자를 발견.
|
||||
|
||||
```mermaid
|
||||
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-drill` 7/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` | 둘 다 아님 |
|
||||
|
||||
```json
|
||||
{"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단계 정리](04-STAGE4-WRAPUP.md): 의미 · 효과 · 업계 이름 · 면접 · 최종 프로젝트 체크리스트.
|
||||
- 77일차 (11/2): **실행 로그 분석과 운영 데이터 기반 Workflow 개선** — 오늘 남긴 실패 · 알림 · 실행 기록을 모아 어디가 자주 · 오래 실패하는지 찾고 고친 뒤 다시 잰다. 바이브 코딩 `tlog`.
|
||||
@@ -0,0 +1,99 @@
|
||||
# 76일차 실습 준비 · 수업 전에 미리 해 두면 좋은 것
|
||||
|
||||
- 수업일: 2026-10-30 (76일차) · 에러 처리와 예외 대응, 안정적 운영 체계
|
||||
- 준비 시간: 약 10분
|
||||
- 먼저 WSL에서 `cd ~/aio-01d4-stu && git pull`로 오늘 자료를 받습니다 (수강생 저장소: https://gl.isitsoft.kr/publicproj/aio-01d4-stu).
|
||||
- **수업 전날까지** 1~2절을 끝내 주세요.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. n8n · 데이터 API 켜기<br/>(76일차 판)"] --> B["2. 기준 · nightly-report 게시 ·<br/>doctor ready"] --> C["3. 미리 읽기"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. n8n과 데이터 API 켜기 (전날 필수, 3분)
|
||||
|
||||
오늘은 **76일차 판 데이터 API**(75일차 + 접수 건 만들기 · DLQ · 알림 · 고장 스위치 `dlq`)를 씁니다. 같은 이름이라 이 폴더에서 다시 만들면 바뀝니다.
|
||||
|
||||
```bash
|
||||
sudo systemctl disable --now ollama 2>/dev/null # WSL에 설치한 Ollama가 있으면 끄고 자동 시작도 끔
|
||||
cd ~/aio-01d4-stu/Last3Mon/65/n8n
|
||||
docker compose rm -sf ollama # Ollama 컨테이너가 있으면 지움 (없으면 그냥 지나감)
|
||||
docker compose start
|
||||
cd ~/aio-01d4-stu/Last3Mon/76/data-api
|
||||
docker compose up -d --force-recreate
|
||||
sleep 3 # 데이터 API가 뜰 때까지
|
||||
curl -s localhost:8800/dlq # {"size": 0, "parked": 0, "items": []}
|
||||
```
|
||||
|
||||
- **66일차부터 LLM은 OpenAI API 키(`Last3Mon/.env`, Credential `OpenAI (.env)`)만 씁니다.** 오늘 LLM은 문의 분류에만 씁니다 (`gpt-4.1-mini`). Ollama는 쓰지 않습니다.
|
||||
- 75일차의 예산 · 사용량 기록 · 고장 스위치는 데이터 API를 새로 띄우면 처음으로 돌아갑니다.
|
||||
- `network day65-n8n_default not found` → n8n이 켜져 있지 않음. 위 n8n 부분부터 다시.
|
||||
|
||||
## 2. 기준 · nightly-report 게시와 준비 확인 (전날 필수, 3분)
|
||||
|
||||
오늘 망가뜨려 볼 **기준 일괄 접수**와 일부러 버그가 있는 **nightly-report**를 게시해 둡니다 (만들지 않고 게시만).
|
||||
|
||||
```bash
|
||||
cd ~/aio-01d4-stu/Last3Mon/76
|
||||
uv run tools/flow.py push reference/baseline-intake.json reference/nightly-report.json
|
||||
uv run tools/flow.py call base-intake requests/batch-ok.json # processed 3
|
||||
uv run tools/flow.py ops --reset
|
||||
uv run tools/flow.py doctor
|
||||
```
|
||||
|
||||
```text
|
||||
✔ n8n container day65-n8n-n8n-1
|
||||
✔ n8n http://localhost:5678
|
||||
✔ data API http://localhost:8800
|
||||
✔ dead letter queue /dlq · alerts · fault switch dlq (day 76 data API)
|
||||
✔ n8n → http://data-api:8800
|
||||
✔ credential 'OpenAI (.env)' (openAiCourse0001)
|
||||
✔ node catalog (for check)
|
||||
✔ CLI coding agent: codex, claude, agy
|
||||
ready
|
||||
```
|
||||
|
||||
| ✘ 항목 | 할 일 |
|
||||
| --- | --- |
|
||||
| n8n container · data API | 1절 |
|
||||
| dead letter queue | 데이터 API가 예전 판 → 1절 `--force-recreate` · `sleep 3` |
|
||||
| `! fault switch is on` · `! DLQ is not empty` | `uv run tools/flow.py ops --reset` |
|
||||
| credential | `cd ~/aio-01d4-stu/Last3Mon/65/n8n && bash register-credentials.sh` |
|
||||
| CLI coding agent | [67일차 준비](../67/00-DAY67-PREP.md) 2절 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 미리 읽어 오면 좋은 내용 (선택, 약 5분)
|
||||
|
||||
### 3.1 500을 받았다면 아무것도 안 된 걸까
|
||||
|
||||
문의 세 건을 한꺼번에 보냈는데 "오류"가 났습니다. 다시 보내야 할까요? 그런데 사실은 두 건이 이미 접수돼 있었다면? 다시 보내면 어떻게 될까요? 오늘 첫 실습에서 직접 겪어 봅니다.
|
||||
|
||||
### 3.2 용어
|
||||
|
||||
| 용어 | 한 줄 설명 |
|
||||
| --- | --- |
|
||||
| 부분 성공 | 여러 건 중 일부만 성공. 무엇이 성공하고 실패했는지 알려 주기 |
|
||||
| 멱등 (idempotent) | 같은 요청을 여러 번 보내도 결과가 한 번 보낸 것과 같음 (키로 알아봄) |
|
||||
| DLQ | Dead Letter Queue — 처리하지 못한 요청을 버리지 않고 모아 두는 곳 |
|
||||
| Error Workflow | n8n에서 다른 워크플로우가 오류로 멈추면 대신 실행되는 워크플로우 |
|
||||
| 런북 (runbook) | 장애가 났을 때 무엇을 어떤 순서로 할지 적은 대응 절차 |
|
||||
| 재시도 · backoff | 실패하면 다시 해 보기 · 다시 할 때마다 기다리는 시간을 늘리기 |
|
||||
| 알림 피로 | 알림이 너무 많아 아무도 보지 않게 되는 것 |
|
||||
|
||||
### 3.3 7교시 요청서
|
||||
|
||||
[VIBE-REQ-TRETRY](../../Last3Mon/76/VIBE-REQ-TRETRY.md)를 한 번 읽어 옵니다. 운영 담당이 원하는 것 열두 가지 중 "꼭"은 무엇일까요? 그리고 "고칠 수 없는 실패"는 무엇일까요?
|
||||
|
||||
## 4. 당일 준비물
|
||||
|
||||
- 충전된 노트북과 충전기, WSL의 Docker, Windows Terminal(WSL) 창 **두 개**, 웹 브라우저
|
||||
- 오후 장애 연습(Lab 03 예제 2)은 **옆 사람과 짝**으로 합니다
|
||||
|
||||
## 확인
|
||||
|
||||
- [ ] `curl -s localhost:8800/dlq`가 `size 0` · `parked 0`
|
||||
- [ ] `call base-intake`가 `processed 3`
|
||||
- [ ] `uv run tools/flow.py doctor` → 마지막 줄 `ready`
|
||||
@@ -0,0 +1,418 @@
|
||||
# 1단계 · 실패의 종류
|
||||
|
||||
- 교시: 1교시 강의 (09:00~09:50) · 2교시 [Lab 01](../../Last3Mon/76/LAB-01-Q-FAILURE-TYPES.md) 실습 5개 (10:00~10:50)
|
||||
- 이 단계가 끝나면: 여러 건을 처리하는 워크플로우에서 실패를 종류별로 나누고, 항목 하나의 실패가 전체를 망치지 않게 부분 성공으로 답하고, 재시도의 효과와 한계를 고장 종류별로 말하고, 재시도 · 시간 초과에도 중복이 생기지 않게 멱등 키를 쓰고, 덧붙인 일(LLM 분류)이 실패해도 핵심 일(접수)을 끝내고, 요청 전체의 규칙을 입구에서 지킬 수 있다.
|
||||
- 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini`(분류), 76일차 판 데이터 API, 10/29 실습을 5개로 늘린 뒤 정답 워크플로우를 학생 순서대로 1회). 출력 전부는 수업 뒤 공개되는 `Last3Mon/76/answers/LAB-TEST-RESULT.md`에 있습니다.
|
||||
- 실습 가리키기: 이 문서에서 "Lab 01 예제 1"은 2교시 [Lab 01 문서](../../Last3Mon/76/LAB-01-Q-FAILURE-TYPES.md)의 그 절입니다. 파일 경로(`cases/` · `requests/` · `my-work/` · `reference/`)는 실습 폴더 `~/aio-01d4-stu/Last3Mon/76` 기준입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 500의 거짓말"] --> B["2. 실패의 종류"] --> C["3. 부분 성공"] --> D["4. 재시도"] --> E["5. 시간 초과는 '모름'"]
|
||||
E --> F["6. 멱등 키"] --> G["7. 핵심 일과 덧붙인 일"] --> H["8. 입구에서 막기"] --> I["9. 요청문(프롬프트)으로"]
|
||||
```
|
||||
|
||||
## 0. 이 시간 (50분)
|
||||
|
||||
| 시간 | 절 · 개요 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| 09:00~09:06 | 개요 1~6장 | 오늘 하루, 실패하면 잃고 다시 보내면 겹치는 접수, 실습 15개 |
|
||||
| 09:06~09:12 | 1 · 개요 7장 | 부분 실패, 500이 숨기는 것 |
|
||||
| 09:12~09:18 | 2 · 개요 8장 | 실패의 종류, 4xx · 5xx · 시간 초과 |
|
||||
| 09:18~09:23 | 3 · 개요 9장 | 부분 성공, 항목마다 상태 |
|
||||
| 09:23~09:31 | 4 · 5 · 개요 10~11장 | 재시도 · backoff · jitter · 재시도 폭풍, 시간 초과 |
|
||||
| 09:31~09:37 | 6 · 개요 12장 | 멱등 키 |
|
||||
| 09:37~09:44 | 7 · 8 · 개요 13~14장 | 우아한 저하, 입구에서 막기 |
|
||||
| 09:44~09:50 | 9 · 10 · 확인 질문 | 요청문, 자주 하는 실수 |
|
||||
|
||||
### 2교시 Lab 01에서 이어서 하는 것
|
||||
|
||||
| 2교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
|
||||
| --- | --- | --- | --- |
|
||||
| 시작 (10:00) | — | 런북 복사 | `cp templates/RUNBOOK-TEMPLATE.md my-work/RUNBOOK.md` (오늘 본 실패를 모두 여기에) |
|
||||
| Lab 01 예제 1 (10:00~10:10, 에이전트 없이) | 1 · 2 | 기준을 망가뜨려 보기: 빈 글 · `flaky` · 다시 보내기 | `call base-intake requests/batch-*.json`, `ops` → `RUNBOOK.md` 1.1 |
|
||||
| Lab 01 예제 2 (10:10~10:25) | 3 · 4 · 6 | 항목 확인 · 재시도 · 멱등 키 · 부분 성공 | `my-work/intake.json` (기준을 복사), 테스트 `cases/lab01.json` |
|
||||
| Lab 01 예제 3 (10:25~10:32, 에이전트 없이) | 4 · 5 | 고장 종류마다 재시도가 하는 일, 늦게 생긴 접수 건 | `test cases/lab01-faults.json -v` → `RUNBOOK.md` 1.2 |
|
||||
| Lab 01 과제 1 (10:32~10:40) | 7 | LLM이 멈춰도 미분류로 접수 | 테스트 `cases/lab01-challenge.json` (`llm down` · `up`) |
|
||||
| Lab 01 과제 2 (10:40~10:50) | 8 | 같은 id · 10건 상한 | 테스트 `cases/lab01-challenge2.json` |
|
||||
|
||||
## 1. 500의 거짓말: 여러 건을 처리할 때의 부분 실패 (2교시 Lab 01 예제 1)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 마트 계산대.** 물건 10개를 계산하다 9번째 우유의 바코드가 안 찍혀 계산원이 "결제 실패입니다"라고만 말했다고 합시다. 손님은 아무것도 안 샀다고 생각하고 다시 줄을 섭니다. 그런데 영수증을 보면 앞의 8개는 **이미 카드로 결제됐습니다.** 다시 계산하면 8개 값을 두 번 냅니다. "실패" 한마디가 "8개는 됐고, 우유 하나가 안 됐다"를 숨긴 것입니다. 여러 건을 한 번에 처리하는 워크플로우의 500이 바로 이 "결제 실패"입니다.
|
||||
|
||||
**한 건과 여러 건은 실패의 모양이 다릅니다.** 요청 하나가 일 하나만 하면, 결과는 "됐다" 아니면 "안 됐다"입니다. 요청 하나가 일 여러 개를 하면(일괄 처리, batch) 세 번째 모양이 생깁니다: **일부만 됐다.** 이것을 부분 실패(partial failure)라고 부릅니다.
|
||||
|
||||
| | 한 건 | 여러 건 (일괄) |
|
||||
| --- | --- | --- |
|
||||
| 결과 | 성공 / 실패 | 성공 / 실패 / **일부만 성공** |
|
||||
| 실패했을 때 서버에 남은 것 | 대개 없음 | **이미 끝난 일**이 남아 있음 |
|
||||
| 다시 보내면 | 대개 안전 | 끝난 일을 **또** 함 (중복) |
|
||||
|
||||
**분산 시스템의 기본 성질입니다.** 워크플로우는 여러 서비스(LLM · 데이터 API · 메일 · DB)를 부릅니다. 이 서비스들은 서로 다른 곳에서 돌고, 그 사이의 네트워크는 언제든 끊기거나 느려집니다. 분산 컴퓨팅 분야에서는 오래전부터 "네트워크는 믿을 수 있다", "지연은 0이다"를 대표적인 **잘못된 가정**(fallacies of distributed computing)으로 꼽습니다. 그래서 "부르면 된다"가 아니라 "부르다 실패할 수 있다, 그것도 **중간에**"를 전제로 설계합니다.
|
||||
|
||||
**500이 숨기는 것.** 서버가 일부를 처리하다 멈추면 많은 시스템은 그냥 500(서버 오류)을 돌려줍니다. 받는 쪽은 500을 보고 "아무것도 안 됐다"고 생각하고 다시 보냅니다. 그러면:
|
||||
|
||||
| 사용자가 아는 것 | 실제 |
|
||||
| --- | --- |
|
||||
| "실패했다" | 일부는 이미 처리됨 |
|
||||
| "다시 보내면 된다" | 처리된 일부가 **두 번** 됨 (중복 접수 · 중복 결제 · 메일 두 통) |
|
||||
| "어디가 실패했는지 모른다" | 서버의 실행 기록에만 있음 |
|
||||
|
||||
**은행 이체로 생각해 보기.** 열 사람에게 급여를 보내는 일괄 이체에서 다섯 번째에 오류가 나 "실패"가 떴습니다. 담당자가 다시 누르면 앞의 네 사람은 급여를 두 번 받습니다. 그래서 금융 · 결제 시스템은 일괄 처리의 결과를 **건마다** 돌려주고, 같은 요청을 다시 받으면 이미 한 것은 하지 않게 만듭니다 (3 · 6절).
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
기준 `base-intake`(문의 여러 건 → 하나씩 LLM 분류 → 접수 건 만들기)를 수업 준비 때 망가뜨려 본 결과입니다 (Lab 01 예제 1).
|
||||
|
||||
| 한 일 | 사용자가 받은 것 | 실제로 |
|
||||
| --- | --- | --- |
|
||||
| 가운데 항목의 글이 빈 3건 | **HTTP 500** · `Error in workflow` | 1번째 · 3번째 **2건은 이미 접수됨** |
|
||||
| `flaky`(키마다 첫 시도만 실패)에서 2건 | 500 | 0건 (재시도 없음) |
|
||||
| 정상 3건을 다시 보냄 | 200 · 새 번호 3개 | 같은 VPN 문의의 접수 건이 **둘** (T-2001 · T-2006) |
|
||||
|
||||
- 2번째의 빈 제목에 데이터 API가 400을 주자 접수 노드가 오류로 멈췄고, 그 앞뒤 항목은 이미 만들어져 있었습니다.
|
||||
- 같은 3건을 다시 보내 중복이 생긴 것은 키가 `$execution.id + 순서`(실행마다 바뀜)여서입니다 (6절).
|
||||
|
||||
## 2. 실패의 종류: 다시 하면 되는 것과 안 되는 것
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 전화 걸기.** 전화가 안 될 때도 까닭이 여럿입니다. **통화 중**이면 조금 뒤 다시 걸면 됩니다 (잠깐의 실패). **없는 번호**면 백 번 걸어도 안 됩니다 (잘못된 입력 → 다시 하지 말고 번호를 고침). **전원이 꺼져 있음**이면 지금은 안 되지만 몇 시간 뒤엔 될 수 있습니다 (오래 멈춤 → 나중에). 셋을 똑같이 "안 됨"으로 처리하면, 없는 번호에 계속 걸거나 통화 중인 사람을 포기하게 됩니다.
|
||||
|
||||
실패를 하나로 다루면 대응도 하나뿐입니다 ("500, 끝"). 실패를 **다시 하면 되는지**로 나누면 대응이 달라집니다.
|
||||
|
||||
| 종류 | 예 | 다시 하면? | 할 일 | 오늘 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **고칠 수 없는 실패** (permanent) | 빈 글, 없는 사용자, 형식 오류, 권한 없음 | 몇 번을 해도 같음 | **그 항목만 거절**, 이유를 알려 고쳐서 보내게 | 1단계 |
|
||||
| **잠깐의 실패** (transient) | 네트워크 끊김, 서버 순간 오류, 잠깐의 과부하 | 대부분 성공 | **재시도** (간격을 두고 몇 번) | 1단계 |
|
||||
| **오래가는 실패** | API가 한 시간 멈춤, 공급자 장애 | 지금은 실패, 나중엔 성공 | **모아 두었다가** 고친 뒤 다시 | 2 · 3단계 (DLQ · 재처리) |
|
||||
| **예상 못 한 실패** | 버그, 모르는 응답 모양 | ? | **알림**, 사람이 봄 | 2단계 (Error Workflow) |
|
||||
|
||||
**HTTP 상태 코드로 나누기.** HTTP를 쓰는 API는 상태 코드가 실패의 종류를 알려 줍니다.
|
||||
|
||||
| 코드 | 뜻 | 다시 하면? |
|
||||
| --- | --- | --- |
|
||||
| 2xx | 성공 | — |
|
||||
| 400 · 401 · 403 · 404 · 422 | **내 요청이 잘못됨** (형식 · 인증 · 권한 · 없는 것) | 아니오 — 요청을 고쳐야 함 |
|
||||
| 408 · 425 · 429 | 시간 초과 · 너무 이름 · **너무 많이 보냄** | 예 — 기다린 뒤 (`Retry-After`) |
|
||||
| 500 · 502 · 503 · 504 | **서버 쪽 문제** | 대개 예 |
|
||||
| (응답 없음) 시간 초과 · 연결 실패 | 모름 (5절) | 예 — 단, 멱등이어야 안전 (6절) |
|
||||
|
||||
Microsoft와 AWS의 클라우드 설계 지침은 모두 이 구분을 "**일시적 오류 처리**(transient fault handling)"라는 이름으로 첫머리에 둡니다: 잠깐의 실패만 다시 하고, 고칠 수 없는 실패는 다시 하지 않는다.
|
||||
|
||||
**LLM API도 같습니다.** 틀린 키(401)나 잘못된 매개변수(400, 75일차의 `max_tokens`)는 다시 해도 같고, 429(속도 제한)와 5xx(공급자 장애)는 기다리면 됩니다. LLM에는 하나가 더 있습니다: **답은 왔는데 모양이 틀린 것**(JSON을 달라 했는데 글이 옴). 이것은 다시 하면 될 수도 있고, 대체 값으로 넘어갈 수도 있습니다 (7절).
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
| 오늘의 실패 | 종류 | 오늘의 대응 | 실습 |
|
||||
| --- | --- | --- | --- |
|
||||
| 빈 글 · 빠진 id · user | 고칠 수 없음 | 그 항목만 `rejected` + 이유 | Lab 01 예제 2 |
|
||||
| 같은 요청 안의 같은 id · 10건 넘음 | 고칠 수 없음 (요청 규칙) | 두 번째만 거절 · 전체 400 | Lab 01 과제 2 |
|
||||
| 데이터 API `flaky` | 잠깐 | 재시도 3번 | Lab 01 예제 2 · 3 |
|
||||
| 데이터 API `error` · `slow` | 오래감 | `failed` → DLQ → 재처리 | Lab 01 예제 3 → Lab 02 · 03 |
|
||||
| LLM 멈춤 (틀린 키) | 오래감 (덧붙인 일) | 미분류로 접수 | Lab 01 과제 1 |
|
||||
| `nightly-report`의 버그 | 예상 못 함 | Error Workflow → critical 알림 | Lab 02 예제 2 |
|
||||
|
||||
## 3. 부분 성공: 항목마다 상태를 돌려주기 (2교시 Lab 01 예제 2)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 택배 배송 조회.** 택배 다섯 상자를 보냈을 때 배송 조회 화면은 "배송 실패" 한 줄을 보여 주지 않습니다. "1~4번 **배달 완료**, 5번 **주소 불명으로 반송**"처럼 상자마다 상태를 보여 줍니다. 보낸 사람은 5번의 주소만 고쳐 다시 보내면 됩니다. 오늘의 부분 성공(HTTP 200 + 항목마다 `created` · `failed` · `rejected`)이 이 배송 조회 화면입니다.
|
||||
|
||||
**"모두 성공 아니면 500" 대신 "무엇이 되고 무엇이 안 됐는지".** 여러 건을 받는 API는 결과를 **항목마다** 돌려줍니다. 전체 응답은 200(요청 자체는 받았다)이고, 항목마다 성공 · 실패 · 거절과 이유를 담습니다.
|
||||
|
||||
| 방식 | 예 |
|
||||
| --- | --- |
|
||||
| 항목마다 상태 | 오늘 `results: [{id, status: created \| failed \| rejected, reason}]` |
|
||||
| HTTP 207 Multi-Status | 여러 자원의 결과를 한 응답에 (WebDAV에서 정의) |
|
||||
| 실패한 것만 따로 | AWS Lambda가 SQS 묶음을 처리할 때 실패한 메시지 id만 돌려주는 방식 (batch item failures) |
|
||||
| 성공 · 실패 개수 + 목록 | 많은 일괄 처리 API (예: 이메일 대량 발송 결과) |
|
||||
|
||||
**받는 쪽이 할 일이 분명해집니다.** `rejected`는 고쳐서, `failed`는 나중에 다시(혹은 서버가 모아 두었다가), `created`는 끝. 500 하나로는 이 셋을 나눌 수 없습니다.
|
||||
|
||||
**n8n에서 항목마다 처리하기.** n8n의 노드는 들어온 **항목(item)마다** 일합니다. 그래서 일괄 처리는 자연스럽게 "항목 → 확인(If) → 처리 / 거절 → 다시 모으기(Merge)"가 됩니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Q(["3건"]) --> V{"항목 확인"}
|
||||
V -->|맞음 2건| P["분류 · 접수"] --> M["Merge<br/>(입력 0)"]
|
||||
V -->|빠짐 1건| R["거절 · 이유"] --> M2["Merge<br/>(입력 1)"]
|
||||
M & M2 --> O(["200 · created 2 · rejected 1"])
|
||||
```
|
||||
|
||||
- Merge는 두 갈래 중 한쪽이 **비어도** 모읍니다. 그래야 "모두 맞음" · "모두 거절"에서도 결과가 나갑니다.
|
||||
- 결과의 순서를 지킵니다. Code 노드에서 앞 노드의 `.all()`과 **같은 순서**로 맞춰야 이유가 엉뚱한 항목에 붙지 않습니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- 고친 뒤 빈 글이 섞인 3건 → 200 · `created: 2, rejected: 1`, 이유 `text is required`. 빈 글을 LLM에 보내지 않아 **1.7초** (Lab 01 예제 2).
|
||||
- 계약은 `AGENTS.md` 3절: `{total, created, failed, rejected, duplicates, unclassified, results: [...]}`.
|
||||
|
||||
## 4. 재시도: 언제 · 몇 번 · 얼마나 기다리나 (2교시 Lab 01 예제 2 · 3)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 자판기와 맛집 줄.** 자판기가 지폐를 뱉으면 한 번 펴서 다시 넣어 봅니다 (재시도). 세 번 뱉으면 다른 지폐를 꺼냅니다 (횟수 상한). 그런데 인기 콘서트 예매에서 모두가 1초마다 새로고침을 누르면 서버가 더 막힙니다 (**재시도 폭풍**). 다시 시도할 때는 기다리는 시간을 점점 늘리고 (1초 → 2초 → 4초, **backoff**), 사람마다 조금씩 다른 순간에 다시 오게 해야 (**jitter**) 한꺼번에 몰리지 않습니다. 맛집이 "30분 뒤에 오세요"라고 할 때 모두가 정확히 30분 뒤에 오면 또 줄이 막히는 것과 같습니다.
|
||||
|
||||
**재시도 정책의 네 가지.**
|
||||
|
||||
| 정하는 것 | 뜻 | 흔한 값 |
|
||||
| --- | --- | --- |
|
||||
| 무엇을 | 다시 할 실패만 (2절) | 5xx · 429 · 시간 초과 · 연결 실패 |
|
||||
| 몇 번 | 첫 시도를 포함한 최대 수 | 3~5 |
|
||||
| 얼마나 기다리나 | 시도 사이 간격 | 고정 · **지수 증가(exponential backoff)** |
|
||||
| 언제 그만 | 횟수 · **전체 마감 시간** | 사용자가 기다릴 수 있는 시간 안 |
|
||||
|
||||
**지수 backoff와 상한.** 0.5 → 1 → 2 → 4초처럼 기다림을 두 배씩 늘립니다. 이미 힘든 서버에 0.1초마다 다시 보내면 더 힘들어지기 때문입니다. 끝없이 늘지 않게 **상한**(예: 8초)을 둡니다.
|
||||
|
||||
```text
|
||||
기다림(i) = min(처음 간격 × 2^(i-1), 상한)
|
||||
```
|
||||
|
||||
**jitter (흔들기).** 서버가 잠깐 멈췄다 살아나는 순간, 그동안 실패한 모든 클라이언트가 **같은 간격**으로 기다렸다가 **같은 순간에** 다시 보냅니다. 서버는 살아나자마자 다시 쓰러집니다 (thundering herd). 기다림에 작은 무작위를 섞으면 다시 보내는 시각이 흩어집니다. AWS 설계 글 "Timeouts, retries and backoff with jitter"가 이것을 자세히 다룹니다.
|
||||
|
||||
**재시도 폭풍 (retry storm).** 재시도는 부하를 **곱합니다.** 세 단계를 거치는 요청에서 단계마다 3번씩 다시 하면 맨 아래 서비스는 최대 3 × 3 × 3 = 27배의 요청을 받습니다. 그래서:
|
||||
|
||||
| 원칙 | 까닭 |
|
||||
| --- | --- |
|
||||
| 한 곳에서만 다시 한다 (보통 가장 바깥 또는 가장 가까운 곳) | 층마다 다시 하면 곱해짐 |
|
||||
| 재시도 예산을 둔다 | 예: 전체 요청의 10%까지만 재시도 (Google SRE 책) |
|
||||
| 계속 실패하면 잠시 부르지 않는다 | 회로 차단기(circuit breaker) — 7교시 `tretry` R10 |
|
||||
| 고칠 수 없는 실패는 다시 하지 않는다 | 400을 다시 보내는 것은 부하만 늘림 |
|
||||
|
||||
**재시도는 사용자를 기다리게 합니다.** 시도마다 시간 제한이 3초이고 3번 하면, 실패를 알기까지 9초 넘게 걸립니다. 사용자가 기다릴 수 있는 시간(예: 5초)을 먼저 정하고 그 안에서 횟수를 정합니다 (전체 마감, deadline).
|
||||
|
||||
**n8n의 재시도.** 노드 설정(Settings 탭)의 Retry On Fail · Max Tries · Wait Between Tries입니다. JSON으로는 노드 바로 아래 `retryOnFail` · `maxTries` · `waitBetweenTries`(밀리초)이고, `parameters` 안에 쓰면 무시됩니다. n8n의 기다림은 **고정 간격**이고(지수 증가 없음), 시도 수는 5번까지입니다. 그리고 **노드 단위**라서, 노드에 들어온 항목을 **모두** 다시 보냅니다. 이미 성공한 항목도요 (6절).
|
||||
|
||||
**재시도는 어디에 두나.** 같은 재시도라도 두는 자리에 따라 하는 일이 다릅니다.
|
||||
|
||||
| 자리 | 예 | 좋은 점 | 조심할 것 |
|
||||
| --- | --- | --- | --- |
|
||||
| 부르는 노드 (오늘) | n8n `retryOnFail` | 설정만으로, 노드마다 | 고정 간격, 항목 모두 다시 |
|
||||
| 코드 · 라이브러리 | SDK의 기본 재시도, tenacity · Polly · resilience4j | 지수 backoff · jitter · 상태 코드별 | 층마다 두면 곱해짐 |
|
||||
| 큐 · 나중에 | DLQ → 재처리 (2 · 3단계) | 사용자를 기다리게 하지 않음 | 결과를 나중에 알려야 함 |
|
||||
| 부르는 사람 | 사용자가 다시 누름 | — | 키가 없으면 중복 |
|
||||
|
||||
- **짧은 실패는 가까이서, 긴 실패는 큐로**: 1초 안에 풀릴 끊김은 노드 재시도로, 몇 분 · 몇 시간 갈 장애는 DLQ에 모았다가 나중에. 오늘 하루가 이 순서입니다.
|
||||
- LLM 공급자 SDK들도 429 · 5xx에 몇 번 다시 하는 기본값을 가진 경우가 많습니다. 그 위에 n8n 재시도를 또 두면 층이 둘입니다. 어느 층에서 다시 하는지 알고 씁니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
같은 `my-intake`(재시도 3번 · 간격 0.3초 · 시간 제한 3초)를 고장 셋에서 잰 결과입니다 (Lab 01 예제 3).
|
||||
|
||||
| 고장 | 시간 | 결과 | 재시도는 |
|
||||
| --- | --- | --- | --- |
|
||||
| `flaky` (키마다 첫 시도만 500) | 2.4초 | `created: 2` | **도움이 됨** |
|
||||
| `error` (늘 500) | 3.4초 | `failed: 3` | 시간만 더함 |
|
||||
| `slow` (8초 뒤 답) | **12.4초** | `failed: 3` (`timeout of 3000ms exceeded` × 3) | 사용자를 12초 기다리게 함 |
|
||||
|
||||
## 5. 시간 초과는 "실패"가 아니라 "모름" (2교시 Lab 01 예제 3)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 식당 주문.** 식당에서 주문한 지 40분째 음식이 안 나옵니다. 주방에 주문이 **안 들어간** 걸까요, 들어갔는데 **늦는** 걸까요? 손님은 알 수 없습니다. 지나가는 다른 직원에게 같은 음식을 또 주문하면, 잠시 뒤 두 접시가 나올 수 있습니다. 안전한 방법은 "주문 번호 12번, 들어갔나요?"라고 **같은 번호로 묻는 것**입니다. 시간 초과는 "실패"가 아니라 이 "모름"이고, 주문 번호가 6절의 멱등 키입니다.
|
||||
|
||||
**응답이 오지 않은 요청은 세 가지 중 하나입니다.**
|
||||
|
||||
| 무슨 일이 있었나 | 서버에 |
|
||||
| --- | --- |
|
||||
| 요청이 서버에 닿지 않음 | 아무것도 없음 |
|
||||
| 서버가 처리하는 중 (느림) | **곧 생김** |
|
||||
| 서버가 처리했는데 응답이 돌아오다 끊김 | **이미 있음** |
|
||||
|
||||
보낸 쪽에서는 셋을 **구분할 수 없습니다.** 그래서 시간 초과 뒤 다시 보내면 두 번째 · 세 번째 경우에 같은 일이 두 번 됩니다. 이것이 분산 시스템에서 "정확히 한 번(exactly-once)"이 어려운 까닭입니다. 실무의 답은 "**최소 한 번(at-least-once)** 보내고, **받는 쪽이 멱등**하게"입니다 (6절).
|
||||
|
||||
**시간 제한은 꼭 둡니다.** 시간 제한이 없으면 느린 서버 하나가 워크플로우 전체를 붙잡습니다. 그 사이 다른 요청도 줄을 섭니다. 시간 제한은 "포기하고 다음으로 간다"는 결정이지, "서버가 안 했다"는 사실이 아닙니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- `slow`에서 워크플로우는 세 번 포기하고 `failed: 3`이라고 답했습니다. 그런데 8초 뒤 접수 건 목록에 R-101~103이 **있었습니다** (데이터 API가 늦게 처리). 시도는 9번 갔지만 키가 같아 3건만 생겼습니다 (Lab 01 예제 3).
|
||||
- 같은 현상을 Lab 03 예제 3에서 재처리로 다시 봅니다: DLQ에 들어간 요청이 사실은 이미 처리된 것 → 재처리는 `duplicates: 2`.
|
||||
|
||||
## 6. 멱등 키: 몇 번 보내도 한 번 한 것처럼 (2교시 Lab 01 예제 2)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
**멱등(idempotent)**은 수학에서 온 말로, 여러 번 해도 한 번 한 것과 결과가 같은 성질입니다. 쉽게 말해 "**한 번 보내든 세 번 보내든 마지막 상태가 같다**"입니다. 재시도가 안전한지가 여기서 갈립니다.
|
||||
|
||||
**GET · PUT · DELETE는 원래 멱등.** 쇼핑몰의 내 주소로 보면:
|
||||
|
||||
| 요청 | 하는 일 | 세 번 보내면 |
|
||||
| --- | --- | --- |
|
||||
| GET 내 주소 | 읽기 | 같은 주소를 세 번 읽음, 바뀌는 것 없음 |
|
||||
| PUT 내 주소 = "서울시 강남구" | 이 값으로 **덮어쓰기** | 세 번 덮어써도 주소는 "서울시 강남구" 하나 |
|
||||
| DELETE 장바구니 3번 상품 | 지우기 | 첫 번에 지워지고 그다음은 지울 것이 없음, "3번 없음" 그대로 |
|
||||
|
||||
**POST(새로 만들기)는 멱등이 아님.** 보낼 때마다 새 것이 하나씩 생깁니다.
|
||||
|
||||
| 요청 | 세 번 보내면 |
|
||||
| --- | --- |
|
||||
| POST 주문 | 주문 3건 |
|
||||
| POST 결제 | 카드에서 3번 빠짐 |
|
||||
| POST 메일 보내기 | 같은 메일 3통 |
|
||||
| POST 문의 접수 (오늘) | 같은 문의가 접수 건 3개 |
|
||||
|
||||
**문제는 "다시 보내기"에서.** 결제 버튼을 눌렀는데 30초째 돌기만 한다면, 보내는 쪽은 둘을 구별할 수 없습니다 (5절: 시간 초과는 "모름").
|
||||
|
||||
- (가) 요청이 서버에 닿지도 않았다 → 다시 보내야 함
|
||||
- (나) 서버는 결제를 끝냈는데 **응답만** 오다가 끊겼다 → 다시 보내면 두 번 결제
|
||||
|
||||
**멱등 키(idempotency key)로 POST를 멱등하게.** 택배 송장 번호처럼, 보내는 쪽이 요청마다 **고유한 번호표(키)**를 붙입니다. 받는 쪽은 처리하면서 "키 R-101 → 접수 T-2001"을 적어 두고, 같은 키가 다시 오면 **새로 만들지 않고 적어 둔 처음 결과를 돌려줍니다.** 그러면 POST를 열 번 보내도 접수는 하나, PUT처럼 몇 번 보내도 안전해집니다.
|
||||
|
||||
- 키는 **처음 보낼 때 한 번 정하고, 재시도에도 똑같이** 보냅니다. 재시도마다 새 키를 만들면 받는 쪽은 처음 보는 요청으로 알고 또 만듭니다 (아래 "무엇을 키로 쓰나").
|
||||
- 키는 **받는 쪽이 기억해 줘야** 효과가 있습니다. 보내는 쪽만 키를 붙이고 받는 쪽이 무시하면 소용없습니다.
|
||||
- 한 줄로: 재시도는 "다시 보내기", 멱등 키는 "다시 보내도 한 번만 처리되게 하는 번호표". 그래서 둘은 짝입니다.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as 보내는 쪽
|
||||
participant S as 받는 쪽
|
||||
C->>S: POST /tickets (key R-101)
|
||||
S-->>S: 새로 만듦 T-2001, 키 기억
|
||||
S--xC: 응답이 끊김
|
||||
C->>S: POST /tickets (key R-101) 다시
|
||||
S-->>C: 200 · T-2001 · duplicate: true (새로 만들지 않음)
|
||||
```
|
||||
|
||||
| 어디서 쓰나 | 예 |
|
||||
| --- | --- |
|
||||
| 결제 API | Stripe는 `Idempotency-Key` 헤더를 받아 같은 키의 결제를 한 번만 처리 |
|
||||
| 표준 | IETF에서 `Idempotency-Key` HTTP 헤더를 표준으로 만드는 중 (초안) |
|
||||
| 메시지 처리 | 메시지 id로 이미 처리한 것을 건너뜀 (중복 제거) |
|
||||
| 오늘 데이터 API | `POST /tickets`의 `key`가 같으면 `duplicate: true` |
|
||||
|
||||
**무엇을 키로 쓰나.** 키는 "**같은 요청이면 같고, 다른 요청이면 다른**" 값이어야 합니다.
|
||||
|
||||
| 키 | 같은 요청을 다시 보내면 | 판정 |
|
||||
| --- | --- | --- |
|
||||
| 실행 번호 (`$execution.id`) | 매번 새 키 | ✘ 중복 |
|
||||
| 현재 시각 · 무작위 | 매번 새 키 | ✘ 중복 |
|
||||
| **요청의 업무 id** (`R-101`, 주문 번호) | 같은 키 | ✔ |
|
||||
| 요청 내용의 해시 | 같은 내용이면 같은 키 | 내용이 같은 **다른** 요청도 막힘 — 주의 |
|
||||
|
||||
**재시도와 멱등 키는 짝입니다.****키를 언제까지 기억하나.** 받는 쪽은 키를 영원히 기억할 수 없습니다. 결제 API들은 보통 키를 정해진 기간(예: 하루) 동안만 기억합니다. 그 기간 안의 재시도 · 재처리만 안전하다는 뜻이므로, 재처리를 며칠 뒤에 돌린다면 키를 기억하는 기간도 함께 정해야 합니다. 오늘 데이터 API는 다시 켜기 전까지 기억합니다 (`ops --reset`이 지움).
|
||||
|
||||
**같은 키에 다른 내용이 오면.** 키는 같은데 내용이 다르면(같은 `R-101`에 다른 글) 대개 보내는 쪽의 버그입니다. 잘 만든 API는 이때 처음 결과를 돌려주지 않고 오류(예: 409 · 422)를 줍니다. 오늘 데이터 API는 단순하게 처음 번호를 돌려줍니다.
|
||||
|
||||
4절에서 본 것처럼 n8n의 재시도는 노드의 항목을 **모두** 다시 보냅니다. 키가 없으면 재시도가 곧 중복입니다. 5절의 시간 초과도 마찬가지입니다. 그래서 "재시도를 켠다"와 "멱등 키를 둔다"는 늘 함께 정합니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- 키를 요청 `id`로 바꾼 뒤 같은 3건을 두 번 보내면 `created: 3, duplicates: 3`, 접수 건은 3개 그대로 (Lab 01 예제 2).
|
||||
- `flaky`에서 재시도가 두 건을 **모두** 다시 보냈지만 `created: 2`, 중복 없음.
|
||||
|
||||
## 7. 핵심 일과 덧붙인 일: 우아한 저하 (2교시 Lab 01 과제 1)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 정전 난 편의점과 내비게이션.** 편의점의 카드 단말기가 고장 나도 가게 문을 닫지 않습니다. "오늘은 현금만 받아요"로 **줄여서라도 판매(핵심 일)를 계속**합니다. 내비게이션의 실시간 교통 정보가 끊겨도 길 안내는 멈추지 않고, "교통 정보 없음" 표시를 띄운 채 기본 경로로 안내합니다. 줄였다는 것을 **숨기지 않는** 것도 같습니다. 오늘 LLM 분류(덧붙인 일)가 멈추면 접수(핵심 일)는 "미분류"로 계속하고, `unclassified` 수로 줄인 양을 알려 줍니다.
|
||||
|
||||
**워크플로우의 일을 둘로 나눕니다.**
|
||||
|
||||
| | 핵심 일 | 덧붙인 일 |
|
||||
| --- | --- | --- |
|
||||
| 뜻 | 이 워크플로우가 있는 이유 | 핵심 일을 더 낫게 함 |
|
||||
| 오늘 | 문의 **접수** | LLM **분류** (분야 · 우선순위) |
|
||||
| 실패하면 | 일을 못 함 | 덜 좋게라도 핵심 일은 함 |
|
||||
|
||||
**우아한 저하(graceful degradation).** 덧붙인 일이 실패하면 **줄여서라도** 핵심 일을 끝냅니다. 쇼핑몰의 추천 기능이 멈추면 인기 상품을 보여 주고 주문은 받습니다. 지도 앱의 실시간 교통 정보가 멈추면 평소 소요 시간으로 길을 안내합니다. 72일차에는 LLM이 멈추면 FAQ 원문으로 "간단 답변"을, 75일차에는 예산을 넘으면 원문으로 답했습니다.
|
||||
|
||||
**줄였다는 것을 숨기지 않습니다.** 대체 값으로 넘어갔다면 결과에 그 수를 남깁니다 (오늘 `unclassified`). 그래야 나중에 사람이 몇 건을 다시 봐야 하는지 알고, 대체가 너무 자주 일어나면 알아챕니다.
|
||||
|
||||
**LLM 노드의 실패 셋.**
|
||||
|
||||
| 실패 | 예 | n8n에서 |
|
||||
| --- | --- | --- |
|
||||
| 호출 실패 | 틀린 키 · 공급자 장애 · 시간 초과 | 노드 `onError: continueRegularOutput` → 항목에 `error` |
|
||||
| 답은 왔는데 모양이 틀림 | JSON 대신 글 · 없는 분야 이름 | 다음 Code에서 `JSON.parse` 실패를 잡음 |
|
||||
| 답이 틀림 | 분야를 잘못 고름 | 테스트 · 사람 검토 (73일차 평가) |
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- 고치기 전 `llm down`(틀린 키): 분류 노드가 멈춰 **0.8초에 500, 접수 0건**.
|
||||
- 고친 뒤: 분류는 실패했지만 `created: 3, unclassified: 3` (0.9초). 미분류 접수 건이 목록에 남고, `llm up` 뒤 평소 분류는 그대로 (Lab 01 과제 1).
|
||||
|
||||
## 8. 입구에서 막기: 요청 전체의 규칙 (2교시 Lab 01 과제 2)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
**잘못된 요청은 비싼 일을 하기 전에 막는 것이 가장 쌉니다** (validate at the edge). LLM · 외부 API · DB를 부른 뒤에 실패하면 시간 · 돈 · 정리할 일이 남습니다.
|
||||
|
||||
| 규칙의 수준 | 예 | 어기면 |
|
||||
| --- | --- | --- |
|
||||
| 항목 | 빈 글 · 빠진 칸 | 그 항목만 거절 |
|
||||
| 항목 사이 | 같은 요청 안의 같은 id | 두 번째부터 거절 |
|
||||
| 요청 전체 | 목록이 아님 · **너무 많음** | 요청 전체를 400 |
|
||||
|
||||
**왜 묶음에 상한을 두나.** 상한이 없으면 한 요청이 몇 분씩 자원을 붙잡고, 그동안 다른 요청이 기다립니다. 실패했을 때 다시 보낼 양도 커집니다. 많은 API가 한 번에 받을 수를 정해 둡니다 (예: Amazon SQS의 묶음 보내기는 한 번에 10개). 상한을 넘으면 일부만 처리하지 않고 **전체를 거절**합니다. 어디까지 됐는지 사용자가 알기 어렵기 때문입니다.
|
||||
|
||||
**멱등 키가 있어도 입구 규칙이 필요한 까닭.** 같은 id 두 개는 키 덕분에 접수 건이 하나만 생깁니다. 하지만 결과는 두 건을 접수했다고 말하고, LLM 분류를 두 번 합니다. 키는 **피해를 막고**, 입구 규칙은 **일을 막습니다.**
|
||||
|
||||
예: 한 요청 안에 같은 문의가 실수로 두 번 들어옴 (`R-401` · `R-401` · `R-402`). 입구 규칙이 없으면 세 줄을 모두 정상 문의로 보고 하나씩 처리합니다.
|
||||
|
||||
| 줄 | 입구 규칙 없음 | 입구 규칙 있음 |
|
||||
| --- | --- | --- |
|
||||
| R-401 | 분류 → 접수 `T-2001` 새로 | 분류 → 접수 `T-2001` 새로 |
|
||||
| R-401 (또) | 분류 **한 번 더** → 접수 → 키가 같아 `T-2001` 그대로 (`duplicate: true`) | **입구에서 바로 거절**, 분류 · 접수 안 함 |
|
||||
| R-402 | 분류 → 접수 `T-2002` 새로 | 분류 → 접수 `T-2002` 새로 |
|
||||
| 답 | `created: 3` (실제 접수 건은 2개) | `created: 2, rejected: 1` |
|
||||
|
||||
- **키가 막은 것 (피해)**: 접수 건이 정말로 둘 생기는 일. 담당자가 같은 문의를 두 번 처리하지 않습니다.
|
||||
- **키가 못 막은 것 (일)**: 두 번째 R-401의 LLM 분류(돈 · 시간), 접수 API 한 번 더, 실제와 다른 숫자(`created: 3`). 키는 **받는 쪽 끝에서** 일하므로, 거기까지 가는 동안의 일은 그대로 다 일어납니다.
|
||||
- 비유: 멱등 키는 물류 센터의 **송장 번호 확인**입니다. 같은 상자가 두 번 와도 배송은 한 번이지만, 상자는 이미 트럭에 실려 왔습니다. 입구 규칙은 **접수 창구 직원**입니다. "방금 접수하셨는데요" 하고 그 자리에서 돌려보내, 트럭에 싣지도 않습니다.
|
||||
- 그래도 키는 필요합니다. 입구 규칙은 **같은 요청 안**의 중복만 봅니다. 다른 요청으로 따로 오는 중복(재시도 · 재처리)은 입구에서 알 수 없고, 키가 막습니다. 그래서 둘 다 둡니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
| | 고치기 전 | 고친 뒤 |
|
||||
| --- | --- | --- |
|
||||
| 같은 id 두 번 + 다른 하나 | `created: 3, duplicates: 1` (분류 세 번) | `created: 2, rejected: 1` (`duplicate id in this request`) |
|
||||
| 11건 | **11.0초** · 200 · 접수 건 13 | **0.2초** · 400 `too many requests (max 10)` · 접수 건 그대로 |
|
||||
|
||||
## 9. 요청문(프롬프트)으로 에이전트에게 맡기기
|
||||
|
||||
에러 처리 요청문은 "에러 처리를 잘 해 줘"가 아니라 **실패마다 어디로 가는지**를 적습니다 (`AGENTS.md` 1절: 에이전트는 먼저 "어떤 실패가 어디로 가는지" 표를 보여 줌).
|
||||
|
||||
| 넣을 것 | 예 |
|
||||
| --- | --- |
|
||||
| 실패의 종류와 갈 곳 | "빈 글은 `rejected`와 이유, API 실패는 재시도 3번 뒤 `failed`" |
|
||||
| 재시도 설정과 자리 | "`Create ticket`에 timeout 3000ms, `maxTries` 3, `waitBetweenTries` 300 (노드 바로 아래)" |
|
||||
| 실패해도 계속 | "`onError: continueRegularOutput`, 다음 Code가 앞 노드와 같은 순서로" |
|
||||
| 멱등 키 | "`key`는 요청의 `id` (실행 번호가 아님)" |
|
||||
| 결과 모양 · 상태 코드 | "AGENTS.md 3절 모양으로 HTTP 200, 목록이 아니면 400" |
|
||||
| 기대 결과 | "`cases/lab01.json`" |
|
||||
|
||||
## 10. 자주 하는 실수
|
||||
|
||||
| 실수 | 결과 | 바로잡기 |
|
||||
| --- | --- | --- |
|
||||
| 재시도 설정을 `parameters` 안에 | 재시도가 안 됨 (조용히) | 노드 바로 아래 (`flow.py check`가 알려 줌) |
|
||||
| 재시도만 켜고 키는 그대로 | 재시도가 곧 중복 | 키 = 요청 id |
|
||||
| 400도 다시 | 같은 실패 × 3, 부하만 늚 | 고칠 수 없는 실패는 거절 |
|
||||
| 재시도 횟수만 늘림 | 사용자가 오래 기다림 (`slow` 12초) | 기다릴 수 있는 시간 안에서 |
|
||||
| 실패한 항목을 결과에서 뺌 | "모두 성공"처럼 보임 | 항목마다 상태, 실패도 그대로 |
|
||||
| LLM 실패로 접수까지 멈춤 | 장애 동안 들어온 문의를 모두 잃음 | 덧붙인 일은 대체 값으로 |
|
||||
| 대체했다는 것을 남기지 않음 | 얼마나 줄였는지 모름 | `unclassified` 같은 수 |
|
||||
|
||||
## 확인 질문
|
||||
|
||||
<details><summary>1. 빈 글이 섞인 3건에 500이 났는데 접수 건 2개가 생겼습니다. 사용자는 무엇을 하고, 그러면 어떻게 되나요?</summary>
|
||||
|
||||
사용자는 실패로 알고 다시 보냅니다. 키가 실행 번호라서 데이터 API는 새 요청으로 보고, 앞서 만들어진 두 건이 또 생깁니다. 그래서 항목마다 결과를 돌려주고(부분 성공), 키를 요청 id로 바꿉니다.
|
||||
</details>
|
||||
|
||||
<details><summary>2. 재시도를 3번에서 5번으로 늘리면 좋아지나요?</summary>
|
||||
|
||||
고장에 따라 다릅니다. 잠깐의 실패(`flaky`)는 이미 2번째에 됩니다. 늘 실패(`error`)는 결과가 같고 시간만 늘고, 느림(`slow`)은 사용자를 더 오래 기다리게 합니다 (3번에 12.4초). 그리고 여러 층이 다시 하면 부하가 곱해집니다. 기다릴 수 있는 시간 · 재시도 예산 안에서 정하고, 오래가는 실패는 DLQ로 보냅니다.
|
||||
</details>
|
||||
|
||||
<details><summary>3. 시간 초과로 `failed`라고 답한 요청을 다시 보내도 안전한가요?</summary>
|
||||
|
||||
멱등 키가 있으면 안전합니다. 시간 초과는 "안 됐다"가 아니라 "모른다"입니다. 오늘 `slow`에서 실패라던 3건이 8초 뒤 만들어져 있었습니다. 같은 키로 다시 보내면 `duplicate: true`로 처음 번호를 받고, 키가 없으면 두 번째 접수 건이 생깁니다.
|
||||
</details>
|
||||
|
||||
<details><summary>4. LLM 분류가 멈췄을 때 접수까지 멈추면 안 되는 까닭은? 대신 무엇을 남기나요?</summary>
|
||||
|
||||
접수는 핵심 일, 분류는 덧붙인 일입니다. 덧붙인 일의 실패로 핵심 일을 멈추면 장애 동안 들어온 문의를 모두 잃습니다. 미분류 · 보통으로 접수하고, 결과의 `unclassified`에 그 수를 남겨 나중에 사람이 몇 건을 분류해야 하는지 알게 합니다.
|
||||
</details>
|
||||
@@ -0,0 +1,303 @@
|
||||
# 2단계 · 모으고 알리기
|
||||
|
||||
- 교시: 3교시 강의 (11:00~11:50) · 4교시 [Lab 02](../../Last3Mon/76/LAB-02-Q-COLLECT-AND-ALERT.md) 실습 5개 (12:00~12:50)
|
||||
- 이 단계가 끝나면: 다시 하면 될 실패를 원래 요청 그대로 DLQ에 모으고, 예상하지 못한 오류를 Error Workflow로 알리고, 실패마다 어느 그물에 걸리는지 지도를 그려 구멍(DLQ가 멈출 때)을 찾아 막고, 알림의 등급과 양을 정해 알림 피로를 막을 수 있다.
|
||||
- 숫자: 모두 강사 노트북 실측입니다 (10/29, 정답 워크플로우를 학생 순서대로 1회). 출력 전부는 수업 뒤 공개되는 `Last3Mon/76/answers/LAB-TEST-RESULT.md`에 있습니다.
|
||||
- 실습 가리키기: "Lab 02 예제 1"은 4교시 [Lab 02 문서](../../Last3Mon/76/LAB-02-Q-COLLECT-AND-ALERT.md)의 그 절입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 사라지는 실패"] --> B["2. DLQ"] --> C["3. 처리한 실패 ·<br/>처리 못 한 실패"] --> D["4. Error Workflow"]
|
||||
D --> E["5. 그물 지도와 구멍"] --> F["6. 알림 등급"] --> G["7. 알림 피로"] --> H["8. 요청문(프롬프트)으로"]
|
||||
```
|
||||
|
||||
## 0. 이 시간 (50분)
|
||||
|
||||
| 시간 | 절 · 개요 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| 11:00~11:07 | 1 · 개요 15장 | 결과에만 남은 실패는 사라진다 |
|
||||
| 11:07~11:15 | 2 · 개요 16장 | DLQ: 무엇을 · 왜 · 넣지 않을 것 |
|
||||
| 11:15~11:25 | 3 · 4 · 개요 17~18장 | 처리한 · 처리하지 못한 실패, Error Workflow |
|
||||
| 11:25~11:32 | 5 · 개요 19장 | 그물 지도, 감시자를 누가 감시하나 |
|
||||
| 11:32~11:42 | 6 · 7 · 개요 20~21장 | 알림 등급, 알림 피로, 같은 알림은 한 번 |
|
||||
| 11:42~11:50 | 8 · 9 · 확인 질문 | 요청문, 자주 하는 실수 |
|
||||
|
||||
### 4교시 Lab 02에서 이어서 하는 것
|
||||
|
||||
| 4교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
|
||||
| --- | --- | --- | --- |
|
||||
| Lab 02 예제 1 (12:00~12:12) | 1 · 2 | 실패한 요청은 원래 그대로 DLQ | `my-work/intake.json`, `fault tickets error`, 테스트 `cases/lab02.json` 앞 두 케이스, `ops` |
|
||||
| Lab 02 예제 2 (12:12~12:24) | 3 · 4 | 에러 처리기 `errHandler76001`, `nightly-report`의 알림 | `my-work/error-handler.json`, 테스트 `cases/lab02.json` |
|
||||
| Lab 02 예제 3 (12:24~12:30, 에이전트 없이) | 5 | 실패 넷이 어느 그물에, DLQ도 멈추면 | `test cases/lab02-nets.json -v` (5/7이 맞음) → `RUNBOOK.md` 1.3 |
|
||||
| Lab 02 과제 1 (12:30~12:40) | 5 · 6 | 처리한 실패 high 알림, DLQ가 멈추면 critical | 테스트 `cases/lab02-challenge.json`, `lab02-nets` 7/7 |
|
||||
| Lab 02 과제 2 (12:40~12:50) | 7 | 같은 이유의 high 알림은 10분에 한 번 | 테스트 `cases/lab02-challenge2.json` |
|
||||
|
||||
## 1. 결과에만 남은 실패는 사라진다 (4교시 Lab 02 예제 1)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 콜센터 전화.** 고객센터에 전화했더니 상담원이 "지금 시스템 장애라 접수가 안 됩니다. 나중에 다시 걸어 주세요"라고 하고 끊었습니다. 상담원은 정직하게 말했지만, 고객의 이름도 문의 내용도 **어디에도 적히지 않았습니다.** 고객이 다시 걸지 않으면 그 문의는 영영 사라집니다. 상담원이 메모지에 "김민지, 화면 공유 문제, 장애 끝나면 연락"이라고 적어 두기만 해도 달라집니다. 그 메모지가 DLQ입니다.
|
||||
|
||||
1단계에서 실패를 **결과에** 정직하게 남겼습니다 (`failed: 2`). 그런데 결과는 그 요청을 보낸 화면 · 프로그램에만 갑니다. 화면을 닫거나, 보낸 프로그램이 결과를 기록하지 않으면 실패도 함께 사라집니다. 오래가는 실패(API가 한 시간 멈춤)는 그 사이 들어온 요청을 **모두** 이렇게 잃습니다.
|
||||
|
||||
**실패한 일을 서버 쪽에 남겨야 합니다.** 그래야 원인을 고친 뒤 다시 할 수 있습니다. 이것이 DLQ이고, 3단계의 재처리가 그것을 꺼내 다시 돌립니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- `fault tickets error` 동안 `my-intake`: 200 · `failed: 2`. `ops`의 DLQ 0 · 알림 0 — 두 문의(R-301 · R-302)는 응답 JSON 말고는 아무 데도 없습니다 (Lab 02 예제 1 고치기 전).
|
||||
|
||||
## 2. DLQ: 배달하지 못한 편지 보관함 (4교시 Lab 02 예제 1)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 택배 보관소.** 집에 아무도 없어 택배를 못 전하면 기사는 상자를 버리지 않고 **보관소**에 둡니다. 보관소에는 ① 상자 **그대로**(뜯거나 바꾸지 않음), ② **왜** 못 전했는지("수취인 부재"), ③ 몇 번째 배송 시도인지가 남습니다. 다음 날 **같은 상자를 다시** 배송하고, 전하면 보관소 목록에서 지웁니다. 하지만 "주소가 아예 없는 번지"인 상자는 보관해도 다시 배송할 수 없으니 보관소가 아니라 보낸 사람에게 돌려보냅니다 (오늘의 `rejected`).
|
||||
|
||||
**DLQ(dead letter queue)**는 처리하지 못한 메시지를 버리지 않고 옮겨 두는 곳입니다. 우체국이 주소가 틀린 편지를 따로 모아 두는 것(dead letter office)에서 온 이름입니다. 메시지 시스템마다 있습니다.
|
||||
|
||||
| 시스템 | DLQ |
|
||||
| --- | --- |
|
||||
| Amazon SQS | 큐마다 DLQ를 지정, 정해진 횟수(`maxReceiveCount`) 실패하면 옮김, 원래 큐로 되돌리기(redrive) |
|
||||
| RabbitMQ | dead letter exchange: 거절 · 시간 초과 · 길이 초과 메시지를 다른 곳으로 |
|
||||
| Apache Kafka (Kafka Connect 등) | 처리 실패 메시지를 따로 토픽(DLQ 토픽)에 |
|
||||
| Azure Service Bus | 큐마다 dead-letter 하위 큐 |
|
||||
| 오늘 데이터 API | `POST /dlq` · `GET /dlq` · `/dlq/update` |
|
||||
|
||||
**무엇을 넣나.**
|
||||
|
||||
| 넣는 것 | 까닭 |
|
||||
| --- | --- |
|
||||
| **원래 요청 그대로** | 재처리 때 같은 입구로 다시 보냄. 중간 결과(분류)는 그때 다시 만들어짐 |
|
||||
| 실패 이유 | 사람이 원인을 앎 (`500 - tickets service is failing`, `timeout`) |
|
||||
| 어디서 (워크플로우 · 단계) | DLQ 하나를 여러 워크플로우가 함께 쓸 때 |
|
||||
| 몇 번째인지 (`attempts`) | 계속 실패하는 것을 가려냄 (3단계 parked) |
|
||||
| 언제 | 오래된 것부터, 보관 기간 |
|
||||
|
||||
**넣지 않는 것.** 다시 해도 같은 실패(고칠 수 없는 실패: 빈 글 · 형식 오류)는 DLQ에 넣지 않고 그 자리에서 **거절**합니다. DLQ에 섞이면 재처리할 때마다 같은 실패가 되풀이되고, 정말 살려야 할 것이 묻힙니다. DLQ는 "**나중에 다시 하면 될 것**"만 모읍니다.
|
||||
|
||||
**개인정보 주의.** DLQ에는 원래 요청이 그대로 들어갑니다. 문의 글에 개인정보가 있으면 DLQ에도 있습니다 (73일차 가리기). 접근 권한과 보관 기간을 정하고, 재처리가 끝난 항목은 지웁니다.
|
||||
|
||||
**DLQ도 운영해야 합니다.** DLQ는 넣기만 하면 끝나는 곳이 아니라, 지켜보고 비워 가는 곳입니다.
|
||||
|
||||
| 운영할 것 | 까닭 | 흔한 방법 |
|
||||
| --- | --- | --- |
|
||||
| 크기 | 쌓이면 무언가 계속 실패 중 | 크기가 0보다 크면 알림 (오늘 high · 3단계 운영 상태) |
|
||||
| 가장 오래된 것의 나이 | 재처리가 밀렸나 | "1시간 넘은 것이 있으면" 알림 |
|
||||
| 보관 기간 | 개인정보 · 저장 공간 | SQS는 메시지 보관 기간을 정함 (최대 14일) |
|
||||
| 재처리 도구 | 사람이 쉽게 되돌리게 | redrive 버튼 · 재처리 워크플로우 (3단계) |
|
||||
| 이유별 수 | 같은 원인인가 | `reason`으로 묶어 세기 (77일차) |
|
||||
|
||||
- DLQ가 하나도 비지 않고 자라기만 한다면, 그것은 "잃지 않음"이 아니라 "**버리는 곳을 옮겼을 뿐**"입니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- 고친 뒤 DLQ에 `D-1 R-301` · `D-2 R-302`가 원래 요청 · 이유 · `attempts 1`과 함께 (Lab 02 예제 1).
|
||||
- 오늘 데이터 API는 같은 `item.id`가 다시 들어오면 새 줄 없이 `attempts`를 올립니다 (Lab 03에서 씀).
|
||||
|
||||
## 3. 처리한 실패와 처리하지 못한 실패
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 집의 고장.** 우유가 떨어지는 것은 **예상한 일**입니다. 냉장고 앞 장보기 목록에 적어 두고 장 볼 때 삽니다 (처리한 실패 → 정해 둔 곳에 모음). 수도관이 갑자기 터지는 것은 **예상 못 한 일**입니다. 목록에 적을 일이 아니라 바로 관리실에 연락해야 합니다 (처리하지 못한 실패 → 마지막 그물이 사람을 부름). 둘을 같은 방법으로 다루면, 우유 때문에 관리실을 부르거나 터진 수도관을 장보기 목록에 적게 됩니다.
|
||||
|
||||
| | 처리한 실패 (handled) | 처리하지 못한 실패 (unhandled) |
|
||||
| --- | --- | --- |
|
||||
| 뜻 | 예상하고 갈 곳을 정해 둔 실패 | 예상하지 못해 워크플로우가 멈춘 실패 |
|
||||
| 예 | API가 멈춤 → DLQ, 빈 글 → 거절 | 버그, 모르는 응답 모양, 누가 바꾼 설정 |
|
||||
| 워크플로우 | 계속, 200 | **멈춤**, 500 |
|
||||
| 알리는 곳 | 내가 만든 알림 (6절) | **Error Workflow** (n8n이 부름) |
|
||||
|
||||
**둘 다 있어야 합니다.** 처리한 실패만 있으면 예상하지 못한 버그는 아무도 모릅니다. 처리하지 못한 실패의 그물(Error Workflow)만 있으면 API가 멈출 때마다 워크플로우가 멈추고, 그 실행의 다른 항목도 함께 잃습니다. 예상한 것은 **그 자리에서**, 예상하지 못한 것은 **마지막 그물로**.
|
||||
|
||||
**프로그래밍 언어와 같은 구조입니다.** 코드에서 `try/catch`로 예상한 예외를 잡고, 잡지 못한 예외는 프로그램 전체의 처리기(웹 서버의 500 처리, 오류 추적 도구 Sentry 등)가 받아 기록 · 알림합니다. n8n에서는 노드의 `onError`가 `try/catch`, Error Workflow가 전체 처리기입니다.
|
||||
|
||||
## 4. Error Workflow: n8n이 부르는 마지막 그물 (4교시 Lab 02 예제 2)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 자동차 경고등.** 운전자는 차의 모든 고장을 미리 알 수 없습니다. 그래서 차는 엔진에 무슨 일이 생기든 **계기판에 경고등**을 켭니다. 경고등은 고장을 고치지 않습니다. 대신 "어디(엔진)가, 무엇이(온도 높음)" 문제인지 알려서 정비소에 가게 합니다. Error Workflow도 버그를 고치지 않습니다. 어느 워크플로우 · 어느 노드 · 무슨 오류인지 알림을 남겨 사람이 고치게 합니다. 서커스의 맨 아래 **안전망**처럼, 다른 장치가 모두 놓친 것을 받는 마지막 그물입니다.
|
||||
|
||||
**n8n의 Error Workflow.** 워크플로우 설정에 `errorWorkflow`(에러 처리기 워크플로우의 id)를 두면, 그 워크플로우가 처리하지 못한 오류로 멈출 때 n8n이 에러 처리기를 부릅니다. 에러 처리기는 **Error Trigger** 노드로 시작합니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
W1["접수 워크플로우"] & W2["nightly-report"] & W3["다른 워크플로우"] -->|settings.errorWorkflow| E["에러 처리기<br/>(Error Trigger)"]
|
||||
E --> C["알림 만들기<br/>워크플로우 · 노드 · 오류 · 실행 번호"] --> A[("알림 저장 · 메신저 · 메일")]
|
||||
```
|
||||
|
||||
| Error Trigger가 넘기는 것 | 알림에 | 쓰임 |
|
||||
| --- | --- | --- |
|
||||
| `workflow.name` · `workflow.id` | 어느 워크플로우 | 담당자 찾기 |
|
||||
| `execution.lastNodeExecuted` | 어느 노드 | 고칠 곳 |
|
||||
| `execution.error.message` | 무슨 오류 | 원인 짐작 |
|
||||
| `execution.id` | 어느 실행 | n8n 화면 Executions에서 그 실행을 열어 노드마다 입력 · 출력을 봄 |
|
||||
|
||||
**처리기 하나를 여러 워크플로우가 함께 씁니다.** 그래서 처리기의 id를 고정하고(오늘 `errHandler76001`, 70일차 모듈처럼), 워크플로우마다 설정에 그 id를 적습니다. 처리기도 **게시**되어 있어야 합니다.
|
||||
|
||||
**알림은 응답 뒤에 갑니다.** Error Workflow는 실패한 실행이 끝난 **뒤에** 따로 돕니다. 부른 사람은 이미 500을 받았고, 알림은 1~3초 늦게 생깁니다.
|
||||
|
||||
**알림에 넣지 말 것.****에러 처리기가 할 수 있는 일.** 알림만이 아닙니다.
|
||||
|
||||
| 일 | 예 |
|
||||
| --- | --- |
|
||||
| 알림 | 메신저 · 메일 · 호출 (등급에 따라 다른 곳) |
|
||||
| 기록 | 오류 기록 저장소 · 오류 추적 도구에 남김 (나중에 묶어 셈, 77일차) |
|
||||
| 일감 만들기 | 같은 오류가 처음이면 이슈 · 티켓을 만듦 |
|
||||
| 정리 | 반쯤 한 일을 되돌림 (보상 트랜잭션, saga) |
|
||||
|
||||
**에러 처리기도 실패합니다.** 처리기 안의 알림 HTTP가 실패하면 그 알림은 그냥 사라집니다. 그래서 처리기는 **아주 단순하게**(노드 몇 개, 외부 호출 하나) 두고, 처리기가 보낸 알림이 실제로 도착하는지는 따로 확인합니다 (가끔 일부러 오류를 내 보는 연습, 3단계).
|
||||
|
||||
오류 문장에는 요청 내용 · 키 · 토큰이 섞일 수 있습니다 (73일차). 알림 채널(메신저)은 많은 사람이 봅니다. 길이를 자르고, 비밀은 지우고, 자세한 것은 실행 번호로 n8n에서 보게 합니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- `nightly-report`(일부러 버그)는 고친 뒤에도 500입니다. 달라진 것은 3초 뒤 알림입니다:
|
||||
`[critical] 76 Nightly report (provided, has a bug) · unexpected · Count by category · rows is not iterable [line 4]` (Lab 02 예제 2).
|
||||
- 수업 준비 때 확인: 에러 처리기를 **게시 해제**하면 n8n이 부르지 않아 알림이 0입니다.
|
||||
|
||||
## 5. 그물 지도와 구멍: 감시자를 누가 감시하나 (4교시 Lab 02 예제 3 · 과제 1)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 건전지가 다 된 화재경보기.** 화재경보기는 불을 알리는 장치입니다. 그런데 건전지가 다 되면 불이 나도 **조용합니다.** 그래서 경보기는 건전지가 떨어지면 따로 "삑" 소리를 냅니다. 감시하는 장치가 멈춘 것을 알리는 한 겹이 더 있는 것입니다. 오늘 DLQ가 멈추면 실패한 문의를 넣을 곳이 없는데 워크플로우는 조용히 끝났습니다. 택배 보관소 문이 잠겨 기사가 상자를 그냥 들고 돌아간 셈이니, 최소한 송장 번호(잃을 뻔한 id)를 메모로 남겨야 합니다.
|
||||
|
||||
**실패마다 어느 그물에 걸리는지 표로 그립니다.** 그물이 여럿이면 그물 **사이**에 구멍이 생기기 쉽습니다.
|
||||
|
||||
| 실패 | 그물 | 사람이 아는 방법 |
|
||||
| --- | --- | --- |
|
||||
| 잘못된 항목 | 결과의 `rejected` | 부른 쪽이 결과를 봄 |
|
||||
| API 오류 | DLQ | 알림 (6절) |
|
||||
| 버그 | Error Workflow | 알림 |
|
||||
| **실패를 처리하는 장치의 실패** (DLQ 저장 실패, 알림 서버 장애) | ? | ? |
|
||||
|
||||
**처리하는 장치도 실패합니다.** DLQ에 넣는 HTTP 호출이 실패하면? 예제 1에서 "DLQ가 실패해도 응답은 나가게" `neverError`를 두었습니다. 그 결과 워크플로우는 멈추지 않고(→ Error Workflow도 안 불림), DLQ에도 없고, 결과는 `failed`만 말합니다. **조용히 잃습니다.** "감시자를 누가 감시하나(who watches the watchers)"라는 오래된 질문이 운영에서는 이렇게 나타납니다.
|
||||
|
||||
**구멍을 막는 방법.**
|
||||
|
||||
| 방법 | 오늘 |
|
||||
| --- | --- |
|
||||
| 처리 장치의 결과를 확인한다 (상태 코드가 아니라 본문의 `stored`) | 과제 1 |
|
||||
| 실패하면 **다른 길**로 알린다 (더 높은 등급, 잃을 뻔한 id를 알림 안에) | 과제 1 `critical · dlq_down` |
|
||||
| 결과에도 남긴다 | `dlq_saved: false` |
|
||||
| 처리 장치 자체를 감시한다 (DLQ가 살아 있나, 알림이 오나) | 3단계 운영 상태 · 79일차 대시보드 |
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- 고치기 전 `fault tickets error` + `fault dlq error`에서 `batch-ok`: 200 · `failed: 3`, DLQ는 2건 그대로 — R-101~103이 **어디에도 남지 않음**, 알림 없음 (`lab02-nets` 5/7, Lab 02 예제 3).
|
||||
- 고친 뒤: `dlq_saved: false` + `[critical] dlq_down` 알림 `DLQ save failed: dlq service is failing (fault switch) · lost ids R-201, R-203` (Lab 02 과제 1, `lab02-nets` 7/7).
|
||||
|
||||
## 6. 알림 등급: 무엇을 · 누구에게 · 얼마나 빨리 (4교시 Lab 02 과제 1)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 응급실의 환자 분류.** 응급실은 온 순서대로 보지 않습니다. 심장이 멈춘 환자는 **바로**, 팔이 부러진 환자는 **30분 안에**, 가벼운 감기는 **몇 시간 뒤**에 봅니다 (환자 분류, triage). 휴대폰도 급한 일은 전화로, 덜 급한 일은 문자로, 참고할 일은 앱 알림으로 옵니다. 알림 등급도 같습니다. `critical`(요청을 잃을 수 있음)은 지금 보고, `high`(DLQ에 쌓임)는 원인을 확인해 재처리하고, 거절 · 미분류는 알림 없이 보고서로 셉니다.
|
||||
|
||||
모든 알림이 같은 무게면 받는 사람은 무엇부터 볼지 모릅니다. 운영 조직은 알림에 **등급**(severity)을 두고, 등급마다 누가 · 몇 분 안에 · 무엇을 하는지 정합니다. 이름은 회사마다 다르지만(SEV1~4, P1~P5, critical · high · warning) 생각은 같습니다.
|
||||
|
||||
| 등급 | 오늘 | 받으면 | 예 (흔한 기준) |
|
||||
| --- | --- | --- | --- |
|
||||
| `critical` | 예상 못 한 오류, **요청을 잃을 수 있음** (DLQ 멈춤) | 바로 본다, 밤에도 깨운다 | 서비스 전체 장애, 데이터 손실 |
|
||||
| `high` | DLQ에 쌓임, 계속 실패해 멈춤(parked) | 업무 시간 안에 원인 확인 · 재처리 | 일부 기능 실패, 자동 복구 실패 |
|
||||
| (알림 없음) | 거절, 미분류 | 결과 · 보고서로 셈 | 사용자 실수, 대체로 처리됨 |
|
||||
|
||||
**알림은 "사람이 할 일이 있을 때만".** 자동으로 처리되었고 사람이 할 일이 없으면 알림이 아니라 **기록**(보고서 · 대시보드)입니다. 구글 SRE 책의 원칙도 같습니다: 알림(page)은 사람이 지금 무엇을 해야 하는 일에만.
|
||||
|
||||
**요청 하나에 알림 하나.****좋은 알림에 담을 것.**
|
||||
|
||||
| 담을 것 | 예 (오늘) | 없으면 |
|
||||
| --- | --- | --- |
|
||||
| 무엇이 · 어디서 | `workflow: intake` · `node: Send to DLQ` | 어느 시스템인지부터 찾음 |
|
||||
| 얼마나 | `count: 2` | 한 건인지 천 건인지 모름 |
|
||||
| 왜 (오류 문장, 짧게) | `500 - tickets service is failing` | 원인 짐작을 못 함 |
|
||||
| 자세히 볼 곳 | `execution_id` | 처음부터 다시 찾음 |
|
||||
| 사람이 할 일 | 런북 주소 · 절 (3단계) | 받은 사람이 할 일을 모름 |
|
||||
| 잃을 수 있는 것 | `lost ids R-201, R-203` | 사람이 살릴 수 없음 |
|
||||
|
||||
실패한 항목마다 알림을 보내면 50건 실패에 알림 50개입니다. 같은 원인이면 **하나로 묶고 개수**(`count`)를 담습니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- 실패 2건 → high 알림 **하나** `count 2`. 실패가 없으면 `alert: none`, 알림 수 그대로 (Lab 02 과제 1).
|
||||
|
||||
## 7. 알림 피로: 같은 알림은 한 번, 대신 숨기지 않기 (4교시 Lab 02 과제 2)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 양치기 소년과 단톡방.** "늑대다!"를 매일 외친 소년의 말은, 정말 늑대가 왔을 때 아무도 믿지 않았습니다. 매일 밤 울리는 주차장 자동차 경보에는 아무도 창문을 열어 보지 않습니다. 하루 수백 개가 오는 단톡방은 결국 알림을 꺼 버립니다. 알림이 많으면 안전한 것이 아니라 **아무도 보지 않게 됩니다.** 그래서 같은 일은 한 번만 알리고 (10분에 한 번), 정말 급한 일(`critical`)은 늘 알립니다. 단톡방 알림을 꺼도 대화 기록은 남는 것처럼, 줄이는 것은 알림이지 기록(DLQ)이 아닙니다.
|
||||
|
||||
**알림 피로(alert fatigue).** 알림이 너무 많으면 사람은 알림을 보지 않게 됩니다. 병원의 의료 기기 경보가 너무 자주 울려 간호사가 무뎌지는 문제로 처음 널리 알려졌고, IT 운영에서도 같은 일이 일어납니다. 그러면 정말 급한 알림도 묻힙니다.
|
||||
|
||||
**같은 문제는 한 번.** 운영 도구(Prometheus Alertmanager · PagerDuty · Opsgenie 등)는 이렇게 줄입니다.
|
||||
|
||||
| 방법 | 뜻 | 오늘 |
|
||||
| --- | --- | --- |
|
||||
| 중복 없애기 (deduplication) | 같은 알림이 이미 열려 있으면 새로 만들지 않음 | 같은 `reason`이 10분 안에 있으면 보내지 않음 |
|
||||
| 묶기 (grouping) | 같은 원인의 알림을 하나로 | 요청 하나에 하나 · `count` |
|
||||
| 반복 간격 (repeat interval) | 해결될 때까지 정해진 간격으로만 다시 | 10분 |
|
||||
| 억누르기 (inhibition) | 더 큰 알림이 있으면 작은 것은 숨김 | (오늘은 안 함) |
|
||||
|
||||
**줄이는 것은 알림이지 기록이 아닙니다.****알림 피로를 재는 법.** 알림이 너무 많은지는 느낌이 아니라 숫자로 봅니다.
|
||||
|
||||
| 볼 숫자 | 뜻 |
|
||||
| --- | --- |
|
||||
| 하루 · 한 주 알림 수 | 사람이 다 볼 수 있는 양인가 |
|
||||
| 할 일이 없었던 알림의 비율 | 높으면 알림이 아니라 기록이어야 함 |
|
||||
| 같은 원인의 반복 수 | 묶기 · 반복 간격이 필요함 |
|
||||
| 알림 → 사람이 확인하기까지 | 늘어나면 이미 무뎌짐 |
|
||||
|
||||
구글 SRE 책은 당번(on-call) 한 번에 처리할 사건 수를 적게 유지하라고 권합니다. 사건이 너무 많으면 알림을 줄이거나 원인을 고치는 일이 먼저입니다.
|
||||
|
||||
보내지 않은 알림도 결과에 `suppressed`로 남기고, 실패한 요청은 **DLQ에 모두** 남깁니다. 그리고 critical은 줄이지 않습니다: DLQ가 멈춘 알림을 10분에 한 번만 보내면, 그 10분 동안 잃을 뻔한 id가 알림에 남지 않습니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
| | 고치기 전 | 고친 뒤 |
|
||||
| --- | --- | --- |
|
||||
| 10분 안에 두 번째 실패 | `alert: sent`, dlq 알림 2 | `alert: suppressed`, dlq 알림 1 |
|
||||
| DLQ | 5건 | **5건 모두** |
|
||||
| DLQ 멈춤 (critical) | `sent` | `sent` (늘) |
|
||||
|
||||
## 8. 요청문(프롬프트)으로 에이전트에게 맡기기
|
||||
|
||||
| 넣을 것 | 예 |
|
||||
| --- | --- |
|
||||
| 언제 · 어디로 | "`failed`가 1 이상일 때만 `POST /dlq`, 본문 `{items: [{item: 원래 요청, reason, workflow}]}`" |
|
||||
| 넣지 않을 것 | "`rejected`는 넣지 않는다" |
|
||||
| 처리 장치가 실패하면 | "DLQ 응답에 `stored`가 없으면 critical `dlq_down`, 잃을 뻔한 id를 message에" |
|
||||
| 처리기의 고정 id · 설정 | "파일 맨 위 `"id": "errHandler76001"`, 부르는 쪽 `settings.errorWorkflow`" |
|
||||
| 알림 모양 · 등급 · 수 | "요청 하나에 하나, `{workflow, severity, reason, count, message}`" |
|
||||
| 줄이는 규칙 | "같은 `reason`의 high가 10분 안에 있으면 보내지 않고 `suppressed`, critical은 늘" |
|
||||
| 읽기는 읽기로 | "확인은 `/alerts/list` (POST `/alerts`는 알림을 만든다)" |
|
||||
|
||||
## 9. 자주 하는 실수
|
||||
|
||||
| 실수 | 결과 | 바로잡기 |
|
||||
| --- | --- | --- |
|
||||
| 거절도 DLQ에 | 재처리 때마다 같은 실패 | DLQ는 다시 하면 될 것만 |
|
||||
| 분류 결과 등 중간 값을 DLQ에 | 재처리 때 원래 요청을 모름 | 원래 요청 그대로 |
|
||||
| 처리기를 게시하지 않음 · id가 다름 | 알림 0 | `"id"` 고정 · push |
|
||||
| `neverError`만 두고 끝 | DLQ가 멈추면 조용히 잃음 | 본문 확인 · critical |
|
||||
| 항목마다 알림 | 50건에 50개 → 아무도 안 봄 | 요청 하나에 하나 · `count` |
|
||||
| 알림을 줄이려고 DLQ에서도 뺌 | 실패를 숨김 | 알림만 줄이고 기록은 그대로 |
|
||||
| 테스트가 `POST /alerts`로 확인 | 확인이 알림을 만듦 | 읽기 전용 `/alerts/list` |
|
||||
|
||||
## 확인 질문
|
||||
|
||||
<details><summary>1. DLQ에 무엇을 넣고, 무엇을 넣지 않나요? 왜요?</summary>
|
||||
|
||||
원래 요청 그대로와 실패 이유 · 워크플로우를 넣습니다. 재처리 때 같은 입구로 다시 보내기 위해서입니다. 다시 해도 같은 실패(빈 글 같은 거절)는 넣지 않습니다. 섞이면 재처리할 때마다 되풀이되고 살려야 할 것이 묻힙니다.
|
||||
</details>
|
||||
|
||||
<details><summary>2. DLQ가 있는데 Error Workflow도 필요한 까닭은?</summary>
|
||||
|
||||
DLQ는 예상한 실패(API가 멈춤)를 그 자리에서 처리한 것입니다. 버그처럼 예상하지 못한 오류는 워크플로우를 멈추게 하고, 그때는 n8n이 부르는 Error Workflow만 알 수 있습니다. 오늘 `nightly-report`의 버그는 처리기 없이는 알림 0, 처리기를 게시한 뒤 3초 만에 critical 알림이 생겼습니다.
|
||||
</details>
|
||||
|
||||
<details><summary>3. DLQ 저장이 실패하면 오늘 워크플로우는 어떻게 되었고, 어떻게 막았나요?</summary>
|
||||
|
||||
DLQ 노드에 `neverError`를 두었기 때문에 워크플로우는 멈추지 않았고(Error Workflow도 안 불림), 세 요청은 DLQ에도 없고 결과도 `failed`만 말했습니다. DLQ 응답의 `stored`를 확인해, 없으면 critical `dlq_down` 알림에 잃을 뻔한 id를 적고 결과에 `dlq_saved: false`를 남겨 막았습니다.
|
||||
</details>
|
||||
|
||||
<details><summary>4. 알림을 줄이면 위험하지 않나요?</summary>
|
||||
|
||||
줄이는 것은 같은 문제의 반복 알림이지, 실패의 기록이 아닙니다. 오늘 두 번째 실패의 알림은 `suppressed`로 결과에 남고, DLQ에는 5건 모두 있습니다. 그리고 critical은 줄이지 않습니다. 알림이 너무 많으면 사람이 알림을 끄게 되고, 그것이 더 위험합니다.
|
||||
</details>
|
||||
@@ -0,0 +1,363 @@
|
||||
# 3단계 · 재처리와 운영 체계
|
||||
|
||||
- 교시: 5교시 강의 (14:00~14:50) · 6교시 [Lab 03](../../Last3Mon/76/LAB-03-Q-REPLAY-AND-RUNBOOK.md) 실습 5개 (15:00~15:50)
|
||||
- 이 단계가 끝나면: 원인을 고친 뒤 DLQ의 요청을 같은 입구로 중복 없이 다시 돌리고, 알림을 받았을 때 할 일을 런북으로 써서 짝과 장애 연습을 하고, 반쯤 처리된 요청도 재처리가 안전한 까닭을 말하고, 계속 실패하는 요청은 정해진 횟수 뒤 사람에게 넘기고, "지금 괜찮은가"를 한 번에 알려 주는 상태 웹훅을 만들 수 있다.
|
||||
- 숫자: 모두 강사 노트북 실측입니다 (10/29, 정답 워크플로우를 학생 순서대로 1회). 출력 전부는 수업 뒤 공개되는 `Last3Mon/76/answers/LAB-TEST-RESULT.md`에 있습니다.
|
||||
- 실습 가리키기: "Lab 03 예제 1"은 6교시 [Lab 03 문서](../../Last3Mon/76/LAB-03-Q-REPLAY-AND-RUNBOOK.md)의 그 절입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 재처리"] --> B["2. 같은 입구로"] --> C["3. 런북"] --> D["4. 장애 연습"]
|
||||
D --> E["5. 최소 한 번 + 멱등"] --> F["6. 끝없이 돌리지 않기"] --> G["7. 운영 상태"] --> H["8. 운영 체계 한 장"] --> I["9. 요청문(프롬프트)으로"]
|
||||
```
|
||||
|
||||
## 0. 이 시간 (50분)
|
||||
|
||||
| 시간 | 절 · 개요 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| 14:00~14:08 | 1 · 2 · 개요 22장 | 재처리(redrive), 같은 입구로 다시 |
|
||||
| 14:08~14:18 | 3 · 4 · 개요 23~24장 | 런북, 장애 연습 (game day) |
|
||||
| 14:18~14:25 | 5 · 개요 25장 | 최소 한 번 + 멱등 = 결과는 한 번 |
|
||||
| 14:25~14:33 | 6 · 개요 26장 | 최대 시도 수, parked, 사람에게 넘기기 |
|
||||
| 14:33~14:40 | 7 · 개요 27장 | 운영 상태, health check · status page |
|
||||
| 14:40~14:50 | 8 · 9 · 10 · 개요 28장 · 확인 질문 | 운영 체계 한 장, 요청문, 자주 하는 실수 |
|
||||
|
||||
### 6교시 Lab 03에서 이어서 하는 것
|
||||
|
||||
| 6교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
|
||||
| --- | --- | --- | --- |
|
||||
| Lab 03 예제 1 (15:00~15:12) | 1 · 2 | 재처리 `my-replay` | `my-work/replay.json`, 테스트 `cases/lab03.json` |
|
||||
| Lab 03 예제 2 (15:12~15:24, 에이전트 없이) | 3 · 4 | 런북 쓰기 · 짝 연습 | `RUNBOOK.md` 2 · 3절, `test cases/lab03-drill.json -v` |
|
||||
| Lab 03 예제 3 (15:24~15:30, 에이전트 없이) | 5 | 반쯤 처리된 요청을 재처리 | `test cases/lab03-half.json -v` |
|
||||
| Lab 03 과제 1 (15:30~15:40) | 6 | 세 번 실패하면 parked | 테스트 `cases/lab03-challenge.json` |
|
||||
| Lab 03 과제 2 (15:40~15:50) | 7 | 운영 상태 `my-ops-status` | `my-work/ops-status.json`, 테스트 `cases/lab03-challenge2.json` |
|
||||
|
||||
## 1. 재처리: 모은 것은 다시 돌려야 끝난다 (6교시 Lab 03 예제 1)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 택배 재배송.** 보관소에 둔 택배는 보관만 해서는 일이 끝나지 않습니다. 받는 사람이 집에 있는 날 **다시 배송**하고, 전하면 보관소 목록에서 **지워야** 끝납니다. 다시 배송하다 또 부재면 목록에 그대로 두고 다음에 또 시도합니다. 재처리는 DLQ(보관소)의 요청을 원인이 고쳐진 뒤 다시 돌리는 이 재배송입니다.
|
||||
|
||||
DLQ는 실패를 잃지 않게 해 줄 뿐, 일을 끝내 주지는 않습니다. 원인(API 장애)을 고친 뒤 모아 둔 요청을 **다시 돌려야** 문의가 접수됩니다. 이것을 재처리(replay, reprocess)라고 하고, 메시지 큐에서는 DLQ의 메시지를 원래 큐로 되돌린다는 뜻으로 redrive라고 부릅니다 (Amazon SQS의 DLQ redrive).
|
||||
|
||||
**재처리의 순서.**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
F["원인 고침"] --> C["확인<br/>(다시 실패하지 않을지)"] --> R["재처리<br/>하나씩"] --> V["확인<br/>DLQ 0 · 중복 없음"]
|
||||
R -->|또 실패| D[("DLQ에 남음<br/>attempts +1")]
|
||||
```
|
||||
|
||||
| 원칙 | 까닭 |
|
||||
| --- | --- |
|
||||
| **원인을 고친 뒤에** | 고치기 전에 돌리면 또 실패하고 `attempts`만 오름 (6절) |
|
||||
| **하나씩** (또는 작은 묶음으로) | 하나가 실패해도 다른 것은 됨, 어느 것이 실패했는지 보임 |
|
||||
| **성공한 것만 지운다** | 실패한 것은 남아 다음 재처리 · 사람이 봄 |
|
||||
| **천천히** | 쌓인 수천 건을 한꺼번에 보내면 막 살아난 서비스가 다시 쓰러짐 |
|
||||
| 재처리 전후로 **센다** | DLQ 수 · 접수 건 수 · 중복 |
|
||||
|
||||
**언제 돌리나.** 사람이 원인을 확인하고 손으로(오늘), 정해진 시간마다 자동으로(Schedule Trigger), 또는 상태가 좋아지면 자동으로. 자동으로 돌릴수록 6절의 "끝없이 돌리지 않기"가 더 중요합니다.
|
||||
|
||||
| 언제 돌리나 | 좋은 점 | 조심할 것 |
|
||||
| --- | --- | --- |
|
||||
| 사람이 손으로 (오늘) | 원인을 확인한 뒤라 안전 | 사람이 잊으면 쌓임 |
|
||||
| 정해진 시간마다 (Schedule Trigger) | 잊지 않음 | 원인이 안 고쳐졌으면 또 실패 → `attempts`만 |
|
||||
| 상태가 좋아지면 자동으로 | 빠름 | "좋아졌다"를 무엇으로 판단하나 (health check · 시험 요청 하나) |
|
||||
|
||||
**순서가 중요한 일.** 접수는 순서가 바뀌어도 괜찮지만, "주소 바꾸기 → 배송"처럼 순서가 중요한 일은 재처리가 순서를 뒤집으면 안 됩니다 (예전 주소로 배송). 그런 일은 같은 대상(같은 사람 · 주문)의 요청을 묶어 차례대로 재처리하거나, 최신 상태를 다시 읽고 처리합니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- 고치기 전: `my-replay`가 없어 404, DLQ 2 · 접수 건 0.
|
||||
- 고친 뒤: `replayed: 2, ok: 2` (1.1초), DLQ 0, 접수 건 2 — 중복 없음. 한 번 더 돌리면 `replayed: 0` (Lab 03 예제 1).
|
||||
|
||||
## 2. 새로 만들지 말고 같은 입구로 다시 보내기
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 은행 창구.** 은행 마감 뒤 맡겨 둔 서류를 다음 날 처리할 때, 직원이 "어제 거니까"라며 뒷문으로 신분 확인 · 서명 없이 처리하면 안 됩니다. **같은 창구, 같은 절차**(신분 확인 → 서명 → 처리)를 거쳐야 합니다. 뒷문 절차를 따로 만들면 언젠가 두 절차가 달라져, 어제 서류만 확인이 빠지는 구멍이 생깁니다. 재처리가 접수 건을 직접 만들지 않고 `my-intake`(같은 창구)로 다시 보내는 까닭입니다.
|
||||
|
||||
**재처리가 일을 직접 하면 규칙이 둘이 됩니다.** 재처리 워크플로우가 접수 건을 직접 만들면 항목 확인 · 분류 · 멱등 키 · DLQ · 알림을 또 만들어야 하고, 언젠가 두 곳의 규칙이 달라집니다 (원래 입구는 고쳤는데 재처리는 옛 규칙). 그래서 재처리는 원래 요청을 **원래 입구로** 다시 보냅니다. 70일차에 공통 모듈을 하나 두고 여러 곳에서 부른 것과 같은 생각입니다.
|
||||
|
||||
| 재처리가 | 장점 | 단점 |
|
||||
| --- | --- | --- |
|
||||
| 원래 입구(`my-intake`)를 다시 부름 | 규칙이 하나, 재처리도 같은 키 · 알림 · DLQ | 입구가 받을 수 있는 모양이어야 함 |
|
||||
| 일을 직접 함 | 빠를 수 있음 | 규칙이 둘, 하나만 고치는 사고 |
|
||||
|
||||
**성공은 결과로 판단합니다.** `my-intake`는 부분 성공이라 늘 200입니다. HTTP 상태가 아니라 결과의 `created`로 판단합니다.
|
||||
|
||||
## 3. 런북: 그때 할 일을 미리 써 두기 (6교시 Lab 03 예제 2)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 비행기 비상 체크리스트와 엘리베이터 안내문.** 조종사는 엔진에 불이 나면 기억에 의존하지 않고 **비상 체크리스트**를 펴서 한 줄씩 따라 합니다. 아무리 숙련된 조종사도 급할 때는 단계를 빠뜨리기 때문입니다. 엘리베이터 안에는 "갇혔을 때: ① 비상벨을 누른다 ② 관리실 ○○○-○○○○ ③ 문을 억지로 열지 않는다"가 붙어 있습니다. 처음 탄 사람도 따라 할 수 있고, **하지 말 것**까지 적혀 있습니다. 런북은 알림 하나에 대한 이런 안내문입니다.
|
||||
|
||||
**런북(runbook)**은 특정 알림 · 장애가 났을 때 할 일을 순서대로 적은 문서입니다. 플레이북(playbook)이라고도 부릅니다. 장애는 대개 바쁠 때, 밤에, 그 시스템을 만든 사람이 없을 때 납니다. 그때 머릿속으로 떠올리면 단계를 빠뜨리고, 급한 마음에 하지 말아야 할 일(DLQ 비우기)을 합니다. 구글 SRE 책은 미리 써 둔 플레이북이 장애 회복 시간(MTTR)을 약 세 배 줄인다고 말합니다.
|
||||
|
||||
**좋은 런북의 모양.**
|
||||
|
||||
| 갖출 것 | 나쁜 예 | 좋은 예 |
|
||||
| --- | --- | --- |
|
||||
| 어느 알림에 대한 것인지 | "장애 대응" | "`high · dlq` 알림을 받으면" |
|
||||
| 명령을 그대로 | "DLQ를 확인한다" | `uv run tools/flow.py ops` |
|
||||
| 확인은 무엇을 보면 되는지 | "정상인지 확인" | "`ops`의 DLQ 0, 같은 key가 둘이 아닌지" |
|
||||
| 갈림길 | (없음) | "`critical · unexpected`면 코드 담당에게, `high · dlq`면 3번으로" |
|
||||
| **하지 말 것** | (없음) | "DLQ를 비우지 말 것 — 재처리할 요청이 사라짐", "parked를 자동으로 돌리지 말 것" |
|
||||
| 누구 · 언제까지 | (없음) | "1차 대응 ○○, 30분 안에 / parked는 데이터 담당이 다음 업무일까지" |
|
||||
| 마지막으로 고친 날 | (없음) | 시스템이 바뀌면 런북도 바뀜 |
|
||||
|
||||
**알림과 런북을 잇습니다.****런북의 뼈대 (오늘 `RUNBOOK.md`와 같음).**
|
||||
|
||||
```text
|
||||
제목: high · dlq 알림을 받으면 (마지막으로 고친 날: 2026-10-30)
|
||||
1. 확인 — 명령 · 볼 숫자
|
||||
2. 나누기 — 알림의 reason · severity에 따라 갈림길
|
||||
3. 원인 — 누구에게 묻나 · 무엇을 보나
|
||||
4. 고침 — (오늘은 고장 스위치)
|
||||
5. 되돌리기 — 재처리 명령
|
||||
6. 확인 — DLQ 0 · 같은 key 없음 · 운영 상태 ok
|
||||
하지 말 것 — DLQ 비우기 · parked 자동 재처리 · 급한 배포
|
||||
누가 · 언제까지 — 1차 대응 · 데이터 담당 · parked 결정
|
||||
```
|
||||
|
||||
알림 메시지에 런북의 주소(또는 절 번호)를 넣으면, 알림을 받은 사람이 바로 할 일로 갑니다. 많은 회사가 알림 규칙마다 `runbook_url`을 붙입니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- `ops` 한 번에 DLQ 2건과 알림 둘: `[high] intake · dlq · count 2`, `[critical] … unexpected · rows is not iterable`. **원인이 다른 두 알림**입니다 (Lab 03 예제 2).
|
||||
- 고장 스위치를 되돌리고 재처리하자 DLQ 0 · 접수 건 2. 그런데 critical 알림은 남았습니다 — 버그는 재처리로 고쳐지지 않습니다. 런북에 갈림길이 있어야 하는 까닭입니다.
|
||||
|
||||
## 4. 장애 연습: 써 둔 런북이 맞는지 일부러 장애를 내 본다
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 소방 훈련.** 학교와 회사는 불이 나지 않았는데도 일부러 경보를 울리고 모두 대피해 봅니다. 해 보면 "비상구 앞에 상자가 쌓여 있다", "3층 경보가 안 들린다", "모이는 곳을 모르는 사람이 있다" 같은 문제가 **불이 나기 전에** 드러납니다. 대피 안내문(런북)은 써 둔 것만으로는 맞는지 모릅니다. 오늘 짝 연습에서 한 사람이 일부러 장애를 내고 다른 사람이 런북만 보고 대응하는 것이 이 소방 훈련입니다.
|
||||
|
||||
런북은 써 두기만 하면 틀린 줄을 모릅니다. **일부러 장애를 내고, 다른 사람이 런북만 보고 대응**해 봐야 빠진 단계가 드러납니다.
|
||||
|
||||
| 이름 | 무엇 |
|
||||
| --- | --- |
|
||||
| 장애 연습 (game day, fire drill) | 정해진 날 정해진 장애를 내고 팀이 대응해 봄 |
|
||||
| 카오스 엔지니어링 | 운영 환경에서 일부러 고장(서버 끄기 · 지연)을 내 약한 곳을 찾음. Netflix의 Chaos Monkey가 유명 |
|
||||
| 사후 검토 (postmortem) | 장애가 끝난 뒤 무엇이 · 왜 · 다음엔 어떻게를 **탓하지 않고**(blameless) 적음 |
|
||||
|
||||
**사후 검토에서 묻는 것.** 장애나 연습이 끝나면 짧게라도 적습니다. "누가 잘못했나"가 아니라 "**시스템과 절차의 무엇이 이것을 가능하게 했나**"를 묻습니다 (탓하지 않는 사후 검토).
|
||||
|
||||
| 질문 | 오늘 연습이라면 |
|
||||
| --- | --- |
|
||||
| 무슨 일이 언제 있었나 (시간 순서) | 장애 시작 · 알림 · 알아챔 · 고침 · 재처리 |
|
||||
| 영향은 | 문의 몇 건이 몇 분 늦었나, 잃은 것은 없나 |
|
||||
| 왜 알아채는 데 그만큼 걸렸나 | 알림은 있었나, 누가 봤나 |
|
||||
| 무엇이 잘 됐나 | DLQ · 키 덕분에 잃지 않고 중복 없음 |
|
||||
| 다음에 바꿀 것 (담당 · 기한) | 런북에 갈림길 더하기 |
|
||||
|
||||
**연습에서 재는 것.**
|
||||
|
||||
| 잴 것 | 뜻 |
|
||||
| --- | --- |
|
||||
| 알아채기까지 (MTTD) | 장애 시작 → 사람이 앎 |
|
||||
| 회복까지 (MTTR) | 장애 시작 → 다시 정상 |
|
||||
| 런북에 없던 단계 | 다음 판 런북에 더할 것 |
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- 짝 연습: 한 사람이 `fault tickets error` · `call my-intake` · `call nightly-report`, 다른 사람이 런북만 보고 `ops → fault ok → my-replay → ops`. `RUNBOOK.md` 3절에 시간과 빠진 단계를 적습니다.
|
||||
- 같은 연습을 테스트로: `lab03-drill` 7/7, 접수 건 5 (평소 3 + 재처리 2), 중복 없음 (Lab 03 예제 2).
|
||||
|
||||
## 5. 최소 한 번 + 멱등 = 결과는 한 번 (6교시 Lab 03 예제 3)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 엘리베이터 버튼.** 급한 사람은 엘리베이터 버튼을 여러 번 누릅니다. 그래도 엘리베이터는 한 대만 옵니다. 버튼이 "이미 불렀음"을 기억하기 때문입니다 (멱등). 누르는 쪽이 "한 번만 눌러야지"를 지키게 하는 것보다, **받는 쪽이 여러 번 눌러도 한 번만 처리**하게 만드는 편이 훨씬 쉽고 확실합니다. 재시도 · 재처리가 있으면 같은 요청이 여러 번 갈 수 있으니 (최소 한 번), 받는 쪽을 이 버튼처럼 만듭니다 (멱등 키).
|
||||
|
||||
**전달 보장의 세 가지.** 메시지 · 요청을 보내는 시스템은 보통 셋 중 하나를 약속합니다.
|
||||
|
||||
| 보장 | 뜻 | 대가 |
|
||||
| --- | --- | --- |
|
||||
| 최대 한 번 (at-most-once) | 다시 보내지 않음 | 실패하면 **잃음** |
|
||||
| 최소 한 번 (at-least-once) | 될 때까지 다시 보냄 | 같은 것이 **두 번 이상** 갈 수 있음 |
|
||||
| 정확히 한 번 (exactly-once) | 꼭 한 번 | 네트워크 너머에서는 매우 어려움 (특별한 저장 · 조정 필요) |
|
||||
|
||||
오늘 재시도 · DLQ · 재처리는 **최소 한 번**입니다. 잃지 않는 대신 같은 요청이 두 번 이상 갈 수 있습니다. 1단계 5절의 시간 초과처럼, DLQ에 들어간 요청이 **사실은 이미 처리된 것**일 수도 있습니다. 그래서 받는 쪽을 **멱등**하게 만들어 "여러 번 가도 결과는 한 번"을 만듭니다. 실무에서 "effectively once"라고 부르는 방식입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["최소 한 번<br/>재시도 · DLQ · 재처리"] --> P["같은 요청이<br/>두 번 이상"] --> K["받는 쪽이 멱등<br/>키 = 요청 id"] --> O(["결과는 한 번"])
|
||||
```
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- `slow`에서 11.6초 뒤 `failed: 2`, DLQ 2건. 8초 뒤 접수 건 **이미 2개** (데이터 API가 늦게 처리) — 같은 문의가 DLQ에도, 접수 건에도.
|
||||
- 재처리: `replayed: 2, ok: 2, duplicates: 2`, 접수 건 2개 그대로 (Lab 03 예제 3). 1단계의 멱등 키가 재처리까지 지켜 줬습니다.
|
||||
|
||||
## 6. 끝없이 돌리지 않기: 최대 시도 수와 사람에게 넘기기 (6교시 Lab 03 과제 1)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 택배 반송과 비밀번호 잠금.** 택배는 재배송을 무한히 하지 않습니다. 몇 번 시도해도 못 전하면 멈추고 **보낸 사람에게 연락**합니다. 은행 앱은 비밀번호를 5번 틀리면 잠기고, 그다음은 **창구에서 사람이** 확인합니다. 자동으로 다시 하는 데에는 끝이 있어야 하고, 그 끝은 "포기"가 아니라 "사람에게 넘기기"입니다. 오늘의 `parked`가 이것입니다.
|
||||
|
||||
**원인이 고쳐지지 않은 요청을 계속 돌리면 독이 됩니다.** 매번 API를 두드리고, 알림이 쌓이고, 새로 들어온 실패가 묻힙니다. 어떤 요청은 영영 성공하지 못합니다 (데이터 API가 받지 않는 내용, 지워진 사용자). 그런 메시지를 "독 메시지(poison message)"라고 부릅니다.
|
||||
|
||||
**정해진 횟수 뒤 따로 빼기.**
|
||||
|
||||
| 시스템 | 방법 |
|
||||
| --- | --- |
|
||||
| Amazon SQS | `maxReceiveCount`를 넘으면 DLQ로 |
|
||||
| 많은 작업 큐 (Sidekiq · Celery 등) | 최대 재시도 뒤 "죽은 작업" 목록으로 |
|
||||
| 오늘 | `attempts ≥ 3`이면 다시 보내지 않고 `parked: true` + high 알림 |
|
||||
|
||||
**사람에게 넘기기 (human in the loop).****parked를 받은 사람이 하는 결정.**
|
||||
|
||||
| 보이는 것 | 결정 |
|
||||
| --- | --- |
|
||||
| 요청 자체가 이상함 (영영 받지 않을 글) | 요청한 사람에게 돌려보내고 DLQ에서 지움 (거절로 바꿈) |
|
||||
| 데이터 API의 다른 문제 (권한 · 설정) | 그쪽을 고친 뒤 parked를 풀어 재처리 |
|
||||
| 이미 다른 길로 처리됨 (전화로 접수) | 기록을 남기고 지움 |
|
||||
| 모르겠음 | 담당에게 넘기고 기한을 정함 (런북 4절) |
|
||||
|
||||
parked는 "포기"가 아니라 "**자동으로는 여기까지, 이제 사람이**"입니다. 사람이 보고 요청을 고치거나, 데이터 API의 다른 문제를 고치거나, 정말 버릴지 정합니다. 그래서 parked는 지우지 않고, 런북에 **누가 · 언제까지**를 적습니다.
|
||||
|
||||
**몇 번이 좋은가.** 숫자 자체보다 **간격 × 횟수 = 포기하기까지 걸리는 시간**으로 정합니다.
|
||||
|
||||
| 재처리 간격 | 3번이면 | 맞는 경우 |
|
||||
| --- | --- | --- |
|
||||
| 1분마다 (자동) | 3분 | 잠깐의 장애가 대부분일 때 |
|
||||
| 1시간마다 (자동) | 3시간 | 공급자 장애가 몇 시간 갈 수 있을 때 |
|
||||
| 하루 한 번 (사람) | 3일 | 담당자가 확인하고 돌릴 때 |
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
| | 고치기 전 | 고친 뒤 |
|
||||
| --- | --- | --- |
|
||||
| 재처리 3 (`attempts` 3) | 또 보냄 `replayed: 2` (2.0초) | **0.1초** `parked: 2`, `[high] parked` |
|
||||
| 고친 뒤 재처리 | 자동으로 다시 보냄 | `replayed: 0` (사람이 보기 전에는 안 돌림) |
|
||||
|
||||
## 7. 운영 상태: "지금 괜찮은가"를 한 번에 (6교시 Lab 03 과제 2)
|
||||
|
||||
### 일반 설명
|
||||
|
||||
> **생활 속 예 · 미세먼지 예보와 자동차 계기판.** 미세먼지 앱은 측정값 여럿(PM10 · PM2.5 · 오존)을 늘어놓지 않고 **좋음 · 보통 · 나쁨**과 "마스크를 쓰세요"를 먼저 보여 줍니다. 자동차 계기판도 숫자 대신 초록 · 노랑 · 빨강 등을 켭니다. 처음 보는 사람도 "지금 괜찮은가, 무엇을 해야 하나"를 바로 압니다. 운영 상태 웹훅은 DLQ · parked · 알림 숫자를 이 신호등(`ok` · `warn` · `critical`)과 까닭으로 줄입니다.
|
||||
|
||||
**상태 확인(health check).** 운영 중인 서비스는 대개 "살아 있나 · 일할 수 있나"를 알려 주는 주소를 둡니다. 쿠버네티스는 그 주소(liveness · readiness probe)로 컨테이너를 다시 띄울지, 요청을 보낼지 정합니다. 많은 회사가 고객이 보는 **상태 페이지(status page)**도 둡니다 (정상 · 일부 장애 · 장애).
|
||||
|
||||
**숫자 여럿을 신호 하나로.** 사람에게는 DLQ 수 · parked 수 · 알림 수를 각각 주는 것보다 **신호등 하나와 까닭**이 빠릅니다.
|
||||
|
||||
| 신호 | 기준 (오늘) | 사람이 할 일 |
|
||||
| --- | --- | --- |
|
||||
| `ok` | 아래 둘 다 아님 | — |
|
||||
| `warn` | parked가 아닌 DLQ가 있음, 10분 안 high 알림 | 원인을 고친 뒤 재처리 |
|
||||
| `critical` | parked가 있음, 10분 안 critical 알림 | 지금 봄 · 담당에게 |
|
||||
|
||||
- 더 나쁜 쪽을 먼저 봅니다 (둘 다 맞으면 `critical`). 까닭(`reasons`)은 맞는 것을 모두 적습니다.
|
||||
|
||||
**무엇을 묻는 상태인가.** 상태 확인에도 종류가 있습니다.
|
||||
|
||||
| 종류 | 묻는 것 | 예 |
|
||||
| --- | --- | --- |
|
||||
| 살아 있나 (liveness) | 프로그램이 멈추지 않았나 | 데이터 API `/health` → 다시 띄울지 |
|
||||
| 받을 수 있나 (readiness) | 지금 요청을 받아도 되나 | DB 연결 · 의존 서비스 확인 → 요청을 보낼지 |
|
||||
| 일이 잘 되나 (오늘) | 실패가 쌓였나 · 사람이 볼 일이 있나 | `my-ops-status` → 사람이 무엇을 할지 |
|
||||
|
||||
오늘 데이터 API의 `/health`는 고장 스위치가 켜져 있어도 `ok`입니다 (살아 있기는 하니까). 그래서 "살아 있다"만으로는 "일이 잘 된다"를 말할 수 없고, 오늘 만든 운영 상태가 따로 필요합니다.
|
||||
|
||||
**확인은 상태를 바꾸지 않습니다.** 상태를 묻는 것이 DLQ나 알림을 바꾸면, 묻는 횟수에 따라 결과가 달라집니다. 2단계에서 테스트가 `/alerts`에 POST해 빈 알림을 만든 일이 그 예입니다. 상태 웹훅은 **읽기만** 합니다.
|
||||
|
||||
### 오늘 실습에서
|
||||
|
||||
- `ok` → 실패 2건 뒤 `warn` (DLQ 2, "원인을 고친 뒤 재처리") → 버그 뒤 `critical` (critical 1) → 재처리 · 알림 확인 뒤 `ok` (Lab 03 과제 2, 7/7). 상태 웹훅은 0.1초 안팎 (읽기만, LLM 없음).
|
||||
- Lab 02 과제 2 테스트가 남긴 상태를 읽으면 `critical` · DLQ 5 · 까닭 셋 (`critical 알림 1건 (10분)`, `DLQ 5건: 원인을 고친 뒤 재처리`, `high 알림 1건 (10분)`).
|
||||
|
||||
## 8. 운영 체계 한 장
|
||||
|
||||
오늘 하루 만든 것을 모으면 하나의 체계입니다.
|
||||
|
||||
> **생활 속 예 · 택배 회사로 본 오늘 하루.** 오늘 만든 것은 모두 택배 회사에 이미 있습니다.
|
||||
>
|
||||
> | 오늘 만든 것 | 택배 회사에서는 |
|
||||
> | --- | --- |
|
||||
> | 부분 성공 (항목마다 상태) | 상자마다 배송 조회 |
|
||||
> | 입구 규칙 · 거절 | 접수 창구에서 주소 없는 상자를 바로 돌려줌 |
|
||||
> | 재시도 | 벨을 한 번 더 누름 |
|
||||
> | 멱등 키 | 송장 번호 (같은 상자를 두 번 배달하지 않음) |
|
||||
> | 우아한 저하 | 분류기가 고장 나도 손으로 분류해 배송은 계속 |
|
||||
> | DLQ | 부재 시 보관소 |
|
||||
> | Error Workflow | 사고 나면 본사 상황실로 자동 보고 |
|
||||
> | 알림 등급 · 폭주 막기 | 사고는 즉시 전화, 지연은 하루 한 번 요약 |
|
||||
> | 재처리 | 다음 날 재배송 |
|
||||
> | parked | 세 번 부재면 보낸 사람에게 연락 |
|
||||
> | 런북 · 장애 연습 | 사고 대응 매뉴얼 · 정기 훈련 |
|
||||
> | 운영 상태 | 상황실 화면의 초록 · 노랑 · 빨강 |
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph 막기["막기 (1단계)"]
|
||||
V["입구 규칙 · 항목 확인"] --> RT["재시도 · 시간 제한"] --> K["멱등 키"] --> G["우아한 저하 (미분류)"]
|
||||
end
|
||||
subgraph 모으고알리기["모으고 알리기 (2단계)"]
|
||||
D[("DLQ")] --> A["알림 high · critical<br/>10분에 한 번"]
|
||||
EW["Error Workflow"] --> A
|
||||
end
|
||||
subgraph 되돌리기["되돌리기 (3단계)"]
|
||||
RB["런북 · 연습"] --> RP["재처리 (같은 입구)"] --> PK["parked → 사람"]
|
||||
ST["운영 상태 ok · warn · critical"]
|
||||
end
|
||||
막기 --> 모으고알리기 --> 되돌리기
|
||||
```
|
||||
|
||||
| 질문 | 답하는 것 |
|
||||
| --- | --- |
|
||||
| 잘못된 요청은? | 입구 규칙 · 거절 + 이유 |
|
||||
| 잠깐 끊기면? | 재시도 (키와 함께) |
|
||||
| 오래 멈추면? | DLQ → 알림 → 재처리 |
|
||||
| 예상 못 한 오류는? | Error Workflow → critical |
|
||||
| 처리 장치가 멈추면? | critical `dlq_down` · 잃을 뻔한 id |
|
||||
| 계속 실패하면? | parked → 사람 |
|
||||
| 지금 괜찮은가? | 운영 상태 |
|
||||
| 그때 누가 무엇을? | 런북 · 연습 |
|
||||
|
||||
## 9. 요청문(프롬프트)으로 에이전트에게 맡기기
|
||||
|
||||
| 넣을 것 | 예 |
|
||||
| --- | --- |
|
||||
| 다시 보내는 곳 · 모양 | "항목마다 `POST /webhook/my-intake` `{requests: [item]}`" |
|
||||
| 성공 판단 | "`my-intake` 결과의 `created`가 1이면 성공 (HTTP는 늘 200)" |
|
||||
| 지우기 · 남기기 | "성공만 `dlq/update remove`, 실패는 그대로" |
|
||||
| 비었을 때 | "`alwaysOutputData`, 할 일 없음 갈래" |
|
||||
| 멈추는 기준 | "`attempts >= max_attempts`(기본 3)면 parked + high 알림, 지우지 않음" |
|
||||
| 상태 규칙의 순서 | "critical 조건을 먼저, 까닭은 모두" |
|
||||
| 읽기만 | "상태 웹훅은 DLQ · 알림을 바꾸지 않는다" |
|
||||
|
||||
## 10. 자주 하는 실수
|
||||
|
||||
| 실수 | 결과 | 바로잡기 |
|
||||
| --- | --- | --- |
|
||||
| 원인을 고치기 전에 재처리 | 또 실패, `attempts`만 오름 → 금방 parked | 런북: 확인 → 재처리 |
|
||||
| 재처리가 접수 건을 직접 만듦 | 규칙이 둘 | 같은 입구로 |
|
||||
| HTTP 상태로 성공 판단 | 늘 성공처럼 보임 | 결과의 `created` |
|
||||
| 쌓인 것을 한꺼번에 | 막 살아난 서비스가 다시 쓰러짐 | 하나씩 · 천천히 |
|
||||
| parked를 지움 | 사람이 볼 것이 사라짐 | 지우지 않기 |
|
||||
| 급해서 DLQ를 비움 | 재처리할 요청이 사라짐 | 런북 "하지 말 것" |
|
||||
| 런북에 "확인한다"만 | 무엇을 봐야 할지 모름 | 볼 숫자까지 |
|
||||
| 상태 확인이 상태를 바꿈 | 볼 때마다 결과가 달라짐 | 읽기만 |
|
||||
|
||||
## 확인 질문
|
||||
|
||||
<details><summary>1. 재처리가 접수 건을 직접 만들지 않고 my-intake를 다시 부르는 까닭은?</summary>
|
||||
|
||||
규칙을 하나로 두기 위해서입니다. 직접 만들면 항목 확인 · 분류 · 키 · DLQ · 알림을 또 만들어야 하고, 한쪽만 고치는 사고가 납니다. 같은 입구로 보내면 재처리도 같은 키로 중복을 막고, 또 실패하면 같은 DLQ로 돌아가 `attempts`가 오릅니다.
|
||||
</details>
|
||||
|
||||
<details><summary>2. DLQ의 요청을 재처리했더니 duplicates: 2가 나왔습니다. 무엇이 일어난 건가요?</summary>
|
||||
|
||||
그 요청들이 사실은 이미 처리되어 있었습니다. 데이터 API가 느려 워크플로우는 시간 초과로 포기하고 DLQ에 넣었지만, 데이터 API는 늦게라도 접수 건을 만들었습니다. 재처리가 같은 키로 보내자 처음 번호를 돌려받았습니다. 최소 한 번 보내고 받는 쪽이 멱등하면 결과는 한 번입니다.
|
||||
</details>
|
||||
|
||||
<details><summary>3. 3번 실패한 요청을 parked로 두는 것과 지우는 것은 무엇이 다른가요?</summary>
|
||||
|
||||
parked는 자동 재처리에서만 빼고 DLQ에 남깁니다. 사람이 원인을 보고 고친 뒤 다시 돌리거나 버릴지 정합니다. 지우면 그 문의는 사라집니다. 런북에 parked를 누가 언제까지 보는지 적습니다.
|
||||
</details>
|
||||
|
||||
<details><summary>4. 좋은 런북과 나쁜 런북의 차이를 오늘 연습으로 말해 보세요.</summary>
|
||||
|
||||
좋은 런북은 어느 알림에 대한 것인지, 그대로 복사해 쓸 명령, 무엇을 보면 확인인지(DLQ 0 · 같은 key 없음), 갈림길(critical unexpected는 코드 담당에게), 하지 말 것(DLQ 비우기), 누가 언제까지를 적습니다. 오늘 연습에서 재처리 뒤에도 critical 알림이 남은 것은 갈림길이 없는 런북으로는 당황하기 쉬운 장면이었습니다.
|
||||
</details>
|
||||
@@ -0,0 +1,219 @@
|
||||
# 4단계 · 정리: 오늘 한 일의 의미 · 효과 · 실전 응용
|
||||
|
||||
- 교시: 8교시 17:10~17:45 (35분, 바이브 코딩 발표 10분 뒤) · 자료: 이 문서 + [개요](00-DAY76-OVERVIEW.md) 30장
|
||||
- 이 시간이 끝나면: 오늘 1~3단계에서 한 일이 **무엇을 지키기 위한 것이었는지**, 숫자로 **무엇이 달라졌고 무엇을 치렀는지**, 회사에서는 같은 생각을 **어떤 이름과 도구로** 쓰는지 말할 수 있고, 최종 프로젝트에 무엇을 가져갈지 정할 수 있다.
|
||||
- 숫자: 오늘 교안의 실측(강사 노트북, 10/29 정답 워크플로우 1회)입니다. "예시"라고 적은 계산은 가정을 둔 어림입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 오늘 한 장"] --> B["2. 왜 실패 처리가<br/>운영의 일인가"] --> C["3. 효과와 값을 숫자로"] --> D["4. 회사에서 부르는 이름"]
|
||||
D --> E["5. 산업별 응용"] --> F["6. 운영 지표"] --> G["7. 하지 말아야 할 때"] --> H["8. 직무 · 면접"] --> I["9. 최종 프로젝트로"]
|
||||
```
|
||||
|
||||
## 0. 이 시간 (35분)
|
||||
|
||||
| 시간 | 절 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| 17:10~17:15 | 1 · 2 | 오늘 한 장, 왜 실패 처리가 운영의 일인가 |
|
||||
| 17:15~17:20 | 3 | 오늘 숫자로 본 효과와 값, 하루로 보면 (예시) |
|
||||
| 17:20~17:28 | 4 · 5 | 같은 생각의 업계 이름 · 도구, 산업별 응용 예 |
|
||||
| 17:28~17:33 | 6 · 7 | 운영 지표 여섯, 하지 말아야 할 때 |
|
||||
| 17:33~17:40 | 8 · 9 | 직무와 면접 질문, 최종 프로젝트 체크리스트 |
|
||||
| 17:40~17:45 | 10 · 토론 | 다음 일차와의 연결, 질문 둘 |
|
||||
|
||||
## 1. 오늘 한 장
|
||||
|
||||
| 단계 | 한 일 | 오늘 숫자 |
|
||||
| --- | --- | --- |
|
||||
| 1단계 · 실패의 종류 | 망가뜨려 보기, 항목 확인 · 재시도 · 멱등 키, 고장 종류마다, LLM 멈춤, 요청 규칙 | 500인데 **2건 접수** · 중복 → 부분 성공 · `duplicates: 3`, `slow` **12.4초** · 늦게 생긴 3건, LLM 멈춰도 `created: 3`, 11건 11.0초 → **0.2초 400** |
|
||||
| 2단계 · 모으고 알리기 | DLQ, Error Workflow, 그물 지도, 처리한 실패 알림 · DLQ가 멈추면, 알림 폭주 | 사라지던 2건 → **DLQ**, 버그 → 3초 뒤 **critical**, DLQ 멈춤 → **잃을 뻔한 id**를 알림에, 두 번째 알림 → **suppressed** (DLQ 5건 그대로) |
|
||||
| 3단계 · 재처리와 운영 체계 | 재처리, 런북 · 짝 연습, 반쯤 처리된 요청, parked, 운영 상태 | 재처리 `ok: 2` · 중복 0, 반쯤 처리 → `duplicates: 2`, 세 번째 재처리 **0.1초 parked**, 상태 `ok → warn → critical → ok` |
|
||||
| 바이브 코딩 | 아무 명령 · HTTP에 재시도 · 마감 · 대체를 붙이는 `tretry` | 요구 14개, `slow`에서 마감 5초 |
|
||||
|
||||
한 문장으로: **실패를 종류별로 나눠 각자 갈 곳을 정하고, 잃지 않게 모으고, 사람이 알게 하되 지치지 않게, 고친 뒤 중복 없이 되돌린다.**
|
||||
|
||||
## 2. 왜 실패 처리가 운영의 일인가
|
||||
|
||||
> **생활 속 예 · 좋은 식당은 실수했을 때 알 수 있다.** 음식이 맛있는 식당은 많지만, 손님이 기억하는 것은 **일이 틀어졌을 때**입니다. 주문이 누락됐을 때 "죄송합니다, 주문서가 빠졌네요. 바로 해 드리고 음료는 서비스입니다"라고 하는 식당과, "주방에 물어보세요" 하는 식당. 앞의 식당에는 주문서 번호(멱등 키), 누락 주문 메모판(DLQ), 실장을 부르는 규칙(알림 등급), 직원 교육 매뉴얼(런북)이 있습니다. 실패 처리는 잘 될 때가 아니라 **안 될 때의 서비스 품질**입니다. 3단계 8절의 "택배 회사로 본 오늘 하루" 표도 함께 봅니다.
|
||||
|
||||
72일차는 "빠르게, 멈춰도 답하게", 73일차는 "속지 않게, 새지 않게", 74일차는 "바꿔도 망가지지 않게", 75일차는 "써도 감당할 수 있게"였습니다. 오늘은 "**실패해도 잃지 않게**"입니다.
|
||||
|
||||
| | 만들기 | 운영하기 (실패 처리) |
|
||||
| --- | --- | --- |
|
||||
| 질문 | "잘 될 때 맞나?" | "안 될 때 무엇이 남나? 누가 아나? 어떻게 되돌리나?" |
|
||||
| 기준 | 정상 요청의 테스트 | **망가뜨린** 상태의 테스트 (고장 스위치 · `llm down`) |
|
||||
| 실패 | 오류 메시지 | 잃은 문의 · 중복 접수 · 아무도 모르는 버그 |
|
||||
| 정하는 사람 | 개발자 | 개발자 + 운영(알림 · 런북) + 업무 담당(parked를 어떻게) |
|
||||
|
||||
- 실패 처리의 사고는 대개 **두 번째 행동**에서 납니다. 500을 받은 사용자가 다시 보내 중복이 생기고, 급한 담당자가 DLQ를 비워 요청이 사라집니다. 그래서 결과를 정직하게(부분 성공), 다시 보내도 안전하게(멱등), 할 일을 미리(런북) 둡니다.
|
||||
- 실패 처리 장치도 실패합니다. 오늘 DLQ가 멈추자 세 문의가 조용히 사라졌습니다. **처리 장치를 처리하는** 한 겹(critical `dlq_down`, 운영 상태)이 있어야 체계입니다.
|
||||
|
||||
## 3. 효과와 값을 숫자로
|
||||
|
||||
### 3.1 오늘 실측 전 · 후
|
||||
|
||||
| 무엇 | 전 | 후 | 바꾼 것 |
|
||||
| --- | --- | --- | --- |
|
||||
| 빈 글이 섞인 3건 | 500 · 사실은 2건 접수 | 200 · `created: 2, rejected: 1` (1.7초) | 항목 확인 · 부분 성공 |
|
||||
| 같은 3건을 다시 | 새 접수 건 3개 (중복) | `duplicates: 3`, 접수 건 그대로 | 키 = 요청 id |
|
||||
| 잠깐의 끊김 (`flaky`) | 500 | `created: 2` (2.4초) | 재시도 3 + 키 |
|
||||
| LLM 멈춤 | 0.8초에 500 · 접수 0 | `created: 3, unclassified: 3` (0.9초) | 실패해도 계속 · 미분류 |
|
||||
| 11건 | 11.0초 · 모두 처리 | **0.2초** · 400 | 입구 상한 |
|
||||
| API가 멈춘 동안 2건 | 결과에만 → 사라짐 | DLQ 2 · high 알림 1 · 재처리 `ok: 2` | DLQ · 알림 · 재처리 |
|
||||
| 버그 (`nightly-report`) | 500 · 아무도 모름 | 3초 뒤 critical (노드 · 오류 문장) | Error Workflow |
|
||||
| DLQ도 멈춤 | 3건 조용히 사라짐 | `dlq_saved: false` · critical · lost ids | 처리 장치의 결과 확인 |
|
||||
| 같은 문제의 두 번째 알림 | 또 보냄 | `suppressed` (DLQ에는 그대로) | 10분 안 같은 알림 확인 |
|
||||
| 세 번 실패한 요청 | 재처리마다 또 보냄 | 0.1초 · parked · high 알림 | 최대 시도 수 |
|
||||
|
||||
**치른 값**:
|
||||
|
||||
- **시간**: 재시도는 실패를 늦게 알게 합니다. `slow`에서 사용자는 12초를 기다리고 실패를 받습니다. 사용자가 기다릴 수 있는 시간을 먼저 정해야 합니다 (`tretry --deadline`).
|
||||
- **복잡도**: 기준 워크플로우는 노드 8개, 오늘 마지막 접수(Lab 02 과제 2 정답)는 22개입니다. 노드가 늘수록 실패 처리 자체가 버그가 될 수 있어(DLQ가 멈출 때의 구멍), 망가뜨린 상태의 테스트가 함께 늘어야 합니다.
|
||||
- **미분류**: LLM이 멈춘 동안의 접수는 사람이 나중에 분류해야 합니다.
|
||||
- **사람의 일**: parked · critical은 결국 사람이 봅니다. 런북의 "누가 · 언제까지"가 비어 있으면 parked는 영원히 parked입니다.
|
||||
- **최소 한 번**: 잃지 않는 대신 같은 요청이 두 번 이상 갈 수 있고, 받는 쪽 모두가 멱등해야 합니다.
|
||||
|
||||
### 3.2 하루로 보면 (예시)
|
||||
|
||||
가정: 하루 문의 1,000건, 데이터 API가 하루 한 번 30분 멈춤 (문의가 고르게 온다면 약 21건이 그 사이), 잠깐의 끊김은 1% (10건).
|
||||
|
||||
| | 기준 (`base-intake`) | 오늘 저녁의 접수 |
|
||||
| --- | --- | --- |
|
||||
| 잠깐의 끊김 10건 | 10번의 500 → 사용자가 다시 보내면 일부는 중복 | 재시도로 거의 모두 접수, 중복 없음 |
|
||||
| 30분 장애 동안 21건 | 21번의 500, 다시 보내지 않으면 잃음 | DLQ 21건 · high 알림 3번 (10분에 한 번) · 고친 뒤 재처리 |
|
||||
| 같은 기간 알림 | 0 (아무도 모름) | high 3 · 필요하면 critical |
|
||||
| 사람이 할 일 | 사용자 문의 · 중복 정리 | 런북대로 재처리, parked 확인 |
|
||||
|
||||
- 숫자는 가정입니다. 그래도 **잃는 건수와 알림 수를 같은 표에서 말할 수 있다**는 것이 오늘 만든 체계의 효과입니다.
|
||||
|
||||
## 4. 회사에서는 같은 생각을 이렇게 부른다
|
||||
|
||||
| 오늘 한 것 | 업계 이름 | 쓰는 도구 · 표준 (예) |
|
||||
| --- | --- | --- |
|
||||
| 실패를 다시 할 것과 아닌 것으로 | 일시적 오류 처리 (transient fault handling) | 클라우드 설계 지침 (Azure Architecture Center · AWS Builders' Library) |
|
||||
| 항목마다 상태 · 200 | 부분 성공, batch item failures | HTTP 207, AWS Lambda의 SQS batch item failures |
|
||||
| 재시도 · backoff · jitter | 재시도 정책, exponential backoff with jitter | 클라우드 SDK 기본 재시도, Polly(.NET) · resilience4j(Java) · tenacity(Python) |
|
||||
| 멱등 키 | idempotency key, 중복 제거 | Stripe `Idempotency-Key`, IETF Idempotency-Key 헤더 초안 |
|
||||
| 미분류로 접수 | 우아한 저하, 대체 경로 (fallback) | 기능 플래그, 기본값 |
|
||||
| 입구 규칙 · 상한 | 입력 검증, 요청 크기 제한 | API 게이트웨이, JSON Schema |
|
||||
| DLQ | dead letter queue | Amazon SQS · RabbitMQ · Kafka · Azure Service Bus |
|
||||
| Error Workflow | 전역 오류 처리기, 오류 추적 | n8n Error Trigger, Sentry, 클라우드 로그 알림 |
|
||||
| 알림 등급 · 줄이기 | 심각도(SEV), 중복 제거 · 묶기 · 반복 간격 | PagerDuty, Opsgenie, Prometheus Alertmanager |
|
||||
| 재처리 | redrive, replay | SQS DLQ redrive, 작업 큐 재시도 화면 |
|
||||
| parked | 독 메시지 · 최대 받기 횟수 | SQS `maxReceiveCount`, 작업 큐의 dead jobs |
|
||||
| 런북 · 짝 연습 | runbook · playbook, game day, chaos engineering | 사내 위키, Chaos Monkey, 사후 검토(blameless postmortem) |
|
||||
| 운영 상태 | health check, status page | 쿠버네티스 probe, 상태 페이지 서비스 |
|
||||
| `tretry` | 재시도 래퍼, 회로 차단기 | 위 재시도 라이브러리, 서비스 메시(Istio 등)의 재시도 · circuit breaking |
|
||||
|
||||
- 오늘은 n8n 노드 · 작은 데이터 API · `flow.py`로 직접 만들었습니다. 회사에서는 큐 · 라이브러리 · 알림 도구가 대신하는 경우가 많습니다. 그래도 **무엇을 다시 할 실패로 볼지, 몇 번 · 얼마나 기다릴지, 어떤 알림을 누가 받을지, parked를 누가 볼지**는 오늘처럼 사람이 정합니다.
|
||||
|
||||
## 5. 산업별 응용 (예)
|
||||
|
||||
아래는 오늘 한 일을 다른 업무에 옮긴 예입니다 (실제 특정 회사의 사례가 아니라 흔한 모양).
|
||||
|
||||
| 업무 | 오늘의 무엇을 | 이렇게 |
|
||||
| --- | --- | --- |
|
||||
| 쇼핑몰 주문 · 결제 | 멱등 키 · 부분 성공 | 주문 번호를 키로 결제 요청, 시간 초과 뒤 다시 보내도 한 번만 결제 |
|
||||
| 급여 · 대량 이체 | 항목마다 상태 · 입구 규칙 | 계좌 형식 오류는 그 건만 거절, 한 번에 보낼 건수 상한, 건마다 결과 파일 |
|
||||
| 대량 메일 · 알림톡 발송 | DLQ · 재처리 · parked | 발송 실패는 모아 두었다 재시도, 세 번 실패한 수신자는 따로 (주소 확인) |
|
||||
| 고객 문의 AI 분류 (오늘) | 우아한 저하 · 미분류 | LLM 장애 때도 접수, 미분류 수를 대시보드에 |
|
||||
| 데이터 수집 · ETL | 그물 지도 · 운영 상태 | 원천이 잠깐 끊기면 재시도, 형식이 틀린 행은 따로, 저녁 작업 상태를 신호등으로 |
|
||||
| 사내 자동화 (n8n · RPA) | Error Workflow · 런북 | 모든 워크플로우에 같은 에러 처리기, 알림에 실행 번호와 런북 주소 |
|
||||
|
||||
**토론 거리**: 내 회사(또는 가고 싶은 회사)의 업무 하나를 골라, "두 번 하면 안 되는 일은 무엇이고, 무엇을 키로 쓸까"를 정해 봅니다.
|
||||
|
||||
## 6. 운영 대시보드에 올릴 숫자 여섯
|
||||
|
||||
오늘 남긴 결과 · DLQ · 알림의 칸만으로 운영 지표가 됩니다 (77일차 로그 분석 · 79일차 운영 대시보드에서 실제로 모읍니다).
|
||||
|
||||
| 지표 | 어느 칸에서 | 무엇을 알 수 있나 |
|
||||
| --- | --- | --- |
|
||||
| 부분 성공 비율 | `failed` · `rejected` ÷ `total` | 실패가 늘었나, 사용자 입력이 나빠졌나 |
|
||||
| 재시도로 살아난 수 | `flaky`처럼 재시도 뒤 `created` (실행 기록) | 잠깐의 끊김이 얼마나 잦나 |
|
||||
| DLQ 크기와 가장 오래된 것 | `/dlq`의 `size` · `at` | 재처리가 밀렸나 |
|
||||
| parked 수 | `/dlq`의 `parked` | 사람이 볼 일이 쌓였나 |
|
||||
| 등급별 알림 수 · suppressed 수 | `/alerts`, 결과의 `alert` | 장애가 잦나, 알림이 너무 많나 |
|
||||
| 미분류 비율 | `unclassified` ÷ `created` | LLM이 자주 멈추나 |
|
||||
|
||||
- 오늘 결과에 `reason` · `attempts` · `alert`를 남기게 한 까닭이 이것입니다. **센 수가 없으면 개선했는지도 말할 수 없습니다.**
|
||||
|
||||
## 7. 하지 말아야 할 때 (흔한 실수)
|
||||
|
||||
| 하기 쉬운 일 | 왜 위험한가 | 대신 |
|
||||
| --- | --- | --- |
|
||||
| 모든 실패를 재시도 | 400을 세 번, 부하만 늚 | 고칠 수 없는 것은 거절 |
|
||||
| 재시도만 켜고 키는 없음 | 재시도가 곧 중복 | 키 = 업무 id |
|
||||
| 층마다 재시도 | 부하가 곱해짐 (재시도 폭풍) | 한 곳에서, 예산 안에서 |
|
||||
| 실패를 결과에서 뺌 | "모두 성공"이라는 거짓 | 항목마다 상태 |
|
||||
| `neverError`만 두고 끝 | 처리 장치의 실패가 조용히 사라짐 | 본문 확인 · 다른 길로 알림 |
|
||||
| 항목마다 알림 | 알림 피로 → 아무도 안 봄 | 요청 하나에 하나, 같은 문제는 한 번 |
|
||||
| 급해서 DLQ를 비움 | 재처리할 요청이 사라짐 | 런북 "하지 말 것" |
|
||||
| 원인을 고치기 전에 재처리 | 또 실패, 금방 parked | 확인 → 재처리 |
|
||||
| 런북을 써 두기만 | 틀린 줄 모름 | 짝 연습 · game day |
|
||||
|
||||
## 8. 직무와 면접
|
||||
|
||||
오늘 한 일은 다음 직무들이 날마다 하는 일입니다: **백엔드 · 플랫폼 엔지니어, SRE · 운영 엔지니어, 데이터 엔지니어(파이프라인), AI · LLM 애플리케이션 엔지니어, 업무 자동화(RPA · n8n) 개발자.** 채용 공고의 "장애 대응", "on-call", "재시도 · 멱등성", "메시지 큐 · DLQ", "모니터링 · 알림", "런북 작성", "안정성(reliability)"이 오늘 내용입니다.
|
||||
|
||||
면접에서 흔히 받는 질문과 오늘 숫자로 답하는 법:
|
||||
|
||||
<details><summary>1. "일괄 처리 API에서 일부가 실패하면 어떻게 답하나요?"</summary>
|
||||
|
||||
전체를 500으로 끝내지 않고 200에 항목마다 상태와 이유를 담습니다. 오늘 기준 워크플로우는 빈 글 하나에 500을 줬지만 사실 두 건은 이미 접수되어 있었고, 사용자가 다시 보내면 중복이 생겼습니다. 고친 뒤에는 빈 글만 `rejected`, 나머지는 `created`로 1.7초에 답했습니다. 받는 쪽은 거절은 고쳐서, 실패는 나중에 다시 보내면 됩니다.
|
||||
</details>
|
||||
|
||||
<details><summary>2. "재시도할 때 주의할 점은?"</summary>
|
||||
|
||||
세 가지입니다. 다시 하면 되는 실패(5xx · 429 · 시간 초과)만 다시 하고, 간격을 지수로 늘리고 흔들며(jitter) 상한과 전체 마감을 둡니다. 그리고 받는 쪽이 멱등해야 합니다. 오늘 `flaky`에서는 재시도로 2.4초에 성공했지만, 늘 실패하는 장애에서는 시간만 늘었고, 느린 장애에서는 사용자가 12초를 기다렸습니다. 게다가 시간 초과로 포기한 요청이 나중에 만들어져 있어서, 키가 없었다면 다시 보낼 때 중복이 생겼을 것입니다.
|
||||
</details>
|
||||
|
||||
<details><summary>3. "멱등성이란 무엇이고 어떻게 구현하나요?"</summary>
|
||||
|
||||
같은 요청을 여러 번 보내도 결과가 한 번 보낸 것과 같은 성질입니다. 보내는 쪽이 업무 id 같은 고유한 키를 붙이고, 받는 쪽은 키를 기억했다가 같은 키가 오면 처음 결과를 돌려줍니다. 실행 번호나 시각은 매번 바뀌어서 키가 될 수 없습니다. 오늘 같은 세 건을 두 번 보내도 `duplicates: 3`, DLQ 재처리에서도 `duplicates: 2`로 접수 건이 늘지 않았습니다.
|
||||
</details>
|
||||
|
||||
<details><summary>4. "DLQ에 쌓인 메시지는 어떻게 처리하나요?"</summary>
|
||||
|
||||
원인을 먼저 고치고, 원래 입구로 하나씩 다시 보내 성공한 것만 지웁니다. 정해진 횟수 넘게 실패한 것은 자동 재처리에서 빼서 사람에게 넘깁니다. 오늘 재처리는 `my-intake`를 다시 불러 같은 규칙과 키로 처리했고, 세 번 실패한 두 건은 parked로 남기고 high 알림을 보냈습니다. DLQ를 비우는 것은 런북의 "하지 말 것"에 적었습니다.
|
||||
</details>
|
||||
|
||||
<details><summary>5. "알림이 너무 많아서 다들 무시합니다. 어떻게 하나요?"</summary>
|
||||
|
||||
등급을 나누고, 사람이 할 일이 있을 때만 알리고, 같은 문제는 묶습니다. 오늘은 요청 하나에 알림 하나(`count`)로, 같은 이유의 high 알림은 10분에 한 번으로 줄였습니다. 대신 보내지 않은 것도 `suppressed`로 남기고 DLQ에는 모두 남겼고, 요청을 잃을 수 있는 critical은 줄이지 않았습니다.
|
||||
</details>
|
||||
|
||||
## 9. 최종 프로젝트로 가져갈 것
|
||||
|
||||
최종 프로젝트(85일차~)의 워크플로우에 아래를 넣으면 "운영을 생각한 프로젝트"가 됩니다.
|
||||
|
||||
- [ ] 여러 건을 받는 곳은 항목마다 상태로 답한다 (부분 성공), 입구 규칙 · 상한
|
||||
- [ ] 외부 API 호출에 시간 제한 · 재시도 (다시 할 실패만), 두 번 하면 안 되는 일에는 멱등 키
|
||||
- [ ] LLM 같은 덧붙인 일이 실패해도 핵심 일은 끝낸다, 대체한 수를 남긴다
|
||||
- [ ] 오래가는 실패는 원래 요청 그대로 DLQ에 (개인정보 · 보관 기간 주의)
|
||||
- [ ] 모든 워크플로우에 같은 에러 처리기 (고정 id, 실행 번호 · 노드 · 오류)
|
||||
- [ ] 알림 등급 · 요청 하나에 하나 · 같은 문제는 한 번, 처리 장치가 실패할 때의 알림
|
||||
- [ ] 재처리는 같은 입구로, 최대 시도 수 뒤 사람에게
|
||||
- [ ] 운영 상태 웹훅 (읽기만)
|
||||
- [ ] 런북 (명령 · 확인 · 갈림길 · 하지 말 것 · 누가) + 한 번은 짝 연습, `RUNBOOK.md`
|
||||
|
||||
## 10. 다음 일차와의 연결
|
||||
|
||||
| 일차 | 오늘의 무엇을 잇나 |
|
||||
| --- | --- |
|
||||
| 77 | 실행 로그 분석 — 오늘 남긴 실패 · 알림 · n8n 실행 기록을 모아 어디가 자주 · 오래 실패하는지 찾고 고친 뒤 다시 잼 |
|
||||
| 78 | 데이터 품질 검증 — 오늘의 입구 규칙을 넓혀 들어오는 데이터의 품질을 자동으로 검사 |
|
||||
| 79 | 운영 대시보드 · `tops` — 6절의 운영 지표 여섯, `tretry`의 기록 · 운영 상태 |
|
||||
| 80~82 | 단위 프로젝트 — 팀 워크플로우에 9절 체크리스트 |
|
||||
|
||||
## 토론 (5분)
|
||||
|
||||
1. 오늘 바꾼 것 중 **사용자에게 보이는 것**과 **운영자에게만 보이는 것**은 각각 무엇인가요? 어느 쪽이 더 중요할까요?
|
||||
2. 내 업무의 자동화 하나에서 "parked가 되면 누가, 언제까지, 무엇을 보고 정하나"를 정해 보세요.
|
||||
|
||||
## 정리
|
||||
|
||||
- [ ] 실패 처리도 **운영**: 실패해도 잃지 않게, 다시 해도 겹치지 않게, 사람이 알게
|
||||
- [ ] 효과는 망가뜨린 상태의 전 · 후 숫자로 (500 · 중복 → 부분 성공 · `duplicates`, 사라짐 → DLQ · 알림 · 재처리, 11.0초 → 0.2초)
|
||||
- [ ] 같은 생각을 업계에서는 transient fault handling · idempotency key · DLQ · redrive · alert fatigue · runbook · game day라고 부른다
|
||||
- [ ] 결과 · DLQ · 알림의 칸이 곧 운영 지표가 된다
|
||||
- [ ] 최종 프로젝트에 체크리스트 9개를 가져간다
|
||||
@@ -0,0 +1,95 @@
|
||||
# 7·8교시 · 바이브 코딩 (`tretry`)
|
||||
|
||||
- 교시: 7교시 16:00~16:50 (각자 만들기), 8교시 17:00~17:50 (발표 10분 · [4단계 정리](04-STAGE4-WRAPUP.md) 35분 · 예고 5분)
|
||||
- 과제: [VIBE-REQ-TRETRY](../../Last3Mon/76/VIBE-REQ-TRETRY.md)(요청서, 요청의 말 ①~⑫) → [VIBE-Q-TRETRY](../../Last3Mon/76/VIBE-Q-TRETRY.md)의 8단계: `Doc/TODO.md` → `Doc/SPEC.md` → `Doc/IMP-PLAN.md`(엔진 · TUI 단계) → 단계별 구현 → 확인 → `uv tool install` → 다른 폴더에서 사용 → 수업 뒤 `VIBE-A-TRETRY.md` 공개
|
||||
- 요구 14개(R1~R14). 50분 안에는 **엔진 1단계(R1 · R2 · R3 · R4 · R12 · R13) + 엔진 2단계(R5 · R6 · R8) + TUI 1단계(R11) + 설치(R14)**, 나머지(R7 마감 · R9 순위 · R10 회로 차단)는 IMP-PLAN의 다음 단계로 집에서.
|
||||
- 72일차 `tbench` · 73일차 `tscan` · 74일차 `tver` · 75일차 `tcost`와 같은 흐름입니다: 사람의 말(요청서)을 **확인할 수 있는 요구**로 바꾸는 일을 학생이 먼저 하고, VIBE-Q는 그 다음 단계(비교 대상)로 씁니다.
|
||||
- 오늘은 `tretry`: 아무 명령이나 HTTP 호출을 감싸 다시 하기(지수 backoff · 상한 · 흔들기) · 시도마다 시간 제한 · 전체 마감 · 대체 명령을 붙이고, 고칠 수 없는 실패(4xx)는 다시 하지 않고, 같은 멱등 키를 붙이고, 기록 · 순위 · 회로 차단을 TUI로. 오늘 n8n 노드 설정으로 한 일을 **n8n 밖 어떤 명령에든.**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
R["요청서 → TODO · SPEC ·<br/>IMP-PLAN 20분"] --> A["7교시<br/>엔진 1 · 2 · TUI · 설치 30분"] --> B["발표<br/>10분"] --> C["4단계 정리<br/>35분"] --> F["77일차 예고<br/>5분"]
|
||||
```
|
||||
|
||||
> **생활 속 예 · 자동 재다이얼 전화기.** 옛날 전화기의 "재다이얼" 버튼은 통화 중이면 몇 초 뒤 알아서 다시 겁니다. 잘 만든 재다이얼은 ① 간격을 점점 늘리고 ② 몇 번에서 멈추고 ③ "없는 번호" 안내가 나오면 다시 걸지 않고 ④ 정해 둔 시간이 지나면 "나중에 다시 거세요"로 끝냅니다. `tretry`는 아무 명령이나 HTTP 호출에 이런 재다이얼을 붙이는 도구입니다.
|
||||
|
||||
> 이 문서는 7 · 8교시 진행 문서입니다. 8교시의 정리 35분은 [04-STAGE4-WRAPUP](04-STAGE4-WRAPUP.md)으로 합니다 (74일차부터 4단계 정리를 따로 두었습니다).
|
||||
|
||||
## 1. 7교시 · 각자 만들기 (50분)
|
||||
|
||||
| 시간 | 단계 | 확인 |
|
||||
| --- | --- | --- |
|
||||
| 16:00~16:09 | 0 준비 · 1 요청서 → `Doc/TODO.md` (에이전트 없이) | 할 일 ①~⑫ · 꼭 / 나중 |
|
||||
| 16:09~16:15 | 2 `Doc/SPEC.md` → VIBE-Q 요구 표(R1~R14)와 비교 · commit | 요구마다 확인 방법 (시도 수 · 기다린 시간 · 종료 코드) |
|
||||
| 16:15~16:20 | 3 `Doc/IMP-PLAN.md` (엔진 1~3단계 · TUI 1단계 · 설치) · commit | 단계마다 체크리스트 |
|
||||
| 16:20~16:44 | 4 구현: 엔진 1단계 → 엔진 2단계 → TUI 1단계, 막히면 5 문제 해결 팁 | 단계마다 `pytest` · 직접 실행 · commit |
|
||||
| 16:44~16:50 | 6 확인 · 7 `uv tool install --editable .` · 8 다른 폴더에서 `tretry` | 데이터 API `flaky`에서 `tretry http POST localhost:8800/tickets ... --idem-key x --body-key key` |
|
||||
|
||||
| 볼 것 | 막혔을 때 한마디 |
|
||||
| --- | --- |
|
||||
| TODO · SPEC 없이 VIBE-Q를 통째로 붙여 넣었나 | "요청서로 Doc/TODO.md부터. 기다린 시간 [0.5, 1.0]이 SPEC에 없으면 맞았는지 모릅니다" |
|
||||
| IMP-PLAN이 한 덩어리 · 첫 요청에 14개 | "엔진 1단계만. 단계마다 확인 · 체크 · commit" |
|
||||
| 테스트가 몇 초씩 | "엔진이 sleep · clock을 인자로, 테스트에서는 가짜로" |
|
||||
| 400도 다시 함 | "고칠 수 없는 실패는 1번, 종료 코드 3, 대체도 하지 않기 (오늘 Lab 01)" |
|
||||
| 간격이 늘 같음 · 끝없이 늚 | "지수 backoff와 상한" |
|
||||
| 다시 보낸 POST로 접수 건이 둘 | "멱등 키는 실행에 한 번 만들고 모든 시도에, 본문 key에도" |
|
||||
| `--timeout`이 안 먹음 | "subprocess와 urlopen 둘 다 timeout, 시간 초과도 다시 할 실패" |
|
||||
| `--` 뒤 옵션을 tretry가 먹음 | "`--` 앞만 tretry 옵션" |
|
||||
| 기록에 토큰 | "헤더 값은 남기지 않기, URL · 오류의 키도 `***`" |
|
||||
| 실행해 보지 않고 다음 요청 | "에이전트가 됐다고 해도 직접 `uv run tretry ...; echo $?`" |
|
||||
|
||||
정답 예(R1~R14를 모두 넣은 `tretry`와 정답본 `VIBE-A-TRETRY.md`)는 수업 뒤 공개됩니다.
|
||||
|
||||
## 2. 8교시 · 발표 (10분)
|
||||
|
||||
- 1~2명. **데이터 API의 `flaky` · `slow` · `error`에서 다시 하기 · 마감 · 대체를 보여 줄 수 있는 사람**, 400에서 멈추는 것(종료 코드 3)을 보여 줄 수 있는 사람을 먼저 고릅니다.
|
||||
- 3분 발표 · 2분 질문: **내 SPEC과 VIBE-Q가 달랐던 곳** / IMP-PLAN을 어떻게 나눴나 / 잘 먹힌 요청문 / 안 먹힌 요청문 / 에이전트가 틀린 곳 / 시연.
|
||||
|
||||
## 3. 강사 정리 (8교시 4단계 정리 안에서)
|
||||
|
||||
| | n8n 일괄 접수 (오늘 Lab) | tretry |
|
||||
| --- | --- | --- |
|
||||
| 다시 하기 | 노드 `retryOnFail` · `maxTries` 3 · 고정 300ms | `--tries` · 지수 backoff · 상한 · jitter (R1 · R2) |
|
||||
| 시간 제한 | 노드 `options.timeout` 3000 | `--timeout`, 전체 `--deadline` (R3 · R7) |
|
||||
| 고칠 수 없는 실패 | 항목 확인 → `rejected` (Lab 01 예제 2) | 4xx는 1번 · 종료 코드 3 (R5) |
|
||||
| 멱등 키 | `key` = 요청 id | `--idem-key` · `--body-key` (R6) |
|
||||
| 대체 | 미분류로 접수 (Lab 01 과제 1) | `--fallback` (R4) |
|
||||
| 실패 기록 | DLQ · 알림 | JSON Lines 기록 · `stats` (R8 · R9) |
|
||||
| 끝없이 하지 않기 | `attempts` 3이면 parked (Lab 03 과제 1) | 회로 차단 `--breaker` (R10) |
|
||||
|
||||
- 풀이 거리: Lab 01 예제 3의 `slow`는 내 접수가 **12.4초** 뒤 실패했습니다. 강사 본 `tretry`에 `--timeout 3 --deadline 5`를 주면 두 번째 시도의 시간 제한을 남은 1.5초로 줄여 **5.00초**에 포기합니다. "몇 번 할까" 대신 "얼마나 기다릴 수 있나"로 정하는 것.
|
||||
|
||||
## 4. 강사 바이브 코딩 (시간이 남으면 · 수업 뒤, 15분)
|
||||
|
||||
| 주제 | 요청문 요지 | 보여 줄 것 |
|
||||
| --- | --- | --- |
|
||||
| 회로 차단의 대상 | "breaker 대상을 METHOD URL 이 아니라 METHOD + 경로 + 상태 코드 종류로, 4xx 는 세지 않기" | 400 실수 때문에 차단이 열리지 않는 것 |
|
||||
| 재시도 예산 | "기록에서 최근 1분 시도 중 재시도 비율이 10% 넘으면 재시도하지 않기" | 재시도 폭풍 막기 |
|
||||
| n8n 웹훅 감싸기 | "tretry http POST localhost:5678/webhook/my-intake --body @requests/batch-two.json" | 부분 성공(200)은 성공 — 결과를 보고 판단해야 함 |
|
||||
|
||||
걸린 시간과 결과를 `Last3Mon/76/VIBE-A-TRETRY.md`에 적어 둡니다.
|
||||
|
||||
## 5. 보충 · 팁 (읽을거리, 4단계 정리 7절과 함께)
|
||||
|
||||
| 팁 | 설명 |
|
||||
| --- | --- |
|
||||
| 요청서 → 요구가 먼저 | 확인 방법(시도 수 · 기다린 시간 · 종료 코드)이 있는 요구가 있어야 에이전트의 "완료"를 판단할 수 있음 |
|
||||
| 시간은 바꿔 끼우게 | `sleep` · `clock` · 무작위를 인자로 받으면 3초짜리 시도도 0초에 시험 |
|
||||
| 엔진은 "한 번 해 보기"만 안다 | 명령 · HTTP의 차이는 실행기에, 엔진은 하나 |
|
||||
| 고칠 수 없는 실패를 가리지 않기 | 대체가 400을 덮으면 실수를 영영 모름 |
|
||||
| 키는 실행에 한 번 | 시도마다 새 키면 멱등이 아님 |
|
||||
| 기록은 한 줄에 하나 | 중간에 죽어도 앞 줄은 남음, 비밀은 지움 |
|
||||
| 단계마다 `git commit` | 에이전트가 망가뜨려도 돌아갈 곳 |
|
||||
|
||||
## 6. 77일차 예고 (5분)
|
||||
|
||||
- 오늘: 실패를 나누고 · 모으고 · 알리고 · 다시 돌렸습니다. 다음 수업(77일차, 11/2): **실행 로그 분석과 운영 데이터 기반 Workflow 개선** — 오늘 남긴 알림 · DLQ · n8n 실행 기록을 모아 어디가 자주 · 오래 실패하는지 찾고 고친 뒤 다시 잽니다.
|
||||
- 바이브 코딩: `tlog`. 오늘 `tretry`는 지우지 마세요. 79일차 `tops`에서 씁니다.
|
||||
|
||||
## 확인 (강사)
|
||||
|
||||
- [ ] 16:00 요청서 → TODO(에이전트 없이) → SPEC → IMP-PLAN 20분을 지켰다, 16:20부터 엔진 1단계.
|
||||
- [ ] 7교시 시작 전 데이터 API가 `ops --reset` 상태이고, 고장 스위치를 바꿀 명령을 화면에 띄웠다.
|
||||
- [ ] 수업 뒤: 요청이 있을 때 학생 저장소에 정답(LAB-A · `answers/` · VIBE-A · `tretry/`)을 올린다.
|
||||
- [ ] 발표자 1~2명, 강사 정리 표, 팁, 예고.
|
||||
- [ ] 수업이 끝날 때 학생들의 데이터 API가 처음 상태인지 (`ops --reset`, `llm up`, `doctor`).
|
||||
@@ -0,0 +1,41 @@
|
||||
# 76일차 · 에러 처리와 예외 대응, 안정적 운영 체계 (바이브 코딩)
|
||||
|
||||
- 일자: 2026-10-30 (76일차)
|
||||
- 교과목: 노코드 멀티 에이전트 워크플로우 설계 및 운영
|
||||
- 단원: AI 워크플로우 운영 및 관리 역량 강화
|
||||
- 학습 내용: 에러 처리 및 예외 대응 Workflow 구성, 안정적 운영 체계 적용
|
||||
- 실습 폴더: [Last3Mon/76](../../Last3Mon/76/README.md)
|
||||
- 실습 환경: 65일차 n8n 2.42.6 + OpenAI API(`gpt-4.1-mini`, 분류) + 76일차 데이터 API(75일차 + 접수 건 만들기 · DLQ · 알림 · 고장 스위치) + **CLI 코딩 에이전트**
|
||||
- **실습 준비: [00-DAY76-PREP.md](00-DAY76-PREP.md) (수업 전날까지 1~2절 완료)**
|
||||
- 수업 형식: 1 · 3 · 5교시 강의, 2 · 4 · 6교시 실습 5개씩 (예제 3 · 과제 2), 7교시 바이브 코딩, 8교시 발표 · 4단계 정리
|
||||
|
||||
## 오늘의 시간표
|
||||
|
||||
| 교시 | 시간 | 내용 | 자료 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 09:00~09:50 | 개요 · 1단계 강의 · 실패의 종류 | [개요](00-DAY76-OVERVIEW.md) 1~14장, [1단계](01-STAGE1-FAILURE-TYPES.md) |
|
||||
| 2 | 10:00~10:50 | Lab 01 실습 5개 (망가뜨려 보기 · 항목 확인 · 재시도 · 멱등 키 · 고장 종류 · LLM 멈춤 · 요청 규칙) | [Lab 01](../../Last3Mon/76/LAB-01-Q-FAILURE-TYPES.md) |
|
||||
| 3 | 11:00~11:50 | 2단계 강의 · 모으고 알리기 | 개요 15~21장, [2단계](02-STAGE2-COLLECT-AND-ALERT.md) |
|
||||
| 4 | 12:00~12:50 | Lab 02 실습 5개 (DLQ · Error Workflow · 그물 지도 · 처리한 실패 알림 · 알림 폭주) | [Lab 02](../../Last3Mon/76/LAB-02-Q-COLLECT-AND-ALERT.md) |
|
||||
| - | 13:00~14:00 | 점심 | |
|
||||
| 5 | 14:00~14:50 | 3단계 강의 · 재처리와 운영 체계 | 개요 22~28장, [3단계](03-STAGE3-REPLAY-AND-RUNBOOK.md) |
|
||||
| 6 | 15:00~15:50 | Lab 03 실습 5개 (재처리 · 런북 · 장애 연습 · 반쯤 처리된 요청 · parked · 운영 상태) | [Lab 03](../../Last3Mon/76/LAB-03-Q-REPLAY-AND-RUNBOOK.md) |
|
||||
| 7 | 16:00~16:50 | 바이브 코딩 `tretry`: 요청서 → TODO → SPEC → IMP-PLAN → 만들기 → 설치 | [VIBE-REQ-TRETRY](../../Last3Mon/76/VIBE-REQ-TRETRY.md), [진행](05-VIBE-CODING.md) |
|
||||
| 8 | 17:00~17:50 | 발표 · **4단계 정리** (의미 · 효과 · 회사에서 쓰는 법) · 77일차 예고 | [4단계 정리](04-STAGE4-WRAPUP.md), [진행](05-VIBE-CODING.md) |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P["개요"] --> S1["1단계<br/>실패의 종류 · 부분 성공<br/>재시도 · 멱등 키"] --> S2["2단계<br/>DLQ · Error Workflow<br/>그물 지도 · 알림"] --> S3["3단계<br/>재처리 · 런북<br/>parked · 운영 상태"] --> V["7교시<br/>tretry"] --> W["8교시<br/>4단계 정리"]
|
||||
```
|
||||
|
||||
## 자료 목록
|
||||
|
||||
| 파일 | 내용 |
|
||||
| --- | --- |
|
||||
| `00-DAY76-PREP.md` | 실습 준비: n8n · 76일차 판 데이터 API 켜기, 기준(`base-intake`) · `nightly-report` 게시, `flow.py doctor` (수업 전) |
|
||||
| `00-DAY76-OVERVIEW.md` | 개요 30장 (PPT 대신 Markdown, 가로줄로 장 구분) |
|
||||
| `01-STAGE1-FAILURE-TYPES.md` | 부분 실패 · 500의 거짓말, 실패의 종류(4xx · 5xx · 시간 초과), 부분 성공, 재시도 · backoff · jitter · 재시도 폭풍, 시간 초과는 "모름", 멱등 키, 우아한 저하, 입구에서 막기 |
|
||||
| `02-STAGE2-COLLECT-AND-ALERT.md` | 사라지는 실패, DLQ, 처리한 · 처리하지 못한 실패, Error Workflow, 그물 지도와 구멍, 알림 등급, 알림 피로 |
|
||||
| `03-STAGE3-REPLAY-AND-RUNBOOK.md` | 재처리 · 같은 입구로, 런북, 장애 연습, 최소 한 번 + 멱등, 끝없이 돌리지 않기 · parked, 운영 상태, 운영 체계 한 장 |
|
||||
| `04-STAGE4-WRAPUP.md` | 8교시 35분: 오늘의 의미 · 효과 숫자 · 업계 이름 · 산업별 응용 · 운영 지표 · 면접 · 최종 프로젝트 체크리스트 |
|
||||
| `05-VIBE-CODING.md` | 7 · 8교시 바이브 코딩 진행 (tretry), 발표, 강사 정리, 강사 바이브 코딩, 팁, 예고 |
|
||||
Reference in new issue
Block a user