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