PostgreSQL FOR UPDATE SKIP LOCKED: DB 락 경합 없이 고성능 작업 큐 구축하기

비동기 백엔드 작업 큐(Worker Queue)를 구축할 때, 추가적인 외부 분산 브로커 인프라를 운영하기에는 리소스 부담이 크고 단일 트랜잭션 내에서 즉각적인 작업 상태 전환이 필요한 경우가 많습니다.

“작업 큐? 그냥 DB 테이블에 STATUS 컬럼 두고 Worker가 1초마다 조회해서 가져오면 되는 거 아닌가?”

하지만 다중 Worker가 동시에 SELECT ... FOR UPDATE를 실행하면 모든 Worker가 동일한 상위 행(Row)에 락을 걸려고 시도하여 심각한 Lock Contention(락 경합)과 대기 지연이 발생합니다.


1. FOR UPDATE SKIP LOCKED의 핵심 메커니즘

PostgreSQL 9.5+에서 지원하는 SKIP LOCKED 옵션은 다른 트랜잭션이 이미 점유(Lock)하고 있는 행을 기다리지 않고 건너뛴 뒤, 아직 락이 걸리지 않은 다음 가용 행을 즉시 반환합니다.

sequenceDiagram
    autonumber
    actor W1 as Worker 1
    actor W2 as Worker 2
    participant DB as PostgreSQL Queue Table

    W1->>DB: BEGIN & SELECT * ... FOR UPDATE SKIP LOCKED LIMIT 1
    DB-->>W1: Task #101 할당 (Lock 획득)
    Note over DB: Task #101 Row Lock 점유 중
    W2->>DB: BEGIN & SELECT * ... FOR UPDATE SKIP LOCKED LIMIT 1
    Note over DB: Task #101 건너뜀 (Skip Lock)
    DB-->>W2: Task #102 즉시 할당 (Lock 획득)
    W1->>DB: 작업 완료 후 UPDATE status='DONE' & COMMIT
    W2->>DB: 작업 완료 후 UPDATE status='DONE' & COMMIT
-- 안전한 동시성 작업 선점 쿼리
WITH next_task AS (
    SELECT id 
    FROM job_queue 
    WHERE status = 'PENDING'
    ORDER BY created_at ASC
    LIMIT 1
    FOR UPDATE SKIP LOCKED
)
UPDATE job_queue
SET status = 'PROCESSING',
    locked_at = NOW()
FROM next_task
WHERE job_queue.id = next_task.id
RETURNING job_queue.*;

2. 연관 아티클 및 실무 링크


📚 공식 레퍼런스 (References)

댓글 남기기