Иногда PostgreSQL начинает «сыпаться» ошибкой could not access status of transaction. Она может проявляться в обычных запросах (SELECT/UPDATE), во время VACUUM или даже при старте сервиса. На практике это почти всегда означает, что серверу сложно или невозможно корректно читать/обновлять статусы транзакций, а рядом часто присутствует риск XID wraparound (переполнения счётчика XID).
Ниже — понятное объяснение связки pg_xact → статусы транзакций → MVCC, а также быстрая диагностика по age(datfrozenxid)/age(relfrozenxid) и план действий: от «вернуть сервис» до нормализации vacuum freeze.
Почему XID и wraparound — это аварийный сценарий, а не теория
PostgreSQL использует 32-битные идентификаторы транзакций (XID). Они растут по мере работы системы. Когда счётчик подходит к пределу и «переполняется», старые XID начинают выглядеть как «будущие» — это ломает MVCC-логику видимости строк. Поэтому PostgreSQL заранее включает защитный механизм: требует заморозки (freeze) старых версий строк.
Заморозка делается через VACUUM (обычно autovacuum). При VACUUM часть строк помечается как «замороженные» (их XID заменяется специальным FrozenXID), а в базе обновляется нижняя граница — datfrozenxid. Если datfrozenxid давно не двигался, растёт xid age и система приближается к wraparound.
Когда autovacuum freeze не успевает, PostgreSQL может начать ограничивать операции ради защиты от wraparound. Ошибки чтения статусов транзакций часто всплывают рядом, потому что обращение к статусам XID — базовая операция MVCC.
Что означает «could not access status of transaction» и при чём тут pg_xact
Чтобы определить видимость версий строк, PostgreSQL должен знать состояние транзакции: committed, aborted или in progress. Эти статусы хранятся в каталоге данных pg_xact (в старых версиях — pg_clog).
Ошибка could not access status of transaction означает: сервер попытался обратиться к статусу конкретной транзакции (XID), но не смог корректно прочитать нужную страницу/сегмент в pg_xact.
Чаще всего на проде встречаются причины:
- Проблемы диска/ФС/снапшотов: I/O ошибки, «битые» блоки, внезапные ребуты, сбои контроллера, проблемы на уровне гипервизора.
- Закончился диск: PostgreSQL не смог вовремя создать новый сегмент
pg_xactили дописать существующий. - Повреждение данных: неконсистентность после аварийного выключения, усиленная аппаратными проблемами.
- Wraparound pressure: autovacuum не успевал, база подошла к опасным значениям, а любые сбои по I/O добивают ситуацию.
Важно: ошибка про pg_xact — это уже инцидент уровня «проверить хранилище и целостность», а не «подкрутить vacuum». Но параллельно обязательно оцените риск wraparound и срочно обеспечьте vacuum freeze, иначе даже после временного «оживления» база может снова уйти в защитный режим.

