명령어/DB

[PostgreSQL] FOR UPDATE SKIP LOCKED 여러 워커가 같은 큐에서 안 겹치게 하나씩 집어간다

jykim23 2026. 7. 23. 21:59
반응형

설치·접속: PostgreSQL 설치와 접속

부제: 워커 여러 대가 같은 작업 큐 테이블을 동시에 폴링할 때, 서로 잡은 행은 건너뛰고 각자 다른 행만 집어가게 한다

-- 각 워커가 실행: 남이 잠근 행은 건너뛰고 안 잠긴 것만 3개 집는다
BEGIN;
SELECT id, payload FROM jobs
WHERE status = 'pending'
ORDER BY id
FOR UPDATE SKIP LOCKED
LIMIT 3;
-- ... 처리 후 status 갱신 ...
COMMIT;
=== Worker B (A가 id 1~3을 잡고 있는 중) ===
BEGIN
 id | payload
----+---------
  4 | job-4
  5 | job-5
  6 | job-6
(3 rows)

=== Worker A got ===
 id | payload
----+---------
  1 | job-1
  2 | job-2
  3 | job-3

Worker A가 트랜잭션 안에서 id 13에 행 락을 걸고 아직 커밋하지 않은 사이, Worker B가 같은 쿼리를 던지면 SKIP LOCKED 덕분에 잠긴 13을 그냥 지나쳐 4~6을 가져온다. 락 대기로 멈추지도, 같은 작업을 중복 처리하지도 않는다. SKIP LOCKED가 없으면 B는 A의 커밋까지 블로킹되고, 그냥 LIMIT만 쓰면 둘 다 같은 행을 읽어 이중 처리된다. 큐에 워커를 여러 대 붙여 수평 확장할 때의 정석이다.

이렇게도 쓴다

집는 즉시 상태를 바꿔 다른 워커 눈에서 치운다. (조합: CTE + UPDATE ... RETURNING)

WITH picked AS (
  SELECT id FROM jobs WHERE status='pending'
  ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1
)
UPDATE jobs j SET status='running'
FROM picked WHERE j.id = picked.id
RETURNING j.id, j.payload;

 

한 건씩 집어 처리하는 단일 워커 루프.

SELECT id FROM jobs WHERE status='pending'
ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;

 

건너뛰지 말고 "잠겨 있으면 바로 에러"로 실패시킨다. (조합: NOWAIT)

SELECT id FROM jobs WHERE id = 5 FOR UPDATE NOWAIT;
-- ERROR: could not obtain lock on row in relation "jobs"

 

우선순위 큐: 급한 것부터 집는다. (조합: ORDER BY priority)

SELECT id FROM jobs WHERE status='pending'
ORDER BY priority DESC, id FOR UPDATE SKIP LOCKED LIMIT 1;

 

특정 행만 잠그고 조인 상대는 잠그지 않는다. (조합: OF 테이블명)

SELECT j.id FROM jobs j JOIN users u ON u.id = j.owner_id
WHERE j.status='pending'
FOR UPDATE OF j SKIP LOCKED LIMIT 1;

 

예약 시각이 된 작업만 골라 집는다. (조합: run_at 필터)

SELECT id FROM jobs WHERE status='pending' AND run_at <= now()
ORDER BY run_at FOR UPDATE SKIP LOCKED LIMIT 1;
반응형