Четыре причины простоев PostgreSQL, и как их лечит pgrust, Rust-реимплементация Postgres

Автор поста и разработчик pgrust (переписывание PostgreSQL на Rust) описал четыре типа проблем, которые, по его наблюдениям за десятками стартапов без выделенного администратора БД, чаще всего приводят к простоям Postgres в проде, и объяснил, что в pgrust собираются сделать иначе.

Первая причина, vacuum и переполнение ID транзакций. Postgres не удаляет строку физически при DELETE, а только помечает её как удалённую (это нужно, например, для отката транзакций); реально освобождает место фоновый процесс vacuum. Проблема в том, что vacuum сам по себе потребляет много дискового ввода-вывода и конкурирует за него с обычными запросами: если его сделать слишком агрессивным, тормозят запросы, если слишком мягким, удалённые строки накапливаются быстрее, чем он успевает их убирать. Хуже того, Postgres помечает строки 32-битным ID транзакции, а значит всего можно провести около 4 миллиардов транзакций, прежде чем ID начнут переиспользоваться; задача vacuum, вовремя «освобождать» старые ID. Если vacuum сильно отстаёт, Postgres принудительно останавливает базу, чтобы не допустить повторного использования ещё «живых» ID и порчи данных. Автор считает, что именно это стало причиной тысяч, если не десятков тысяч простоев Postgres в индустрии. В pgrust рассматривают два пути: давно существующий, но так и не принятый в апстрим патч с переходом на 64-битные ID транзакций (отклонён из-за разрыва совместимости со старыми версиями и роста накладных расходов на 32 бита на строку), и альтернативную архитектуру вовсе без vacuum, по образцу undo-лога Oracle, где уже есть отдельные исследовательские проекты для Postgres.

Вторая причина, лимит соединений и параллелизм запросов. У Postgres есть настраиваемый максимум одновременных подключений; при его превышении новые подключения просто не проходят, и приложение падает. Изменить этот лимит можно только с перезапуском базы, поэтому его нужно правильно оценить заранее, иначе выручают костыли вроде PgBouncer. Причина лимита в том, что каждое новое соединение в Postgres, это отдельный ОС-процесс, а процессы дороги и по CPU, и по времени запуска; это же ограничивает и параллельную обработку внутри одного запроса (Postgres распараллеливает запрос через дополнительные процессы только для долгих запросов). Идея перехода Postgres с процессной модели на потоковую обсуждается годами, но так и не была реализована, это слишком масштабный и рискованный рефакторинг, а процессы дают изоляцию: сбой в одном процессе не должен затрагивать остальные. pgrust с самого начала спроектирован на потоках, а нужную изоляцию и безопасность памяти вместо процессной модели обеспечивают гарантии Rust, проверяемые на этапе компиляции.

Третья причина, неудачные планы запросов. Планировщик Postgres выбирает из десятков алгоритмов выполнения запроса на основе собранной статистики по таблицам, и обычно справляется хорошо, но когда ошибается, разница между хорошим и плохим планом может составлять 10 миллисекунд против 10 минут. При этом у Postgres нет управляемых «подсказок планировщику» (planner hints), как в MySQL: единственный доступный рычаг, полностью отключить целый класс алгоритмов глобально. Автор приводит пример из практики: в одной из его прежних компаний пришлось глобально отключить nested loop join, потому что Postgres упорно выбирал этот алгоритм и из-за активного использования JSON резко замедлял почти все запросы. В pgrust на перспективу закладывают адаптивный планировщик: если запрос, обычно выполнявшийся за 10 мс, вдруг занимает 10 секунд, система сама распознаёт регресс, проверяет, изменился ли план или статистика, и вносит коррекцию, вместо того чтобы ждать, пока это заметит администратор. Пока эта функция не реализована.

