15.3. Параллельные планы

Так как каждый рабочий процесс выполняет параллельную часть плана до конца, нельзя просто взять обычный план запроса и запустить его в нескольких исполнителях. В этом случае все исполнители выдавали бы полные копии выходного набора результатов, так что запрос выполнится не быстрее, чем обычно, а его результаты могут быть некорректными. Вместо этого параллельной частью плана должно быть то, что для оптимизатора представляется как частичный план; то есть такой план, при выполнении которого в отдельном процессе будет получено только подмножество выходных строк, а каждая требующаяся строка результата будет гарантированно выдана ровно одним из сотрудничающих процессов.

15.3.1. Параллельные сканирования

В настоящее время единственным вариантом сканирования, адаптированным для работы в параллельном режиме, является последовательное сканирование. Таким образом, целевая таблица в параллельном плане всегда будет сканироваться узлом Parallel Seq Scan. Блоки отношения разделяются между сотрудничающими процессами и выдаются им по одному, так что доступ к отношению остаётся последовательным. Каждый отдельный процесс сначала посещает все кортежи на назначенной ему странице, и только затем переходит к новой.

15.3.2. Параллельные соединения

Целевая таблица может соединяться с одной или несколькими другими таблицами, используя вложенные циклы или соединения по хешу. Внешней стороной соединения может быть любой вид не параллельного плана, который в остальном поддерживается планировщиком, с условием того, что он безопасен для выполнения в параллельном исполнителе. Например, это может быть сканирование индекса, при котором находится значение по колонке, взятой из внутренней таблицы. Каждый рабочий процесс будет выполнять внешнюю сторону плана в полном объёме, поэтому соединения слияниям в таком режиме не поддерживаются. Внешняя сторона соединения слиянием часто включает сортировку всей внутренней таблицы; даже если при этом задействуется индекс, вряд ли будет продуктивным действие несколько процессов, производящих полное сканирование по индексу этой внутренней таблицы.

15.3.3. Параллельное агрегирование

Часть запроса, собственно производящую агрегирование, невозможно выполнять полностью в параллельном режиме. Например, если запрос включает выборку COUNT(*), каждый рабочий процесс сможет подсчитать своё количество строк, но эти количества нужно будет сложить, чтобы получить окончательный ответ. Если запрос включает предложение GROUP BY, отдельное общее количество надо будет вычислять для каждой группы. И даже несмотря на то, что агрегирование нельзя осуществлять полностью параллельно, запросы с агрегированием часто становятся отличными кандидатами на распараллеливание, так как они обычно читают множество строк, но возвращают клиенту только небольшое их число. Запросы, возвращающие клиенту множество строк, часто ограничены скоростью, с которой клиент может принимать данные, так что в таких случаях параллельное выполнение запроса может быть не очень полезным.

PostgreSQL поддерживает параллельное агрегирование, выполняя агрегирование дважды. Сначала каждый процесс, задействованный в параллельной части запроса, выполняет шаг агрегирования, выдавая частичный результат для каждой известной ему группы. В плане это отражает узел PartialAggregate. Затем эти промежуточные результаты передаются ведущему через узел Gather. И наконец, ведущий заново агрегирует результаты всех рабочих процессов, чтобы получить окончательный результат. Это отражает в плане узел FinalizeAggregate.

Параллельное агрегирование поддерживается не во всех случаях. Чтобы оно поддерживалось, агрегатная функция должна быть безопасной для распараллеливания и должна иметь комбинирующую функцию. Если переходное состояние агрегатной функции имеет тип internal, она должна также иметь функции сериализации и десериализации. За подробностями обратитесь к CREATE AGGREGATE. Параллельное агрегирование не поддерживается для сортирующих агрегатов или когда запрос включает предложение GROUPING SETS. Оно может использоваться только когда все соединения, задействованные в запросе, также входят в параллельную часть плана.

15.3.4. Советы по параллельным планам

Если для запроса ожидается параллельный план, но такой план не строится, можно попытаться уменьшить parallel_setup_cost или parallel_tuple_cost. Разумеется, этот план может оказаться медленнее последовательного плана, предпочитаемого планировщиком, но не всегда. Если вы не получаете параллельный план даже с очень маленькими значениями этих параметров (например, сбросив оба их в ноль), может быть какая-то веская причина тому, что планировщик запросов не может построить параллельный план для вашего запроса. За информацией о возможных причинах обратитесь к Разделу 15.2 и Разделу 15.4.

Когда выполняется параллельный план, вы можете применить EXPLAIN (ANALYZE, VERBOSE), чтобы просмотреть статистику по каждому узлу плана в разрезе рабочих процессов. Это может помочь определить, равномерно ли распределяется работа между всеми узлами плана, и на более общем уровне понимать характеристики производительности плана.