PostgreSQLMiddleTechnical

Что такое 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.

Sources

Related topics