비동기 백엔드 작업 큐(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. 연관 아티클 및 실무 링크
- 분산 시스템 정합성 보장: Transactional Outbox Pattern으로 매칭 이벤트 발행하기
- Spring Boot와 Redisson 분산 락 구축 가이드
- Airflow 백필 멱등성 회고
📚 공식 레퍼런스 (References)
- PostgreSQL Documentation — The Locking Clause (SKIP LOCKED)
- 2ndQuadrant PostgreSQL — What is SKIP LOCKED
함께 읽으면 좋은 글
- Redis Cache Stampede 현상 극복기: 캐시 적용하면 DB 부하 끝 아닌가?
- JPA N+1 문제와 Fetch Join 실무 고찰: lazy loading이면 다 해결되나?
- Spring Boot와 Redisson 분산 락: @Transactional만 걸면 되는 거 아니야?
공식 문서와 확인 자료
아래 1차 자료를 기준으로 명령 동작과 적용 조건을 다시 확인할 수 있습니다.