76일차 실습 실행 결과 15개: 실행한 명령 · 고치기 전 출력과 까닭 · 고친 뒤 출력 · 비교 표 · 까닭
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bbf17e4696
commit
9871646773
3 files changed
+715
-120
No files matched your search
@@ -116,34 +116,104 @@ flowchart LR
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다 (줄임). LLM 분류 · 시간 · 접수 건 번호의 순서는 실행마다 조금씩 다를 수 있고, 500 · 접수 건 수는 같아야 합니다.
|
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다 (줄임). LLM 분류 · 시간 · 접수 건 번호의 순서는 실행마다 조금씩 다를 수 있고, 500 · 접수 건 수는 같아야 합니다.
|
||||||
|
|
||||||
```text
|
이 예제는 **고치지 않습니다.** 기준 `base-intake`에 실험 넷(0 · ① · ② · ③)을 차례로 해 보고 무엇이 망가지는지 봅니다. 고치는 것은 예제 2입니다.
|
||||||
$ call base-intake requests/batch-ok.json
|
|
||||||
HTTP 200 · 5.2s {"processed": 3, "tickets": ["T-2001", "T-2002", "T-2003"]}
|
|
||||||
|
|
||||||
$ call base-intake requests/batch-bad-item.json
|
#### 0 · 정상 3건 (비교할 기준)
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py ops --reset
|
||||||
|
uv run tools/flow.py call base-intake requests/batch-ok.json
|
||||||
|
```
|
||||||
|
|
||||||
|
출력:
|
||||||
|
|
||||||
|
```text
|
||||||
|
✔ created tickets, DLQ and alerts emptied, fault switch all ok
|
||||||
|
|
||||||
|
HTTP 200 · 5.2s {"processed": 3, "tickets": ["T-2001", "T-2002", "T-2003"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
**왜 이렇게 나왔나:** 고장 스위치가 모두 `ok`이고 세 문의 모두 글이 있으니 분류와 접수 건 만들기가 세 번 다 성공했습니다. 기준은 **잘될 때는 잘됩니다.** 아래 셋은 잘 안 될 때입니다.
|
||||||
|
|
||||||
|
#### ① 빈 글이 섞인 요청 → 500인데 접수 건이 생김
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py call base-intake requests/batch-bad-item.json
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력:
|
||||||
|
|
||||||
|
```text
|
||||||
HTTP 500 · 3.2s {"message": "Error in workflow"}
|
HTTP 500 · 3.2s {"message": "Error in workflow"}
|
||||||
|
|
||||||
$ ops
|
|
||||||
created tickets: 5
|
created tickets: 5
|
||||||
T-2004 key 10058-0 장비/보통 프린터 용지가 걸려요
|
T-2004 key 10058-0 장비/보통 프린터 용지가 걸려요
|
||||||
T-2005 key 10058-2 소프트웨어/보통 메일 첨부가 안 열려요
|
T-2005 key 10058-2 소프트웨어/보통 메일 첨부가 안 열려요
|
||||||
|
DLQ: 0 (parked 0)
|
||||||
$ fault tickets flaky → call base-intake requests/batch-two.json
|
alerts: 0
|
||||||
HTTP 500 · 2.5s {"message": "Error in workflow"}
|
|
||||||
|
|
||||||
$ fault tickets ok → call base-intake requests/batch-ok.json
|
|
||||||
HTTP 200 · 3.0s {"processed": 3, "tickets": ["T-2006", "T-2008", "T-2007"]}
|
|
||||||
|
|
||||||
$ ops
|
|
||||||
created tickets: 8
|
|
||||||
T-2006 key 10060-0 네트워크/높음 VPN이 아침부터 계속 끊겨서 고객 미팅을 못 하고 있
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**왜 이렇게 나왔나**
|
||||||
|
|
||||||
- 빈 글이 섞인 요청은 **500인데 접수 건이 2개(T-2004 · T-2005) 생겼습니다.** 접수 건 만들기 노드가 1번째 · 3번째는 만들고, 2번째에서 데이터 API가 `title`이 비었다고 400을 주자 노드가 오류로 멈췄기 때문입니다. 사용자는 "실패"로 알고 다시 보낼 것이고, 그러면 두 건이 또 생깁니다.
|
- 응답은 500, 곧 "실패"입니다. 그런데 접수 건이 3개에서 **5개**로 늘었습니다 (0번 3개 + 이번 2개).
|
||||||
- `flaky`에서 500: 첫 시도만 실패하는 **잠깐의 끊김**인데도 기준에는 재시도가 없어 바로 멈췄습니다.
|
- 키를 보면 까닭이 보입니다. `10058-0`은 실행 번호 10058의 0번째 항목이고, `10058-2`는 2번째 항목입니다. **1번째(빈 글)만 없습니다.** 접수 건 만들기 노드는 항목을 하나씩 처리하다가 빈 글에서 데이터 API의 400(`title`이 비었음)을 받았고, 거기서 워크플로우 전체가 멈췄습니다. 앞뒤 항목은 이미 처리된 뒤였습니다.
|
||||||
- 같은 정상 3건을 다시 보내자 **새 번호 3개(T-2006~2008)** 가 생겼습니다. VPN 문의 접수 건이 T-2001 · T-2006 둘입니다. 키가 `10057-0` → `10060-0`처럼 실행 번호라서, 데이터 API는 같은 문의인 줄 모릅니다.
|
- 그래서 생긴 문제는 **부분 성공을 실패라고 말한 것**입니다. 사용자는 "실패"를 믿고 다시 보낼 것이고, 그러면 프린터 · 메일 문의가 한 번 더 생깁니다.
|
||||||
|
|
||||||
|
#### ② 잠깐 끊김 (`flaky`) → 다시 해 보면 될 것을 바로 포기
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py fault tickets flaky
|
||||||
|
uv run tools/flow.py call base-intake requests/batch-two.json
|
||||||
|
uv run tools/flow.py fault tickets ok
|
||||||
|
```
|
||||||
|
|
||||||
|
출력:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP 500 · 2.5s {"message": "Error in workflow"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**왜 이렇게 나왔나:** `flaky`는 키마다 **첫 시도만** 500을 주고, 두 번째부터는 성공하는 고장입니다. 한 번만 더 보냈으면 됐을 텐데, 기준에는 재시도가 없어 첫 500에서 바로 멈췄습니다. 이번 요청으로는 접수 건이 하나도 생기지 않았습니다.
|
||||||
|
|
||||||
|
#### ③ 0번과 같은 요청을 다시 → 같은 문의가 두 번
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py call base-intake requests/batch-ok.json
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP 200 · 3.0s {"processed": 3, "tickets": ["T-2006", "T-2008", "T-2007"]}
|
||||||
|
|
||||||
|
created tickets: 8
|
||||||
|
T-2006 key 10060-0 네트워크/높음 VPN이 아침부터 계속 끊겨서 고객 미팅을 못 하고 있
|
||||||
|
…
|
||||||
|
```
|
||||||
|
|
||||||
|
**왜 이렇게 나왔나**
|
||||||
|
|
||||||
|
- 0번과 똑같은 3건을 보냈는데 **새 번호 3개(T-2006~2008)** 가 생겼습니다. 접수 건 8개 = 0번 3 + ① 2 + ② 0 + ③ 3.
|
||||||
|
- VPN 문의 하나에 접수 건이 둘(T-2001 · T-2006)입니다. 0번의 키는 `10057-0`, 이번 키는 `10060-0`입니다. 키가 **실행 번호**라 보낼 때마다 바뀌니, 데이터 API는 같은 문의인 줄 모릅니다.
|
||||||
|
- 사용자가 ①에서 "실패"를 보고 다시 보내면 바로 이 일이 생깁니다.
|
||||||
|
|
||||||
|
#### 정리 · 예제 2에서 고칠 것
|
||||||
|
|
||||||
|
| 실험 | 본 것 | 예제 2에서 고칠 것 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| ① 빈 글 | 500인데 2건이 이미 생김 | 항목마다 확인하고, 결과에 항목마다 상태를 |
|
||||||
|
| ② `flaky` | 한 번 더 하면 될 것을 바로 500 | 잠깐의 실패는 다시 해 보기 |
|
||||||
|
| ③ 같은 요청 | 같은 문의가 두 번 접수 | 키를 실행 번호가 아니라 요청 `id`로 |
|
||||||
|
|
||||||
## 예제 2 · 항목 확인 · 재시도 · 멱등 키 (15분)
|
## 예제 2 · 항목 확인 · 재시도 · 멱등 키 (15분)
|
||||||
|
|
||||||
@@ -272,7 +342,15 @@ uv run tools/flow.py ops
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 정답 워크플로우로 실제로 나온 출력입니다. 시간은 실행마다 조금씩 다르고, ✔ · ✘는 같아야 합니다.
|
수업 준비 때(10/29) 강사 노트북에서 정답 워크플로우로 실제로 나온 출력입니다. 시간은 실행마다 조금씩 다르고, ✔ · ✘는 같아야 합니다.
|
||||||
|
|
||||||
고치기 전 (`test cases/lab01.json --webhook base-intake`)
|
#### 고치기 전 · 기준(`base-intake`)에 같은 테스트
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab01.json --webhook base-intake -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만, `-v`의 요청 · 응답은 줄임):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✘ 정상 3건 · 2.7s · created = null (expected 3); failed = null (expected 0); rejected = null (expected 0)
|
✘ 정상 3건 · 2.7s · created = null (expected 3); failed = null (expected 0); rejected = null (expected 0)
|
||||||
@@ -284,7 +362,26 @@ uv run tools/flow.py ops
|
|||||||
Passed 0/6
|
Passed 0/6
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤 (`test cases/lab01.json`)
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- **정상 3건 ✘ `created = null`:** 기준은 200을 주고 접수 건도 만들었습니다. 하지만 결과가 `{processed, tickets}` 모양이라 테스트가 찾는 `created` · `failed` · `rejected` 칸(AGENTS.md 3절의 결과 모양)이 없습니다. `null`은 "그런 칸이 없음"이라는 뜻입니다.
|
||||||
|
- **같은 요청 ✘ · 접수 건 ✘ `count = 6`:** 같은 3건을 두 번 보냈더니 6개가 되었습니다. 예제 1 ③과 같은 까닭으로, 키가 실행 번호라서입니다.
|
||||||
|
- **잘못된 항목 ✘ 500:** 예제 1 ①과 같습니다. 빈 글 하나 때문에 요청 전체가 500으로 끝났습니다.
|
||||||
|
- **목록이 아니면 ✘ 500 (400 기대):** 본문이 목록인지 보지 않고 바로 나누다가 n8n이 오류를 냈습니다. 400은 "보낸 쪽이 잘못"이고 500은 "받는 쪽이 고장"이라, 보낸 사람에게 틀린 신호를 줍니다.
|
||||||
|
- **가끔 실패 ✘ 500:** 예제 1 ②와 같습니다. 재시도가 없습니다.
|
||||||
|
- 0/6은 **예상한 결과**입니다. 기준이 무엇을 못 하는지 숫자로 확인했습니다.
|
||||||
|
|
||||||
|
#### 고친 뒤 · 내 `my-intake`
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/intake.json
|
||||||
|
uv run tools/flow.py test cases/lab01.json -v
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 `ops`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 정상 3건 · 3.9s
|
✔ 정상 3건 · 3.9s
|
||||||
@@ -294,14 +391,30 @@ Passed 0/6
|
|||||||
✔ 목록이 아니면 400 · 0.0s
|
✔ 목록이 아니면 400 · 0.0s
|
||||||
✔ 가끔 실패하는 API · 다시 해 보기로 성공 · 2.4s
|
✔ 가끔 실패하는 API · 다시 해 보기로 성공 · 2.4s
|
||||||
Passed 6/6
|
Passed 6/6
|
||||||
|
|
||||||
|
created tickets: 7
|
||||||
|
T-2003 key R-103 소프트웨어/높음 엑셀 라이선스가 만료됐다고 나와요
|
||||||
|
T-2004 key R-201 장비/보통 프린터 용지가 걸려요
|
||||||
|
T-2005 key R-203 소프트웨어/보통 메일 첨부가 안 열려요
|
||||||
|
T-2006 key R-301 계정/높음 사내 위키 로그인이 안 돼요
|
||||||
|
T-2007 key R-302 장비/보통 노트북 배터리가 금방 닳아요
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전에 `created = null`인 것은 기준의 결과가 `{processed, tickets}` 모양이라 오늘 계약의 칸이 없기 때문입니다. 접수 건이 **6개**(3이 기대)인 것은 같은 3건을 두 번 보내 중복이 생겼기 때문입니다. 목록이 아닌 본문도 확인 없이 하나씩 나누다 500이 되었습니다.
|
| 케이스 | 고치기 전 | 고친 뒤 | 무엇이 바꿨나 |
|
||||||
- 고친 뒤 두 번째 `batch-ok`는 `created: 3, duplicates: 3`입니다. 키가 `R-101`처럼 요청 `id`라서 데이터 API가 처음 번호를 돌려줬습니다. 접수 건은 3개 그대로.
|
| --- | --- | --- | --- |
|
||||||
- `flaky`에서 2건 모두 `created`: 첫 시도가 500이었지만 0.3초 뒤 두 번째 시도에서 성공했습니다. 재시도는 두 건을 **모두** 다시 보냈지만, 키 덕분에 중복이 생기지 않았습니다.
|
| 정상 3건 | `created = null` | `created: 3` | 결과를 오늘의 결과 모양으로 |
|
||||||
- 잘못된 항목 케이스가 1.7초로 빠른 것은 빈 글 하나를 LLM에 보내지 않고 거절했기 때문입니다.
|
| 같은 요청을 다시 | 새로 3건 | `created: 3, duplicates: 3` | 키 = 요청 `id` |
|
||||||
|
| 접수 건 수 | 6 | 3 | 같은 까닭 |
|
||||||
|
| 잘못된 항목 | 500 | 200 · 그 항목만 `rejected` | 항목마다 확인 |
|
||||||
|
| 목록이 아님 | 500 | 400 (`requests must be a list`) | 입구에서 본문 확인 |
|
||||||
|
| 가끔 실패 | 500 | 200 · `created: 2` | 재시도 + 키 |
|
||||||
|
|
||||||
|
- **두 번째 `batch-ok`:** 응답에 `duplicates: 3`이 있습니다. 키가 `R-101`처럼 요청 `id`라, 데이터 API가 "이미 있는 건"으로 알아보고 새로 만들지 않고 처음 번호를 돌려줬습니다. `created: 3`은 "세 문의 모두 접수 건이 있다"는 뜻이고, 새로 생긴 것은 없습니다.
|
||||||
|
- **잘못된 항목:** 빈 글 항목만 `rejected`(`text is required`)이고 나머지 2건(R-201 · R-203)은 접수되었습니다. 1.7초로 빨라진 것은 빈 글을 LLM에 보내지 않고 거절했기 때문입니다.
|
||||||
|
- **가끔 실패:** 두 건 모두 첫 시도가 500이었지만 0.3초 뒤 두 번째 시도에서 성공했습니다. 재시도는 두 건을 모두 다시 보냈지만, 키가 같아 중복은 생기지 않았습니다.
|
||||||
|
- **`ops`의 7개** = 정상 3(R-101~103) + 잘못된 항목 요청의 2(R-201 · R-203) + 가끔 실패의 2(R-301 · R-302). 같은 요청을 다시 보낸 것은 하나도 늘리지 않았습니다. 화면에는 마지막 5개만 보입니다. 키가 모두 `R-…`인 것을 확인합니다.
|
||||||
|
|
||||||
## 예제 3 · 고장 종류마다 재시도가 하는 일 (7분, 에이전트 없이)
|
## 예제 3 · 고장 종류마다 재시도가 하는 일 (7분, 에이전트 없이)
|
||||||
|
|
||||||
@@ -383,7 +496,16 @@ sequenceDiagram
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 예제 2 정답으로 실제로 나온 출력입니다. 시간은 실행마다 조금씩 다르고, ✔ · ✘는 같아야 합니다.
|
수업 준비 때(10/29) 강사 노트북에서 예제 2 정답으로 실제로 나온 출력입니다. 시간은 실행마다 조금씩 다르고, ✔ · ✘는 같아야 합니다.
|
||||||
|
|
||||||
`test cases/lab01-faults.json` (결과 줄과 이유만)
|
이 예제는 **고치지 않습니다.** 예제 2에서 고친 `my-intake`를 그대로 두고, 테스트가 케이스마다 고장 스위치를 바꿔 가며 재시도가 무엇을 하는지 봅니다.
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab01-faults.json -v
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 실패 이유만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ flaky · 다시 해 보면 됨 · 2.4s
|
✔ flaky · 다시 해 보면 됨 · 2.4s
|
||||||
@@ -396,18 +518,30 @@ sequenceDiagram
|
|||||||
✔ 같은 요청을 다시 · 멱등 키로 중복 없음 · 2.9s
|
✔ 같은 요청을 다시 · 멱등 키로 중복 없음 · 2.9s
|
||||||
Passed 5/5
|
Passed 5/5
|
||||||
|
|
||||||
$ ops
|
|
||||||
created tickets: 5
|
created tickets: 5
|
||||||
T-2001 key R-301 · T-2002 key R-302 · T-2003 key R-103 · T-2004 key R-102 · T-2005 key R-101
|
T-2001 key R-301 계정/높음 사내 위키 로그인이 안 돼요
|
||||||
|
T-2002 key R-302 장비/보통 노트북 배터리가 금방 닳아요
|
||||||
|
T-2003 key R-103 소프트웨어/높음 엑셀 라이선스가 만료됐다고 나와요
|
||||||
|
T-2004 key R-102 장비/낮음 새 모니터를 하나 더 받을 수 있을까요?
|
||||||
|
T-2005 key R-101 네트워크/높음 VPN이 아침부터 계속 끊겨서 고객 미팅을 못 하고 있
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
✔는 "재시도가 성공했다"가 아니라 **"이 고장에서 기대한 대로 움직였다"** 는 뜻입니다. `error` · `slow`는 실패하는 것이 기대입니다.
|
||||||
|
|
||||||
- `flaky` 2.4초: 첫 시도 500 → 0.3초 뒤 두 번째 성공. 재시도가 **도움이 된** 경우입니다.
|
**왜 이렇게 나왔나 (케이스마다)**
|
||||||
- `error` 3.4초: 세 번 모두 500이라 `failed: 3`. 재시도가 결과를 바꾸지 못하고 시간만 더했습니다 (분류 약 2.5초 + 시도 셋과 간격 둘).
|
|
||||||
- `slow` 12.4초: 시도마다 3초에 끊겨(`timeout of 3000ms exceeded`) 세 번 실패했습니다. 사용자는 12초를 기다리고 실패를 받았습니다.
|
| 케이스 | 고장 스위치 | 시간 | 결과 | 까닭 |
|
||||||
- 그런데 8초 뒤 접수 건이 **5개**입니다. 실패라던 R-101 · R-102 · R-103이 늦게 만들어졌습니다 (데이터 API가 8초 뒤 처리). 시도는 9번 갔지만 키가 같아 3건만 생겼습니다.
|
| --- | --- | --- | --- | --- |
|
||||||
- 마지막에 같은 3건을 다시 보내자 `duplicates: 3`: 이미 있는 것을 알아보고 새로 만들지 않았습니다.
|
| flaky | 키마다 첫 시도만 500 | 2.4s | `created: 2` | 첫 시도 500 → 0.3초 뒤 두 번째 성공. 재시도가 **도운** 경우 |
|
||||||
|
| error | 계속 500 | 3.4s | `failed: 3` | 세 번 모두 500. 재시도가 결과는 못 바꾸고 시간만 더함 (분류 약 2.5초 + 시도 셋과 간격 둘) |
|
||||||
|
| slow | 응답이 8초 늦음 | 12.4s | `failed: 3` | 시도마다 3초에 끊김(`timeout of 3000ms exceeded`). 사용자는 12초를 기다리고 실패를 받음 |
|
||||||
|
| 늦게 만들어짐 | (8초 기다림) | 0.0s | 접수 건 5개 | 실패라던 R-101~103이 사실은 만들어짐 |
|
||||||
|
| 같은 요청을 다시 | ok | 2.9s | `duplicates: 3` | 이미 있는 것을 알아보고 새로 만들지 않음 |
|
||||||
|
|
||||||
|
- **가장 중요한 줄은 "늦게 만들어짐"입니다.** `slow`에서 n8n은 3초를 기다리다 끊었지만, 데이터 API는 요청을 이미 받았고 8초 뒤에 처리했습니다. 그래서 "실패"라던 R-101 · R-102 · R-103이 `ops`에 있습니다. **시간 초과는 "안 됨"이 아니라 "모름"** 입니다.
|
||||||
|
- 시도는 3건 × 3번 = 9번 갔지만, 키가 같아 접수 건은 3개만 생겼습니다. 키가 실행 번호였다면 9개가 될 수도 있었습니다.
|
||||||
|
- `ops`의 5개 = flaky의 2(R-301 · R-302) + 늦게 만들어진 3(R-101~103). 마지막 "같은 요청을 다시"는 늘리지 않았습니다.
|
||||||
|
- 이 표의 시간 · 결과 · 실제로 생긴 접수 건을 `my-work/RUNBOOK.md` 1.2 표에 적습니다. `failed`가 된 요청을 어디에 모을지는 Lab 02에서 다룹니다.
|
||||||
|
|
||||||
## 과제 1 · LLM이 멈춰도 접수는 (8분)
|
## 과제 1 · LLM이 멈춰도 접수는 (8분)
|
||||||
|
|
||||||
@@ -506,7 +640,15 @@ uv run tools/flow.py test cases/lab01.json
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
||||||
|
|
||||||
고치기 전 (예제 2 정답)
|
#### 고치기 전 · 예제 2까지 고친 `my-intake`
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab01-challenge.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 평소에는 분류됨 · 2.1s
|
✔ 평소에는 분류됨 · 2.1s
|
||||||
@@ -517,7 +659,24 @@ uv run tools/flow.py test cases/lab01.json
|
|||||||
Passed 1/3
|
Passed 1/3
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- 가운데 `OpenAI credential` 두 줄은 케이스가 아니라 **테스트가 대신 해 준 준비와 뒷정리**입니다. 두 번째 케이스 전에 `llm down`(일부러 틀린 키), 끝난 뒤 `llm up`(`.env`의 진짜 키로 되돌림)을 했습니다.
|
||||||
|
- **LLM이 멈추면 ✘ 0.8초에 500:** 키가 틀려 OpenAI가 호출을 거절하자 분류 노드가 오류로 멈췄고, n8n은 `Error in workflow`를 돌려줬습니다. 분류 뒤에 있는 접수 건 만들기까지 가지 못했으니 세 문의 모두 **사라졌습니다.** 0.8초로 빠른 것은 LLM이 답을 만들기 전에 거절했기 때문입니다.
|
||||||
|
- **미분류 접수 건 ✘:** 접수 건 목록에 `미분류`가 하나도 없습니다. 위 케이스에서 아무것도 만들지 못했으니 당연합니다.
|
||||||
|
- 1/3은 **예상한 결과**입니다. 예제 2의 재시도 · 키는 데이터 API의 실패만 다뤘고, LLM의 실패는 아직 아무도 다루지 않습니다.
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/intake.json
|
||||||
|
uv run tools/flow.py test cases/lab01-challenge.json -v
|
||||||
|
uv run tools/flow.py test cases/lab01.json
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 평소에는 분류됨 · 3.5s
|
✔ 평소에는 분류됨 · 3.5s
|
||||||
@@ -527,11 +686,17 @@ Passed 3/3
|
|||||||
(lab01 6/6)
|
(lab01 6/6)
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전 0.8초 만에 500: 키가 틀려 OpenAI가 호출을 거절하자 분류 노드가 오류로 멈췄습니다 (n8n은 `Error in workflow`). 접수 건 만들기까지 가지 못해 3건 모두 사라졌습니다.
|
| 케이스 | 고치기 전 | 고친 뒤 | 무엇이 바꿨나 |
|
||||||
- 고친 뒤 0.9초에 `created: 3, unclassified: 3`: 분류는 실패했지만 `Parse`가 `error`를 보고 미분류 · 보통으로 정해 접수했습니다. LLM을 기다리지 않아 오히려 빨랐습니다.
|
| --- | --- | --- | --- |
|
||||||
- `llm up`으로 진짜 키를 되돌린 뒤의 `lab01` 6/6은 평소 분류가 그대로라는 뜻입니다.
|
| LLM이 멈춤 | 500 · 접수 0 | 200 · `created: 3, unclassified: 3` | 분류 노드가 멈추지 않고 오류를 다음으로 넘김 |
|
||||||
|
| 미분류 접수 건 | 없음 | `미분류` 3건 | `Parse`가 오류를 보고 미분류 · 보통으로 정함 |
|
||||||
|
|
||||||
|
- 분류는 여전히 실패했습니다 (키가 틀렸으니까요). 달라진 것은 **실패를 받은 뒤**입니다. 분류 노드가 워크플로우를 멈추지 않고 `error`를 넘겼고, `Parse`가 그것을 보고 분류를 `미분류`, 우선순위를 `보통`으로 정해 접수했습니다. 문의는 잃지 않고, 사람이 나중에 분류하면 됩니다.
|
||||||
|
- 0.9초로 **오히려 빨랐습니다.** LLM이 거절한 뒤 기다리지 않고 바로 접수로 갔기 때문입니다. 이것이 **우아한 저하**(전부 멈추는 대신 덜 좋은 결과라도 내기)입니다.
|
||||||
|
- `unclassified: 3`이 결과에 따로 있으니, 사용자나 운영자는 "분류가 안 된 것이 3건"이라는 것을 압니다. 조용히 넘어간 것이 아닙니다.
|
||||||
|
- `llm up`으로 진짜 키를 되돌린 뒤 `lab01` 6/6: 평소 분류와 예제 2의 규칙은 그대로입니다.
|
||||||
|
|
||||||
## 과제 2 · 요청 안의 규칙 (10분)
|
## 과제 2 · 요청 안의 규칙 (10분)
|
||||||
|
|
||||||
@@ -639,7 +804,15 @@ uv run tools/flow.py ops --reset
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
||||||
|
|
||||||
고치기 전 (과제 1 정답)
|
#### 고치기 전 · 과제 1까지 고친 `my-intake`
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab01-challenge2.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✘ 같은 id가 두 번이면 두 번째는 거절 · 2.8s · created = 3 (expected 2); rejected = 0 (expected 1); duplicates = 1 (expected 0); results has none of ['duplicate id']
|
✘ 같은 id가 두 번이면 두 번째는 거절 · 2.8s · created = 3 (expected 2); rejected = 0 (expected 1); duplicates = 1 (expected 0); results has none of ['duplicate id']
|
||||||
@@ -649,7 +822,27 @@ uv run tools/flow.py ops --reset
|
|||||||
Passed 1/4
|
Passed 1/4
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- **같은 id 두 번 ✘ `created = 3, duplicates = 1`:** 요청 안에 R-401이 두 번 있습니다. 멱등 키 덕분에 접수 건은 둘(R-401 · R-402)만 생겼습니다. 하지만 결과는 "세 건을 접수했다"고 말하고, 두 번째 R-401도 LLM 분류를 한 번 더 했습니다. 보낸 쪽의 실수(같은 id를 두 번 넣음)를 아무도 알려 주지 않습니다.
|
||||||
|
- **11건 ✘ 200 · 11.0초:** 한 번에 받을 수 있는 수의 상한이 없어 11건을 모두 분류 · 접수했습니다. 11초 동안 n8n이 이 요청 하나에 붙잡혀 있었습니다. 100건, 1000건이 와도 그대로 받습니다.
|
||||||
|
- **접수 건 ✘ `count = 13`:** 2(같은 id 케이스) + 11(이번 케이스). 막아야 할 요청이 접수 건을 11개 만들었습니다.
|
||||||
|
- **평소 요청 ✔:** 정상 3건은 원래 잘 됩니다. 이 케이스는 고친 뒤에도 ✔여야 합니다.
|
||||||
|
- 1/4는 **예상한 결과**입니다. 키는 "같은 건을 두 번 만들기"는 막지만, "이상한 요청을 받기"는 막지 않습니다 (교안 1단계 8절).
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/intake.json
|
||||||
|
uv run tools/flow.py test cases/lab01-challenge2.json -v
|
||||||
|
uv run tools/flow.py test cases/lab01.json
|
||||||
|
uv run tools/flow.py test cases/lab01-challenge.json
|
||||||
|
uv run tools/flow.py ops --reset
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 같은 id가 두 번이면 두 번째는 거절 · 3.4s
|
✔ 같은 id가 두 번이면 두 번째는 거절 · 3.4s
|
||||||
@@ -660,12 +853,19 @@ Passed 4/4
|
|||||||
(lab01 6/6 · lab01-challenge 3/3)
|
(lab01 6/6 · lab01-challenge 3/3)
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전 같은 id 두 번은 `created: 3, duplicates: 1`: 멱등 키 덕분에 접수 건은 둘만 생겼지만, 결과는 세 건을 접수했다고 말하고 LLM 분류도 세 번 했습니다.
|
| 케이스 | 고치기 전 | 고친 뒤 | 무엇이 바꿨나 |
|
||||||
- 고치기 전 11건은 **11.0초** 걸려 모두 처리됐고, 접수 건이 13개(2 + 11)가 되었습니다. 상한이 없으면 한 요청이 그만큼 n8n을 붙잡습니다.
|
| --- | --- | --- | --- |
|
||||||
- 고친 뒤 11건은 **0.2초**에 400: LLM과 API를 부르기 전에 입구에서 막았습니다. 접수 건은 2개 그대로입니다.
|
| 같은 id 두 번 | `created: 3, duplicates: 1` | `created: 2, rejected: 1` (`duplicate id …`) | 입구에서 요청 안의 id를 셈 |
|
||||||
- 평소 요청 3건은 그대로 `created: 3`: 새 규칙이 정상 요청을 막지 않았습니다.
|
| 11건 | 200 · 11.0초 | 400 · 0.2초 (`too many requests (max 10)`) | 입구에서 건수를 셈 |
|
||||||
|
| 접수 건 수 | 13 | 2 | 400이면 아무것도 하지 않음 |
|
||||||
|
| 평소 요청 | ✔ | ✔ | 정상 요청은 막지 않음 |
|
||||||
|
|
||||||
|
- **같은 id:** 두 번째 R-401은 분류하지 않고 `rejected`로, 이유는 `duplicate id …`로 시작합니다. 결과가 실제와 같아졌습니다 (접수 건 2 = `created: 2`). 보낸 쪽은 자기 실수를 바로 압니다.
|
||||||
|
- **11건 0.2초:** LLM과 데이터 API를 부르기 **전에** 입구에서 세고 400을 돌려줬습니다. 11.0초 → 0.2초. 하나도 만들지 않았으니 접수 건은 2개 그대로입니다. 400이라 보낸 쪽은 "내가 나눠서 다시 보내야 한다"는 것을 압니다.
|
||||||
|
- 키와 입구 규칙은 **둘 다** 필요합니다. 키는 같은 건이 두 번 생기는 피해를 막고, 입구 규칙은 하지 말아야 할 일을 시작도 하지 않게 막습니다.
|
||||||
|
- `lab01` 6/6 · `lab01-challenge` 3/3: 앞의 규칙(항목 확인 · 재시도 · 키 · 미분류)은 그대로입니다.
|
||||||
|
|
||||||
## 막혔을 때
|
## 막혔을 때
|
||||||
|
|
||||||
|
|||||||
@@ -131,12 +131,28 @@ uv run tools/flow.py ops
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 정답 워크플로우로 실제로 나온 출력입니다. 시간은 실행마다 조금씩 다르고, ✔ · ✘는 같아야 합니다.
|
수업 준비 때(10/29) 강사 노트북에서 정답 워크플로우로 실제로 나온 출력입니다. 시간은 실행마다 조금씩 다르고, ✔ · ✘는 같아야 합니다.
|
||||||
|
|
||||||
고치기 전 (Lab 01 과제 2 정답)
|
#### 고치기 전 · Lab 01 과제 2까지 고친 `my-intake`
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py ops --reset
|
||||||
|
uv run tools/flow.py fault tickets error
|
||||||
|
uv run tools/flow.py call my-intake requests/batch-two.json
|
||||||
|
uv run tools/flow.py fault tickets ok
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
uv run tools/flow.py test cases/lab02.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (줄임):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$ fault tickets error → call my-intake requests/batch-two.json
|
HTTP 200 · 2.8s
|
||||||
HTTP 200 · 2.8s ( "failed": 2 )
|
{ "total": 2, "created": 0, "failed": 2, "rejected": 0, "duplicates": 0, "unclassified": 0,
|
||||||
$ ops
|
"results": [ { "id": "R-301", "status": "failed", "reason": "500 - … tickets service is failing (fault switch)" },
|
||||||
|
{ "id": "R-302", "status": "failed", "reason": "500 - … tickets service is failing (fault switch)" } ] }
|
||||||
|
|
||||||
|
fault switch: all ok
|
||||||
created tickets: 0
|
created tickets: 0
|
||||||
DLQ: 0 (parked 0)
|
DLQ: 0 (parked 0)
|
||||||
alerts: 0
|
alerts: 0
|
||||||
@@ -148,7 +164,25 @@ alerts: 0
|
|||||||
Passed 2/4
|
Passed 2/4
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- **응답은 정직합니다.** 200 · `failed: 2`, 항목마다 이유(`tickets service is failing`)까지 있습니다. Lab 01에서 이미 이렇게 고쳤습니다. 그래서 "실패로 표시" 케이스는 ✔입니다.
|
||||||
|
- **그런데 `ops`에는 아무것도 없습니다.** 접수 건 0, DLQ 0, 알림 0. 결과를 받은 화면(사용자의 브라우저, 부른 프로그램)이 닫히면 R-301 · R-302 두 문의는 **어디에도 남지 않습니다.** 고장 스위치를 `ok`로 되돌려도 다시 보낼 것이 없습니다.
|
||||||
|
- **DLQ 케이스 ✘ `size = 0`:** DLQ에 넣는 갈래가 아직 없으니 당연합니다.
|
||||||
|
- **Error Workflow 케이스 ✘:** 예제 2에서 고칠 것입니다. 지금은 신경 쓰지 않습니다.
|
||||||
|
- 2/4는 **예상한 결과**입니다.
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/intake.json
|
||||||
|
uv run tools/flow.py test cases/lab02.json -v
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 `ops`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 예제 1 · API가 멈추면 실패로 표시 · 200 · 4.1s
|
✔ 예제 1 · API가 멈추면 실패로 표시 · 200 · 4.1s
|
||||||
@@ -157,17 +191,22 @@ Passed 2/4
|
|||||||
✘ 예제 2 · Error Workflow가 알림을 남김 · 0.0s · severity = null (expected critical); ...
|
✘ 예제 2 · Error Workflow가 알림을 남김 · 0.0s · severity = null (expected critical); ...
|
||||||
Passed 3/4
|
Passed 3/4
|
||||||
|
|
||||||
$ ops
|
|
||||||
DLQ: 2 (parked 0)
|
DLQ: 2 (parked 0)
|
||||||
D-1 R-301 attempts 1 parked False 500 - "{\"error\": \"tickets service is failing (f
|
D-1 R-301 attempts 1 parked False 500 - "{\"error\": \"tickets service is failing (f
|
||||||
D-2 R-302 attempts 1 parked False 500 - "{\"error\": \"tickets service is failing (f
|
D-2 R-302 attempts 1 parked False 500 - "{\"error\": \"tickets service is failing (f
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전 `failed: 2`는 Lab 01에서 이미 있었습니다. 하지만 `ops`의 DLQ가 0입니다. 결과를 받은 화면이 닫히면 두 문의는 **아무 데도 남지 않습니다.**
|
| 볼 것 | 고치기 전 | 고친 뒤 | 무엇이 바꿨나 |
|
||||||
- 고친 뒤 DLQ에 R-301 · R-302가 원래 요청 그대로, 실패 이유 · `attempts 1`과 함께 들어갔습니다. 이제 원인을 고친 뒤 다시 돌릴 수 있습니다 (Lab 03).
|
| --- | --- | --- | --- |
|
||||||
- 남은 ✘는 `nightly-report`의 오류를 아무도 알리지 않아서입니다 (예제 2).
|
| 응답 | `failed: 2` | `failed: 2` (그대로) | 응답은 바꾸지 않음 |
|
||||||
|
| DLQ | 0 | 2 (D-1 · D-2) | `failed` 항목을 DLQ에 넣는 갈래 |
|
||||||
|
| 다시 돌릴 수 있나 | 없음 | 원래 요청 그대로 있음 | DLQ에 요청 전체를 저장 |
|
||||||
|
|
||||||
|
- 응답은 똑같이 `failed: 2`입니다. 달라진 것은 **실패가 남았느냐**입니다. DLQ 한 줄마다 `R-301`(원래 요청 id), `attempts 1`(DLQ에 들어간 횟수), `parked False`(아직 사람이 볼 것으로 멈추지 않음), 실패 이유가 있습니다.
|
||||||
|
- 원래 요청이 그대로 있으니, 원인(데이터 API 장애)을 고친 뒤 **같은 요청을 다시 돌릴 수 있습니다.** 그것이 Lab 03의 재처리입니다. 다시 돌려도 키가 요청 id라 중복은 생기지 않습니다.
|
||||||
|
- 남은 ✘ 하나는 `nightly-report`의 오류를 아무도 알리지 않아서입니다. 예제 2에서 고칩니다.
|
||||||
|
|
||||||
## 예제 2 · Error Workflow (12분)
|
## 예제 2 · Error Workflow (12분)
|
||||||
|
|
||||||
@@ -286,7 +325,44 @@ uv run tools/flow.py test cases/lab01.json
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 정답 워크플로우로 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 정답 워크플로우로 실제로 나온 출력입니다.
|
||||||
|
|
||||||
고친 뒤
|
#### 고치기 전 · 에러 처리기 없음
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py call nightly-report
|
||||||
|
uv run tools/flow.py test cases/lab02.json -v # 예제 1을 고친 뒤의 결과
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (줄임):
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP 500 · 0.1s
|
||||||
|
{ "message": "Error in workflow" }
|
||||||
|
|
||||||
|
✔ 예제 2 · 예상 못 한 오류 (nightly-report) · 0.1s
|
||||||
|
✘ 예제 2 · Error Workflow가 알림을 남김 · 0.0s · severity = null (expected critical); ...
|
||||||
|
Passed 3/4
|
||||||
|
```
|
||||||
|
|
||||||
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- `nightly-report`에는 일부러 넣은 버그가 있어 0.1초 만에 500으로 끝납니다. 이것은 DLQ로 받을 수 없는 실패입니다. 항목 하나가 아니라 **워크플로우가 통째로 멈춘 것**이고, 오늘 만든 갈래들은 `my-intake` 안에만 있기 때문입니다.
|
||||||
|
- 500을 본 것은 `call`을 한 우리뿐입니다. 밤에 스케줄로 돌았다면 아무도 보지 못했을 것입니다. 알림 케이스의 `severity = null`은 "알림이 하나도 없음"이라는 뜻입니다.
|
||||||
|
- `ops`를 봐도 `alerts: 0`입니다. 실패는 했는데 **누구도 모르는** 상태입니다.
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/error-handler.json my-work/intake.json
|
||||||
|
uv run tools/flow.py test cases/lab02.json -v
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
uv run tools/flow.py test cases/lab01.json
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 `ops`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 예제 1 · API가 멈추면 실패로 표시 · 200 · 4.8s
|
✔ 예제 1 · API가 멈추면 실패로 표시 · 200 · 4.8s
|
||||||
@@ -296,18 +372,24 @@ uv run tools/flow.py test cases/lab01.json
|
|||||||
✔ 예제 2 · Error Workflow가 알림을 남김 · 0.0s
|
✔ 예제 2 · Error Workflow가 알림을 남김 · 0.0s
|
||||||
Passed 4/4
|
Passed 4/4
|
||||||
|
|
||||||
$ ops
|
|
||||||
alerts: 1
|
alerts: 1
|
||||||
[critical] 76 Nightly report (provided, has a bug) · unexpected · Count by category · rows is not iterable [line 4]
|
[critical] 76 Nightly report (provided, has a bug) · unexpected · Count by category · rows is not iterable [line 4]
|
||||||
(lab01 6/6)
|
(lab01 6/6)
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- `nightly-report`는 여전히 500입니다 (버그를 고치지 않음). 달라진 것은 **누가 아느냐**입니다. 3초 뒤 critical 알림이 생겼고, 어느 워크플로우 · 어느 노드(`Count by category`) · 무슨 오류(`rows is not iterable [line 4]`)인지 한 줄로 보입니다.
|
| 볼 것 | 고치기 전 | 고친 뒤 | 무엇이 바꿨나 |
|
||||||
- 오류 문장은 버그를 가리킵니다: 응답의 `tickets`를 `items`로 잘못 읽어 `rows`가 비어(`undefined`) 있었습니다. 알림만 보고 고칠 곳을 찾을 수 있습니다.
|
| --- | --- | --- | --- |
|
||||||
- `nightly-report`의 설정에 이미 `errorWorkflow: errHandler76001`이 있었기 때문에, 처리기를 그 id로 **게시하는 것만으로** 알림이 시작되었습니다.
|
| `nightly-report` | 500 | 500 (그대로) | 버그는 고치지 않음 |
|
||||||
- 내 `my-intake`도 같은 처리기를 씁니다. 하지만 오늘의 실패는 모두 처리하므로(200) 처리기가 불리지 않습니다. `lab01` 6/6.
|
| 알림 | 0 | critical 1 | 에러 처리기 `errHandler76001`을 게시 |
|
||||||
|
| 누가 아나 | `call`한 사람만 | 알림을 보는 사람 모두 | |
|
||||||
|
|
||||||
|
- `nightly-report`는 **여전히 500**입니다. 버그를 고친 것이 아니라 **누가 아느냐**를 바꿨습니다.
|
||||||
|
- `… before: wait 3s`: 알림은 응답이 나간 **뒤에** 생깁니다. n8n이 워크플로우가 멈춘 것을 보고 에러 처리기를 따로 부르기 때문입니다. 그래서 테스트가 3초 기다린 뒤 확인했습니다.
|
||||||
|
- 알림 한 줄에 **어느 워크플로우**(`76 Nightly report`) · **어떤 종류**(`unexpected`, 예상 못 한 오류) · **어느 노드**(`Count by category`) · **무슨 오류**(`rows is not iterable [line 4]`)가 다 있습니다. 응답의 `tickets`를 `items`로 잘못 읽어 `rows`가 비어 있었던 것입니다. 알림만 보고 고칠 곳을 찾을 수 있습니다.
|
||||||
|
- `nightly-report`의 설정에는 처음부터 `errorWorkflow: errHandler76001`이 있었습니다. 그래서 처리기를 그 id로 **게시하는 것만으로** 알림이 시작되었습니다.
|
||||||
|
- 내 `my-intake`도 같은 처리기를 씁니다. 하지만 오늘의 실패를 모두 스스로 처리하고 200으로 끝나므로 처리기가 불리지 않습니다. `lab01` 6/6도 그대로입니다.
|
||||||
|
|
||||||
## 예제 3 · 어느 실패가 어느 그물에 (6분, 에이전트 없이)
|
## 예제 3 · 어느 실패가 어느 그물에 (6분, 에이전트 없이)
|
||||||
|
|
||||||
@@ -381,6 +463,19 @@ flowchart LR
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 예제 2 정답으로 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 예제 2 정답으로 실제로 나온 출력입니다.
|
||||||
|
|
||||||
|
이 예제는 **고치지 않습니다.** 예제 2까지 만든 그물(결과 · DLQ · 알림)에 실패 넷을 던져 보고, 어디에 걸리는지 봅니다. 6 · 7번의 구멍은 과제 1에서 막습니다.
|
||||||
|
|
||||||
|
#### 지금 상태 · 예제 2까지
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab02-nets.json -v
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 `ops`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 1 · 잘못된 항목 → 결과에만 (rejected) · 1.7s
|
✔ 1 · 잘못된 항목 → 결과에만 (rejected) · 1.7s
|
||||||
✔ 2 · API 오류 → failed · 2.8s
|
✔ 2 · API 오류 → failed · 2.8s
|
||||||
@@ -392,18 +487,51 @@ flowchart LR
|
|||||||
✘ 7 · DLQ가 멈춘 것을 누가 아나 (critical · dlq_down) · 0.0s · reason = unexpected (expected dlq_down)
|
✘ 7 · DLQ가 멈춘 것을 누가 아나 (critical · dlq_down) · 0.0s · reason = unexpected (expected dlq_down)
|
||||||
Passed 5/7
|
Passed 5/7
|
||||||
|
|
||||||
$ ops
|
created tickets: 2
|
||||||
|
T-2001 key R-201 장비/보통 프린터 용지가 걸려요
|
||||||
|
T-2002 key R-203 소프트웨어/보통 메일 첨부가 안 열려요
|
||||||
DLQ: 2 (parked 0)
|
DLQ: 2 (parked 0)
|
||||||
D-1 R-301 … D-2 R-302 …
|
D-1 R-301 … D-2 R-302 …
|
||||||
alerts: 1
|
alerts: 1
|
||||||
[critical] 76 Nightly report (provided, has a bug) · unexpected · Count by category · rows is not iterable [line 4]
|
[critical] 76 Nightly report (provided, has a bug) · unexpected · Count by category · rows is not iterable [line 4]
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**왜 이렇게 나왔나 (케이스마다)**
|
||||||
|
|
||||||
- 1~5: 그물 셋이 각자 일했습니다. 빈 글은 결과에, API 오류는 DLQ에, 버그는 알림으로.
|
| 케이스 | 던진 실패 | 걸린 그물 | 남은 흔적 |
|
||||||
- 6: 응답은 200 · `failed: 3`. 그런데 DLQ는 **2건 그대로**입니다. `batch-ok`의 세 문의(R-101~103)는 DLQ 저장이 500으로 실패했고, `neverError` 때문에 워크플로우는 아무 일 없다는 듯 끝났습니다. 결과에도 "DLQ에 못 넣었다"는 말이 없습니다.
|
| --- | --- | --- | --- |
|
||||||
- 7: 마지막 알림은 여전히 5번의 버그 알림입니다. **세 문의를 잃었다는 것을 아무도 모릅니다.** 과제 1에서 이 구멍을 막습니다.
|
| 1 | 빈 글 항목 | 결과 (`rejected`) | 응답에만. 보낸 쪽이 고칠 일 |
|
||||||
|
| 2 · 3 | 데이터 API 오류 (`fault tickets error`) | 결과 (`failed`) + DLQ | D-1 · D-2 |
|
||||||
|
| 4 · 5 | `nightly-report`의 버그 | Error Workflow | critical 알림 |
|
||||||
|
| 6 · 7 | 데이터 API **와** DLQ 모두 오류 (`fault dlq error`) | **없음** | **없음** |
|
||||||
|
|
||||||
|
- 1~5: 그물 셋이 각자 맡은 실패를 잡았습니다. `ops`의 접수 건 2개(R-201 · R-203)는 1번 요청의 정상 항목, DLQ 2건은 2번, 알림은 4번의 것입니다.
|
||||||
|
- **6 · 7이 오늘의 구멍입니다.** 6번에서 `batch-ok`의 세 문의(R-101~103)는 접수 건 만들기도 DLQ 저장도 500으로 실패했습니다. 응답은 200 · `failed: 3`이지만, DLQ는 **2건 그대로**입니다. DLQ 저장 노드가 실패해도 멈추지 않게(`neverError`) 해 두었기 때문에 워크플로우는 아무 일 없다는 듯 끝났고, 결과에도 "DLQ에 못 넣었다"(`dlq_saved`)는 말이 없습니다.
|
||||||
|
- 7번의 `reason = unexpected`: 마지막 알림이 여전히 5번의 버그 알림이라는 뜻입니다. DLQ가 멈췄다는 알림(`dlq_down`)은 없습니다. **세 문의를 잃었는데 아무도 모릅니다.**
|
||||||
|
- 5/7은 **예상한 결과**입니다. 그물이 있어도 **그물이 고장 나면** 실패가 빠져나갑니다.
|
||||||
|
|
||||||
|
#### 과제 1을 고친 뒤 · 같은 테스트
|
||||||
|
|
||||||
|
실행한 명령 (과제 1 끝에서):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab02-nets.json
|
||||||
|
```
|
||||||
|
|
||||||
|
출력:
|
||||||
|
|
||||||
|
```text
|
||||||
|
✔ 1 · 잘못된 항목 → 결과에만 (rejected) · 2.1s
|
||||||
|
✔ 2 · API 오류 → failed · 2.9s
|
||||||
|
✔ 3 · failed는 DLQ에 · 0.0s
|
||||||
|
✔ 4 · 버그 → 500 · 0.1s
|
||||||
|
✔ 5 · Error Workflow → critical 알림 · 0.0s
|
||||||
|
✔ 6 · DLQ마저 멈추면 결과가 알려야 · 3.7s
|
||||||
|
✔ 7 · DLQ가 멈춘 것을 누가 아나 (critical · dlq_down) · 0.0s
|
||||||
|
Passed 7/7
|
||||||
|
```
|
||||||
|
|
||||||
|
**고친 뒤 이렇게 나온 까닭:** 과제 1에서 DLQ 저장 결과를 확인해 결과에 `dlq_saved: false`를 넣고, 저장에 실패하면 `dlq_down` critical 알림에 잃어버린 id를 적게 했습니다. 그래서 6 · 7번이 ✔가 되었습니다. 1~5는 그대로입니다. 자세한 것은 과제 1의 실행 결과에 있습니다.
|
||||||
|
|
||||||
## 과제 1 · 처리한 실패도 알리기, DLQ가 멈추면 (10분)
|
## 과제 1 · 처리한 실패도 알리기, DLQ가 멈추면 (10분)
|
||||||
|
|
||||||
@@ -521,7 +649,15 @@ uv run tools/flow.py ops
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
||||||
|
|
||||||
고치기 전 (예제 2 정답)
|
#### 고치기 전 · 예제 2까지 고친 `my-intake`
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab02-challenge.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✘ API가 멈춤 → DLQ · 2.7s · dlq_saved = null (expected true); alert = null (expected sent)
|
✘ API가 멈춤 → DLQ · 2.7s · dlq_saved = null (expected true); alert = null (expected sent)
|
||||||
@@ -533,7 +669,27 @@ uv run tools/flow.py ops
|
|||||||
Passed 0/6
|
Passed 0/6
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- **첫 줄 ✘:** DLQ에는 실제로 넣었습니다. 하지만 결과에 `dlq_saved` · `alert` 칸이 없어 `null`입니다. 보낸 쪽은 실패한 것이 안전하게 보관됐는지 알 수 없습니다.
|
||||||
|
- **처리한 실패 알림 ✘:** 예제 2의 Error Workflow는 **워크플로우가 멈춘 실패**만 알립니다. `my-intake`는 실패를 스스로 처리하고 200으로 끝나니 처리기가 불리지 않습니다. 그래서 DLQ에 2건이 쌓여도 알림은 0개입니다. DLQ를 누가 들여다보지 않으면 아무도 모릅니다.
|
||||||
|
- **실패가 없으면 ✘ · 알림 1개 ✘:** 결과에 `alert: "none"`이 없고, 앞 케이스에서 알림이 0개라 "그대로 1개"도 맞지 않습니다.
|
||||||
|
- **DLQ도 멈추면 ✘ · 잃어버린 id ✘:** 예제 3의 구멍 그대로입니다.
|
||||||
|
- 0/6은 **예상한 결과**입니다. 결과와 알림의 새 칸을 아직 만들지 않았으니 모두 ✘입니다.
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/intake.json
|
||||||
|
uv run tools/flow.py test cases/lab02-challenge.json -v
|
||||||
|
uv run tools/flow.py test cases/lab02-nets.json
|
||||||
|
uv run tools/flow.py test cases/lab02.json
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과, 응답에 있던 알림 두 개):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ API가 멈춤 → DLQ · 4.3s
|
✔ API가 멈춤 → DLQ · 4.3s
|
||||||
@@ -545,16 +701,22 @@ Passed 0/6
|
|||||||
Passed 6/6
|
Passed 6/6
|
||||||
(lab02-nets 7/7 · lab02 4/4)
|
(lab02-nets 7/7 · lab02 4/4)
|
||||||
|
|
||||||
알림 두 개 (응답에서):
|
[high] intake · dlq · count 2 · "2 request(s) could not be created and wait in the DLQ"
|
||||||
[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: dlq service is failing (fault switch) · lost ids R-201, R-203"
|
||||||
[critical] intake · dlq_down · count 2 · "DLQ save failed: dlq service is failing (fault switch) · lost ids R-201, R-203"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전에는 DLQ에는 넣지만 알림이 0개라 모두 ✘입니다. 결과에 `dlq_saved` · `alert` 칸도 없습니다.
|
| 상황 | 고치기 전 | 고친 뒤 결과 | 알림 |
|
||||||
- 고친 뒤 실패 2건 → high 알림 **하나**(`count 2`). 실패가 없는 `batch-ok`에는 `alert: none`, 알림은 1개 그대로.
|
| --- | --- | --- | --- |
|
||||||
- DLQ가 멈추면 `dlq_saved: false` + critical `dlq_down` 알림, 메시지에 `lost ids R-201, R-203`. DLQ에 없어도 사람이 알림만 보고 두 문의를 살릴 수 있습니다. 예제 3의 구멍이 막혀 `lab02-nets` 7/7.
|
| API가 멈춤 (2건 실패) | DLQ에만 | `dlq_saved: true, alert: "sent"` | high `dlq` **하나** (`count 2`) |
|
||||||
|
| 실패 없음 | 칸 없음 | `alert: "none"` | 없음 (1개 그대로) |
|
||||||
|
| DLQ도 멈춤 | 아무 흔적 없음 | `dlq_saved: false` | critical `dlq_down` + `lost ids R-201, R-203` |
|
||||||
|
|
||||||
|
- **실패 2건 → 알림 하나:** 실패마다 하나씩이 아니라 요청 하나에 하나, 개수(`count 2`)를 담았습니다. 알림을 받은 사람은 "DLQ에 2건이 기다린다"를 압니다. 이 알림은 high입니다. 문의는 DLQ에 안전하게 있고, 원인을 고친 뒤 재처리하면 되기 때문입니다.
|
||||||
|
- **실패가 없으면 알림 없음:** 정상 요청은 알림을 만들지 않습니다. 알림이 "무슨 일이 있다"는 뜻을 잃지 않게 하는 규칙입니다.
|
||||||
|
- **DLQ도 멈추면 critical:** 이번에는 문의가 DLQ에도 없습니다. 그래서 등급이 critical이고, 메시지에 **잃어버린 id**(R-201 · R-203)를 적었습니다. 사람이 이 알림만 보고 보낸 쪽에 연락하거나 손으로 다시 넣을 수 있습니다.
|
||||||
|
- `lab02-nets` 7/7: 예제 3의 구멍(6 · 7번)이 막혔습니다. `lab02` 4/4: DLQ와 Error Workflow는 그대로입니다.
|
||||||
|
|
||||||
## 과제 2 · 알림 폭주 막기 (10분)
|
## 과제 2 · 알림 폭주 막기 (10분)
|
||||||
|
|
||||||
@@ -666,7 +828,15 @@ uv run tools/flow.py ops --reset
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
||||||
|
|
||||||
고치기 전 (과제 1 정답)
|
#### 고치기 전 · 과제 1까지 고친 `my-intake`
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab02-challenge2.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 첫 실패 → 알림 보냄 · 2.9s
|
✔ 첫 실패 → 알림 보냄 · 2.9s
|
||||||
@@ -678,7 +848,26 @@ uv run tools/flow.py ops --reset
|
|||||||
Passed 4/6
|
Passed 4/6
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- **10분 안에 또 실패 ✘ `alert = sent`:** 과제 1의 규칙은 "실패가 있으면 보낸다" 하나뿐입니다. 같은 장애가 이어져도 요청마다 알림을 또 보냅니다. 그래서 dlq 알림이 **2개**입니다.
|
||||||
|
- 장애가 30분 이어지고 요청이 1분에 한 번 오면 같은 알림이 30개 쌓입니다. 사람은 곧 알림을 읽지 않게 되고(**알림 피로**), 정말 새로운 알림도 놓칩니다.
|
||||||
|
- **✔ 넷:** DLQ에 5건 모두 들어간 것, DLQ가 멈춘 critical을 보낸 것, 실패가 없을 때 알림이 없는 것은 과제 1에서 이미 됩니다. 고친 뒤에도 그대로여야 합니다.
|
||||||
|
- 4/6은 **예상한 결과**입니다.
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/intake.json
|
||||||
|
uv run tools/flow.py test cases/lab02-challenge2.json -v
|
||||||
|
uv run tools/flow.py test cases/lab02-challenge.json
|
||||||
|
uv run tools/flow.py test cases/lab01-challenge2.json
|
||||||
|
uv run tools/flow.py ops --reset
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 첫 실패 → 알림 보냄 · 4.5s
|
✔ 첫 실패 → 알림 보냄 · 4.5s
|
||||||
@@ -691,12 +880,20 @@ Passed 6/6
|
|||||||
(lab02-challenge 6/6 · lab01-challenge2 4/4)
|
(lab02-challenge 6/6 · lab01-challenge2 4/4)
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전에는 두 번째 실패에도 알림을 또 보내 dlq 알림이 2개입니다. 장애가 30분 이어지면 요청마다 하나씩 쌓입니다.
|
| 케이스 | 고치기 전 | 고친 뒤 | 까닭 |
|
||||||
- 고친 뒤 두 번째는 `suppressed`, dlq 알림 1개. 그래도 **DLQ에는 5건 모두** 있습니다. 줄인 것은 알림이지 실패의 기록이 아닙니다.
|
| --- | --- | --- | --- |
|
||||||
- DLQ가 멈춘 critical은 10분 안이라도 `sent`. 급한 것은 줄이지 않았습니다.
|
| 첫 실패 | `sent` | `sent` | 10분 안에 같은 이유의 알림이 없음 |
|
||||||
- 마지막 `batch-dup-id`는 `failed: 0` · `alert: none`이고, Lab 01 과제 2의 같은 id 거절도 그대로입니다.
|
| 10분 안에 또 실패 | `sent` | `suppressed` | 같은 이유(`dlq`)의 알림이 이미 있음 |
|
||||||
|
| dlq 알림 수 | 2 | 1 | 같은 까닭 |
|
||||||
|
| DLQ | 5건 | 5건 (그대로) | 줄인 것은 알림뿐 |
|
||||||
|
| DLQ가 멈춤 | critical `sent` | critical `sent` | 급한 것은 줄이지 않음 |
|
||||||
|
|
||||||
|
- 보내기 전에 지난 10분 안에 같은 이유의 알림이 있었는지 보고, 있으면 `suppressed`로 정했습니다. 결과에 `suppressed`라고 쓰니 **보내지 않은 것도 기록에 남습니다.**
|
||||||
|
- **DLQ에는 5건 모두 있습니다.** 줄인 것은 사람에게 가는 알림이지, 실패의 기록이 아닙니다. 첫 알림을 본 사람이 DLQ를 열면 그 뒤에 쌓인 것까지 다 보입니다.
|
||||||
|
- DLQ가 멈춘 critical은 10분 안이라도 `sent`입니다. 문의를 잃고 있는 중이라, 줄이면 안 되는 알림입니다.
|
||||||
|
- 마지막 케이스(`batch-dup-id`)는 `failed: 0` · `alert: none`입니다. `lab01-challenge2` 4/4로 Lab 01 과제 2의 같은 id 거절도 그대로입니다.
|
||||||
|
|
||||||
## 막혔을 때
|
## 막혔을 때
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,15 @@ uv run tools/flow.py ops
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 정답 워크플로우로 실제로 나온 출력입니다. 시간은 실행마다 조금씩 다르고, ✔ · ✘는 같아야 합니다.
|
수업 준비 때(10/29) 강사 노트북에서 정답 워크플로우로 실제로 나온 출력입니다. 시간은 실행마다 조금씩 다르고, ✔ · ✘는 같아야 합니다.
|
||||||
|
|
||||||
고치기 전
|
#### 고치기 전 · `my-replay`가 아직 없음
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab03.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ API가 멈춘 동안 2건 → DLQ · 2.9s
|
✔ API가 멈춘 동안 2건 → DLQ · 2.9s
|
||||||
@@ -139,7 +147,24 @@ uv run tools/flow.py ops
|
|||||||
Passed 1/5
|
Passed 1/5
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- **첫 줄 ✔:** Lab 02에서 만든 `my-intake`가 장애 중의 2건을 DLQ에 잘 넣었습니다. 여기까지는 이미 됩니다.
|
||||||
|
- **다시 돌리기 ✘ 404:** 테스트가 고장 스위치를 `ok`로 돌린 뒤 `my-replay` 웹훅을 불렀는데, 그런 웹훅이 없어 n8n이 404(찾을 수 없음)를 줬습니다. `replayed` 등이 `null`인 것도 응답이 없어서입니다.
|
||||||
|
- **DLQ ✘ `size = 2` · 접수 건 ✘ `count = 0`:** 아무도 다시 돌리지 않았으니 2건은 DLQ에 쌓인 채이고, 접수 건은 하나도 없습니다. 원인을 고쳐도 **모아 둔 실패는 저절로 처리되지 않습니다.**
|
||||||
|
- 1/5는 **예상한 결과**입니다.
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/replay.json
|
||||||
|
uv run tools/flow.py test cases/lab03.json -v
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 `ops`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ API가 멈춘 동안 2건 → DLQ · 4.4s
|
✔ API가 멈춘 동안 2건 → DLQ · 4.4s
|
||||||
@@ -148,13 +173,28 @@ Passed 1/5
|
|||||||
✔ 접수 건은 2개 (중복 없음) · 0.0s
|
✔ 접수 건은 2개 (중복 없음) · 0.0s
|
||||||
✔ 한 번 더 돌려도 할 일 없음 · 0.0s
|
✔ 한 번 더 돌려도 할 일 없음 · 0.0s
|
||||||
Passed 5/5
|
Passed 5/5
|
||||||
|
|
||||||
|
created tickets: 2
|
||||||
|
T-2001 key R-302 장비/보통 노트북 배터리가 금방 닳아요
|
||||||
|
T-2002 key R-301 계정/높음 사내 위키 로그인이 안 돼요
|
||||||
|
DLQ: 0 (parked 0)
|
||||||
|
alerts: 1
|
||||||
|
[high] intake · dlq · Send to DLQ · count 2 · 2 request(s) could not be created and wait in the DLQ
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전에는 `my-replay` 웹훅이 없어 404. DLQ의 2건은 쌓인 채 접수 건 0개입니다.
|
| 케이스 | 고치기 전 | 고친 뒤 | 무엇이 바꿨나 |
|
||||||
- 고친 뒤 1.1초에 `ok: 2`: 두 요청을 `my-intake`로 다시 보내 접수 건을 만들고, DLQ에서 지웠습니다. 다시 보낸 요청도 `my-intake`의 확인 · 분류 · 키 규칙을 똑같이 거쳤습니다.
|
| --- | --- | --- | --- |
|
||||||
- 한 번 더 돌리면 DLQ가 비어 `replayed: 0` (0.0초). 비었을 때 갈래가 바로 답했습니다.
|
| 다시 돌리기 | 404 | 200 · `replayed: 2, ok: 2` | `my-replay`가 DLQ를 읽어 `my-intake`로 다시 보냄 |
|
||||||
|
| DLQ | 2 | 0 | 성공한 것은 DLQ에서 지움 |
|
||||||
|
| 접수 건 | 0 | 2 | 다시 보낸 2건이 접수됨 |
|
||||||
|
| 한 번 더 | 404 | `replayed: 0` | DLQ가 비면 바로 끝 |
|
||||||
|
|
||||||
|
- **1.1초에 `ok: 2`:** DLQ의 두 요청을 `my-intake`로 다시 보냈고, 둘 다 접수 건이 되어 DLQ에서 지웠습니다. 재처리가 접수 건을 직접 만들지 않고 `my-intake`를 다시 부르는 것이 중요합니다. 다시 보낸 요청도 항목 확인 · 분류 · 키 · 입구 규칙을 **똑같이** 거칩니다.
|
||||||
|
- **접수 건 2개, 키 `R-301` · `R-302`:** 원래 요청 id가 그대로 키가 되었습니다. 그래서 같은 재처리를 두 번 돌려도 중복이 생기지 않습니다.
|
||||||
|
- **한 번 더 돌리면 0.0초에 `replayed: 0`:** DLQ가 비었을 때 바로 답하는 갈래가 있어, 할 일이 없으면 아무것도 부르지 않습니다.
|
||||||
|
- `ops`의 high 알림은 장애 중에 `my-intake`가 남긴 것입니다 (Lab 02 과제 1). 재처리가 끝나도 알림은 사람이 확인할 때까지 남습니다.
|
||||||
|
|
||||||
## 예제 2 · 런북과 장애 연습 (12분, 에이전트 없이)
|
## 예제 2 · 런북과 장애 연습 (12분, 에이전트 없이)
|
||||||
|
|
||||||
@@ -244,21 +284,86 @@ sequenceDiagram
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다 (줄임).
|
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다 (줄임).
|
||||||
|
|
||||||
|
이 예제는 워크플로우를 **고치지 않습니다.** 런북대로 장애를 내고, 보고, 원인을 고치고, 재처리합니다. 여기서 "고치기 전 · 고친 뒤"는 **장애 원인**(고장 스위치)을 고치기 전과 뒤입니다.
|
||||||
|
|
||||||
|
#### 고치기 전 · 장애 중
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py ops --reset
|
||||||
|
uv run tools/flow.py fault tickets error
|
||||||
|
uv run tools/flow.py call my-intake requests/batch-two.json
|
||||||
|
uv run tools/flow.py call nightly-report
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$ fault tickets error → call my-intake requests/batch-two.json HTTP 200 · 2.8s ("failed": 2)
|
HTTP 200 · 2.8s ("failed": 2)
|
||||||
$ call nightly-report HTTP 500 · 0.1s
|
HTTP 500 · 0.1s
|
||||||
$ ops
|
|
||||||
|
created tickets: 0
|
||||||
DLQ: 2 (parked 0)
|
DLQ: 2 (parked 0)
|
||||||
D-1 R-301 attempts 1 … D-2 R-302 attempts 1 …
|
D-1 R-301 attempts 1 … D-2 R-302 attempts 1 …
|
||||||
alerts: 2
|
alerts: 2
|
||||||
[high] intake · dlq · Send to DLQ · count 2 · 2 request(s) could not be created and wait in the DLQ
|
[high] intake · dlq · Send to DLQ · count 2 · 2 request(s) could not be created and wait in the DLQ
|
||||||
[critical] 76 Nightly report (provided, has a bug) · unexpected · Count by category · rows is not iterable [line 4]
|
[critical] 76 Nightly report (provided, has a bug) · unexpected · Count by category · rows is not iterable [line 4]
|
||||||
$ fault tickets ok → call my-replay HTTP 200 · 1.4s
|
```
|
||||||
$ ops
|
|
||||||
|
**왜 이렇게 나왔나 (장애 중)**
|
||||||
|
|
||||||
|
- `ops` 한 번으로 지금 무슨 일이 있는지가 다 보입니다. DLQ 2건, 알림 둘입니다.
|
||||||
|
- **알림 둘은 원인이 다릅니다.** high `dlq`는 데이터 API 장애(고장 스위치) 때문에 `my-intake`가 처리하지 못한 2건이고, critical `unexpected`는 `nightly-report`의 **버그**입니다. 런북의 첫 일은 "알림마다 원인을 가르기"입니다.
|
||||||
|
- 접수 건 0: 장애 동안 들어온 문의는 모두 DLQ에서 기다립니다. 잃은 것은 없습니다.
|
||||||
|
|
||||||
|
#### 고친 뒤 · 원인을 고치고 재처리
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py fault tickets ok # "고침" (오늘은 고장 스위치를 되돌리는 것)
|
||||||
|
uv run tools/flow.py call my-replay
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP 200 · 1.4s
|
||||||
|
|
||||||
created tickets: 2
|
created tickets: 2
|
||||||
DLQ: 0 (parked 0)
|
DLQ: 0 (parked 0)
|
||||||
|
alerts: 2
|
||||||
|
[high] intake · dlq · …
|
||||||
|
[critical] 76 Nightly report (provided, has a bug) · unexpected · …
|
||||||
|
```
|
||||||
|
|
||||||
$ test cases/lab03-drill.json
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
|
| 볼 것 | 장애 중 | 고친 뒤 | 까닭 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| DLQ | 2 | 0 | 재처리가 2건을 `my-intake`로 다시 보냄 |
|
||||||
|
| 접수 건 | 0 | 2 | 같은 까닭, 키가 같아 중복 없음 |
|
||||||
|
| high 알림 | 있음 | 있음 | 알림은 사람이 확인하고 지울 때까지 남음 |
|
||||||
|
| critical 알림 | 있음 | 있음 | **버그는 재처리로 고쳐지지 않음** |
|
||||||
|
|
||||||
|
- 데이터 API 장애는 원인(고장 스위치)을 되돌리고 재처리하면 끝입니다. DLQ 0 · 접수 건 2.
|
||||||
|
- 그런데 critical 알림은 그대로입니다. `nightly-report`의 버그는 재처리로 고쳐지지 않습니다. 런북에 "critical `unexpected`는 코드 담당에게"가 **따로** 있어야 하는 까닭입니다.
|
||||||
|
- 알림 둘이 아직 남아 있는 것도 일부러입니다. 알림은 "처리했다"를 사람이 확인한 뒤에 지웁니다 (런북 3절).
|
||||||
|
|
||||||
|
#### 같은 연습을 테스트로
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab03-drill.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
|
```text
|
||||||
✔ 1 · 평소 · 2.6s
|
✔ 1 · 평소 · 2.6s
|
||||||
✔ 2 · 장애 시작 · API 오류 · 3.0s
|
✔ 2 · 장애 시작 · API 오류 · 3.0s
|
||||||
✔ 3 · DLQ 확인 · 0.0s
|
✔ 3 · DLQ 확인 · 0.0s
|
||||||
@@ -269,11 +374,7 @@ $ test cases/lab03-drill.json
|
|||||||
Passed 7/7
|
Passed 7/7
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**이렇게 나온 까닭:** 위에서 손으로 한 순서(평소 → 장애 → 확인 → 원인 고침 → 재처리 → 확인)를 테스트가 그대로 했습니다. 마지막 5건 = 평소 3 + 재처리 2. 사람이 해도 테스트가 해도 결과가 같으니, 런북의 명령이 **그대로 따라 할 수 있는** 것임을 확인했습니다.
|
||||||
|
|
||||||
- 장애를 낸 뒤 `ops` 한 번으로 DLQ 2건과 알림 둘이 보였습니다. **원인이 다른 알림 둘**입니다: high는 데이터 API 장애(고장 스위치), critical은 `nightly-report`의 버그입니다.
|
|
||||||
- 고장 스위치를 되돌리고 재처리하자 DLQ 0 · 접수 건 2. 하지만 critical 알림은 남아 있습니다. 버그는 재처리로 고쳐지지 않습니다 — 런북에 "critical unexpected는 코드 담당에게"가 따로 있어야 합니다.
|
|
||||||
- 테스트의 접수 건 5건 = 평소 3 + 재처리 2. 같은 연습을 사람이 해도, 테스트가 해도 결과가 같습니다.
|
|
||||||
|
|
||||||
## 예제 3 · 반쯤 처리된 요청 (6분, 에이전트 없이)
|
## 예제 3 · 반쯤 처리된 요청 (6분, 에이전트 없이)
|
||||||
|
|
||||||
@@ -346,6 +447,17 @@ Lab 01 예제 1의 기준처럼 키가 실행 번호였다면, 4번의 재처리
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 예제 1 정답으로 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 예제 1 정답으로 실제로 나온 출력입니다.
|
||||||
|
|
||||||
|
이 예제는 **고치지 않습니다.** 예제 1의 `my-replay`를 그대로 두고, "실패했다"던 요청이 사실은 처리된 경우에 재처리가 안전한지 봅니다.
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab03-half.json -v
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 `ops`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 1 · API가 느림 → 기다리다 실패 · 11.6s
|
✔ 1 · API가 느림 → 기다리다 실패 · 11.6s
|
||||||
✔ 2 · DLQ에 2건 · 0.0s
|
✔ 2 · DLQ에 2건 · 0.0s
|
||||||
@@ -356,13 +468,26 @@ Lab 01 예제 1의 기준처럼 키가 실행 번호였다면, 4번의 재처리
|
|||||||
✔ 5 · 그래도 2개 (중복 없음) · 0.0s
|
✔ 5 · 그래도 2개 (중복 없음) · 0.0s
|
||||||
✔ 6 · DLQ가 비었음 · 0.0s
|
✔ 6 · DLQ가 비었음 · 0.0s
|
||||||
Passed 6/6
|
Passed 6/6
|
||||||
|
|
||||||
|
created tickets: 2
|
||||||
|
T-2001 key R-301 계정/높음 사내 위키 로그인이 안 돼요
|
||||||
|
T-2002 key R-302 장비/보통 노트북 배터리가 금방 닳아요
|
||||||
|
DLQ: 0 (parked 0)
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**왜 이렇게 나왔나 (케이스마다)**
|
||||||
|
|
||||||
- 1 · 2: `slow`라 3초마다 끊겨 11.6초 뒤 `failed: 2`, 두 요청이 DLQ로.
|
| 케이스 | 일어난 일 | 그 순간 DLQ | 그 순간 접수 건 |
|
||||||
- 3: 8초 뒤 접수 건이 **이미 2개**입니다. 데이터 API가 늦게라도 처리했습니다. 이 순간 DLQ에도 2건, 접수 건도 2건 — 같은 문의가 두 곳에 있습니다.
|
| --- | --- | --- | --- |
|
||||||
- 4 · 5: 재처리가 두 건을 다시 보냈고, 키(`R-301` · `R-302`)가 같아 데이터 API가 처음 번호를 돌려줬습니다 (`duplicates: 2`). 접수 건은 2개 그대로, DLQ는 0. **반쯤 처리된 요청도 재처리가 안전한 것은 키 덕분입니다.**
|
| 1 · 2 | `slow`라 3초마다 끊겨 11.6초 뒤 `failed: 2` → DLQ | 2 | 0 |
|
||||||
|
| 3 | 8초 뒤 데이터 API가 늦게 처리 | 2 | **2** |
|
||||||
|
| 4 | 재처리가 두 건을 다시 보냄 → 둘 다 "이미 있음" | 0 | 2 |
|
||||||
|
| 5 · 6 | 확인 | 0 | 2 |
|
||||||
|
|
||||||
|
- **3번이 핵심입니다.** 이 순간 같은 문의가 **두 곳**에 있습니다. DLQ에는 "실패"로, 접수 건에는 "성공"으로. n8n은 3초에 끊었지만 데이터 API는 요청을 받아 두었다가 8초 뒤 처리했습니다. Lab 01 예제 3의 "시간 초과는 모름"이 여기서 DLQ로 이어졌습니다.
|
||||||
|
- **4번 `duplicates: 2`:** 재처리는 그 사실을 모르고 두 건을 다시 보냈습니다. 키(`R-301` · `R-302`)가 같아 데이터 API가 처음 번호를 돌려줬고, `my-intake`는 이것을 `ok`이면서 `duplicate`로 셌습니다.
|
||||||
|
- **5번 접수 건 2개 그대로:** 키가 실행 번호였다면 여기서 4개가 되었을 것입니다. **반쯤 처리된 요청도 재처리가 안전한 것은 키 덕분입니다.** 재처리는 "최소 한 번"(at-least-once) 보내고, 키가 "결과는 한 번"을 지킵니다.
|
||||||
|
- 6/6은 **예상한 결과**입니다. 고칠 것은 없고, 이 결과가 "재처리 전에 접수 건을 일일이 확인하지 않아도 되는" 근거입니다.
|
||||||
|
|
||||||
## 과제 1 · 세 번 실패하면 멈추기 (10분)
|
## 과제 1 · 세 번 실패하면 멈추기 (10분)
|
||||||
|
|
||||||
@@ -472,7 +597,15 @@ uv run tools/flow.py ops
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
||||||
|
|
||||||
고치기 전 (예제 1 정답)
|
#### 고치기 전 · 예제 1의 `my-replay`
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab03-challenge.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ API가 오래 멈춤 · 처음 실패 · 3.4s
|
✔ API가 오래 멈춤 · 처음 실패 · 3.4s
|
||||||
@@ -485,7 +618,26 @@ uv run tools/flow.py ops
|
|||||||
Passed 3/7
|
Passed 3/7
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- **처음 ~ 재처리 2 ✔:** 데이터 API가 계속 멈춰 있으니 다시 보내도 실패하는 것이 기대입니다. 실패할 때마다 DLQ의 `attempts`가 1 → 2 → 3으로 늘었습니다.
|
||||||
|
- **재처리 3 ✘ `replayed = 2`:** `attempts`가 3인데도 두 건을 또 보냈습니다. 예제 1의 `my-replay`는 횟수를 보지 않으니 **몇 번을 돌려도 같습니다.** 스케줄로 1분마다 돌린다면 고장 난 API를 끝없이 두드립니다.
|
||||||
|
- **parked ✘ · 알림 ✘ `reason = dlq`:** 멈춘 것이 없으니 parked도 0이고, 마지막 알림은 `my-intake`의 평소 `dlq` 알림입니다. "사람이 봐야 할 것"이라는 신호가 없습니다.
|
||||||
|
- **고친 뒤 ✘ `replayed = 2`:** 원인이 고쳐지자 재처리가 두 건을 **사람이 보기 전에** 자동으로 보내 버렸습니다. 세 번이나 실패한 요청은 요청 자체가 이상할 수도 있는데, 확인 없이 처리되었습니다.
|
||||||
|
- 3/7은 **예상한 결과**입니다.
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/replay.json
|
||||||
|
uv run tools/flow.py test cases/lab03-challenge.json -v
|
||||||
|
uv run tools/flow.py test cases/lab03.json
|
||||||
|
uv run tools/flow.py ops
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 `ops`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ API가 오래 멈춤 · 처음 실패 · 4.3s
|
✔ API가 오래 멈춤 · 처음 실패 · 4.3s
|
||||||
@@ -497,13 +649,25 @@ Passed 3/7
|
|||||||
✔ 고친 뒤에도 parked는 자동으로 돌리지 않음 · 0.0s
|
✔ 고친 뒤에도 parked는 자동으로 돌리지 않음 · 0.0s
|
||||||
Passed 7/7
|
Passed 7/7
|
||||||
(lab03 5/5)
|
(lab03 5/5)
|
||||||
|
|
||||||
|
created tickets: 2
|
||||||
|
T-2001 key R-301 계정/높음 사내 위키 로그인이 안 돼요
|
||||||
|
T-2002 key R-302 장비/보통 노트북 배터리가 금방 닳아요
|
||||||
|
DLQ: 0 (parked 0)
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전 재처리 3도 두 건을 또 보냈습니다 (2.0초, `replayed: 2`). 몇 번을 돌려도 같습니다. 마지막 알림은 `my-intake`의 `dlq`라 parked 알림이 없고, 고친 뒤 재처리는 사람이 보기 전에 자동으로 보내 버렸습니다.
|
| 케이스 | 고치기 전 | 고친 뒤 | 무엇이 바꿨나 |
|
||||||
- 고친 뒤 재처리 3은 **0.1초**: `attempts`가 3이 된 두 건을 다시 보내지 않고 parked로 표시하고 high 알림을 남겼습니다. DLQ에는 `parked: 2`로 남아 사람을 기다립니다.
|
| --- | --- | --- | --- |
|
||||||
- 고친 뒤 재처리도 `replayed: 0`: parked는 자동으로 돌리지 않습니다. `lab03` 5/5로 평소 재처리는 그대로입니다.
|
| 재처리 3 | 2.0초 · `replayed: 2` | 0.1초 · `replayed: 0, parked: 2` | `attempts`가 3이면 보내지 않고 parked로 |
|
||||||
|
| parked로 남음 | 0 | 2 | DLQ에서 지우지 않고 표시만 |
|
||||||
|
| 알림 | `dlq` | high `parked` | 멈춘 것은 사람에게 알림 |
|
||||||
|
| 고친 뒤 재처리 | `replayed: 2` | `replayed: 0` | parked는 자동으로 돌리지 않음 |
|
||||||
|
|
||||||
|
- **재처리 3이 0.1초:** 두 건을 `my-intake`로 보내지 않았기 때문에 빠릅니다. 대신 parked로 표시하고 high 알림을 남겼습니다. DLQ에는 `parked: 2`로 남아 사람을 기다립니다.
|
||||||
|
- **고친 뒤에도 `replayed: 0`:** 원인이 고쳐져도 parked는 그대로입니다. 사람이 보고 "다시 보내도 된다"고 정할 때까지 기다립니다. 끝없이 다시 보내지도 않고, 버리지도 않습니다.
|
||||||
|
- **`ops`가 DLQ 0인 까닭:** 마지막에 돌린 `lab03` 테스트가 처음에 DLQ를 비우고 자기 시나리오를 돌렸기 때문입니다. 접수 건 2개는 그 테스트의 재처리 결과입니다. `lab03` 5/5로 평소 재처리(세 번 미만)는 그대로입니다.
|
||||||
|
|
||||||
## 과제 2 · 운영 상태 한눈에 (10분)
|
## 과제 2 · 운영 상태 한눈에 (10분)
|
||||||
|
|
||||||
@@ -612,7 +776,15 @@ uv run tools/flow.py ops --reset
|
|||||||
|
|
||||||
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
수업 준비 때(10/29) 강사 노트북에서 실제로 나온 출력입니다.
|
||||||
|
|
||||||
고치기 전
|
#### 고치기 전 · `my-ops-status`가 아직 없음
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py test cases/lab03-challenge2.json -v
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄만):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✘ 1 · 아무 일 없음 → ok · 0.0s · status 404 (expected 200); status = null (expected ok); dlq = null (expected 0)
|
✘ 1 · 아무 일 없음 → ok · 0.0s · status 404 (expected 200); status = null (expected ok); dlq = null (expected 0)
|
||||||
@@ -625,7 +797,27 @@ uv run tools/flow.py ops --reset
|
|||||||
Passed 3/7
|
Passed 3/7
|
||||||
```
|
```
|
||||||
|
|
||||||
고친 뒤
|
**왜 이렇게 나왔나 (고치기 전)**
|
||||||
|
|
||||||
|
- 이 테스트의 케이스는 두 종류입니다. 괄호 안의 2 · 4 · 6은 **상태를 바꾸는** 일(접수 실패를 내기, 보고서 버그를 내기, 재처리하고 알림을 비우기)이고, 1 · 3 · 5 · 7은 **상태를 묻는** 일입니다.
|
||||||
|
- 상태를 바꾸는 셋은 이미 있는 `my-intake` · `nightly-report` · `my-replay`가 하니 ✔입니다.
|
||||||
|
- 상태를 묻는 넷은 `my-ops-status` 웹훅이 없어 모두 404입니다. 지금 운영 상태를 알려면 `ops`를 돌려 DLQ · parked · 알림 숫자를 사람이 읽고 판단해야 합니다.
|
||||||
|
- 3/7은 **예상한 결과**입니다.
|
||||||
|
|
||||||
|
#### 고친 뒤
|
||||||
|
|
||||||
|
실행한 명령:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run tools/flow.py push my-work/ops-status.json
|
||||||
|
uv run tools/flow.py test cases/lab03-challenge2.json -v
|
||||||
|
uv run tools/flow.py test cases/lab03-challenge.json
|
||||||
|
uv run tools/flow.py test cases/lab02-challenge2.json # 아래 call을 위해 강사가 더 돌린 것
|
||||||
|
uv run tools/flow.py call my-ops-status
|
||||||
|
uv run tools/flow.py ops --reset
|
||||||
|
```
|
||||||
|
|
||||||
|
출력 (결과 줄과 `call`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
✔ 1 · 아무 일 없음 → ok · 0.9s
|
✔ 1 · 아무 일 없음 → ok · 0.9s
|
||||||
@@ -639,18 +831,24 @@ Passed 3/7
|
|||||||
Passed 7/7
|
Passed 7/7
|
||||||
(lab03-challenge 7/7)
|
(lab03-challenge 7/7)
|
||||||
|
|
||||||
$ call my-ops-status (lab02-challenge2 바로 뒤, 비우기 전)
|
|
||||||
HTTP 200 · 0.1s
|
HTTP 200 · 0.1s
|
||||||
{ "status": "critical", "dlq": 5, "parked": 0, "created": 2, "alerts": { "critical": 1, "high": 1 },
|
{ "status": "critical", "dlq": 5, "parked": 0, "created": 2, "alerts": { "critical": 1, "high": 1 },
|
||||||
"reasons": [ "critical 알림 1건 (10분)", "DLQ 5건: 원인을 고친 뒤 재처리", "high 알림 1건 (10분)" ] }
|
"reasons": [ "critical 알림 1건 (10분)", "DLQ 5건: 원인을 고친 뒤 재처리", "high 알림 1건 (10분)" ] }
|
||||||
```
|
```
|
||||||
|
|
||||||
**왜 이렇게 나왔나**
|
**고친 뒤 이렇게 나온 까닭**
|
||||||
|
|
||||||
- 고치기 전에는 웹훅이 없어 상태 케이스 넷이 404. 상태를 바꾸는 케이스 셋(실패 · 버그 · 재처리)만 ✔.
|
| 순서 | 그 전에 생긴 일 | 상태 | 까닭 (`reasons`) |
|
||||||
- 고친 뒤 `ok` → 실패 2건 뒤 `warn`(DLQ 2, "원인을 고친 뒤 재처리") → 버그 뒤 `critical` → 재처리하고 알림을 확인(비움)한 뒤 다시 `ok`. 신호가 실제 상태를 따라갑니다.
|
| --- | --- | --- | --- |
|
||||||
- 마지막 `call`은 Lab 02 과제 2 테스트가 남긴 상태를 그대로 읽었습니다: DLQ 5건에 `dlq_down` critical 알림 → `critical`, 까닭 셋. 숫자 여럿을 읽지 않아도 무엇부터 할지 보입니다.
|
| 1 | 없음 | `ok` | DLQ 0 · 알림 0 |
|
||||||
- 상태 웹훅은 0.1초 안팎: 데이터 API를 세 번 읽기만 하고 LLM을 부르지 않습니다.
|
| 3 | 접수 실패 2건 | `warn` | DLQ 2건: 원인을 고친 뒤 재처리 |
|
||||||
|
| 5 | 보고서 버그 | `critical` | critical 알림 1건 |
|
||||||
|
| 7 | 재처리 · 알림 확인(비움) | `ok` | 다시 깨끗함 |
|
||||||
|
|
||||||
|
- 상태 하나가 실제 상황을 따라갔습니다. 문의가 DLQ에서 기다리면 `warn`(급하지 않지만 할 일이 있음), 버그 같은 critical 알림이 있으면 `critical`(지금 사람이 봐야 함), 다 처리하면 `ok`입니다. 가장 나쁜 것이 상태가 됩니다.
|
||||||
|
- `reasons`에 **무엇을 할지**가 있습니다 ("원인을 고친 뒤 재처리"). 숫자 여럿을 읽고 판단하지 않아도 무엇부터 할지 보입니다.
|
||||||
|
- **마지막 `call`이 `critical`인 까닭:** 바로 앞에 돌린 `lab02-challenge2` 테스트가 남긴 상태(DLQ 5건, `dlq_down` critical 알림)를 그대로 읽었기 때문입니다. 수강생 순서에서 `lab03-challenge` 바로 뒤에 부르면 다른 숫자가 나올 수 있고, 그것이 맞습니다. 상태 웹훅은 지금 있는 것을 읽을 뿐입니다.
|
||||||
|
- 0.1초 안팎인 것은 데이터 API를 세 번 **읽기만** 하고 LLM을 부르지 않기 때문입니다. 모니터링이 1분마다 불러도 부담이 없습니다.
|
||||||
|
|
||||||
## 막혔을 때
|
## 막혔을 때
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user