DOCS/journey/research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md

3.4 KiB

9시 네이버 이메일 분석 미전송 실패 은닉 리서치

tags: [naverworks, email, briefing, timeout, failure-observability]

날짜: 2026-03-09
작성자: Codex
상위 원칙: 문서 작성 원칙, Backend Coding Principles

관련 문서


조사 대상

  • 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_attimestamp 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시 작업 실패가 성공처럼 은닉됐다"는 점이다.
  • 따라서 우회 없는 해법은 재시도 강화가 아니라 실패 타입 분리, 예외 재전파, 단계별 추적 로그 추가다.