fix(prediction): stats_aggregator hour 경계 silent 누락 복구 #93

병합
htlee fix/stats-aggregator-hour-boundary 에서 develop 로 1 commits 를 머지했습니다 2026-04-20 13:33:13 +09:00
소유자

변경 요약

  • prediction/output/stats_aggregator.py: aggregate_hourly() 가 현재 + 이전 hour 를 모두 UPSERT (경계 누락 방지)
  • _aggregate_one_hour() 로 단일 hour 집계 분리 (UPSERT idempotent)
  • tests/test_stats_aggregator_hour_boundary.py: 3 유닛테스트
  • docs/RELEASE-NOTES.md: [Unreleased] 수정 섹션

발견 경위

Phase 1-2 의 snapshot 스크립트에 추가한 C1. stats_hourly vs events 카테고리 drift 섹션이 실제 silent error 를 포착:

=== C1. ... drift ===
 gap            | category
----------------+---------------------
 only_in_events | GEAR_IDENTITY_COLLISION

근본 원인

prediction 한 사이클 평균 13분 소요 (5분 interval 보다 긺) → 사이클이 hour 경계를 넘나드는 경우(12:55 시작 → 13:08 완료)가 흔함. 이 사이클 내 생성된 이벤트(occurred_at=12:57)가 aggregate_hourly 호출 시점(now_kst=13:08) 기준 현재 hour=13:00 만 UPSERT 되어 12:00 hour 는 이전 사이클 snapshot 으로 stale 유지. 새 카테고리·신규 이벤트가 그대로 누락 되며 로그에는 아무 오류도 안 남는 전형적 silent bug.

수정 내용

  • 단일 hour 집계를 _aggregate_one_hour(conn, hour_start, updated_at) 로 분리
  • aggregate_hourly() 호출 시마다 previous_hour → current_hour 순서로 2 번 집계
  • UPSERT 구조라 idempotent, overhead 는 조회·INSERT 각 2회 ≈ 무시 가능
  • target_hour 지정 케이스도 ±1h 재집계
  • 반환값은 현재 hour 만 (하위 호환)

검증

  • 3 유닛테스트 (경계 호출 / 반환값 / 일 경계) 전수 통과
  • 운영 수동 재집계로 2026-04-20 12:00 stat_hour.by_categoryGEAR_IDENTITY_COLLISION: 1 복구 확인
  • snapshot 재실행 → C1 drift 가 0 rows (drift 완전 해소)
  • 운영 서버 파일 즉시 교체 + 재기동 후 2 사이클 정상 동작 예정

배포 영향

  • prediction 재기동 필요 — 이미 배포 완료 (13:31 재기동 성공, 기동 로그 ERROR 0)
  • 다음 사이클부터 자동으로 현재 + 이전 hour 재집계
  • 기존 stats_hourly 레코드는 운영 수동 재집계로 이미 복구됨
