Files
aio-01d4-stu/Lecture/80-82-Project/student/06-RELIABILITY-AND-SECURITY.md
T

68 lines
5.7 KiB
Markdown
Raw Normal View History

# 운영 안정화 · 예외 처리 · 보안
원본 요구 3단계: "예상 가능한 상황을 확인하고, 각 예외에서 재시도 · 실행 중단 · 담당자 이관 · 알림 중 어떤 처리가 실행되는지 설정하고, 개인정보와 접근 권한이 흐름과 로그에 반영되는지 점검합니다." 이 문서는 그 표를 미리 채운 출발점입니다. 팀은 자기 워크플로우에 맞게 고치고 **실제로 일으켜 보고** 결과를 결과서에 씁니다.
## 1. 예외 처리 표 (설계서 7절의 출발점)
| 상황 | 알아채는 법 | 처리 | 사용자에게 | 기록 | 시나리오 | 64~79일차 |
| --- | --- | --- | --- | --- | --- | --- |
| 잘못된 입력 | Code 검사 | 즉시 400 | `codes` | 형식 오류 수 | WF-13 | 73 |
| 인증 실패 (Webhook 키) | n8n Header Auth | 401 (n8n) | — | n8n 실행 기록 | 팀 | — |
| 인증 실패 (외부 API 401 · 403) | HTTP 상태 | **재시도 없이** 중단 → 이관 | "담당자에게 연결합니다" | 알림 `severity: high` | 팀 | 76 |
| 호출 한도 초과 (429) | HTTP 상태 | `Retry-After`만큼 기다려 재시도 (최대 N회) → 이관 | 이관 안내 | 재시도 수 | 팀 | 72 · 75 |
| 외부 서비스 장애 (5xx) | HTTP 상태 · 오류 출력 | 재시도 1~2회 → DLQ + 알림 → 이관 | 이관 안내 (`ticket: null`이어도 200) | DLQ · 알림 | WF-04 | 72 · 76 |
| 외부 서비스 느림 | `timeout` | 시간 초과 → 대체 경로 | 정해진 시간 안 답 | `reason` | WF-11 | 72 |
| LLM 멈춤 · 시간 초과 | Agent 오류 출력 | 근거 원문 안내 또는 이관 (fail-closed: 판별 · 검증이 멈추면 자동 답변하지 않음) | 이유 있는 안내 | `reason: llm_down` | WF-10 | 72 · 73 |
| 검색 결과 없음 · 점수 미달 | Code (점수 기준) | 답 생성 중단 → 이관 | "확인 후 담당자가 연락" | `reason: no_evidence` | WF-03 | 71 |
| 검증 실패 (근거 밖 내용) | 검증 Agent + Code | 이관 (또는 한 번 다시 쓰기) | 이관 안내 | `issues` | 팀 | 69 |
| 중복 요청 | `request_id` 캐시 · 티켓 `key` | 저장된 결과 그대로, 티켓 하나 | 같은 답 | `duplicate` | WF-09 | 72 · 76 |
| 프롬프트 주입 | 규칙 · 판별 | 차단 (LLM 앞에서) | 정해진 안내 | `security` | WF-08 | 73 |
| 권한 없음 | 데이터 API 403 · 메타데이터 | 권한 없음 | 정해진 안내 | `security` | WF-05 · 06 | 73 |
| 처리 못 한 예외 (버그) | **Error Workflow** | 알림 + DLQ | (n8n이 500) | 알림 | 팀 | 76 |
**재시도 원칙 (72 · 76일차)**
| 다시 해도 되는 것 | 다시 하면 안 되는 것 |
| --- | --- |
| timeout, 5xx, 429 (기다린 뒤) | 400 · 401 · 403 · 404 (요청 · 권한이 틀림, 몇 번 해도 같음) |
| 조회 (GET) | 같은 생성(POST)을 key 없이 (티켓이 두 개 생김) → **중복 방지 key**를 달면 다시 해도 됨 |
- 최악의 시간 = 시도 수 × timeout + 기다림. 응답 시간 기준(예: 6초) 안에 들어오게 정합니다 (72일차).
- 재시도도 실패하면 **DLQ**에 남깁니다: 무엇을(요청 요약 · 개인정보 없이), 왜(사유), 몇 번(시도 수). 나중에 다시 처리(76일차 replay)할 수 있어야 합니다.
- 알림은 **실패마다가 아니라 사람이 할 일이 있을 때** 보냅니다. 같은 장애로 알림이 100개 오면 아무도 보지 않습니다 (76일차 묶어 보내기).
## 2. Error Workflow (원본 "실행 중단 · 알림")
- n8n 워크플로우 설정의 **Error Workflow**에 팀의 `<팀>-errors`를 지정하고, 그 워크플로우는 Error Trigger → 알림(`POST /alerts`) → DLQ로 둡니다 (76일차).
- 오류 출력(`onError: continueErrorOutput`)으로 **예상한** 실패를 처리하고, Error Workflow는 **예상하지 못한** 실패의 마지막 그물입니다. 둘 다 둡니다.
- Error Workflow는 **게시(활성화)되어 있어야** 동작합니다.
## 3. 보안 기준 (73일차를 프로젝트에)
| 기준 | 확인 방법 |
| --- | --- |
| 입력 검증은 LLM 앞에서 | WF-13 400, LLM 호출 0 |
| 주입 차단 (규칙 + 판별, 글자 정리) | WF-08, 팀 시나리오에 숨긴 공격 하나 |
| 정상 질문을 막지 않음 | 팀 시나리오 "지나치게 막지 않음" |
| 권한의 근거는 시스템 (`user` → `/employees`의 `dept`, `requester=user`) | WF-05 · WF-06 |
| 지시문(systemMessage)에 비밀 없음 | 워크플로우 JSON `grep` |
| 출력 필터 (전화 · 메일 · 주민번호 가림) | WF-07, `security.masked` |
| 티켓 본문 · 알림 · 로그에도 개인정보 가림 | `GET /tickets-created`, `GET /alerts`, 로그에 연락처 모양 0 |
| 키는 Credential에만 | 워크플로우 JSON · 문서 · 저장소에 키 없음 (`tscan scan .`) |
## 4. 실행 기록 (원본 결과서 2절 "실행 로그")
요청마다 한 줄을 남깁니다 (`POST /logs`, 73일차 감사 로그 · 77일차 실행 기록).
```json
{"request_id": "req-001", "user": "박서연", "started_at": "...", "ended_at": "...", "route": "자동 답변",
"agents": ["triage", "retriever", "writer", "verifier"], "nodes": 14, "llm_calls": 3,
"sources": [{"doc": "remote-work-vpn", "score": 0.71}], "retries": 0, "external": {"tickets": "ok"},
"error_code": null, "masked": 0, "total_ms": 2840}
```
| 남김 | 남기지 않음 |
| --- | --- |
| 원본 요구 결과서 2절의 칸: request_id · 시각 · 실행 노드 · 외부 API 상태 · 재시도 수 · 검색 문서 ID와 점수 · 최종 분기 · 오류 코드 · 마스킹 여부 | 질문 · 답 글자 전체, 연락처 (가린 것도) |
- 결과서의 "노드별 입력 · 출력 요약"은 n8n 실행 화면(Executions)에서 시나리오마다 한 번 캡처하거나 복사합니다 (개인정보를 가리고).