DOCS/journey/troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_해결.md

3.4 KiB

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
상위 원칙: 문서 작성 원칙, Backend Coding Principles

관련 문서


문제 상황

  • 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 타입과 비교 기준을 같은 시간 축으로 맞춰야 한다.