Если инцидент случился на виртуальной машине, отдельно проверьте базовые вещи: есть ли гарантированный IOPS, не упёрлись ли в лимиты диска/квоты, не было ли проблем на ноде. На практике перенос PostgreSQL на более предсказуемую виртуализацию и диск часто снимает «плавающие» ошибки чтения. Для проектов, которым важна стабильная дисковая подсистема, обычно разумнее держать БД на VDS с понятными ресурсами, чем зависеть от соседей.
Быстрая диагностика: проверяем xid age и datfrozenxid
Подключитесь под суперпользователем и посмотрите возраст XID по базам:
psql -X
SELECT datname,
age(datfrozenxid) AS xid_age,
datfrozenxid
FROM pg_database
ORDER BY xid_age DESC;
Как читать результат:
- xid_age большой и растёт — autovacuum freeze не справляется или ему мешают блокировки/ограничения.
- Одна база сильно «старше» остальных — проблема локальная (долгие транзакции, слоты, отключённый autovacuum на таблицах, особенности нагрузки).
Дальше найдите самые «старые» таблицы по relfrozenxid:
SELECT n.nspname,
c.relname,
age(c.relfrozenxid) AS xid_age,
c.relfrozenxid
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','m')
ORDER BY xid_age DESC
LIMIT 50;
Проверяем, не мешают ли длинные транзакции
Самая частая причина, почему freeze «не двигается»: кто-то держит snapshot — долгий idle in transaction, зависшая миграция, большой бэкап-сценарий, открытый курсор. Найдите самые длинные транзакции:
SELECT pid,
usename,
application_name,
client_addr,
state,
now() - xact_start AS xact_age,
wait_event_type,
wait_event,
LEFT(query, 200) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_age DESC
LIMIT 30;
Пока такие транзакции живы, vacuum freeze может не продвинуть datfrozenxid. Если есть возможность, сначала остановите/ограничьте приложение, чтобы «не плодить» новые долгие транзакции, и только затем переходите к freeze.
Проверяем здоровье диска и симптомы проблемы с pg_xact
Если ошибка could not access status of transaction уже была, проверка хранилища обязательна. Минимальный чек-лист на Linux:
df -h
df -i
journalctl -u postgresql --since "-2h"
dmesg -T | tail -n 200
Что искать: I/O errors, filesystem errors, remount read-only, timeout/reset устройств, NVMe/SATA ошибки. Если ФС стала read-only или диск уходит в ошибки — сначала стабилизируйте хранение. Иначе любые действия внутри PostgreSQL будут непредсказуемыми.
План действий при инциденте: от «вернуть сервис» к нормализации freeze
Шаг 1. Зафиксируйте состояние и снимите копию (если возможно)
Если кластер ещё стартует, первым делом постарайтесь снять дамп критичных данных или хотя бы самых важных схем. При ошибках чтения pg_xact дамп может падать на части таблиц — сохраняйте то, что получается, и параллельно готовьте восстановление из бэкапа/PITR.
Если вы используете WAL-архивацию, держите под рукой отработанный сценарий восстановления. Полезные практики собраны в материале про восстановление PostgreSQL через PITR и WAL.
Шаг 2. Остановите «пожирателей freeze»: длинные транзакции
Если вы нашли долгие транзакции (особенно idle in transaction), их нужно аккуратно завершить. Технически — через pg_terminate_backend, но сначала оцените влияние на приложение (платежи, миграции, важные джобы). Иногда безопаснее временно остановить приложение и завершить сессии пакетно.
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid <> pg_backend_pid()
AND xact_start IS NOT NULL
AND now() - xact_start > interval '30 minutes';
Порог подберите под ситуацию. Цель — убрать «якоря», которые не дают VACUUM продвинуть заморозку.
Шаг 3. Срочный VACUUM FREEZE на проблемных объектах
Когда риск wraparound высокий, ускоряйте заморозку вручную. Начинайте с таблиц с максимальным xid age (из запроса по pg_class). Помните: VACUUM (FREEZE) может быть тяжёлым по I/O, поэтому действуйте в окно и следите за нагрузкой.
VACUUM (FREEZE, VERBOSE) public.big_table;
Если база большая, идите батчами: сначала топ самых старых таблиц, затем остальные. После серии вакуумов снова проверьте age(datfrozenxid) по базам.
Шаг 4. Если autovacuum «молчит» — проверьте настройки и ограничения
Типовые причины, почему autovacuum freeze не успевает:
- слишком маленькие лимиты по воркерам:
autovacuum_max_workers; - слишком «бережные» лимиты по стоимости:
autovacuum_vacuum_cost_limitиautovacuum_vacuum_cost_delay; - autovacuum отключён на отдельных таблицах через storage parameters;
- постоянные блокировки из-за DDL, долгих транзакций или тяжёлых batch-операций.
Для точечной диагностики смотрите pg_stat_all_tables (когда был последний autovacuum/vacuum) и сравнивайте это с реальным ростом данных. Отдельно полезно держать под рукой разбор тюнинга: настройка autovacuum и индексов в PostgreSQL.
Когда проблема именно в pg_xact: что можно и чего нельзя делать
Если pg_xact реально повреждён, это уже не «просто vacuum». Дальнейшие действия зависят от того, есть ли валидный бэкап и насколько критичны данные.
Правильный путь: восстановление из бэкапа/PITR
Если есть проверенный бэкап + WAL (или реплика), восстановление почти всегда безопаснее, чем попытки «чинить файлы». Повреждение pg_xact означает, что PostgreSQL не может доверять части информации о транзакциях, и последствия могут быть непредсказуемыми.
Аварийный путь: pg_resetwal и риски
У PostgreSQL есть утилита pg_resetwal, которую иногда применяют в крайне аварийных случаях. Это инструмент последней надежды: он может помочь запустить кластер, но не гарантирует логическую целостность. Использовать его стоит только после фиксации копии кластера и понимания, что вы готовы к потере/расхождению данных.
Если вы рассматриваете pg_resetwal, сначала сделайте поблочную копию PGDATA или хотя бы архив каталога данных. Это даст шанс на форензику или частичное извлечение данных позже.
Профилактика: как не доводить до wraparound и ошибок статусов транзакций
1) Мониторьте xid age и лидеров по возрасту
В мониторинг стоит вынести минимум: age(datfrozenxid) по каждой базе и топ таблиц по age(relfrozenxid). Так вы увидите проблему за недели до аварии, а не в момент, когда VACUUM уже «поздно».
2) Следите за «idle in transaction» и долгими миграциями
Лимиты вроде idle_in_transaction_session_timeout</code часто спасают от случайных зависших транзакций, которые тихо держат snapshot и блокируют freeze. Для миграций — делайте батчи, коммитьте чаще, избегайте огромных транзакций на часы.</p>
<h3>3) Дайте autovacuum ресурсы</h3>
<p>На нагруженных системах autovacuum — такой же «боевой» воркер, как и обработчики запросов. Ему нужны CPU, I/O и память. Если база переехала на более слабый диск или выросла конкуренция за ресурсы (особенно в виртуализации), autovacuum может хронически не успевать.</p>
<h3>4) Держите регулярные проверяемые бэкапы</h3>
<p>Инциденты уровня <code>pg_xact — ровно тот случай, когда бэкап «решает всё». Важно не только делать бэкап, но и регулярно проверять восстановление на тестовом контуре.

Краткая шпаргалка: что делать прямо сейчас
- Проверьте диск: место, inode, ошибки I/O в
dmesgиjournalctl. - Оцените риск wraparound:
SELECT age(datfrozenxid)по базам. - Найдите лидеров по возрасту:
age(relfrozenxid)по таблицам. - Уберите длинные транзакции, которые держат snapshot.
- Запустите
VACUUM (FREEZE)на самых старых таблицах и контролируйте прогресс. - Если ошибка повторяется и есть признаки порчи
pg_xact— готовьтесь к восстановлению из бэкапа/PITR, а не к «магии» на живом кластере.
Если вы пришли к этой ошибке на фоне переполненного диска или нестабильного хранилища, не откладывайте перенос базы на более надёжный слой хранения и пересмотр ресурсов autovacuum: wraparound — это таймер, который не ждёт, а pg_xact — один из самых чувствительных узлов к I/O сбоям.


