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

План не знает, сколько запросов уже ждут в очереди
PostgreSQL может выбрать план, который выглядит разумно по оценке стоимости, а под нагрузкой превращается в тысячи лишних обращений к таблице, переполнение хеш-таблиц и ожидание диска. В PostgreSQL 17 планировщик строит план по статистике и настройкам стоимости, а не подстраивает его под текущую очередь запросов. Поэтому первым обычно ломается не сам оптимизатор, а предположения, на которых он строит план.
Ниже — разбор для PostgreSQL 17: как читать симптомы, где проверять оценки и почему подсказка планировщику может закрепить проблему вместо того, чтобы решить её. Инвалидация кешей и настройка пула соединений — отдельные темы; здесь речь только о выборе и исполнении плана.
Первый сбой — расхождение между оценкой и реальностью
Планировщик оценивает, сколько строк пройдёт через каждый узел, и сравнивает предполагаемую стоимость альтернатив. Если статистика устарела или не описывает распределение данных, оценка расходится с фактом. Дальше ошибка распространяется по плану: выбор соединения, порядок операций и потребность в памяти могут оказаться неподходящими.
Смотрите не только на итоговое время. В плане, полученном через EXPLAIN ANALYZE, сравните строки «rows» и «actual rows» у каждого узла, а также число повторов «loops». Например, узел может возвращать мало строк за один проход, но запускаться тысячи раз. При этом EXPLAIN ANALYZE действительно выполняет запрос и добавляет накладные расходы на измерение. Не запускайте его бездумно на запросе, который меняет данные или дорого нагружает прод.
Второй сигнал — внезапная смена плана после роста таблицы, массовой загрузки или обновления статистики. Само обновление не означает, что планировщик сломался: изменились данные, а вместе с ними — оценки. Важно выяснить, какая оценка поменялась и стала ли она ближе к фактическому числу строк.
Где планировщик ошибается чаще всего
Статистика не успевает за данными
Обычная статистика описывает таблицу и распределение отдельных значений, но собирается по выборке. После резкой загрузки данных выборка или прежние сведения могут хуже отражать актуальную картину. Проверьте, когда таблицу анализировали, и не отключено ли для неё автообновление статистики. Ручной ANALYZE может быть уместен после крупного изменения данных, но запускать его наугад для всех таблиц — не диагностика.
Для секционированных таблиц есть отдельная ловушка в PostgreSQL 17: фоновый процесс автообслуживания не анализирует родительскую секционированную таблицу. Изменения в секциях сами по себе не гарантируют обновления статистики родителя. Если запросы обращаются к нему, проверьте статистику родительской таблицы и при необходимости запланируйте ручной ANALYZE.
Данные связаны, а оценки — нет
Предположим, запрос фильтрует строки по городу и почтовому индексу. Если эти поля тесно связаны, оценка по каждому условию отдельно может сильно ошибиться для их сочетания. В PostgreSQL есть расширенная статистика для нескольких столбцов одной таблицы: зависимости, наиболее частые комбинации и число различных сочетаний. Она помогает не во всех случаях и не описывает связи между разными таблицами.
Расширенная статистика не исправит неверный тип данных, неудачный индекс или перекос, который плохо попал в выборку. Сначала найдите конкретный узел с расхождением оценённых и фактических строк. Затем решайте, что проверять: свежесть ANALYZE, распределение значений, статистику по комбинации столбцов или саму форму запроса.
Параметры запроса имеют разную цену
Подготовленный запрос может выполняться с разными параметрами: один выбирает десяток строк, другой — значительную долю таблицы. План, удачный для первого случая, может проиграть для второго. В PostgreSQL планировщик способен выбирать между планом для конкретных значений и универсальным планом; для диагностики опытные инженеры проверяют поведение подготовленных запросов и настройку plan_cache_mode. Это не повод глобально переключать режим: решение зависит от распределения параметров и частоты запросов.
Что становится дорогим при росте параллельности
Планировщик оценивает относительную стоимость операций, а не знает, сколько таких операций одновременно выполняют другие сессии. При росте параллельности умножаются последствия неудачной оценки: дорогой вложенный цикл или повторное чтение страниц начинают занимать больше ресурсов и дольше удерживают соединения.
Другой кандидат на первый сбой — операции сортировки и хеширования. Если доступной рабочей памяти недостаточно, часть работы может перейти на диск. Но work_mem задаётся не как единый бюджет на сервер: память могут использовать несколько операций внутри запроса и множество одновременных запросов. Поднимать значение «для ускорения» без учёта параллельности опасно — выигрыш одного запроса способен обернуться давлением на память всего сервера.
Параллельный план тоже не бесплатен. Он может помочь тяжёлому запросу, но на загруженной системе появляются дополнительные рабочие процессы и конкуренция за CPU и пропускную способность хранилища. Сравнивайте не только время одного запроса, но и очередь, загрузку ресурсов и задержки соседних запросов при одинаковой нагрузке.
| Симптом | Что проверить | Чего не делать первым |
|---|---|---|
| Фактических строк намного больше оценки | Свежесть статистики, перекос значений, сочетания столбцов | Не добавлять индекс без проверки узла плана |
| Много loops у узла с чтением таблицы | Оценки строк до соединения и порядок соединений | Не считать малое число строк за проход доказательством низкой цены |
| Сортировка или хеширование стали медленнее под параллельной нагрузкой | Файлы временных данных, параллельность, общий расход памяти | Не увеличивать work_mem глобально без нагрузочного теста |
| Один подготовленный запрос ведёт себя по-разному на разных параметрах | Распределение значений и выбор плана для параметров | Не фиксировать один план для всех значений без проверки |
Подсказка может убрать симптом и закрепить ошибку
В PostgreSQL нет встроенного универсального механизма подсказок в комментариях SQL, который штатно заставляет планировщик выбирать конкретный индекс или порядок соединения. Существуют сторонние расширения, например pg_hint_plan, но их совместимость и поведение нужно отдельно проверять для конкретной версии PostgreSQL.
Подсказка бывает полезна как диагностический эксперимент: сравнить альтернативный план и проверить гипотезу. Цена — дополнительная зависимость от расширения и риск того, что зафиксированный выбор перестанет подходить после изменения данных или версии. Особенно опасно лечить подсказкой неверную оценку строк: причина останется, а план может стать ещё хуже для других значений параметров.
Если вы не можете объяснить, какая оценка заставила PostgreSQL выбрать плохой план, подсказка пока не решение. Сначала найдите расхождение между ожидаемыми и фактическими строками.
Как разбирать проблему без догадок
Зафиксируйте запрос, параметры, версию PostgreSQL и момент появления замедления. Проверьте, изменилась ли параллельность, объём данных или план после обновления статистики. Результат шага — воспроизводимый запрос и условия сравнения.
Снимите план через EXPLAIN (ANALYZE, BUFFERS, SETTINGS) на безопасном запросе и сравните «rows» с «actual rows», учитывая loops. SETTINGS помогает увидеть некоторые отличающиеся настройки планировщика; BUFFERS показывает работу с буферами. Результат — узел, где оценка или стоимость исполнения расходится с ожиданием.
Проверьте статистику именно тех таблиц, которые участвуют в этом узле, включая родителя секционированной таблицы. Если данные недавно менялись, сравните план до и после актуального ANALYZE на копии или в подходящее окно. Результат — подтверждение или исключение устаревших статистических данных.
Изменяйте одну вещь за раз: статистику, индекс, запрос или настройку. Сравнивайте не только задержку одного запуска, но и поведение при репрезентативной конкурентной нагрузке. Результат — понятно, какое изменение помогло и не перенесло ли оно проблему на другие запросы.
Границы метода: что ломается после исправления
Улучшение оценок не гарантирует ускорения, если узкое место — диск, блокировки, нехватка соединений или конкуренция за CPU. EXPLAIN ANALYZE показывает исполнение одного запроса в конкретных условиях, а не весь эффект на очередь в проде. А замер на прогретом кеше нельзя напрямую сравнивать с холодным запуском.
Не делайте вывод по одному удачному прогону. Повторите сравнение с разными значениями параметров, проверьте характерные объёмы данных и нагрузку, близкую к рабочей. Если план хорош для частого значения, но проваливается на редком тяжёлом запросе, среднее время может скрыть проблему, которую пользователи видят как длинный хвост задержек.
Что делать дальше
Для PostgreSQL 17 начните с самого медленного запроса, который регулярно появляется в нагрузке: сохраните его план, найдите узел с наибольшим расхождением «rows» и «actual rows» и установите причину. Затем проверьте статистику, параметры запроса и расход временных данных под параллельной нагрузкой. Измеряйте задержки и число одновременных запросов до и после каждого изменения. Если переходите на другую основную версию PostgreSQL, повторите проверку планов: смена версии может изменить поведение оптимизатора.
Термин: кардинальность — число строк, которое планировщик ожидает получить из таблицы или узла плана. Ошибка в оценке кардинальности может изменить выбор соединения и порядок операций.
IT-архитектор / SQL-оптимизатор: Поиск N+1, оптимизация запросов, code review, поиск багов.
Спросить за 20 ₽Источники: PostgreSQL 17: Planner Statistics, PostgreSQL 17: EXPLAIN
Частые вопросы
EXPLAIN ANALYZE безопасно запускать на проде?
Что делать, если фактических строк больше, чем указано в rows?
Можно ли заставить PostgreSQL использовать конкретный индекс подсказкой?
Почему запрос тормозит только под нагрузкой, хотя план тот же?
Что проверить, если таблица секционирована?
Подойдёт ли этот разбор для другой версии PostgreSQL?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.