운영 번들 자동 로그인 자격증명 유출, 온보딩 COPY 잡이 Gemini 429 로 죽던 것, 크롤링 실패가 로그에만 남던 것을 한 번에 정리한다. 실측(2026-09-15 밤, 킹서버): 사진분석 배치가 Gemini 분당 쿼터를 다 써서 같은 키를 쓰는 온보딩 COPY 잡도 같이 429 를 맞고 DEAD 로 갔다 — 확인된 fact 만으로도 편집·발행이 되는데 잡을 죽일 이유가 없었다. - solution/frontend: `VITE_AUTO_LOGIN_ID`·`PW` 를 운영 진입점에 안 넘긴다(자동 로그인은 dev 서버 전용) + `Step5Generating` 겉모습을 이전 카드 스타일로, 데이터는 실제 잡 진행(useGenerationJob) 그대로 - solution/backend: copy_service — Gemini 호출 실패해도 잡을 안 죽이고 fact 만으로 계속. db_session_manager — 유니크 제약 충돌(정상 경로) 로그를 ERROR → WARN. worker/runner + alert_service + teams_webhook — 잡 dead-letter·발행 실패·큐 정체를 Teams 로 알림(영구 저장 + 재시도 + dedupe). `/readyz` 추가. collect_diagnostics(신규) — 크롤링 채널별 실패를 jobs.result 에 구조화해서 싣는다. - postgres-init: 0015(users token_version) · 0016(alert_outbox) 마이그레이션 검증: 백엔드 pytest 759 passed. tsc(solution/frontend) 통과. Teams 알림 실채널 수신 확인.
36 lines
2.0 KiB
SQL
36 lines
2.0 KiB
SQL
-- 0016 · alert_outbox — 장애 알림 발송함(services/alert_service.py)
|
|
--
|
|
-- ★ 왜 필요한가 — 최종 생성 실패(JobStatus.DEAD) · BUILD 잡 업무 실패(게이트 반려가 아닌
|
|
-- 렌더·인프라 실패) · 노래 등 부분 실패 · 잡 큐 정체를 Teams Workflows webhook 으로
|
|
-- 알린다. 워커·스케줄러가 죽어도 알림 자체는 DB 에 남아야 하므로(메모리 큐면 장애를
|
|
-- 알릴 메시지까지 같이 잃는다) 영구 저장 + 재시도 + 중복 억제를 이 표 하나로 한다.
|
|
|
|
CREATE TABLE IF NOT EXISTS public.alert_outbox (
|
|
alert_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
kind VARCHAR(50) NOT NULL,
|
|
dedupe_key VARCHAR(200) NULL,
|
|
title VARCHAR(200) NOT NULL,
|
|
detail TEXT NULL,
|
|
status SMALLINT NOT NULL DEFAULT 1,
|
|
attempts SMALLINT NOT NULL DEFAULT 0,
|
|
next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
sent_at TIMESTAMPTZ NULL,
|
|
resolved_at TIMESTAMPTZ NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
|
);
|
|
|
|
COMMENT ON COLUMN public.alert_outbox.kind IS
|
|
'job_dead · build_failed · partial_failure · queue_stuck · recovery …';
|
|
COMMENT ON COLUMN public.alert_outbox.status IS
|
|
'AlertStatus: 1=pending 2=sent 3=failed(재시도 소진)';
|
|
COMMENT ON COLUMN public.alert_outbox.detail IS
|
|
'이미 비밀·개인정보를 걷어낸 텍스트만 들어온다 — alert_service._scrub 가 저장 전에 거른다.';
|
|
COMMENT ON COLUMN public.alert_outbox.resolved_at IS
|
|
'채워지면 그 dedupe_key 는 복구됨으로 본다 — 다음 문제 발생 때 새 알림을 보낸다.';
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_alert_outbox_pending ON public.alert_outbox (status, next_attempt_at);
|
|
CREATE INDEX IF NOT EXISTS ix_alert_outbox_dedupe ON public.alert_outbox (dedupe_key, created_at DESC)
|
|
WHERE dedupe_key IS NOT NULL;
|