-- 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;