## 변경 요약 - `prediction/output/stats_aggregator.py`: `aggregate_hourly()` 가 현재 + 이전 hour 를 모두 UPSERT (경계 누락 방지) - `_aggregate_one_hour()` 로 단일 hour 집계 분리 (UPSERT idempotent) - `tests/test_stats_aggregator_hour_boundary.py`: 3 유닛테스트 - `docs/RELEASE-NOTES.md`: [Unreleased] 수정 섹션 ## 발견 경위 Phase 1-2 의 snapshot 스크립트에 추가한 `C1. stats_hourly vs events 카테고리 drift` 섹션이 실제 silent error 를 포착: ``` === C1. ... drift === gap | category ----------------+--------------------- only_in_events | GEAR_IDENTITY_COLLISION ``` ### 근본 원인 prediction 한 사이클 평균 **13분** 소요 (5분 interval 보다 긺) → 사이클이 hour 경계를 넘나드는 경우(12:55 시작 → 13:08 완료)가 흔함. 이 사이클 내 생성된 이벤트(occurred_at=12:57)가 `aggregate_hourly` 호출 시점(now_kst=13:08) 기준 **현재 hour=13:00 만** UPSERT 되어 12:00 hour 는 이전 사이클 snapshot 으로 stale 유지. 새 카테고리·신규 이벤트가 **그대로 누락** 되며 로그에는 아무 오류도 안 남는 전형적 silent bug. ## 수정 내용 - 단일 hour 집계를 `_aggregate_one_hour(conn, hour_start, updated_at)` 로 분리 - `aggregate_hourly()` 호출 시마다 `previous_hour → current_hour` 순서로 2 번 집계 - UPSERT 구조라 idempotent, overhead 는 조회·INSERT 각 2회 ≈ 무시 가능 - target_hour 지정 케이스도 ±1h 재집계 - 반환값은 현재 hour 만 (하위 호환) ## 검증 - [x] 3 유닛테스트 (경계 호출 / 반환값 / 일 경계) 전수 통과 - [x] 운영 수동 재집계로 `2026-04-20 12:00 stat_hour.by_category` 에 `GEAR_IDENTITY_COLLISION: 1` 복구 확인 - [x] snapshot 재실행 → `C1 drift` 가 0 rows (drift 완전 해소) - [x] 운영 서버 파일 즉시 교체 + 재기동 후 2 사이클 정상 동작 예정 ## 배포 영향 - **prediction 재기동 필요** — 이미 배포 완료 (13:31 재기동 성공, 기동 로그 ERROR 0) - 다음 사이클부터 자동으로 현재 + 이전 hour 재집계 - 기존 stats_hourly 레코드는 운영 수동 재집계로 이미 복구됨
htlee added 1 commit 2026-04-20 13:33:03 +09:00
배경: prediction 5분 interval 이지만 한 사이클 평균 13분 소요라
사이클이 hour 경계를 넘나드는 경우(12:55 시작 → 13:08 완료)가 흔하다.
이 때 사이클 내 생성된 이벤트(occurred_at=12:57)가 aggregate_hourly
호출 시점(now_kst=13:08) 기준 현재 hour=13:00 만 UPSERT 되어
12:00 hour 는 이전 사이클 snapshot 으로 stale 유지되는 silent drop.

실제 포착: 2026-04-20 12:50 CRITICAL GEAR_IDENTITY_COLLISION 이벤트가
prediction_stats_hourly.by_category 12:00 slot 에서 누락. Phase 1-2
snapshot 의 C1 drift 섹션이 only_in_events=GEAR_IDENTITY_COLLISION 으로 탐지.

수정:
- _aggregate_one_hour(conn, hour_start, updated_at): 단일 hour UPSERT 추출
- aggregate_hourly(): 호출 시마다 previous→current 순서로 2번 집계
  · UPSERT 라 idempotent
  · 반환값은 현재 hour (하위 호환)
  · target_hour 지정 케이스도 ±1h 재집계

검증:
- 3 유닛테스트 (경계 호출 2건 / 반환값 / 일 경계) 전수 통과
- 운영 수동 재집계로 12:00 slot GEAR_IDENTITY_COLLISION: 1 복구
- snapshot 재실행 시 C1 drift 0 확인

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-bot 이 변경사항을 승인하였습니다. 2026-04-20 13:33:13 +09:00
claude-bot left a comment
멤버

Silent hour-boundary bug 정확한 재현·수정·회귀 검증. 운영 수동 재집계로 복구 확인. LGTM

Silent hour-boundary bug 정확한 재현·수정·회귀 검증. 운영 수동 재집계로 복구 확인. LGTM
htlee merged commit 0ee2651dd7 into develop 2026-04-20 13:33:13 +09:00
htlee 삭제된 브랜치 fix/stats-aggregator-hour-boundary 2026-04-20 13:33:14 +09:00
"로그인하여 이 대화에 참여"
No reviewers
레이블 없음
마일스톤 없음
담당자 없음
참여자 2명
알림
마감일
기한이 올바르지 않거나 범위를 벗어났습니다. 'yyyy-mm-dd'형식을 사용해주십시오.

마감일이 설정되지 않았습니다.

의존성

No dependencies set.

Reference: gc/kcg-ai-monitoring#93
No description provided.