Коротко. Дедлок это несогласованный порядок обращения к строкам в двух местах прикладного кода. Инфраструктура включает log_lock_waits и префикс с именем приложения, собирает обе стороны и частоту, а чинят разработчики. Один алерт на всплеск deadlocks показывает, стало ли лучше.

В логах сервисов RX периодически появляется ERROR: deadlock detected с кодом 40P01. Пользователь видит ошибку при сохранении или при массовой операции, повтор проходит успешно. Тикет вендору «иногда падает с дедлоком» возвращается с вопросом «пришлите детали», и на этом всё останавливается. Разбираем, откуда дедлок берётся, как собрать детали за один инцидент и что с ними делать.

Симптом

Для пользователя: сообщение об ошибке при сохранении документа, отправке задачи или выполнении операции над несколькими объектами. После повтора всё проходит. Чаще проявляется в часы пик и во время фоновых заданий.

Для администратора: в логах сервисов RX ошибка с кодом 40P01, в логе PostgreSQL строка deadlock detected и рядом DETAIL: Process 12345 waits for ShareLock on transaction ...; blocked by process 12346. Если log_lock_waits выключен, в логе базы только сам факт.

Почему так

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

В системе документооборота типовые пары:

  • пользователь сохраняет карточку и меняет права, а фоновое задание в это же время обновляет те же записи по регламенту в другом порядке;
  • массовая операция над списком объектов идёт в одном порядке, а другой процесс проходит по тому же списку в обратном;
  • длинная транзакция интеграции держит строки часами, и любое действие пользователя над ними встаёт в очередь, пока не сложится цикл.

Дедлок сам по себе стоит одной откаченной транзакции. Проблемой он становится, когда происходит регулярно: за каждым дедлоком стоят секунды ожидания у обеих сторон, а частота показывает, что порядок обращения к строкам в двух местах кода не согласован. Исправляется это в прикладном коде, то есть у вендора или у ваших разработчиков. Задача инфраструктуры дать им точную фактуру.

Диагностика

Настроить журнал так, чтобы каждый дедлок оставлял обе стороны:

log_lock_waits = on            # писать ожидания дольше deadlock_timeout
deadlock_timeout = 1s          # значение по умолчанию; меньше делать не нужно
log_min_duration_statement = 2000   # долгие запросы, они часто и есть вторая сторона
log_line_prefix = '%m [%p] %u@%d app=%a '   # время, pid, пользователь, база, имя приложения

Параметры применяются перезагрузкой конфигурации без остановки базы. Имя приложения в префиксе важно: сервисы RX подставляют его в строку подключения, и по нему видно, какой сервис был участником.

После следующего дедлока в логе базы будет полная запись: оба процесса, тип блокировки, обе транзакции и текст запроса той стороны, которую откатили. Запрос второй стороны берётся из строк log_lock_waits за ту же секунду или из log_min_duration_statement.

Пока дедлок ещё не случился, посмотреть на живые ожидания:

select a.pid, a.application_name, a.state, now() - a.xact_start as xact_age,
       a.wait_event_type, a.wait_event, left(a.query, 120) as query
from pg_stat_activity a
where a.state <> 'idle' and a.xact_start < now() - interval '30 seconds'
order by xact_age desc;

Транзакции, открытые минутами, и есть кандидаты в участники. Полезно посмотреть их application_name и время суток: если они совпадают с расписанием фонового задания, причина найдена наполовину.

Что сделать

Собрать пакет фактов на один инцидент. Этого достаточно, чтобы разработчики воспроизвели проблему, и это снимает переписку на недели:

  1. Полная запись о дедлоке из лога PostgreSQL с DETAIL и CONTEXT.
  2. Запросы обеих сторон и их application_name.
  3. Что делали в этот момент: какая операция у пользователя, какое фоновое задание выполнялось.
  4. Частота: сколько дедлоков в сутки за последнюю неделю. Считается grep -c "deadlock detected" по логам за день.
  5. Версия RX и PostgreSQL.

Снизить частоту, пока ждёте исправления. Развести по времени фоновые задания и часы пиковой активности; проверить, нет ли транзакций интеграции, открытых на минуты, и укоротить их; убедиться, что на колонках внешних ключей есть индексы: без них обновление родительской записи блокирует полную проверку дочерней таблицы, и ожидания растут.

Следить постоянно. Метрика deadlocks из pg_stat_database уже есть в любом экспортёре PostgreSQL. Алерт словами: число дедлоков за час выросло относительно обычного для этого часа. Ноль не цель, цель стабильно низкая частота и отсутствие всплесков после обновлений.

Чего не делать

  • Увеличивать deadlock_timeout, чтобы дедлоков «стало меньше». Их станет ровно столько же, просто ждать перед обнаружением будут дольше.
  • Ставить log_lock_waits и забывать про размер логов. На нагруженной базе ожидания пишутся часто; ротация логов должна быть настроена заранее.
  • Отправлять вендору одну строку deadlock detected. Без второй стороны и запросов это не воспроизводится, тикет закроется вопросом.
  • Перезапускать сервис RX после каждого дедлока. Транзакция уже откачена базой, перезапуск ничего не чинит и добавляет простой.