Дедлоки 40P01 в PostgreSQL под Directum RX: как читать и что отдавать разработчикам
Ошибка deadlock detected в логах Directum RX на PostgreSQL. Откуда берётся, как настроить журнал, чтобы увидеть обе стороны, и какой набор фактов нужен вендору или вашим разработчикам вместо тикета «иногда падает».
Коротко. Дедлок это несогласованный порядок обращения к строкам в двух местах прикладного кода. Инфраструктура включает
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 и время суток:
если они совпадают с расписанием фонового задания, причина найдена наполовину.
Что сделать
Собрать пакет фактов на один инцидент. Этого достаточно, чтобы разработчики воспроизвели проблему, и это снимает переписку на недели:
- Полная запись о дедлоке из лога PostgreSQL с
DETAILиCONTEXT. - Запросы обеих сторон и их
application_name. - Что делали в этот момент: какая операция у пользователя, какое фоновое задание выполнялось.
- Частота: сколько дедлоков в сутки за последнюю неделю. Считается
grep -c "deadlock detected"по логам за день. - Версия RX и PostgreSQL.
Снизить частоту, пока ждёте исправления. Развести по времени фоновые задания и часы пиковой активности; проверить, нет ли транзакций интеграции, открытых на минуты, и укоротить их; убедиться, что на колонках внешних ключей есть индексы: без них обновление родительской записи блокирует полную проверку дочерней таблицы, и ожидания растут.
Следить постоянно. Метрика deadlocks из pg_stat_database уже есть в любом экспортёре PostgreSQL. Алерт словами:
число дедлоков за час выросло относительно обычного для этого часа. Ноль не цель, цель стабильно низкая частота и отсутствие всплесков после обновлений.
Чего не делать
- Увеличивать
deadlock_timeout, чтобы дедлоков «стало меньше». Их станет ровно столько же, просто ждать перед обнаружением будут дольше. - Ставить
log_lock_waitsи забывать про размер логов. На нагруженной базе ожидания пишутся часто; ротация логов должна быть настроена заранее. - Отправлять вендору одну строку
deadlock detected. Без второй стороны и запросов это не воспроизводится, тикет закроется вопросом. - Перезапускать сервис RX после каждого дедлока. Транзакция уже откачена базой, перезапуск ничего не чинит и добавляет простой.
Похожая картина у вас?
Разберём ваш контур за 30 минут бесплатного созвона: версия RX, состав сервисов, что болит сильнее всего.
Написать