Skip to content

통계 worker 운영 runbook — 예산 바꾸기·일시정지·소진 잡 재개 (D08 #1412)

한 문장 — 통계 worker(claim → compute → publish, 1초마다 도는 예약 작업 셋)의 예산·정지·재개를 어떻게 조작하는지, 그리고 대기·실패를 어떻게 읽는지 적은 운영 문서다. 계약 정본 = Stats Refresh Jobs D08 절, 통계 투영 정책 §6.

모든 조작은 service_role(또는 관리자 대리) 권한에서 한다. 앱 역할(anon·authenticated)은 설정 표·worker 함수에 접근할 수 없다.

1. 지금 상태 보기

sql
select public.stats_projection_worker_status_v1();

반환: 설정 행, 대기(pending 유저·잡·가장 오래된 대기 초), 시도 소진 실패 잡, stale 유저 수, 임대(활성·만료 미회수·backoff 대기·활성 목록), 최근 1시간 완료/실패/실패 코드/계산·게시 시간(p95·max). 배포된 함수는 Edge GET /stats-process-refresh-jobs 에 bearer 토큰을 주면 worker_status 로도 나온다.

읽는 법:

  • leases.expired_unrecovered 가 계속 0 이 아니면 → cron 이 멈췄거나 claim 이 만료 회수를 못 하고 있다(§4).
  • queue.exhausted_failed_jobs 가 0 이 아니면 → 시도 상한(기본 3)을 다 쓴 잡이 dirty 를 남긴 채 멈췄다(§3 재개).
  • last_hour.publish_ms_max 가 게시 예산에 근접하면 → 예산을 올릴지 판단(§2).

2. 예산 바꾸기

예산은 설정 표 한 행이 정본이다. cron 명령·함수·게이트가 모두 이 행을 읽으므로, 값 하나만 바꾸면 세 곳이 함께 바뀐다. cron 명령 문자열을 손으로 고치지 않는다(그 방식은 마이그레이션 재적용 때 되돌아가 #1432 를 냈다).

sql
-- 예: 게시 예산을 6초 → 7초로(저장 RPC 8초보다 작아야 한다는 CHECK 안에서)
update public.stats_projection_worker_settings
set publish_timeout_ms = 7000, note = '사유·이슈번호', updated_at = now()
where id = 1;

CHECK 로 막히는 것: publish_timeout_ms < 8000(저장 RPC 상한보다 작다), lease_seconds*1000 > compute_timeout_ms + publish_timeout_ms(임대가 계산+게시를 덮는다), 각 열의 하한·상한. 위반하면 23514 로 거부된다.

바꾼 뒤 다음 cron 실행(최대 1초)부터 새 예산이 적용된다. 되돌리려면 같은 UPDATE 로 기본값을 다시 넣는다(claim 3000·compute 60000·publish 6000·lease 300·active 2·attempts 3·backoff 30).

주의: 게시 예산을 저장 지연 없이 3초로 되돌리는 것은 이 트랙 범위가 아니다 — 대표 workload 로 변경행 게시(delta publish)를 검증한 뒤(D11) 재실측한다.

3. 시도 소진 잡 재개

계산/게시가 세 번(설정 max_attempts) 다 실패하면 잡은 failed 로 남고 dirty 가 보존된다(성공으로 지우지 않는다). 원인을 고친 뒤 다시 돌리려면 그 잡의 시도 횟수를 되돌리고 pending 으로 바꾼다.

sql
-- 대상 유저의 소진 실패 잡을 다시 pending 으로(backoff·시도 초기화)
update public.user_exercise_stats_refresh_jobs
set status = 'pending', attempts = 0, error_message = '', updated_at = now()
where user_id = '<uuid>' and status = 'failed' and absorbed_into_job_id is null;

update public.user_stats_projection_runs
set retry_not_before = null
where user_id = '<uuid>' and phase = 'failed';

원본을 만지지 않는다(원본 불변 §2) — 위 UPDATE 는 잡 큐·임대 행(시스템 표)만 건드린다. 다음 cron 이 이 잡을 다시 집는다. 반복 실패하면 원인(계산 초과·원본 참조 깨짐 23503·순서 방어 55000)을 로그·상태 함수의 failed_by_code 로 확인한다.

4. 일시정지·재개

worker 를 잠깐 멈추려면 예약 작업 셋을 비활성화한다(같은 명령이 D01 격리에도 쓰인다).

sql
-- 정지
select cron.alter_job(jobid, active := false)
from cron.job
where jobname in ('lift-guild-stats-refresh', 'barbelic-stats-projection-compute', 'barbelic-stats-projection-publish');

-- 재개
select cron.alter_job(jobid, active := true)
from cron.job
where jobname in ('lift-guild-stats-refresh', 'barbelic-stats-projection-compute', 'barbelic-stats-projection-publish');

active := false이미 시작된 실행을 취소하지 않는다 — 그 실행이 끝난 뒤 다음 주기부터 멈춘다. 정지 중에는 enqueue 만 쌓이고(요청 세대는 올라가고 applied 는 그대로) 화면은 stale 을 표시한다. 재개하면 claim 이 가장 낮은 세대부터 흡수·처리한다.

만료된 임대가 회수되지 않으면(정지 중 임대가 만료된 경우 포함), 재개 뒤 첫 claim 이 만료 회수(backoff 없음)로 그 잡을 다시 집는다. 살아 있는 worker 가 있으면 그 행 잠금 때문에 회수하지 않는다.

5. 관측·경보 연결

  • cron 실행 기록은 barbelic-stats-cron-history-purge 가 10분마다 7일 넘은 것만 지운다(잡·원본·투영은 안 지운다).
  • G04 릴리스 예산 계약(tests/react/resourceContract.test.mjs)이 설정 표 기본값·저장 RPC 상한·cron 명령이 설정 함수를 읽는지 대조한다. 예산 기본값을 바꾸면 이 계약과 자원 계약 표를 함께 갱신한다.
  • 실측 예산(대표 workload 로 60초/6초가 충분한지)은 R04 몫이다 — 이 runbook 은 조작 절차이고, 예산 값의 근거는 R04·D11 이 채운다.