Learning Platform
Урок 13.02 · 23 мин
Продвинутый
Partition pruningEXPLAINRuntime pruningenable_partition_pruning

В прошлом уроке мы научились разбивать таблицу на partitions. Но если planner всё равно читает все 24 partition при запросе за один день — никакого выигрыша нет, только overhead. Реальная сила partitioning — в partition pruning: способности planner’а отсечь partitions, которые гарантированно не содержат искомых строк, до того как Append-узел дойдёт до их сканирования.

Pruning — это не runtime-фильтрация. Это исключение partition из плана целиком: к heap-файлам этих partitions запрос даже не обращается, в EXPLAIN они вообще не появляются.

Как работает pruning

Когда ты пишешь WHERE placed_at >= '2024-03-01' AND placed_at < '2024-04-01', planner смотрит на partition bounds:

Логика partition pruning

Planner сравнивает predicate с partition bounds. Если пересечения нет — partition отбрасывается, и её heap не читается совсем.

WHERE placed_at >= '2024-03-01' AND placed_at < '2024-04-01'predicate
orders_2024_01[Jan, Feb)
нет пересеченияpruned
orders_2024_02[Feb, Mar)
нет пересеченияpruned
orders_2024_03[Mar, Apr)
полностью внутривключена в план
orders_2024_04[Apr, May)
нет пересеченияpruned
Append рассматривает только 1 partition вместо 4seq_scan на 1/4 объёма

Этот анализ называется

partition pruning
. Включён по умолчанию (enable_partition_pruning = on). Если выключить — все partitions включаются в план, и Append проходит по всем (это удобно для дебага: видно, что было бы без оптимизации).

Compile-time vs runtime pruning

Есть два момента, когда Postgres может применить pruning:

  • Compile-time (planning) — predicate состоит из констант, planner вычисляет его сразу и строит план только из подходящих partitions. В EXPLAIN остальные просто не появляются.
  • Runtime (execution, Postgres 11+) — predicate содержит параметр, например prepared statement или подзапрос. Planner не знает значения заранее, поэтому строит план со всеми partitions, но во время выполнения отсекает ненужные. В EXPLAIN они видны с пометкой Subplans Removed.
Compile-time vs runtime pruning

Compile-time убирает partitions из плана; runtime оставляет их в плане, но при выполнении динамически пропускает.

Compile-timeplanning phase
WHERE placed_at >= '2024-03-01'константа
EXPLAINвидно только нужные partitions
Когдаliterals, immutable functions
Runtimeexecution phase
WHERE placed_at = $1параметр
EXPLAIN ANALYZESubplans Removed: N
Когдаprepared, subquery params, nested loop

Подготовим стенд

Создаём partitioned orders с 12 месячными partition. Берём год данных, чтобы pruning было заметно.

PostgreSQL

Compile-time pruning в действии

Запрос с константой — самый простой и понятный случай.

Compile-time pruning. EXPLAIN покажет только orders_2024_03 — остальные partitions Postgres даже не упомянет.

PostgreSQL

Ищи в плане две вещи: (1) под Append должна быть одна Seq Scan на orders_2024_03, остальные partitions отсутствуют; (2) Buffers: hit/read — на порядок меньше, чем при запросе ко всем partitions.

Сравним с тем же запросом, но без partition pruning:

Тот же запрос, но pruning выключен. Видно, как Append сканирует ВСЕ 12 partition — это то, что было бы без оптимизации.

PostgreSQL

Видишь разницу: с pruning — 1 partition × ~8K строк. Без pruning — 12 partitions × средние 8K строк, фильтрация в каждой даёт пустой результат, но buffers всё равно потрачены.

Runtime pruning через параметры

Когда predicate содержит параметр, planner не знает его значения. В Postgres 10 это означало «pruning не работает». С Postgres 11 появился механизм runtime pruning: planner оставляет в плане все partition, но кладёт условие в Init Plan, который вычисляется в начале выполнения и динамически отбрасывает Append-узлы.

