여러 worker가 같은 row를 기다리는 문제

여러 worker가 ready 작업을 FOR UPDATE로 가져가면 앞 transaction이 잡은 row 때문에 뒤 worker가 기다릴 수 있습니다. 처리 가능한 다른 row가 있는데도 queue 전체 처리량이 낮아지는 상황입니다. 이때 PostgreSQL SKIP LOCKED 작업 큐 패턴을 사용할 수 있습니다.

PostgreSQL의 SKIP LOCKED는 즉시 잠글 수 없는 row를 건너뛰어 queue consumer 경합을 줄일 수 있지만 일관되지 않은 view를 만들기 때문에 일반 조회에는 적합하지 않습니다.

선택과 상태 변경을 묶는 흐름

잠근 row를 고른 뒤 같은 transaction에서 담당자와 상태를 바꿉니다. 안정적인 우선순위를 위해 ORDER BY도 명시합니다. 다른 worker가 잠근 row는 결과에서 빠지므로 이 query를 전체 미처리 건수나 감사 조회에 재사용하면 안 됩니다.

BEGIN;

WITH picked AS (
  SELECT id
  FROM jobs
  WHERE status = 'ready'
  ORDER BY priority DESC, id
  FOR UPDATE SKIP LOCKED
  LIMIT 10
)
UPDATE jobs AS job
SET status = 'running', worker_id = 'worker-3', started_at = now()
FROM picked
WHERE job.id = picked.id
RETURNING job.id, job.payload;

COMMIT;

장애 복구까지 포함한 해결

SKIP LOCKED만으로 exactly-once 처리가 보장되지는 않습니다. 상태를 commit한 뒤 worker가 죽을 수 있으므로 started_at lease와 재시도 횟수, idempotent한 side effect 또는 고유 제약을 별도로 설계합니다. 두 worker를 동시에 실행해 같은 ID를 받지 않는지, 하나를 강제 종료한 뒤 lease가 지난 작업이 회수되는지 확인합니다. queue depth를 세는 일반 조회는 lock을 건너뛰지 않는 별도 query로 둡니다.

공식 문서

사용 중인 PostgreSQL version과 transaction isolation, index, 실제 worker 실패 계약에서 검증합니다.