ADG Оставить заявку
Блог Данные и аналитика 5 мин чтения

PostgreSQL 17 в продуктиве: полгода с incremental vacuum и реальный тюнинг под write-нагрузку

Полгода гоняем PostgreSQL 17 под высоким write-трафиком. Разбираем, что дал incremental vacuum в реальных таблицах и какие параметры пришлось менять.

Контекст момента

PostgreSQL 17 в продуктивных нагрузках: отчёты раннего опыта от российских команд

PostgreSQL 17 вышел в сентябре прошлого года, и к этому моменту у нас накопилось примерно полгода боевого опыта - достаточно, чтобы говорить не о фичах из release notes, а о том, как это работает под реальной нагрузкой. Основная нагрузка в наших случаях - DWH с постоянным потоком вставок и обновлений: ETL-процессы, которые льют данные круглосуточно, плюс аналитические запросы поверх.

Главная вещь, за которой мы следили - incremental vacuum. Ниже честный разбор того, что получили и что пришлось крутить руками.

Почему vacuum был болью раньше

В PostgreSQL любое обновление строки не изменяет её на месте, а создаёт новую версию и помечает старую как мёртвую. Autovacuum чистит мёртвые версии - но в версиях до 17 он это делал за один проход по всей таблице. На больших таблицах с непрерывным write-трафиком это означало: autovacuum запускается, идёт через всю таблицу (долго), за это время накапливаются новые мёртвые строки, и круг замыкается. При определённой интенсивности записи autovacuum не успевал за потоком, таблица пухла, bloat рос.

У нас была одна таблица - центральный лог событий для DWH, около 400 млн строк, с постоянными UPDATE по статусным полям - которая регулярно приходила с bloat-ом в 20-30% по данным pgstattuple. Не катастрофа, но неприятно, и VACUUM FULL на 400 млн строк - это окно обслуживания.

Что сделал incremental vacuum в PostgreSQL 17

Ключевое изменение в PG 17 - переработанный алгоритм обработки мёртвых строк: вместо старой структуры vacuum теперь использует TIDStore - компактное хранилище TID мёртвых строк, которое позволяет за один проход по таблице собрать и обработать значительно больший объём dead tuples при том же maintenance_work_mem. Следствие: число проходов по индексам в рамках одного vacuum-цикла сокращается - на больших таблицах с агрессивным UPDATE это могло быть несколько проходов по каждому индексу, теперь чаще один.

На практике это меняет динамику. На тех же ресурсах autovacuum справляется с большим потоком мёртвых строк за цикл, реже уходит в несколько итераций по индексам - и это критично для write-heavy нагрузки, где длинный autovacuum конкурирует за I/O с ETL.

Что мы увидели на той проблемной таблице: bloat стабилизировался в районе 8-12% вместо 20-30%. Конфликтов autovacuum с ETL стало заметно меньше - это было видно и по логам autovacuum, и по тому, что пропали характерные пики latency на ETL в моменты, когда autovacuum активно работал.

Параметры, которые пришлось поменять

Incremental vacuum работает автоматически, но из коробки параметры autovacuum не оптимальны для высокого write-трафика. Что мы реально поменяли:

autovacuum_vacuum_cost_delay. По умолчанию 2ms - это throttle, который не даёт autovacuum сожрать всё I/O. На наших серверах с NVMe и умеренной I/O-нагрузкой в ночное время мы опустили до 1ms для write-heavy таблиц через storage parameters. Для аналитических таблиц с низким write-трафиком оставили дефолт - там торопиться незачем.

autovacuum_vacuum_scale_factor. Дефолт 0.2 - autovacuum запускается когда мёртвых строк накопилось 20% от размера таблицы. На таблице в 400 млн строк это означает ждать 80 млн мёртвых строк - слишком поздно. Поставили 0.01 на проблемную таблицу через ALTER TABLE ... SET (autovacuum_vacuum_scale_factor = 0.01). Запускается чаще, каждый проход меньше - с incremental vacuum это работает хорошо.

autovacuum_vacuum_insert_scale_factor. Новый параметр, появился ещё в PG 13, но мы на него обратили внимание именно на 17-м. Контролирует запуск vacuum для таблиц, куда идут только INSERT (без UPDATE/DELETE). У нас есть такие - staging-таблицы для ETL. Оставили дефолт 0.2 - там vacuum нужен реже.

maintenance_work_mem. Увеличили с 64MB до 256MB. Vacuum использует этот параметр для хранения TIDStore - структуры с мёртвыми строками в памяти во время прохода. Чем больше maintenance_work_mem, тем больше dead TID влезает за один проход и тем меньше потребуется проходов по индексам.

Что не изменилось и где ожидания не совпали

Incremental vacuum не решает проблему table bloat целиком - он снижает темп его накопления. Если таблица уже сильно раздута, VACUUM FULL всё равно нужен. Мы это сделали один раз при переходе на 17-й как плановое окно обслуживания, и потом уже bloat держится в норме.

Также incremental vacuum не помогает с bloat индексов - индексы vacuum'ируются отдельной фазой, и она не стала incremental в PostgreSQL 17. Индексный bloat мониторим отдельно и периодически делаем REINDEX CONCURRENTLY на нагруженных индексах.

Ещё одно наблюдение: на таблицах с очень высоким UPDATE-трафиком (тысячи обновлений в секунду) incremental vacuum заметно помогает, а на таблицах с умеренной нагрузкой разница между 16-м и 17-м практически незаметна - там и раньше autovacuum справлялся нормально.

Что ещё полезного вытащили из 17-го

Помимо vacuum: улучшенный планировщик лучше справляется с партиционированными таблицами. У нас несколько DWH-таблиц партиционированы по дате - план стал чище, в частности pruning партиций при фильтрации по дате перестал давать сюрпризы в EXPLAIN ANALYZE.

Логическая репликация в 17-м тоже стала надёжнее - конкретно пропало несколько нюансов с репликацией DDL-изменений, которые в 16-м требовали ручного вмешательства. Но это отдельная тема.

Итог на сейчас

Полгода в продуктиве под write-нагрузкой - достаточный срок, чтобы сказать: переход на 17-й оправдан именно для таблиц с высоким UPDATE-трафиком. Incremental vacuum - не маркетинговая фича, а реальное изменение, которое меняет поведение autovacuum в лучшую сторону.

Тем, кто занимается аналитическими базами данных и DWH и ещё сидит на 15-м или 16-м: апгрейд стоит запланировать, особенно если bloat является регулярной головной болью. Сам апгрейд через pg_upgrade с 16 на 17 прошёл у нас без сюрпризов - стандартная процедура.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.