FOR UPDATE без SKIP LOCKED — классический способ превратить очередь задач в дедлок

Типичная задача: таблица задач, несколько воркеров разбирают её конкурентно. Воркер делает SELECT ... FOR UPDATE, чтобы захватить строку и никто другой её не тронул, пока он работает.

Без SKIP LOCKED второй воркер, наткнувшись на уже заблокированную строку, не берёт следующую — он встаёт в очередь на эту же самую строку и ждёт снятия блокировки. При достаточном параллелизме и неудачном порядке блокировки нескольких строк сразу — classic deadlock, Postgres сам его обнаружит и прибьёт одну из транзакций.

SKIP LOCKED решает это иначе: воркер видит, что строка занята, и просто пропускает её, переходя к следующей свободной. Никакого ожидания, никакого дедлока — цена в том, что при высокой конкуренции воркер может несколько раз подряд пропускать занятые строки.

SELECT id, payload FROM task_queue WHERE status = 'pending' ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1;

На этом месте интервьюер почти всегда спросит про идемпотентность: что если воркер взял строку, начал обработку, а процесс упал до UPDATE статуса на 'done'? Строка останется в 'processing' навсегда, если нет отдельного таймаута-возврата в очередь по visibility timeout. SKIP LOCKED решает конкуренцию за строку, но не решает проблему недообработанных задач — это два разных механизма.

FOR UPDATE SKIP LOCKED — стандартный паттерн очереди поверх реляционной БД без отдельного брокера, и его стоит знать именно как альтернативу Kafka/RabbitMQ для несложных сценариев.

Тренажёр: 1900+ вопросов и задач, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки