PostgreSQL

Параллельные запросы в PostgreSQL

Аналитический запрос по таблице в 200 GB идёт 3 минуты. Один CPU-ядро скучает. Ещё 15 ядер простаивают. PostgreSQL умеет задействовать все - нужно только разрешить.

  • **Cloudflare** использует Parallel Seq Scan для hourly aggregation логов (~500 млн строк/час) - 8 workers сокращают время с 4 мин до 40 сек на том же железе
  • **Zalando** перевела аналитические отчёты inventory на параллельные запросы: таблица stock_movements (80 GB) теперь агрегируется за 12 сек вместо 70 сек
  • **GitLab** для внутренней аналитики (таблица events, 2 млрд строк) настроила max_parallel_workers_per_gather=6 для reporting-роли и отдельно =0 для app-роли - разные профили нагрузки

Как работает параллелизм в PG

PostgreSQL делит тяжёлую работу между несколькими процессами-workers. Один процесс - leader - координирует, остальные - workers - параллельно сканируют куски таблицы или вычисляют агрегаты. Результаты собираются через Gather-узел в план-дереве.

Параллелизм включается автоматически, если размер таблицы превышает `min_parallel_table_scan_size` (8 MB по умолчанию) и planner оценивает достаточно строк. Принудительно проверить: `SET max_parallel_workers_per_gather = 4; EXPLAIN SELECT ...`

Что делает узел Gather в параллельном плане?

Parallel Seq Scan

Parallel Sequential Scan делит heap-файл таблицы на блоки (по 8 KB) и раздаёт их workers динамически. Каждый worker берёт следующую группу блоков через shared memory-очередь. На таблице orders в 50 млн строк Uber сократил время аналитических выборок с 18 с до 4 с, добавив 4 workers.

Workers Planned: 4 означает запрошено 4 дополнительных process + 1 leader = 5 CPU. Workers Launched (в ANALYZE) показывает реально запущенных - если меньше, значит `max_parallel_workers` на сервере ограничивает.

Workers Planned: 3 в EXPLAIN. Что означает Workers Launched: 2 в EXPLAIN ANALYZE?

Параллельные JOIN-ы

PG умеет распараллеливать Hash Join и Merge Join. При Parallel Hash Join каждый worker строит свою часть хэш-таблицы из inner relation, затем все вместе сканируют outer relation. Это работает особенно эффективно для OLAP-запросов с большими join-ами.

Nested Loop Join не поддерживает параллелизм - inner relation сканируется per-row. Если planner выбирает NL join для большого outer, добавьте `enable_nestloop = off` в сессии и проверьте альтернативный план.

При Parallel Hash Join workers строят хэш-таблицу из inner relation. Как они избегают конфликтов при записи?

Parallel Aggregation

Агрегация разбивается на два этапа: каждый worker вычисляет частичный агрегат (Partial Aggregate) над своим куском данных, лидер финально объединяет через Finalize Aggregate. Для count(*) это прямолинейно - суммирование частичных count-ов. Для percentile_cont параллелизм не поддерживается.

Агрегатные функции делятся на combinable (sum, count, min, max, avg - можно partial) и non-combinable (percentile, array_agg с ORDER BY, jsonb_agg). Non-combinable автоматически отключают параллелизм для всего запроса.

Partial Aggregate + Finalize Aggregate - зачем два шага вместо одного?

Настройка parallel workers

Три ключевых параметра управляют параллелизмом. `max_parallel_workers` - сколько worker процессов можно держать суммарно на весь сервер. `max_parallel_workers_per_gather` - максимум на один запрос. `parallel_setup_cost` и `parallel_tuple_cost` управляют порогом, при котором planner предпочитает параллельный план.

На OLTP-сервере с 100+ одновременных коротких запросов параллелизм может навредить: 100 запросов × 4 workers = 400 процессов, контекстные переключения бьют по latency. Для OLAP с 10 тяжёлых запросов - наоборот, ускорение ×3-4.

Больше workers = всегда быстрее

Параллелизм имеет overhead на синхронизацию и IPC. После определённого числа workers прирост останавливается из-за Amdahl's law - последовательная часть (Finalize Aggregate, Gather) становится узким местом.

На практике 4-8 workers дают 70-85% ускорения от теоретического максимума. 16 workers дают лишь 90% - линейного масштабирования нет.

Почему для OLTP рекомендуют снижать max_parallel_workers_per_gather до 0-1?

Ключевые идеи

  • Gather-узел собирает результаты от workers - всё выше Gather работает в одном процессе
  • Parallel Aggregation: каждый worker считает Partial Aggregate, лидер делает Finalize - без конкуренции за общую структуру
  • OLTP: workers вредят из-за overhead запуска; OLAP: gains от 2x до 5x на 4-8 workers

Связанные темы

Параллельные запросы взаимодействуют с другими механизмами PG:

  • EXPLAIN и план запроса — Parallel-узлы видны в EXPLAIN - Workers Planned и Workers Launched позволяют диагностировать проблемы с параллелизмом
  • Индексы и сканирование — Parallel Index Scan и Parallel Bitmap Heap Scan дополняют Parallel Seq Scan для запросов с фильтрами
  • Настройка памяти PG — work_mem выделяется на каждый worker отдельно - при 4 workers запрос может использовать 4x work_mem

Вопросы для размышления

  • Если запрос содержит ORDER BY без LIMIT, как это влияет на параллелизм - Gather или Gather Merge?
  • При parallel_setup_cost = 0 PG будет распараллеливать даже запрос к таблице в 100 строк. Почему это может замедлить, а не ускорить?
  • Как убедиться, что увеличение max_parallel_workers не вызвало деградацию OLTP-запросов на том же сервере?

Связанные уроки

  • db-11-query-optimization
Параллельные запросы в PostgreSQL

0

1

Войти