docs: record naverworks briefing failure handling

This commit is contained in:
happybell80 2026-03-09 22:28:25 +09:00
parent de9e61b663
commit ffb0bf3366
6 changed files with 102 additions and 23 deletions

View File

@ -144,6 +144,8 @@ utils
- **원인 직접 수정 우선**: 증상을 감추는 광범위 예외 폴백/임시 우회 코드를 금지한다. 재현 가능한 근본 원인(입력/경로/설정/데이터)을 먼저 수정한다.
- **상태코드 의미 보존**: 인증 실패(401/403), 권한 문제(403), 입력 오류(400)는 원래 의미대로 반환하고 500으로 왜곡하지 않는다.
- **폴백 남용 금지**: 폴백은 사용자 보호용 최후 수단으로만 사용한다. 개선 신호를 가리는 무조건 폴백(`catch-all fallback`)을 금지한다.
- **실패와 0건 결과 혼동 금지**: 외부 조회 실패, timeout, refresh 실패를 `[]`, `None`, `"no data"` 같은 정상 결과로 치환하지 않는다. "실패"와 "실제 데이터 없음"은 서로 다른 타입과 로그로 분리한다.
- **스케줄러 성공 오판 금지**: 배치/스케줄러 래퍼는 하위 실패를 삼키지 않고 다시 올린다. 운영 로그의 success는 실제 성공과 같아야 한다.
- **관측 가능한 실패 보장**: 운영/카나리 경로에는 `strict_observe` 모드를 두어 일부 요청은 폴백 없이 실패를 노출하고, 구조화된 오류 로그/메트릭을 반드시 남긴다.
- **로그 없는 폴백 금지**: 폴백이 발생하면 `request_id`, `route`, `template/version`, `fallback_reason`, `upstream_error`를 필수 기록한다.

View File

@ -19,6 +19,11 @@
- Slack 응답 및 스킬 호출 문제 `troubleshooting/250723_happybell80_Slack응답및스킬호출문제.md`
- 프론트엔드 로빙 연결 및 UX 개선 `troubleshooting/250730_happybell80_프론트엔드로빙연결.md`
### 운영 / 브리핑
- 네이버 이메일 분석 미전송과 실패 은닉 해결 `troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_해결.md`
- 네이버 이메일 분석 실패 은닉 리서치 `research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md`
### Intent / 리뷰 큐
- Intent 리뷰 큐 API 구현 `troubleshooting/251116_admin_intent_review_queue_api_implementation.md`
@ -57,4 +62,4 @@
---
> 새로운 문서가 생기면 관련 섹션에 링크만 추가합니다. 상세 내용은 원본 문서를 참조하세요.
> 새로운 문서가 생기면 관련 섹션에 링크만 추가합니다. 상세 내용은 원본 문서를 참조하세요.

View File