Prepared statement с параметром — runtime pruning в действии. Смотри на `Subplans Removed: N` в плане.

PostgreSQL

Ищи строчку Subplans Removed: 11 (или похожую) — это означает, что из 12 partition-планов 11 были динамически удалены. До Postgres 11 ты увидел бы Append на все 12 и фильтрацию строк во время сканирования. С runtime pruning — heap-файлов 11 partition даже не касаются.

Когда pruning не работает

Главное правило: функция от partition key убивает pruning. Planner сравнивает constraint expressions символьно. Если ты пишешь WHERE date_trunc('month', placed_at) = '2024-03-01', planner не понимает, что date_trunc('month', x) = M эквивалентно x >= M AND x < M + INTERVAL '1 month'. Он видит произвольную функцию и сдаётся.

Функция на partition key — pruning не сработал. Все 12 partition в плане.

PostgreSQL

Это типовая ошибка: разработчик пишет «удобный» predicate с функцией, и pruning тихо ломается. Переписывай в чистые сравнения:

-- ПЛОХО:
WHERE date_trunc('month', placed_at) = '2024-03-01'

-- ХОРОШО:
WHERE placed_at >= '2024-03-01'
  AND placed_at <  '2024-04-01'

То же относится к EXTRACT(month FROM placed_at) = 3, placed_at::date = '2024-03-15', TO_CHAR(placed_at, 'YYYY-MM') = '2024-03' — все они блокируют pruning. Если ты не можешь переписать запрос (например, ORM генерирует), есть выход: создать expression index на нужную функцию и работать через него — но pruning всё равно не сработает, индекс просто ускорит full scan на каждой partition.

Pruning по JOIN-у (partitionwise)

Pruning работает не только по WHERE, но и по JOIN — при определённых условиях. Если planner видит, что условие JOIN сводит partition key к константе из другой таблицы, он применяет pruning. На простых запросах это работает «само».

JOIN с фильтром на partition key через другую таблицу. Pruning срабатывает на orders по дате из dates.

PostgreSQL

Planner отсекает 11 из 12 partition по диапазону, и только потом делает IN-фильтрацию.

Проверка знанийKnowledge check
У тебя 24 monthly partition orders. Запрос: SELECT * FROM orders WHERE placed_at::date BETWEEN '2024-03-01' AND '2024-03-31'. План показывает Append на все 24 partition. В чём причина и как починить?
ОтветAnswer
Причина — приведение типа placed_at::date. Postgres видит выражение на partition key (cast в date), а не само placed_at, и pruning отключается. Починки две. (1) Перепиши: WHERE placed_at >= '2024-03-01' AND placed_at < '2024-04-01'. Тип placed_at тот же, planner понимает диапазон и оставляет 1 partition вместо 24. (2) Если запрос приходит от ORM и переписать нельзя — изменить тип колонки placed_at на DATE (если время не нужно) или сделать generated column placed_date GENERATED ALWAYS AS (placed_at::date) STORED и партиционировать по ней.

Чек-лист

  • Pruning — исключение partition из плана до их чтения; работает при enable_partition_pruning = on (по умолчанию).
  • Compile-time pruning срабатывает при literal-константах; в EXPLAIN видны только живые partition.
  • Runtime pruning (Postgres 11+) работает для prepared statements и подзапросов; в EXPLAIN ANALYZE — Subplans Removed: N.
  • Функция на partition key убивает pruning: date_trunc(...), EXTRACT(...), cast::date. Пиши чистые сравнения по самой колонке.
  • HASH partitioning поддерживает pruning только при равенстве (=), не при < или BETWEEN.
  • Диагностика: SET enable_partition_pruning = off и сравни планы — увидишь, что было бы без оптимизации.
Metadata и Statistics в Apache Parquet Разреженный индекс

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Чем compile-time partition pruning отличается от runtime pruning?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5