751cdc5be8
기존 UNIQUE(document_id, stage, status)는 pending+processing 동시 존재를 허용해서 stale 복구 시 충돌 발생. 2-layer 방어로 근본 차단: 1) DB: partial unique index uq_queue_active — 활성 행(pending/processing)은 (document_id, stage)당 최대 1개만 허용 2) App: enqueue_stage() 중앙 함수 — INSERT ON CONFLICT DO NOTHING으로 모든 9개 경로의 check-then-insert TOCTOU race 제거 migration 117은 guard check 포함 — 활성 중복이 남아있으면 RAISE EXCEPTION 으로 중단, 수동 정리 유도. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
37 lines
1.1 KiB
SQL
37 lines
1.1 KiB
SQL
-- Step 1: stale duplicate 삭제 (processing 10분+ 방치 + 같은 doc/stage에 pending 존재)
|
|
DELETE FROM processing_queue a
|
|
USING processing_queue b
|
|
WHERE a.document_id = b.document_id
|
|
AND a.stage = b.stage
|
|
AND a.status = 'processing'
|
|
AND a.started_at < NOW() - INTERVAL '10 minutes'
|
|
AND b.status = 'pending';
|
|
|
|
-- Step 2: guard check — 활성 중복이 남아있으면 migration 중단
|
|
DO $$
|
|
DECLARE
|
|
dup_count INTEGER;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO dup_count
|
|
FROM (
|
|
SELECT document_id, stage
|
|
FROM processing_queue
|
|
WHERE status IN ('pending', 'processing')
|
|
GROUP BY document_id, stage
|
|
HAVING COUNT(*) > 1
|
|
) sub;
|
|
|
|
IF dup_count > 0 THEN
|
|
RAISE EXCEPTION 'migration 117 blocked: % active duplicate(s) remain. Run manual cleanup first.', dup_count;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Step 3: 기존 constraint 제거
|
|
ALTER TABLE processing_queue
|
|
DROP CONSTRAINT processing_queue_document_id_stage_status_key;
|
|
|
|
-- Step 4: partial unique index (활성 행은 (document_id, stage)당 최대 1개)
|
|
CREATE UNIQUE INDEX uq_queue_active
|
|
ON processing_queue (document_id, stage)
|
|
WHERE status IN ('pending', 'processing');
|