docs: record naverworks briefing failure handling
This commit is contained in:
parent
d9d7f6b5e8
commit
71c5749439
@ -36,6 +36,7 @@
|
|||||||
- [260226 NAS(192.168.219.51) 접속불가 임시 백업 복구](./troubleshooting/260226_NAS_192_168_219_51_접속불가_임시백업복구.md)
|
- [260226 NAS(192.168.219.51) 접속불가 임시 백업 복구](./troubleshooting/260226_NAS_192_168_219_51_접속불가_임시백업복구.md)
|
||||||
- [260217 starsandi DNS/nginx/SSL 분리 적용](./troubleshooting/260217_starsandi_dns_nginx_ssl_분리적용.md)
|
- [260217 starsandi DNS/nginx/SSL 분리 적용](./troubleshooting/260217_starsandi_dns_nginx_ssl_분리적용.md)
|
||||||
- [51123 구 IP 하드코딩과 런타임 SSOT 불일치 이슈](./troubleshooting/260309_51123_구IP하드코딩_런타임SSOT불일치_이슈.md)
|
- [51123 구 IP 하드코딩과 런타임 SSOT 불일치 이슈](./troubleshooting/260309_51123_구IP하드코딩_런타임SSOT불일치_이슈.md)
|
||||||
|
- [9시 네이버 이메일 분석 미전송과 실패 은닉 이슈](./troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_이슈.md)
|
||||||
- [24서버 우분투 터미널 불가, 네트워크 대역 오류, python3-apt 복구 기록](./troubleshooting/260309_24서버_우분투터미널불가_네트워크대역오류_python3apt복구.md)
|
- [24서버 우분투 터미널 불가, 네트워크 대역 오류, python3-apt 복구 기록](./troubleshooting/260309_24서버_우분투터미널불가_네트워크대역오류_python3apt복구.md)
|
||||||
- [Gitea git credential helper 표준화](./troubleshooting/260309_gitea_git_credential_helper_표준화.md)
|
- [Gitea git credential helper 표준화](./troubleshooting/260309_gitea_git_credential_helper_표준화.md)
|
||||||
- [23서버 워크스페이스 인프라 구조정리 이슈](./troubleshooting/260307_23서버_워크스페이스_인프라_구조정리_이슈.md)
|
- [23서버 워크스페이스 인프라 구조정리 이슈](./troubleshooting/260307_23서버_워크스페이스_인프라_구조정리_이슈.md)
|
||||||
@ -45,14 +46,17 @@
|
|||||||
- [외부 NAS -> 내부 NAS 컴퍼니엑스 동기화 리서치](./research/260307_external_nas_companyx_sync_research.md)
|
- [외부 NAS -> 내부 NAS 컴퍼니엑스 동기화 리서치](./research/260307_external_nas_companyx_sync_research.md)
|
||||||
- [컴퍼니엑스 직원용 모바일 파일 포털 리서치](./research/260307_companyx_mobile_file_portal_research.md)
|
- [컴퍼니엑스 직원용 모바일 파일 포털 리서치](./research/260307_companyx_mobile_file_portal_research.md)
|
||||||
- [51123 구 IP 하드코딩 실행 경로와 런타임 SSOT 불일치 리서치](./research/260309_51123_구IP하드코딩_실행경로_SSOT불일치_리서치.md)
|
- [51123 구 IP 하드코딩 실행 경로와 런타임 SSOT 불일치 리서치](./research/260309_51123_구IP하드코딩_실행경로_SSOT불일치_리서치.md)
|
||||||
|
- [9시 네이버 이메일 분석 미전송 실패 은닉 리서치](./research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md)
|
||||||
- [24서버 로빙 운영 자료 수집](./research/260309_24서버_로빙운영_자료수집.md)
|
- [24서버 로빙 운영 자료 수집](./research/260309_24서버_로빙운영_자료수집.md)
|
||||||
- [24서버 실서비스 운영전환 리서치](./research/260309_24서버_실서비스운영전환_리서치.md)
|
- [24서버 실서비스 운영전환 리서치](./research/260309_24서버_실서비스운영전환_리서치.md)
|
||||||
- [23서버 워크스페이스 SSOT 구조전환 리서치](./research/260309_23서버_워크스페이스_SSOT_구조전환_리서치.md)
|
- [23서버 워크스페이스 SSOT 구조전환 리서치](./research/260309_23서버_워크스페이스_SSOT_구조전환_리서치.md)
|
||||||
- [51123 구 IP 하드코딩 실행 경로 제거 계획](./plans/260309_51123_구IP하드코딩_실행경로제거_계획.md)
|
- [51123 구 IP 하드코딩 실행 경로 제거 계획](./plans/260309_51123_구IP하드코딩_실행경로제거_계획.md)
|
||||||
- [24서버 실서비스 운영전환 계획](./plans/260309_24서버_실서비스운영전환_계획.md)
|
- [24서버 실서비스 운영전환 계획](./plans/260309_24서버_실서비스운영전환_계획.md)
|
||||||
- [23서버 워크스페이스 SSOT 구조전환 계획](./plans/260309_23서버_워크스페이스_SSOT_구조전환_계획.md)
|
- [23서버 워크스페이스 SSOT 구조전환 계획](./plans/260309_23서버_워크스페이스_SSOT_구조전환_계획.md)
|
||||||
|
- [9시 네이버 이메일 분석 실패 은닉 해결 계획](./plans/260309_9시_네이버이메일분석_실패은닉_해결계획.md)
|
||||||
- [24서버 ONNX 모델 NAS 백업 정리](./worklog/260309_24서버_onnx모델_nas백업_정리.md)
|
- [24서버 ONNX 모델 NAS 백업 정리](./worklog/260309_24서버_onnx모델_nas백업_정리.md)
|
||||||
- [23제어면 gateway workspace-config 단일화](./worklog/260309_23제어면_gateway_workspace_config_단일화.md)
|
- [23제어면 gateway workspace-config 단일화](./worklog/260309_23제어면_gateway_workspace_config_단일화.md)
|
||||||
- [51123 구IP active runtime 제거 1차](./worklog/260309_51123_구IP_active_runtime_제거_1차.md)
|
- [51123 구IP active runtime 제거 1차](./worklog/260309_51123_구IP_active_runtime_제거_1차.md)
|
||||||
- [24전환 배포대상과 ingress 24IP 교정 1차](./worklog/260309_24전환_배포대상과_ingress_24IP_교정_1차.md)
|
- [24전환 배포대상과 ingress 24IP 교정 1차](./worklog/260309_24전환_배포대상과_ingress_24IP_교정_1차.md)
|
||||||
- [24서버 NAS 마운트 복원 및 ONNX 백업 실행](./worklog/260309_24서버_nas마운트복원_onnx백업실행.md)
|
- [24서버 NAS 마운트 복원 및 ONNX 백업 실행](./worklog/260309_24서버_nas마운트복원_onnx백업실행.md)
|
||||||
|
- [9시 네이버 이메일 분석 실패 은닉 1차 해결](./worklog/260309_9시_네이버이메일분석_실패은닉_1차해결.md)
|
||||||
|
|||||||
108
journey/plans/260309_9시_네이버이메일분석_실패은닉_해결계획.md
Normal file
108
journey/plans/260309_9시_네이버이메일분석_실패은닉_해결계획.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
tags: [infra, robeing, rb8001, skill-email, naverworks, plans]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 260309 9시 네이버 이메일 분석 실패 은닉 해결 계획
|
||||||
|
|
||||||
|
## 상위 원칙
|
||||||
|
- [Infra Project Identity](../../00_Philosophy/00_IDENTITY/Infra_Project_Identity.md)
|
||||||
|
- [Core Infrastructure Principles](../../00_Philosophy/01_PRINCIPLES/Core_Infrastructure_Principles.md)
|
||||||
|
- [Operational Guardrails](../../00_Philosophy/02_GUARDRAILS/Operational_Guardrails.md)
|
||||||
|
- 공통 작성 원칙: [0_VALUE Writing Principles](https://github.com/happybell80/0_VALUE/blob/main/02_Governance/writing-principles.md)
|
||||||
|
|
||||||
|
## 관련 문서
|
||||||
|
- [Infra Journey](../README.md)
|
||||||
|
- [9시 네이버 이메일 분석 미전송과 실패 은닉 이슈](../troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_이슈.md)
|
||||||
|
- [9시 네이버 이메일 분석 미전송 실패 은닉 리서치](../research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md)
|
||||||
|
- [24서버 실서비스 운영전환 계획](./260309_24서버_실서비스운영전환_계획.md)
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
- 네이버 이메일 분석 브리핑에서 `메일 조회 실패`와 `실제 메일 0건`을 분리한다.
|
||||||
|
- `rb8001` 스케줄러가 실패를 성공처럼 기록하지 않도록 한다.
|
||||||
|
- 재발 시 `auth refresh 지연`인지 `NAVER WORKS 외부 API 지연`인지 로그만으로 구분 가능하게 만든다.
|
||||||
|
- `expires_at` 시간대 정합성 위험을 별도 판단 가능한 상태로 만든다.
|
||||||
|
|
||||||
|
## 문제 정의
|
||||||
|
- 현재 `rb8001/app/services/skills/naverworks_briefing.py`는 `skill-email /messages` 호출 실패를 `return []`로 삼킨다.
|
||||||
|
- 그 결과 `process_briefing()`는 실패를 `No emails in the last 24 hours`로 오인하고 종료한다.
|
||||||
|
- 이 구조는 상위 SSOT의 `Truth First`, `근본 원인 직접 수정 우선`, `광범위 예외 폴백 금지` 원칙과 충돌한다.
|
||||||
|
|
||||||
|
## 범위 고정
|
||||||
|
|
||||||
|
### 1차 수정 대상
|
||||||
|
- `rb8001/app/services/skills/naverworks_briefing.py`
|
||||||
|
- `skill-email/main.py`
|
||||||
|
- `skill-email/services/naverworks_provider.py`
|
||||||
|
|
||||||
|
### 1차 검증 대상
|
||||||
|
- `rb8001` 09:00 스케줄 실행 경로
|
||||||
|
- `skill-email /messages?provider=naverworks`
|
||||||
|
- `auth-server /auth/naverworks/passport/refresh`
|
||||||
|
|
||||||
|
### 2차 판단 대상
|
||||||
|
- `main_db.naverworks_token.expires_at` 컬럼 타입 변경 필요 여부
|
||||||
|
- 컨테이너 시간대 통일 여부 또는 만료 판단 로직의 시간대 명시 처리
|
||||||
|
|
||||||
|
## 실행 원칙
|
||||||
|
- 실패를 `메일 0건`으로 대체하지 않는다.
|
||||||
|
- `except Exception`으로 원인성 실패를 일괄 래핑하지 않는다.
|
||||||
|
- `timeout`, `비200 응답`, `응답 구조 이상`, `토큰 갱신 실패`를 구분해 기록한다.
|
||||||
|
- 수정 후에는 로그, 헬스체크, 재현 테스트로 실제 실패 표현이 교정됐는지 확인한다.
|
||||||
|
|
||||||
|
## 단계별 계획
|
||||||
|
|
||||||
|
### 1. `rb8001`에서 실패 은닉을 제거한다
|
||||||
|
- `_fetch_recent_emails()`가 `[]` 대신 원인성 예외를 올리도록 바꾼다.
|
||||||
|
- 최소 분기 대상:
|
||||||
|
- `httpx.ReadTimeout`
|
||||||
|
- `httpx.ConnectError`
|
||||||
|
- `비200 응답`
|
||||||
|
- 응답 JSON 구조 이상
|
||||||
|
- `process_briefing()`는 `실제 0건`일 때만 `No emails`로 처리하고, 조회 실패는 스케줄러 실패로 남기게 바꾼다.
|
||||||
|
|
||||||
|
### 2. `skill-email` 단계별 원인 로그를 보강한다
|
||||||
|
- `/messages` 경로에서 `provider=naverworks` 처리 시 다음 단계 로그를 남긴다.
|
||||||
|
- DB 토큰 조회 시작/종료
|
||||||
|
- 만료 판단 결과
|
||||||
|
- refresh 호출 여부와 응답 코드
|
||||||
|
- NAVER WORKS 외부 API 호출 시작/종료와 소요시간
|
||||||
|
- 로그는 성공/실패 모두 같은 요청 흐름에서 이어서 대조 가능해야 한다.
|
||||||
|
|
||||||
|
### 3. `expires_at` 정합성 위험을 코드와 데이터 기준으로 판단한다
|
||||||
|
- 현재 `naverworks_token.expires_at`가 `timestamp without time zone`인 상태를 전제로, 실제 만료 판단이 어떤 시간대 기준으로 이뤄지는지 명시한다.
|
||||||
|
- `skill-email`의 만료 비교 시각과 DB 기록 시각이 같은 시간대 기준으로 비교되도록 정리한다.
|
||||||
|
- 선택지는 둘 중 하나로 고정한다.
|
||||||
|
- 컬럼 타입을 `timestamptz`로 승격
|
||||||
|
- 비교 전후를 모두 명시 timezone 기준으로 변환
|
||||||
|
- 이 단계는 폴백 추가가 아니라, 판단 기준을 하나로 고정하는 작업이어야 한다.
|
||||||
|
|
||||||
|
### 4. 재현 검증으로 닫는다
|
||||||
|
- 정상 케이스:
|
||||||
|
- 현재와 같은 `/messages` 요청이 정상 응답하고 브리핑이 실제 전송되는지 확인한다.
|
||||||
|
- 실패 케이스:
|
||||||
|
- 내부적으로 timeout 또는 비정상 응답을 재현했을 때 스케줄러가 실패로 남는지 확인한다.
|
||||||
|
- 토큰 만료 케이스:
|
||||||
|
- 만료 상태에서 refresh가 정상 수행되면 성공으로 이어지고, refresh 실패 시 원인성 실패로 남는지 확인한다.
|
||||||
|
|
||||||
|
## 검증 계획
|
||||||
|
|
||||||
|
### 1. 코드 검증
|
||||||
|
- `rb8001`에 `return []` 기반 실패 은닉 경로가 남아 있지 않은지 확인한다.
|
||||||
|
- `skill-email` 로그가 단계별로 남는지 확인한다.
|
||||||
|
|
||||||
|
### 2. 로그 검증
|
||||||
|
- 성공 케이스에서는 `메일 0건`과 `조회 성공 0건`이 구분된다.
|
||||||
|
- 실패 케이스에서는 `No emails` 대신 `timeout`, `refresh failed`, `works api failed` 같은 원인 로그가 남는다.
|
||||||
|
- 스케줄러 로그에는 실패 시 `completed successfully`가 남지 않는다.
|
||||||
|
|
||||||
|
### 3. 실행 검증
|
||||||
|
- `rb8001`에서 수동 브리핑 실행 시 정상 메일 조회와 Slack 전송이 통과한다.
|
||||||
|
- `/messages` 단건 호출로 성공/실패 응답 시간이 확인된다.
|
||||||
|
- `auth-server /refresh`와 `skill-email /messages`를 같은 시각대에 대조해 병목 위치를 구분할 수 있다.
|
||||||
|
|
||||||
|
## 완료 조건
|
||||||
|
- 네이버 이메일 조회 실패가 더 이상 `No emails`로 표시되지 않는다.
|
||||||
|
- 9시 브리핑 실패 시 스케줄러와 로그가 모두 실패로 기록된다.
|
||||||
|
- `skill-email` 로그만으로도 `DB/refresh/외부 API` 중 어느 단계에서 지연됐는지 구분 가능하다.
|
||||||
|
- `expires_at` 만료 판단 기준이 시간대 혼선 없이 하나로 설명 가능하다.
|
||||||
|
|
||||||
81
journey/research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md
Normal file
81
journey/research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
tags: [infra, robeing, rb8001, skill-email, naverworks, research]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 260309 9시 네이버 이메일 분석 미전송 실패 은닉 리서치
|
||||||
|
|
||||||
|
## 상위 원칙
|
||||||
|
- [Infra Project Identity](../../00_Philosophy/00_IDENTITY/Infra_Project_Identity.md)
|
||||||
|
- [Core Infrastructure Principles](../../00_Philosophy/01_PRINCIPLES/Core_Infrastructure_Principles.md)
|
||||||
|
- [Operational Guardrails](../../00_Philosophy/02_GUARDRAILS/Operational_Guardrails.md)
|
||||||
|
- 공통 작성 원칙: [0_VALUE Writing Principles](https://github.com/happybell80/0_VALUE/blob/main/02_Governance/writing-principles.md)
|
||||||
|
|
||||||
|
## 관련 문서
|
||||||
|
- [Infra Journey](../README.md)
|
||||||
|
- [9시 네이버 이메일 분석 미전송과 실패 은닉 이슈](../troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_이슈.md)
|
||||||
|
- [24서버 실서비스 운영전환 리서치](./260309_24서버_실서비스운영전환_리서치.md)
|
||||||
|
- [51123 구 IP 하드코딩 실행 경로와 런타임 SSOT 불일치 리서치](./260309_51123_구IP하드코딩_실행경로_SSOT불일치_리서치.md)
|
||||||
|
|
||||||
|
## 목적
|
||||||
|
- 오늘 09:00 네이버 이메일 분석 미전송의 원인을 `스케줄 문제`와 `실행 실패 은닉 문제`로 분리해 특정합니다.
|
||||||
|
- 실제 실패 지점이 `rb8001`, `skill-email`, `auth-server`, `NAVER WORKS 외부 API` 중 어디인지 좁힙니다.
|
||||||
|
- 이후 계획 문서가 우회 없이 원인 경로를 직접 수정할 수 있도록 실패 표현 구조를 분해합니다.
|
||||||
|
|
||||||
|
## Facts
|
||||||
|
|
||||||
|
### 1. 스케줄 등록과 실행은 정상이다
|
||||||
|
- `rb8001` 로그에는 `2026-03-09 09:00:00`에 `naverworks_daily`가 실행된 기록이 있다.
|
||||||
|
- 같은 시각 활성 잡 목록에도 `naverworks_daily`가 포함된다.
|
||||||
|
- DB `scheduled_jobs` 테이블에도 `name=naverworks_daily`, `job_type=naverworks_briefing`, `cron_expression=0 9 * * mon-fri`, `enabled=true`가 존재한다.
|
||||||
|
|
||||||
|
### 2. 실패는 `rb8001`의 메일 조회 단계에서 발생했다
|
||||||
|
- `rb8001/app/services/skills/naverworks_briefing.py`는 `process_briefing()`에서 `_fetch_recent_emails()`를 먼저 호출한다.
|
||||||
|
- 실제 09:00 로그에는 메일 조회 파라미터가 기록된 뒤 `09:00:30`에 `httpx.ReadTimeout` 예외가 남아 있다.
|
||||||
|
- 예외는 `http://localhost:8501/messages` 요청에서 발생했으며, 이 요청은 `skill-email` 컨테이너를 향한다.
|
||||||
|
|
||||||
|
### 3. 현재 구현은 실패와 0건을 구분하지 않는다
|
||||||
|
- [naverworks_briefing.py](https://github.com/happybell80/rb8001/blob/main/app/services/skills/naverworks_briefing.py)에서 `_fetch_recent_emails()`는 `비200 응답`이면 `return []`, 예외가 나도 `return []`를 반환한다.
|
||||||
|
- 같은 함수의 호출부 `process_briefing()`는 `if not emails:` 분기에서 모두 `No emails in the last 24 hours`로 처리한다.
|
||||||
|
- 이 구조 때문에 `타임아웃`, `토큰 갱신 실패`, `실제 메일 없음`이 동일한 결과값으로 합쳐진다.
|
||||||
|
|
||||||
|
### 4. `skill-email`의 NAVER WORKS 조회 경로에는 토큰 만료와 외부 API 호출이 포함된다
|
||||||
|
- `skill-email/services/naverworks_provider.py`는 `_get_account_context()`에서 DB `naverworks_token`을 읽고, `expires_at < now`이면 `auth-server /auth/naverworks/passport/refresh`를 호출한다.
|
||||||
|
- 그 뒤 `list_messages()`에서 `https://www.worksapis.com/v1.0/users/{account_id}/mail/mailfolders/0/children`를 호출해 실제 메일 목록을 가져온다.
|
||||||
|
- 즉 조회 경로에는 `DB -> auth-server refresh -> NAVER WORKS 외부 API` 단계가 포함된다.
|
||||||
|
|
||||||
|
### 5. 당시 토큰은 실제로 만료돼 있었다
|
||||||
|
- `main_db.naverworks_token` 조회 결과, 해당 사용자 `3550cef6-63e1-4ceb-8802-a25c9d1c6917`의 `expires_at`는 09시 조사 시점 기준 `2026-03-05`였다.
|
||||||
|
- 따라서 09:00 요청은 `skill-email` 내부에서 토큰 갱신 경로를 먼저 탔을 가능성이 높다.
|
||||||
|
- 수동 갱신 후 현재 `expires_at`가 `2026-03-09 13:59:37`로 바뀐 것도 확인됐다.
|
||||||
|
|
||||||
|
### 6. 현재 시점에는 같은 요청이 정상 응답한다
|
||||||
|
- 같은 파라미터로 `127.0.0.1:8501/messages`를 호출했을 때 `HTTP 200`, `0.612889초`로 응답했다.
|
||||||
|
- `127.0.0.1:9000/auth/naverworks/passport/refresh` 수동 호출도 `HTTP 200`, `0.233118초`로 성공했다.
|
||||||
|
- `127.0.0.1:9000/health`도 `HTTP 200`으로 정상이다.
|
||||||
|
|
||||||
|
### 7. 만료 판단 경로에는 시간대와 컬럼 타입 취약성이 있다
|
||||||
|
- `rb8001` 컨테이너 시간대는 `KST +0900`이고, `skill-email`, `auth-server` 컨테이너 시간대는 `UTC +0000`이다.
|
||||||
|
- `main_db.naverworks_token.expires_at` 컬럼 타입은 `timestamp without time zone`이다.
|
||||||
|
- 검증용으로 호스트 SQL에서 `expires_at = NOW() - INTERVAL '10 minutes'`를 기록한 뒤 `/messages`를 다시 호출했을 때, 요청은 `HTTP 200`, `0.253145초`로 바로 끝났고 같은 시각 `auth-server`에는 refresh 요청 로그가 남지 않았다.
|
||||||
|
- 즉 `expires_at` 만료 판단은 기록 시점의 시간대 해석에 따라 흔들릴 수 있는 취약한 구조다.
|
||||||
|
|
||||||
|
### 8. 현재 근거만으로는 외부 NAVER WORKS 지연과 내부 refresh 지연을 최종 분리할 수 없다
|
||||||
|
- 09:00 당시 `skill-email`과 `auth-server` 로그에는 요청 단계별 세부 로그가 충분히 남아 있지 않다.
|
||||||
|
- `skill-email` 09:00 구간 로그가 거의 비어 있어 `refresh`에서 멈췄는지, `worksapis` 조회에서 멈췄는지 단정할 직접 로그가 없다.
|
||||||
|
- 따라서 현재 확정 가능한 직접 원인은 `skill-email /messages read timeout`, 구조 원인은 `실패 은닉`, 보조 위험 요인은 `만료 토큰 상태`까지다.
|
||||||
|
|
||||||
|
## Interpretation
|
||||||
|
- 오늘 사건의 핵심 문제는 `9시 스케줄이 안 돌았다`가 아니라 `9시 스케줄은 돌았지만 실패가 성공처럼 기록됐다`는 점이다.
|
||||||
|
- 운영상 가장 먼저 고쳐야 할 부분은 `실패 원인 분리`이며, 그래야 그 다음에 `refresh 지연`인지 `NAVER WORKS 외부 API 지연`인지 재발 시 확정할 수 있다.
|
||||||
|
- 현재 구조는 `관측 불가능한 실패`를 만든다. 이는 상위 SSOT의 `Truth First`와 충돌한다.
|
||||||
|
- `expires_at`가 `timestamp without time zone`이고 컨테이너 시간대가 섞여 있는 점은, 오늘 사건의 직접 원인과 별개로 토큰 만료 판단을 불안정하게 만드는 구조 위험이다.
|
||||||
|
- 따라서 수정 우선순위는 `1. 실패 은닉 제거`, `2. 단계별 원인 로그 추가`, `3. expires_at 시간대 정합성 교정`, `4. 토큰 만료/외부 API 지연 재발 여부 관측` 순서가 맞다.
|
||||||
|
|
||||||
|
## Unresolved
|
||||||
|
- 09:00 시점 read timeout의 최종 블로킹 지점이 `auth-server refresh`인지 `NAVER WORKS 외부 API`인지는 직접 로그가 부족해 아직 확정하지 못했다.
|
||||||
|
- `skill-email`의 `/messages` 경로에서 단계별 소요시간 로그가 없어, 같은 증상이 재발해도 현재 구조만으로는 즉시 구분이 어렵다.
|
||||||
|
- `expires_at` 시간대 취약성이 실제 운영 경로에서도 재현되는지, 아니면 호스트 SQL 갱신 같은 예외 경로에서만 드러나는지도 별도 확인이 더 필요하다.
|
||||||
|
- 따라서 다음 계획 문서는 `실패 은닉 제거`와 함께 `단계별 원인 로그 보강`, `expires_at 타입/시간대 정합성 교정 여부 판단`을 포함해야 한다.
|
||||||
|
|
||||||
|
## 한 줄 결론
|
||||||
|
- 이번 사건의 직접 원인은 `rb8001 -> skill-email /messages` read timeout이고, 더 큰 구조 문제는 이 실패가 현재 코드에서 `No emails`로 은닉된다는 점이다.
|
||||||
70
journey/troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_이슈.md
Normal file
70
journey/troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_이슈.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
tags: [infra, robeing, rb8001, skill-email, naverworks, troubleshooting]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 9시 네이버 이메일 분석 미전송과 실패 은닉 이슈
|
||||||
|
|
||||||
|
## 상위 원칙
|
||||||
|
- [Infra Project Identity](../../00_Philosophy/00_IDENTITY/Infra_Project_Identity.md)
|
||||||
|
- [Core Infrastructure Principles](../../00_Philosophy/01_PRINCIPLES/Core_Infrastructure_Principles.md)
|
||||||
|
- [Operational Guardrails](../../00_Philosophy/02_GUARDRAILS/Operational_Guardrails.md)
|
||||||
|
- 공통 작성 원칙: [0_VALUE Writing Principles](https://github.com/happybell80/0_VALUE/blob/main/02_Governance/writing-principles.md)
|
||||||
|
|
||||||
|
## 관련 문서
|
||||||
|
- [Infra Journey](../README.md)
|
||||||
|
- [9시 네이버 이메일 분석 미전송 실패 은닉 리서치](../research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md)
|
||||||
|
- [24서버 실서비스 운영전환 리서치](../research/260309_24서버_실서비스운영전환_리서치.md)
|
||||||
|
- [51123 구 IP 하드코딩 실행 경로와 런타임 SSOT 불일치 리서치](../research/260309_51123_구IP하드코딩_실행경로_SSOT불일치_리서치.md)
|
||||||
|
|
||||||
|
## 문제 정의
|
||||||
|
- 2026-03-09 09:00 KST에 로빙의 NAVER WORKS(네이버웍스) 이메일 분석 브리핑이 전송되지 않았다.
|
||||||
|
- 그러나 스케줄러 로그에는 작업이 `completed successfully`로 남아 있어, 실제 실패가 성공처럼 보였다.
|
||||||
|
- 이번 이슈의 본질은 "스케줄 미실행"이 아니라 `rb8001 -> skill-email` 호출 실패가 `메일 없음`으로 은닉되는 실행 경로 문제다.
|
||||||
|
|
||||||
|
## 확인된 사실
|
||||||
|
|
||||||
|
### 1. 9시 스케줄 자체는 정상 실행됐다
|
||||||
|
- `rb8001` 로그에는 `2026-03-09 09:00:00`에 `naverworks_daily`가 실행된 기록이 있다.
|
||||||
|
- 같은 로그에 `Running job: NAVER WORKS Daily Briefing`와 `Starting NaverWorks briefing process for info@company-x.partners`가 남아 있다.
|
||||||
|
- 따라서 이번 사건은 스케줄 등록 누락이나 프로세스 다운으로 보이지 않는다.
|
||||||
|
|
||||||
|
### 2. 실제 실패 지점은 `skill-email /messages` 호출 타임아웃이다
|
||||||
|
- `rb8001` 로그에는 `2026-03-09 09:00:30` 시점 `httpx.ReadTimeout` 예외가 남아 있다.
|
||||||
|
- 실패가 발생한 호출은 `http://localhost:8501/messages`이며, 파라미터는 `provider=naverworks`, `searchDateType=read`, `startSearchDate=2026-03-06T09:00:00+09:00`, `endSearchDate=2026-03-09T09:00:00+09:00`, `limit=100`이다.
|
||||||
|
- 따라서 오늘 미전송의 직접 원인은 메일 조회 단계의 읽기 타임아웃이다.
|
||||||
|
|
||||||
|
### 3. 실패 후 작업은 성공처럼 기록됐다
|
||||||
|
- `rb8001/app/services/skills/naverworks_briefing.py`는 `_fetch_recent_emails()`에서 예외나 비정상 응답을 `return []`로 처리한다.
|
||||||
|
- 같은 파일 `process_briefing()`는 빈 리스트를 `No emails in the last 24 hours`로 처리하고 그대로 종료한다.
|
||||||
|
- 그 결과 스케줄러 로그에는 `NAVER WORKS Daily Briefing completed successfully`가 남는다.
|
||||||
|
|
||||||
|
### 4. 같은 요청은 현재 시점에는 정상 응답한다
|
||||||
|
- 같은 파라미터로 `http://127.0.0.1:8501/messages`를 다시 호출했을 때 `HTTP 200`, 총 응답시간 약 `0.61초`로 정상 응답했다.
|
||||||
|
- 따라서 현재는 상시 장애 상태가 아니라, 09:00 시점의 단발성 타임아웃 또는 외부 의존성 지연이 있었던 것으로 좁혀진다.
|
||||||
|
|
||||||
|
### 5. 당시 NAVER WORKS 토큰은 만료 상태였다
|
||||||
|
- `main_db.naverworks_token` 조회 결과, 해당 계정 토큰의 `expires_at`는 당시 `2026-03-05`로 남아 있었다.
|
||||||
|
- 이후 수동 토큰 갱신 호출은 `HTTP 200`, 약 `0.23초`에 성공했고, 현재 `expires_at`는 `2026-03-09 13:59:37`로 갱신됐다.
|
||||||
|
- 즉 9시 시점에는 토큰 만료 상태가 실제로 존재했다.
|
||||||
|
|
||||||
|
## 왜 문제인가
|
||||||
|
- 실패가 성공처럼 기록되면 운영자는 "메일이 없었다"고 오인하고 실제 장애를 놓치게 된다.
|
||||||
|
- 상위 SSOT의 `Truth First`, `광범위 예외 폴백 금지`, `근본 원인 직접 수정 우선` 원칙과 충돌한다.
|
||||||
|
- 특히 외부 의존성 지연, 토큰 만료, 내부 서비스 타임아웃이 다시 발생해도 같은 방식으로 재발할 수 있다.
|
||||||
|
|
||||||
|
## 직접 원인
|
||||||
|
- `rb8001`의 NAVER WORKS 브리핑 로직이 `skill-email` 조회 실패를 원인별 실패로 올리지 않고 빈 메일 목록으로 삼킨다.
|
||||||
|
|
||||||
|
## 근본 원인
|
||||||
|
- 브리핑 로직이 `메일이 실제로 0건인 경우`와 `메일 조회 자체가 실패한 경우`를 같은 값 `[]`로 표현하는 구조다.
|
||||||
|
|
||||||
|
## 우회가 아닌 해결 기준
|
||||||
|
- `timeout`, `토큰 갱신 실패`, `비200 응답`, `응답 구조 이상`을 `메일 0건`과 분리한다.
|
||||||
|
- 스케줄러 실행 결과는 실패면 실패로 기록되도록 올린다.
|
||||||
|
- 로그에는 `No emails`가 아니라 실제 실패 원인과 단계가 남아야 한다.
|
||||||
|
|
||||||
|
## 완료 판단 기준
|
||||||
|
- `rb8001` 9시 브리핑에서 `skill-email` 조회 실패 시 스케줄러가 실패로 기록된다.
|
||||||
|
- 실제 메일 0건일 때만 `No emails` 로그가 남는다.
|
||||||
|
- 토큰 만료, 외부 API 지연, 내부 타임아웃이 발생해도 각각의 원인이 로그에서 구분된다.
|
||||||
|
|
||||||
29
journey/worklog/260309_9시_네이버이메일분석_실패은닉_1차해결.md
Normal file
29
journey/worklog/260309_9시_네이버이메일분석_실패은닉_1차해결.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
tags: [infra, robeing, rb8001, skill-email, naverworks, worklog]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 260309 9시 네이버 이메일 분석 실패 은닉 1차 해결
|
||||||
|
|
||||||
|
## 관련 문서
|
||||||
|
- [9시 네이버 이메일 분석 미전송과 실패 은닉 이슈](../troubleshooting/260309_9시_네이버이메일분석_미전송_실패은닉_이슈.md)
|
||||||
|
- [9시 네이버 이메일 분석 미전송 실패 은닉 리서치](../research/260309_9시_네이버이메일분석_미전송_실패은닉_리서치.md)
|
||||||
|
- [9시 네이버 이메일 분석 실패 은닉 해결 계획](../plans/260309_9시_네이버이메일분석_실패은닉_해결계획.md)
|
||||||
|
|
||||||
|
## 작업
|
||||||
|
|
||||||
|
- `rb8001/app/services/skills/naverworks_briefing.py`에서 `skill-email` 조회 실패를 `[]`로 삼키지 않고 예외로 올리도록 교정했습니다.
|
||||||
|
- `rb8001/app/scheduler/jobs/naverworks_briefing.py`에서 스케줄러 래퍼가 실패를 다시 올리도록 바꿔, APScheduler가 실패를 성공처럼 기록하지 않게 했습니다.
|
||||||
|
- `skill-email/services/naverworks_provider.py`에 `context lookup -> token state -> refresh -> NAVER WORKS API` 단계별 로그와 소요시간 로그를 추가하고, `expires_at` 비교 기준을 명시적인 UTC naive 시각으로 고정했습니다.
|
||||||
|
- `rb8001/tests/test_naverworks_briefing.py`에 실패 전파와 스케줄러 래퍼 예외 전파 테스트를 추가했습니다.
|
||||||
|
|
||||||
|
## 검증
|
||||||
|
|
||||||
|
- `docker exec -e PYTHONPATH=/code rb8001 pytest -q tests/test_naverworks_briefing.py` 결과 `10 passed`였습니다.
|
||||||
|
- `curl http://127.0.0.1:8001/health`와 `curl http://127.0.0.1:8501/health`는 모두 `HTTP 200`이었습니다.
|
||||||
|
- `curl http://127.0.0.1:8501/messages?...provider=naverworks...`는 `HTTP 200`, 약 `0.35초`에 정상 응답했습니다.
|
||||||
|
- `skill-email` 로그에는 `NAVER WORKS context lookup started`, `token state`, `API request started/finished`, `elapsed_ms`가 실제로 남는 것을 확인했습니다.
|
||||||
|
|
||||||
|
## 한 줄 결론
|
||||||
|
|
||||||
|
- 네이버 이메일 분석 실패가 더 이상 `No emails`로 은닉되지 않도록 `rb8001`과 `skill-email` 실행 경로를 교정했고, 테스트와 실응답, 로그로 1차 검증했습니다.
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user