MVCC в PostgreSQL под нагрузкой: что ломается первым
MVCC позволяет PostgreSQL читать и изменять данные параллельно, но каждое обновление оставляет версии строк, которые нужно убирать. Разбираем, как задержка VACUUM превращается в рост таблиц, WAL и задержек — и что измерять до инцидента.

В PostgreSQL 16 высокая доля обновлений может незаметно превратить обычную нагрузку в очередь за диском: старые версии строк копятся, VACUUM не успевает их убирать, а таблицы и индексы растут. Но единого сценария «первым ломается именно это» нет: результат зависит от долгих транзакций, слотов репликации, частоты обновлений и того, какие поля индексируются.
Что именно хранит MVCC
PostgreSQL не перезаписывает строку на месте при каждом UPDATE. Он создаёт новую версию строки, а прежняя остаётся в таблице, пока её могут видеть открытые снимки данных. DELETE тоже не вычищает строку немедленно: помечает её удалённой, а физическое удаление становится возможным позже.
Так работает снимок транзакции: он определяет, какие версии строк видны конкретному запросу. Читатель обычно не блокирует запись, а запись не обязана ждать, пока обычный SELECT закончит читать старую версию. Цена этой свободы — уборка устаревших версий, обслуживание индексов и дополнительный ввод-вывод.
Термин — dead tuple. Так называют версию строки, которая уже не нужна ни одному актуальному снимку, но ещё физически находится в таблице. Оценка числа таких строк в статистике PostgreSQL не точная перепись: это ориентир для диагностики.
MVCC не делает обновления бесплатными. Он переносит часть цены с блокировки чтения на хранение версий, работу VACUUM и контроль за тем, кто удерживает старый снимок.
Почему VACUUM может не догнать нагрузку
VACUUM отмечает место старых версий как пригодное для повторного использования и обслуживает связанные с ними индексные записи. Обычный VACUUM, как правило, не уменьшает файл таблицы на диске: освободившееся место остаётся доступным для будущих записей внутри отношения. Поэтому размер файла может не снижаться, даже если уборка работает.
Критическое ограничение — старейший снимок, который ещё может потребовать старую версию. Долгая транзакция, зависшая сессия или слот репликации, удерживающий xmin, способны отодвинуть границу уборки. Тогда VACUUM может запускаться, но не освобождать весь ожидаемый объём. Если обновления продолжаются быстрее уборки, долг постепенно превращается в рост таблиц и индексов.
Есть и второй механизм. Если UPDATE меняет поле, которое не входит в индекс, и на странице таблицы достаточно места, PostgreSQL может выполнить HOT-обновление: новая версия не требует новых записей во всех индексах. Это снижает часть нагрузки, но не отменяет создание версии строки и не помогает, если обновляемое поле индексировано или страница заполнена.
Что обычно выходит из строя первым
Ниже — не универсальный рейтинг, а цепочка симптомов. В одних системах сначала растёт задержка дисковых операций, в других — WAL или размер хранилища. Для диагностики важны темп изменений и причина, по которой старые версии нельзя убрать.
| Условие | Что накапливается | Что проверять |
|---|---|---|
| Долгие транзакции и старые снимки | Мёртвые версии строк, затем раздувание таблиц и индексов | Длительность транзакций, xmin активных сессий, динамику n_dead_tup и работу VACUUM |
| Слот репликации отстаёт или потребитель остановился | Старые версии могут оставаться нужными; при удержании WAL растёт pg_wal | xmin и restart_lsn слотов, задержку репликации и свободное место на диске |
| Много обновлений индексируемых полей | Новые индексные записи, WAL и дополнительная работа при уборке | Частоту изменений, структуру индексов, долю HOT-обновлений |
| Поток изменений выше пропускной способности обслуживания | Очередь уборки и конкуренцию за дисковый ввод-вывод | Прогресс VACUUM, длительность операций и задержки диска под типичной нагрузкой |
Наблюдайте не за одной цифрой, а за трендом. В PostgreSQL 16 для этого полезны статистика таблиц, включая n_dead_tup и время последнего autovacuum, представление прогресса VACUUM, активные транзакции и состояние replication slots. Сверяйте показания с размером таблиц и индексов, темпом записи, WAL и свободным местом. Само по себе большое n_dead_tup не доказывает аварию, а стабильный размер таблицы не гарантирует, что репликация и уборка здоровы.
Как отличить причину от похожих симптомов
Рост времени запросов ещё не означает, что виновата уборка версий. Если сессии ждут блокировку строки или таблицы, ищите конкретный конфликт операций. VACUUM обычно не блокирует обычные чтения и записи так, как это делает переписывание таблицы. Команда VACUUM FULL возвращает место операционной системе, но переписывает таблицу и требует более жёсткой блокировки; это не безопасный способ «ускорить уборку» в разгар нагрузки.
Разбирайте инцидент в таком порядке:
- Подтвердите симптом. Сравните рост задержек, дисковой нагрузки, WAL и размеров отношений с обычным уровнем для этой системы. Проверка пройдена, если вы видите, какой показатель изменился первым, а не только итоговый рост базы.
- Найдите удерживающие снимки. Посмотрите на давно открытые транзакции, активность сессий и значения xmin у слотов репликации. Проверка пройдена, если найден владелец самой старой границы или установлено, что такого удержания нет.
- Проверьте, успевает ли обслуживание. Сопоставьте частоту обновлений с запусками и длительностью autovacuum, а также с прогрессом VACUUM. Проверка пройдена, если ясно, накапливается ли очередь между циклами.
- Свяжите рост с конкретными таблицами и индексами. Смотрите на динамику, а не на один снимок статистики. Проверка пройдена, если понятно, какие отношения дают основную долю роста и есть ли у них интенсивные обновления.
- Проверьте эффект изменения. После устранения причины или корректировки обслуживания сравните те же показатели на сопоставимой нагрузке. Проверка пройдена, если тенденция изменилась, а не просто исчезло одно предупреждение.
Граничные случаи: где MVCC становится дорогим
Самые опасные ситуации часто начинаются не с нехватки мощности, а с процесса, который долго не завершается. Открытая транзакция, ожидающая действий приложения, может удерживать снимок часами. Слот репликации может сохранять WAL, если потребитель отстал или отключился. Это связанные, но не тождественные проблемы: удержание старых версий и удержание WAL проверяют разными показателями.
Ещё одна граница — заморозка старых транзакционных идентификаторов. VACUUM не только освобождает место, но и помечает старые версии так, чтобы PostgreSQL мог безопасно работать с ограниченным пространством идентификаторов транзакций. Игнорировать предупреждения о приближении к заморозке нельзя: здесь вопрос уже не только в размере базы, но и в возможности продолжать запись. Настройку и пороги проверяйте по документации именно вашей версии и по журналам сервера.
Наконец, рост файла не равен росту полезных данных. Освобождённое VACUUM место обычно остаётся внутри таблицы; повторное использование зависит от того, где именно оно находится и как приходят новые строки. Если нужен возврат места операционной системе, это отдельная операция с собственными блокировками и риском простоя.
Что делать дальше
Для PostgreSQL 16 начните с базовой линии: зафиксируйте размеры крупнейших таблиц и индексов, количество изменений, работу autovacuum, длительность транзакций, старейшие xmin и состояние слотов. Повторяйте измерения на пике и после него. Если показатели ухудшаются, сначала найдите, кто удерживает старые версии и успевает ли уборка, а затем решайте, менять ли приложение, индексы или настройки обслуживания. Инвалидация кэша и выбор уровня изоляции — отдельные темы; здесь важны жизненный цикл версий и причины, по которым VACUUM не может их убрать.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: PostgreSQL 16: Introduction to MVCC, PostgreSQL 16: Routine Vacuuming
Частые вопросы
Если таблица растёт, значит ли это, что VACUUM сломан?
Почему autovacuum запускается, но проблема не исчезает?
Слот репликации увеличивает только WAL или ещё и таблицы?
Можно ли запустить VACUUM FULL, чтобы быстро освободить диск?
Почему обновления раздувают индексы сильнее, чем ожидалось?
Достаточно ли следить за n_dead_tup?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.