Найти в Дзене
Postgres Professional

Иногда один и тот же запрос внезапно раздувается по времени: с долей миллисекунды до минут и даже часов

Причина часто не в данных и не в железе. Планировщик может недооценить кардинальность и стоимость шагов, выбрать слишком дешевый для себя план, а в реальности получить Nested Loop там, где выгоднее Hash Join, или Seq Scan вместо Index Scan. Бывает и так, что дерево запроса устроено неудачно: нужные условия спрятаны, и часть эффективных алгоритмов просто не рассматривается. Эти случаи закрывает расширение pgpro_planner. Оно переписывает неудобные для оптимизатора куски дерева запроса в более дружелюбный вид еще до того, как основной планировщик выберет план. Ниже — три примера небольших оптимизаций с большим эффектом. ✔️ IN (VALUES ...) → = ANY(array) В одном запросе из мира 1С фильтры были оформлены как IN (VALUES ...) для numeric и bytea. До обновления статистики он работал быстрее 1 мс, после — около 7,5 с. В плане львиная доля времени ушла в Nested Loop Semi Join и материализацию маленьких таблиц из VALUES. Индекс почти не помогал, потому что сравнение было внутри join-фильтр

Иногда один и тот же запрос внезапно раздувается по времени: с долей миллисекунды до минут и даже часов. Причина часто не в данных и не в железе.

Планировщик может недооценить кардинальность и стоимость шагов, выбрать слишком дешевый для себя план, а в реальности получить Nested Loop там, где выгоднее Hash Join, или Seq Scan вместо Index Scan.

Бывает и так, что дерево запроса устроено неудачно: нужные условия спрятаны, и часть эффективных алгоритмов просто не рассматривается.

Эти случаи закрывает расширение pgpro_planner. Оно переписывает неудобные для оптимизатора куски дерева запроса в более дружелюбный вид еще до того, как основной планировщик выберет план.

Ниже — три примера небольших оптимизаций с большим эффектом.

✔️ IN (VALUES ...) → = ANY(array)

В одном запросе из мира 1С фильтры были оформлены как IN (VALUES ...) для numeric и bytea. До обновления статистики он работал быстрее 1 мс, после — около 7,5 с.

В плане львиная доля времени ушла в Nested Loop Semi Join и материализацию маленьких таблиц из VALUES. Индекс почти не помогал, потому что сравнение было внутри join-фильтра, а не в Index Cond.

Решение: собрать константы VALUES в массив и переписать условие как = ANY(array). Тогда индекс попадает в Index Cond, из плана исчезают лишние Semi Join и временные таблицы, а время падает с секунд до долей миллисекунды.

Эту функциональность в итоге закоммитили в PostgreSQL 18.

✔️ Простейшая арифметика, которая ломает индекс

Выражения x + 0, x * 1, x / 1, x - 0 равны x, но планировщик не обязан это распознавать. Поэтому условие x + 0 > 900 может дать Seq Scan, хотя x > 900 позволяет построить Index Only Scan. Шаг упрощения делает предикат индексируемым еще до планирования.

✔️ Memoize для коррелированных подзапросов

В бенчмарке один и тот же подзапрос выполнялся 14 835 720 раз и давал Execution Time 496 690 мс. Узел Memoize кеширует результат по ключу параметра и возвращает его из кеша при повторе. Итог — 115 528 мс, то есть ускорение в четыре с лишним раза.

Подключается это все как обычное расширение оптимизатора: через LOAD pgpro_planner в сессии или через shared_preload_libraries с перезагрузкой.

Затем включают pgpro_planner.enable = true и при необходимости управляют отдельными функциями: преобразованием VALUES, упрощением тривиальных выражений и Memoize.

Подробности читайте на Хабре.