@ -0,0 +1,47 @@
# 9시 네이버 이메일 분석 미전송 실패 은닉 리서치
tags: [naverworks, email, briefing, timeout, failure-observability]
**날짜**: 2026-03-09
**작성자**: Codex
**상위 원칙**: [문서 작성 원칙](../../book/300_architecture/312_writing-principles.md), [백엔드 구조 원칙](../../book/300_architecture/311_백엔드_구조_원칙.md)
## 관련 문서
- [9시 네이버 이메일 분석 미전송과 실패 은닉 해결](../troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_해결.md)
- [NAVER WORKS Refresh unauthorized_client 장애 및 재인증 복구 기록](../troubleshooting/260227_naverworks_refresh_unauthorized_client_reauth_recovery.md)
---
## 조사 대상
- 왜 `2026-03-09 09:00` 네이버 이메일 분석 브리핑이 전송되지 않았는가
- 왜 실패가 운영 로그에서는 성공처럼 보였는가
- 이번 문제를 우회 없이 닫으려면 무엇을 직접 수정해야 하는가
## 확인된 사실
1. `rb8001` 스케줄 로그 기준 `2026-03-09 09:00:00` KST에 `naverworks_daily` 작업은 실행됐다.
2. 같은 요청 흐름에서 `2026-03-09 09:00:30` KST에 `httpx.ReadTimeout`이 발생했다.
3. `rb8001/app/services/skills/naverworks_briefing.py`는 메일 조회 실패를 빈 목록처럼 다뤄 `No emails`로 끝낼 수 있는 구조였다.
4. `rb8001/app/scheduler/jobs/naverworks_briefing.py`는 작업 예외를 다시 올리지 않아 APScheduler 성공 로그와 실제 실패가 어긋날 수 있는 구조였다.
5. `skill-email/services/naverworks_provider.py`는 당시 단계별 elapsed 로그가 부족해, `30초 블로킹`의 최종 지점이 `auth-server refresh`인지 NAVER WORKS 외부 API인지 즉시 분리하기 어려웠다.
6. `skill-email`, `auth-server` 컨테이너는 UTC 기준으로 동작하고, `rb8001`은 KST 기준 스케줄을 사용한다.
7. `main_db.naverworks_token.expires_at``timestamp without time zone`이라 비교 기준이 코드에서 명시되지 않으면 해석 흔들림이 생긴다.
## 해석
- 직접 원인은 `rb8001 -> skill-email /messages` 경로의 read timeout이다.
- 구조 원인은 "조회 실패"와 "조회 결과 0건"을 같은 값으로 취급한 설계다.
- 관측 실패 원인은 스케줄러 래퍼가 예외를 다시 올리지 않은 점이다.
- 시간대 혼재와 naive timestamp는 재발 위험을 높이는 구조 요인이지만, 이번 09:00 미전송의 직접 원인 자체를 대체하지는 않는다.
## 이번 문제를 닫는 수정 기준
1. 메일 조회 실패는 빈 목록이 아니라 명시적 실패 타입으로 분리한다.
2. 스케줄러는 실패를 다시 올려 운영 로그와 실제 결과를 일치시킨다.
3. `skill-email`에는 단계별 추적 로그를 추가해 다음 장애에서 block point를 즉시 좁힐 수 있게 한다.
4. 토큰 만료 판단은 UTC 기준을 코드에서 명시해 DB 값과 비교 축을 고정한다.
## 미확정
- `2026-03-09 09:00`의 실제 30초 block point가 `auth-server refresh`였는지, 외부 NAVER WORKS API였는지는 당시 세부 로그 부재로 100% 확정하지 못했다.
- 이 항목은 이번 수정 후 재발 시 새 추적 로그로 닫는다.
## 결론
- 이 문제의 핵심은 "9시 작업이 안 돌았다"가 아니라 "9시 작업 실패가 성공처럼 은닉됐다"는 점이다.
- 따라서 우회 없는 해법은 재시도 강화가 아니라 실패 타입 분리, 예외 재전파, 단계별 추적 로그 추가다.

View File

@ -9,6 +9,7 @@
- [아침브리핑 형식혼선·동남아영어노출 원인확정 리서치 (260305)](./260305_아침브리핑_형식혼선_동남아영어노출_원인확정_리서치.md)
- [51124 먹통 48시간 코드 차분 원인 확정 리서치 (260304)](./260304_51124_먹통_48시간_코드차분_원인확정_리서치.md)
- [9시 네이버 이메일 분석 미전송 실패 은닉 리서치 (260309)](./260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md)
### [기억(Memory)](./memory/README.md)
- 장단기 기억 메커니즘

View File

