Что такое advisory lock в Postgres и зачем он нужен?
Advisory lock — добровольная блокировка по числовому ключу (bigint или два int4). PostgreSQL не связывает её с конкретной строкой и не enforce-ит смысл — все участники должны договориться об одном ключе и протоколе сами.
Что такое advisory lock
Advisory lock — механизм явной кооперативной блокировки в PostgreSQL. Вы резервируете числовой ключ (bigint или пару int4), и база гарантирует взаимное исключение между всеми сессиями, которые запрашивают тот же ключ. Никакой связи с таблицами или строками нет — это чистая координационная примитива.
Session-level vs transaction-level
Session-level (pg_advisory_lock, pg_try_advisory_lock) — блокировка живёт до явного pg_advisory_unlock или закрытия соединения. Она не освобождается при COMMIT/ROLLBACK.
Transaction-level (pg_advisory_xact_lock, pg_try_advisory_xact_lock) — автоматически снимается в конце транзакции. Удобнее и безопаснее при использовании с connection pool.
Зачем нужен
- Запретить параллельный запуск cron-job для одного tenant:
pg_try_advisory_lock(tenant_id)→ еслиfalse, другой worker уже работает. - Сериализовать DDL-миграцию или внешний ресурс, который не имеет собственной блокировки.
- Idempotent command: захватить lock перед операцией, освободить после — без лишних строк в БД.
- Distributed leader election: первый worker, захвативший ключ, становится лидером.
Runnable пример
-- Попытка захватить advisory lock без ожидания
SELECT pg_try_advisory_lock(12345) AS acquired;
-- acquired = true → lock наш
-- acquired = false → кто-то уже держит
-- Блокирующий вариант (ждёт освобождения)
SELECT pg_advisory_lock(12345);
-- Transaction-level: снимается автоматически
BEGIN;
SELECT pg_advisory_xact_lock(99);
-- ... работа ...
COMMIT; -- lock снят
-- Явное освобождение session-level
SELECT pg_advisory_unlock(12345);
-- Посмотреть активные advisory locks
SELECT pid, classid, objid, mode, granted
FROM pg_locks
WHERE locktype = 'advisory';
Namespace ключей
Функция принимает bigint или два int4 (classid + objid). Для избежания коллизий часто кодируют доменный namespace в старших битах, например hashtext('payments') :: bigint << 32 | entity_id. Документируйте namespace в коде — без этого разные части приложения легко случайно используют один ключ.
Подводные камни
- Session-level + connection pool = утечка блокировки. Если код бросает исключение до
pg_advisory_unlock, соединение возвращается в пул с активным lock. Следующий запрос из другого логического контекста получит чужую блокировку. Используйте transaction-level или try/finally. - Вложенные вызовы считаются. Session-level lock реентерабелен: два вызова
pg_advisory_lock(X)требуют двухpg_advisory_unlock(X). Несовпадение счётчика — частая причина зависших блокировок. - Advisory lock не заменяет row lock и constraints. База не знает, какие строки должна защищать блокировка — если один процесс использует advisory lock, а другой напрямую делает UPDATE без него, изоляция нарушается.
- Коллизии ключей между модулями. Если два разных компонента выбрали одинаковое число (например, оба используют
1), они будут мешать друг другу без очевидной диагностики. Используйте явный namespace. - Блокирующий вызов на долго подвешивает воркер.
pg_advisory_lockбез timeout ждёт бесконечно. В production всегда предпочитайтеpg_try_advisory_lockс логикой повтора илиlock_timeout. - Advisory locks не реплицируются. На standby-реплике ваши advisory locks отдельны от primary. Если вы координируете через advisory lock между primary и replica, это не работает.
- pg_advisory_unlock_all при ошибке подключения. При аварийном обрыве соединения PostgreSQL снимает все session-level locks автоматически — это плюс, но нужно проектировать код так, чтобы повторный захват lock не вызвал проблем.
Common mistakes
- Считать advisory lock автоматической защитой строк.
- Забывать unlock для session-level.
- Использовать с пулером без понимания сессий.
What the interviewer is testing
- Просит отличие session и transaction lock.
- Проверяет добровольную природу механизма.
- Уточняет совместимость с pooling.