Материалы / Технические статьи / Параллельные планы PostgreSQL
Статья

Параллельные планы PostgreSQL

Почему планировщик отказывается от параллелизма, какие операции должны быть parallel safe и как читать Gather в плане.

Лена задумчиво изучала монитор:

— Макс, вот вроде параллелизм должен ускорять запросы. Почему Postgres обычно его не включает, даже если таблица огромная? Я вижу свободные ядра, но запрос упорно ползет в один поток.

Макс кивнул:

— Это классика. Кажется, что Parallel Seq Scan — это кнопка «Турбо», но планировщик считает стоимости планов лучше любого бухгалтера. И у него есть как минимум три причины для отказа от параллелизма:

1. Gather и стоимость передачи строк

Вся мощь воркеров разбивается об один процесс — Gather (Leader), который собирает результаты.

  • Если воркеры жестко фильтруют данные (WHERE status = 'ERROR') и отдают наверх лишь крупицы — это выгодно.

  • Если воркеры читают всё подряд и шлют миллионы строк Лидеру — это тормоз. Лидер захлебнется, а накладные расходы на пересылку данных между процессами (IPC) съедят весь выигрыш.

Представь себе, что воркеры это Экскаваторы, а Gather — Грузовик. Несколько экскаваторов работают слаженно и быстро, но всё, что они добывают, можно увезти только одним грузовиком. Если они грузят в него весь грунт подряд — образуется пробка, машины простаивают, а результат будет почти такой же, как если бы копал один. Но если каждый из них сортирует породу на месте и отправляет в кузов лишь ценный материал — даже этот узкий выезд перестает быть проблемой.

2. Размер имеет значение

По дефолту Postgres не параллелит таблицы меньше 8 MB (min_parallel_table_scan_size). Он считает, что накладные расходы на запуск процессов для такой «мелочи» выше выгоды.

3. Стоп-факторы

Параллелизма не будет, если:

  • Уровень изоляции транзакции SERIALIZABLE.

  • В запросе есть CTE с модификацией данных (INSERT/UPDATE/DELETE).

  • Используются курсоры (DECLARE ... CURSOR).

  • В запросе есть функции, не помеченные как PARALLEL SAFE.

— А как понять, что функция SAFE? — уточнила Лена.

— Это должен явно указать разработчик через ALTER FUNCTION. Если функция не меняет данные, не дергает сиквенсы (nextval), не лезет во временные таблицы и не имеет побочных эффектов — она может быть PARALLEL SAFE. Но если ошибешься — получишь не ускорение, а трудноуловимые баги.

Лена потерла виски:

— Слишком много «если». А что делать когда я знаю, что таблица тяжелая, знаю, что на сервере полно ресурсов? Но планировщик «стесняется» и запускает один поток, потому что по его формулам это «немного лучше». Можно как-то стукнуть кулаком по столу? Сказать: «Я босс, включай все ядра для этого отчета», но не ломая конфиг всего сервера?

Макс хитро улыбнулся и придвинулся ближе:

— Можно. Есть способ выдать конкретной таблице «VIP-пропуск».

-- Подсказать желаемое число воркеров для параллельного сканирования таблицы
ALTER TABLE sales_archive SET (parallel_workers = 4);

Лена чуть подалась к экрану:

— А когда этот «VIP-пропуск» оправдан?

Макс задумался на секунду:

— Представь старые архивные таблицы. К ним редко обращаются, но когда обращаются — метко. Годовые отчёты, аудиты, исторические сводки. Вот там есть смысл сказать планировщику: «В этот раз — без скромности».

Он постучал по команде на экране:

— Планировщик учтёт это значение при выборе степени параллельного сканирования таблицы. Но команда не заставляет его выбрать параллельный план и не отменяет ограничения max_parallel_workers_per_gather, общего пула воркеров и требований PARALLEL SAFE.

Лена кивнула, но тут же нахмурилась:

— А если кто-нибудь пустит по этой таблице обычные запросы?

— Вот именно, — Макс повернулся к ней. — Запросы, для которых планировщик выберет параллельный scan этой таблицы, могут запросить больше воркеров. При конкурентной нагрузке это способно упереться в CPU и общий лимит процессов. Поэтому параметр нужно проверять планами и нагрузочным тестом, а при необходимости вернуть к исходному значению через ALTER TABLE sales_archive RESET (parallel_workers).

Он снова улыбнулся и откинулся на спинку кресла:

— Настоящая сила не в том, чтобы включить 32 ядра… а в том, чтобы знать, когда их лучше не трогать.