@ -0,0 +1,43 @@
# 9시 네이버 이메일 분석 미전송과 실패 은닉 해결
tags: [naverworks, email, briefing, scheduler, timeout, fallback]
**날짜**: 2026-03-09
**작성자**: Codex
**관련 파일**: `rb8001/app/services/skills/naverworks_briefing.py`, `rb8001/app/scheduler/jobs/naverworks_briefing.py`, `skill-email/services/naverworks_provider.py`
**상위 원칙**: [문서 작성 원칙](../../book/300_architecture/312_writing-principles.md), [백엔드 구조 원칙](../../book/300_architecture/311_백엔드_구조_원칙.md)
## 관련 문서
- [9시 네이버 이메일 분석 미전송 실패 은닉 리서치](../research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md)
- [NAVER WORKS Refresh unauthorized_client 장애 및 재인증 복구 기록](./260227_naverworks_refresh_unauthorized_client_reauth_recovery.md)
---
## 문제 상황
- `2026-03-09 09:00:00` KST에 `naverworks_daily` 작업은 실제 실행됐다.
- `2026-03-09 09:00:30` KST에 `rb8001 -> skill-email /messages` 호출이 `httpx.ReadTimeout`으로 실패했다.
- 그런데 `rb8001/app/services/skills/naverworks_briefing.py:57` 경로가 이 실패를 `No emails`처럼 처리해, 운영자가 "메일이 없었다"고 오인할 수 있는 상태가 됐다.
- `rb8001/app/scheduler/jobs/naverworks_briefing.py:87`도 예외를 다시 올리지 않아 스케줄러에는 성공처럼 남았다.
## 해결 방안
- `rb8001/app/services/skills/naverworks_briefing.py:16`: `BriefingFetchError`를 추가해 메일 조회 실패를 빈 결과와 분리했다.
- `rb8001/app/services/skills/naverworks_briefing.py:57`: 실제 빈 목록일 때만 `No emails in the configured briefing window`를 기록하도록 바꿨다.
- `rb8001/app/services/skills/naverworks_briefing.py:109`: `httpx.ReadTimeout`, `httpx.HTTPError`, 비정상 응답, 잘못된 payload를 `BriefingFetchError`로 올리도록 바꿨다.
- `rb8001/app/scheduler/jobs/naverworks_briefing.py:89`: 스케줄러 래퍼가 `ImportError`와 일반 예외를 다시 올려 APScheduler가 실패로 기록하게 바꿨다.
- `skill-email/services/naverworks_provider.py:38`: 토큰 만료 판단 기준을 `datetime.utcnow()`로 명시해 UTC naive DB 값과 비교 기준을 맞췄다.
- `skill-email/services/naverworks_provider.py:49`, `skill-email/services/naverworks_provider.py:197`, `skill-email/services/naverworks_provider.py:357`: 컨텍스트 조회, 외부 NAVER WORKS API 호출, refresh 호출에 단계별 추적 로그와 timeout 분기 로그를 추가했다.
## 검증
- `docker exec -e PYTHONPATH=/code rb8001 pytest -q tests/test_naverworks_briefing.py` 결과 `10 passed`
- `https://ro-being.com/rb8001/health` 응답 `200`
- `https://ro-being.com/skill-email/health` 응답 `200`
- `skill-email /messages` 재호출 시 `200` 응답과 단계별 로그(`context lookup`, `token state`, `API request finished`)를 확인했다.
## 구현 완료
- `rb8001` 커밋: `6b2280e` (`fix: surface naverworks briefing failures`)
- `skill-email` 커밋: `7a57aae` (`fix: add naverworks fetch tracing`)
## 교훈
- 외부 API 조회 실패와 "실제 0건"은 같은 값으로 처리하면 안 된다.
- 스케줄러 성공 로그는 실제 작업 성공과 같지 않으므로, 예외를 다시 올려 관측 가능한 실패로 남겨야 한다.
- 시간대가 섞인 토큰 만료 판단은 장애를 숨기기 쉬우므로, DB 타입과 비교 기준을 같은 시간 축으로 맞춰야 한다.

View File

@ -1,13 +1,12 @@
# Troubleshooting Guide
51124 서버 운용 중 발생한 모든 이슈는 날짜 기반 마크다운 파일(`yymmdd_작성자_주제.md`)로 저장됩니다. 이 README는 340개가 넘는 문서 중 필요한 정보를 빠르게 찾을 수 있도록 돕습니다.
51124 서버 운용 중 발생한 이슈 기록의 진입점입니다.
## 구조와 네이밍
- 경로: `DOCS/troubleshooting/`
- 파일명: `YYMMDD_author_topic.md`
- 예: `250723_happybell80_Slack_3초룰.md`
- 각 문서는 **상황 → 원인 → 해결 → 교훈** 순서를 기본으로 하며, 교훈 섹션의 규칙은 `AGENTS.md`에 반영됩니다.
## 빠르게 찾는 방법
@ -33,25 +32,7 @@
| Storage/용량 | `250716_SSD_용량_부족_및_Docker_정리.md` | 정리 스크립트 가이드 |
| Intent 리뷰 큐 | `251116_admin_intent_review_queue_api_implementation.md` | 최신 TDD 사례 |
## 문서 작성 시 체크리스트
- [ ] 문제 원인/증상 구분
- [ ] 명령어/로그는 fenced code block 사용
- [ ] 교훈 섹션에 재발 방지 규칙 명시
- [ ] 관련 문서/참고 링크를 하단에 추가
## 자주 쓰는 명령
```bash
rg "교훈" DOCS/troubleshooting -n # 교훈 섹션 검색
rg "Docker" DOCS/troubleshooting -g "*.md" # 도커 관련 문서 검색
```
## 다음 단계
- 월별 인덱스 문서 작성 (예: `2025-07.md`)
- Slack/Email/Calendar 등 서비스별 태그 파일 정리
- 교훈 섹션 자동 추출 스크립트 추가 (plans/ 디렉토리 예정)
- [260226_51124_openclaw_gateway_상주프로세스_정리.md](./260226_51124_openclaw_gateway_상주프로세스_정리.md)
- 51124에서 비관리 openclaw-gateway 상주 프로세스 확인 및 종료/검증 기록
- [260309_9시_네이버이메일분석_미전송_실패은닉_해결.md](./260309_9시_네이버이메일분석_미전송_실패은닉_해결.md)
- 9시 브리핑 미전송의 직접 원인인 `skill-email` timeout과 실패 은닉 제거 기록