통계 정체 감지와 대응
#1432·BUG-084 PM-08. 대상은 v0.17.9이며, 구현·검증과 운영 활성화를 구분한다. 최신 반영 증거는 작업 기록에 남긴다.
감지하는 상태
운동 기록은 저장됐지만 통계가 계속 옛 값에 머무는 경우를 감지한다. cron.job_run_details의 성공은 worker 내부 성공을 보장하지 않는다. 과거 failed 행도 이미 더 높은 세대가 게시됐다면 현재 장애가 아니다.
scripts/monitor-stats.mjs는 요청 세대가 게시 세대보다 앞선 계정과 아직 필요한 작업을 함께 읽는다. 계정 식별자·운동 내용·SQL 오류 본문을 반환하지 않고 아래 건수만 출력한다.
| 경보 | 판정 |
|---|---|
delayed_owners | 미반영 계정의 요청 시각과 필요한 가장 오래된 미완료 작업 시각 중 이른 시점이 5분 초과. 연속 새 저장으로 요청 시각이 바뀌어도 오래된 정체를 숨기지 않음 |
unknown_age_owners | 미반영 계정인데 시작 시각을 확인할 수 없음 |
exhausted_owners | 아직 필요한 작업이 3회 이상 실패했고 이를 대신할 같은/높은 세대의 pending·processing 작업도 없음 |
unattempted_jobs | 필요한 pending 작업이 5분 넘게 attempts=0 |
expired_leases | 필요한 claimed·computed 작업의 임대 만료 후 1분이 지나도 회수되지 않음 |
orphaned_jobs | 5분 넘게 processing인데 유효한 claimed·computed 임대가 없음 |
inactive_cron | claim·compute·publish 예약 중 하나라도 없거나 중지됨 |
waiting_writes | 현재 실행 시간이 2초 이상인 저장·삭제·기존 수정 RPC가 DB 잠금에 대기 중 |
owners와 stale_owners는 참고 수치다. 새로 저장한 직후의 정상 반영 대기는 실패로 판정하지 않는다. 필요한 작업은 게시 세대보다 높고 요청 세대 이하인 미완료 작업이며, 이미 정착한 세대의 실패·임대 이력은 제외한다. 후속 작업이 진행 중이라 시도 소진 경보를 제외하더라도, 오래된 미반영 경보는 유지한다.
실행과 알림
앱 저장소의 Stats health 워크플로(.github/workflows/stats-health.yml)는 main 반영 후 매시 7·22·37·52분과 수동 실행으로 동작한다. GitHub 예약은 지연될 수 있어 탐지 시간의 상한을 보장하지 않는다. 15분 간격은 하루 96회 짧은 Actions 실행에 해당한다.
- 기존
SUPABASE_ACCESS_TOKEN과PROD_SUPABASE_PROJECT_REFsecret을 사용한다. 설치나 앱 빌드는 실행하지 않는다. - Supabase Management API를 한 번,
read_only: true, HTTP 요청 10초 제한으로 호출한다. 데이터·job·cron·통계값을 수정하거나 재등록하지 않는다. - 비정상 건수가 있으면 프로세스 exit 1과 Actions 실패가 된다. API·자격증명 오류, 잘못된 집계 응답도 실패이며 정상 0건이나 skip으로 바꾸지 않는다.
- 알림 전달은 사용자의 GitHub Actions 알림 설정을 따른다. 별도 이메일·Slack 발송은 없으며 실제 메시지 수신 여부를 코드 검사 통과로 주장하지 않는다.
- 정상/비정상 집계는
stats-health-<run_id>artifact로 7일 보관한다. API 호출 자체가 실패하면 정상 집계 artifact를 만들지 않는다. 잡 전체 상한은 2분이다.
로컬에서 권한 있는 환경변수 SUPABASE_ACCESS_TOKEN, SUPABASE_PROJECT_REF를 주입한 뒤 아래 명령을 쓴다. 토큰을 CLI 인수나 로그에 넣지 않는다.
node scripts/monitor-stats.mjs --out work/stats-health.json경보가 발생했을 때
- 실패한 실행의 집계와 시각을 확인한다. HTTP·응답 오류면 조회 실패와 통계 장애를 구분한다.
- 지연·시도 소진·미회수 경보면 권한 있는 읽기 전용 조회로 해당 세대와 job/run의 오류 코드·실제 compute/publish 경과 시간을 확인한다. cron의 succeeded만으로 정상으로 판단하지 않는다.
- 저장 잠금 대기는 관측 순간의 표본이다. 차단 연결과 실제 실행 경로를 확인한다. 요청 실행 2초는 실제 잠금 대기 2초를 뜻하지 않으며, 표본에 없었다고 과거 저장 지연이 없었다고 판단하지 않는다.
- 원인을 확인한 담당자가 승인된 worker 복구 절차를 적용한다. 이 검사를 통과시키기 위해 applied_version을 올리거나 failed 이력을 지우거나 무한 재등록하지 않는다. D08의 설정·lease·재개 절차를 따른다.
- 다음 읽기 전용 검사에서 경보 해소를 확인한다. 알림만 사라진 것과 실제 요청 세대=게시 세대·읽기 응답 회복을 구분한다.
검증과 한계
단위 15건과 실제 PostgreSQL 회귀 2건을 남겼다. 독립 DB에서 과거 실패, 정상 대기, 오래된 attempts=0, 시도 소진, 후속 재처리, 만료·유효 임대, cron 누락·중지를 만들었다. 별도 연결로 쓰기를 실제 행 잠금에 대기시켜 경보 발생과 해제 후 정상 복귀를 확인했다. 원본 샌드박스 DB는 수정하지 않고 각 테스트 DB의 삭제를 검증했다.
DB 회귀는 앱 precheck와 hosted DB 단계에서 실행한다. 동시 저장·큰 이력 수렴 검사를 대체하지 않는다. 현재 표본의 진행 상태를 감지하는 도구이며, 모든 계산 비용·강제 종료·세대 게시의 정합성을 인증하는 도구는 아니다. D08·D11의 구조 수리는 별도다.