Четвёртая причина, работа с JSON. Postgres удобно хранит и выбирает неструктурированные данные в JSON, но для JSON-колонок он не собирает почти никакой статистики, в отличие от обычных типов, где ведётся гистограмма и список самых частых значений. При фильтрации по JSON-полю Postgres просто предполагает, что совпадёт 0,1% строк, это захардкоженное магическое число, никак не связанное с реальными данными (в реальности может быть и 80%, и 0,0001%), из-за чего планировщик почти гарантированно строит плохой план. Вдобавок Postgres не сжимает JSON: одинаковые ключи и значения при повторении хранятся заново, без экономии места. По опыту автора в компании Heap, где активно использовали JSON, это была серьёзная головная боль. В pgrust сначала хотят добавить реальную статистику для JSON (по оценке автора, это закроет 80% проблем), опираясь на существующие наработки-патчи, а затем, сжатие данных, кросс-строчное либо словарное.

Сейчас, по словам автора, pgrust проходит 96% официального регрессионного тестового набора Postgres. Текущие приоритеты проекта: довести совместимость до 100%, стабилизировать код до состояния, которому можно доверять, и затем перейти к архитектурным улучшениям, начиная с 64-битных ID транзакций. Попробовать pgrust можно через WASM-демо в браузере на pgrust.com; исходники выложены на GitHub, есть сообщество в Discord и рассылка с обновлениями.

Ключевые факты

  • Четыре главные причины простоев Postgres в проде: vacuum и переполнение ID транзакций, лимит соединений, плохие планы запросов, проблемы с JSON
  • 32-битный ID транзакций ограничивает Postgres примерно 4 миллиардами транзакций; при сильном отставании vacuum база принудительно останавливается, чтобы не допустить порчи данных
  • Каждое соединение в Postgres, отдельный ОС-процесс, что ограничивает параллелизм и требует ручной настройки лимита соединений с перезапуском базы при изменении
  • У Postgres нет подсказок планировщику (в отличие от MySQL); при плохом плане единственный выход, отключать целые классы алгоритмов глобально
  • Postgres не собирает статистику и не сжимает JSON: фильтры по JSON-полям оцениваются по захардкоженной цифре 0,1%, что ведёт к плохим планам

Почему это важно

Простои Postgres из-за vacuum, лимита соединений, плохих планов и JSON, не гипотетические, а самые частые причины реальных инцидентов, особенно у стартапов без выделенного администратора БД. Автор поста не просто перечисляет известные боли, а разрабатывает pgrust, реимплементацию Postgres на Rust, которая пытается устранить эти проблемы на уровне архитектуры (потоки вместо процессов, статистика и сжатие для JSON, потенциально отказ от vacuum), а не точечными патчами поверх существующего движка.

Кому это важно

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

Как это применить

Часть проблем можно снизить уже сегодня в ванильном Postgres: заранее закладывать разумный лимит соединений и использовать PgBouncer, следить за отставанием vacuum и переполнением ID транзакций, при необходимости отключать проблемные алгоритмы планировщика (как автор отключил nested loop join). Сам pgrust можно опробовать через WASM-демо в браузере на pgrust.com, посмотреть исходный код на GitHub или подключиться к сообществу в Discord.

Можно ли доверять

Автор опирается на собственный практический опыт, общение со множеством стартапов и работу с JSON в компании Heap, а не на абстрактные рассуждения. По его словам, pgrust уже проходит 96% официального регрессионного набора тестов Postgres, что говорит о серьёзной степени совместимости. При этом сам автор честно называет одним из текущих приоритетов «довести pgrust до состояния, которому люди смогут доверять», то есть проект сам признаёт себя ещё не готовым к продакшену, а часть заявленных решений (адаптивный планировщик, 64-битные ID) существуют пока на уровне планов, а не реализованного кода.

Риски и подводные камни

Многие из описанных решений, 64-битные ID транзакций, архитектура без vacuum по типу undo-лога Oracle, адаптивный планировщик, находятся на стадии идеи или исследования, а не готовой функциональности. Переход с процессной модели Postgres на потоковую, фундаментальное архитектурное решение с историей многолетних неудачных попыток в самом Postgres; безопасность здесь целиком опирается на гарантии Rust, а не на проверенную десятилетиями изоляцию процессов. Совместимость pgrust пока 96%, а не 100%, и по признанию самого автора проект ещё не достиг уровня доверия, нужного для продакшен-нагрузки.

«Готов поспорить, что именно это стало причиной тысяч, если не десятков тысяч простоев Postgres.»

— craigkerstiens, автор поста и разработчик pgrust