o2o-site-AEO/docs/ALERTS.md
Mina Choi 87343c791b [feat] solution/backend,frontend,site: 수집·사진 분석·발행 진행을 Teams 로 발송 — 수집 중 배포에도 사진 목록 복구
컨테이너를 재생성하면 docker logs 가 사라져 크롤링이 잘 됐는지 확인할 방법이 없었다.
실측(09-30 버터브루): 수집 도중 배포로 API 가 80초 끊기자 화면이 사진 분석 폴링을
3회 실패 후 포기해 사진 10장이 DB 에 있는데도 0장으로 보였다.

- services/activity_feed.py: 기존 alert_outbox·장애 채널로 kind=activity 이벤트 적재(중복 억제 없음)
- collect_service: 수집 시작 · 채널 크롤링 실패 즉시 · 완료 요약(채널별·fact·사진·누락 필수항목·소요초) · 실패
- vision_service: 사진 분석 결과
- build_service · rollback_service: 첫 발행/재발행/빌드만/되돌리기 시작·끝 — URL · 굽기 사진 미러링 수
- site/prerender.ts: 렌더 보고서에 사진 미러링 수(media) 추가
- frontend pollJob: 연속 3회 실패여도 3분간 무응답일 때만 unreachable
- frontend collectJobs: 사진 분석 폴링을 못 끝내도 사진 목록을 다시 읽고 경고
- test_build_publish: 게이트 반려 검사에서 activity 이벤트는 제외

관련 테스트 17개 파일 295 passed · 2 failed(test_search_console_service — 변경 전에도 실패)
frontend tsc·eslint, site tsc·eslint 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:09:52 +09:00

4.0 KiB

장애 알림 (2026-09-15)

구현: services/alert_service.py(적재·재시도·중복 억제) · services/teams_webhook.py(전송) · worker/runner.py · services/build_service.py · services/rollback_service.py(발생 지점) · scheduler/jobs.py(발송·큐 정체 스윕). 전용 컨테이너 없음 — 기존 API·워커 프로세스가 한다.

무엇을 알리나

kind 언제 dedupe_key
job_dead 잡이 재시도를 소진해 DEAD job_dead:{JobType}:{place_id 또는 job_id}
build_failed BUILD·ROLLBACK 이 게이트 반려가 아닌 렌더·인프라 실패로 끝남 build_failed:{place_id}
partial_failure 노래 등 곁가지 생성 실패(발행 자체는 계속) song_failed:{place_id}
queue_stuck dead-letter 누적·좀비 실행·PENDING 30분 이상 정체 queue_health
recovery 위 dedupe_key 가 다음 정상 상태에서 풀릴 때 한 번 없음(매번 새 행)
activity 수집 시작·채널 실패·완료 · 사진 분석 · 첫 발행/재발행/빌드/되돌리기 시작·끝(services/activity_feed.py) 없음(매번 보낸다)

★ 게이트 반려는 알리지 않는다. 사장님이 fact 를 안 채웠거나 고유 콘텐츠가 없어서 막힌 건 운영자가 손댈 일이 아니다 — build_service._fail(reason, gate=None) 일 때만 build_failed.

중복 억제·재시도

send_alert(kind, title, detail, dedupe_key) — 같은 dedupe_key 로 "안 풀린"(resolved_at NULL) 알림이 이미 있으면 새로 만들지 않는다. resolve_alert(dedupe_key, ...) 가 그 알림을 풀고 복구 알림을 한 번 보낸다. 실제 전송은 scheduler.jobs.sweep_alert_outbox(1분마다) — 실패하면 crud/job_crud.compute_backoff 와 같은 백오프로 최대 5회 재시도 후 FAILED(소진)로 멈춘다. TEAMS_WEBHOOK_URL 이 비어 있으면 적재만 되고 전송은 안 나간다(서버 동작엔 영향 없음).

detail 은 저장 전에 alert_service._scrub 이 쿼리스트링 키·Bearer 토큰·password= 류· 이메일을 마스킹한다 — 외부 API 예외 메시지가 URL 에 키를 실어 보내는 경우가 있다.

설정

TEAMS_WEBHOOK_URL=       # Teams Workflows 수신 webhook. 비우면 알림이 DB(alert_outbox)에
                         # 쌓이기만 하고 안 나간다 — 서버는 그대로 뜬다.
ALERT_DEDUPE_WINDOW_MIN=60

GSC_ALERT_WEBHOOK_URL(search_console_alerts.py)과는 다른 값이다 — 색인 감시 전용과 이 잡 큐·발행 알림은 목적이 달라 의도적으로 분리했다(services/teams_webhook.py 머리주석).

서버·DB 전체 장애 — 이 알림 체계로는 못 잡는다

alert_service·scheduler가 도는 프로세스 자체가 죽으면(서버 다운·DB 완전 단절) 이 체계는 자기 장애를 자기가 못 알린다. 외부 감시가 필요하다 — uptime 모니터 등에서 주기적으로 GET /readyz 를 찌른다(router/router.py). /healthz 와 다르다: /healthz 는 프로세스 생존만(항상 200), /readyz 는 DB 에 실제로 SELECT 1 을 던져 200/503 을 가른다.

절차:

  1. 외부 모니터가 https://<host>/readyz 를 1~5분 간격으로 확인한다.
  2. 2xx 가 아니거나 타임아웃이면 그 모니터 자신의 채널로 알린다 — 이 레포의 TEAMS_WEBHOOK_URL 로 보내면 안 된다(webhook 이 죽은 서버 안에 있을 수 있다).
  3. 이 모니터의 실제 설정(어느 서비스·어느 채널)은 이 세션에서 만들지 않았다 — 운영 계정· 외부 서비스 연결은 사용자 승인 후 진행한다.

아직 안 한 것 — 운영 미적용

  • 실제 Teams Workflows webhook 생성·채널 지정 — mock 테스트만 했다(tests/test_alert_service.py).
  • 외부 uptime 모니터 실제 연결(2절 3번).
  • 마이그레이션(0016_alert_outbox.sql) 서버 적용.
  • alert_outbox 오래된 SENT/FAILED 행 보관 정책(지금은 무기한 보관 — 운영 부하를 보고 정한다).