Skip to content

Всё о GUC по порядку: enable_partitionwise_aggregate

Автор: Christophe Pettus, All Your GUCs in a Row: enable_partitionwise_aggregate


Оптимизация для секционирования и примечательное исключение в семействе enable_*: по умолчанию она выключена. Почти все остальные члены семейства по умолчанию включены и существуют для того, чтобы вы могли отключить возможность для диагностики; этот же по умолчанию выключен и существует для того, чтобы вы могли включить его, когда решите, что оно стоит затрат. Это обратное поведение — и стоимость, которая его мотивирует, — составляют суть данного поста. Контекст — пользовательский. И в отличие от большинства членов семейства, изменение этого параметра — это легитимное решение по настройке, а не просто диагностический зонд.

Что делает агрегация по разделам (partitionwise aggregation)


Агрегируйте секционированную таблицу обычным способом — SELECT category, sum(amount) FROM sales GROUP BY category — и PostgreSQL сканирует каждый раздел, объединяет строки через Append и агрегирует объединённый поток на верхнем уровне с помощью одного GroupAggregate или HashAggregate. Вся работа по группировке выполняется один раз, в конце, над всеми данными.



Агрегация по разделам, добавленная в PostgreSQL 11, проталкивает группировку внутрь каждого раздела. Каждый раздел агрегируется отдельно, а результаты затем объединяются. Существует две формы, и какая из них будет использована, зависит от одного вопроса: включает ли GROUP BY ключ секционирования?



Если включает — вы группируете по столбцу, по которому таблица секционирована, — то проталкивание является полным. Каждая группа полностью находится внутри одного раздела (в этом и заключается смысл секционирования по этому ключу), поэтому каждый раздел может быть агрегирован до финальных результатов независимо, и план представляет собой просто Append поверх HashAggregate для каждого раздела. Финализация не требуется; разделы выдают непересекающиеся группы, которые просто объединяются.



Если не включает — вы группируете по какому-то другому столбцу, — то строки группы могут быть распределены по многим разделам, поэтому каждый раздел может вычислить только частичный агрегат, и шаг Finalize Aggregate над Append должен объединить эти частичные результаты. План показывает Partial GroupAggregate под каждым разделом, питающий Finalize GroupAggregate на верхнем уровне — ту же двухступенчатую структуру partial/finalize, которую использует параллельная агрегация. Это всё равно часто даёт выигрыш, потому что частичные агрегаты работают на меньших входных данных на раздел и хорошо параллелизуются, но это менее «чистый» случай, чем полное проталкивание.



Выгода в обеих формах реальна: меньшие входные данные для агрегации, лучшее поведение кэша и — что важно — это сочетается с параллельными запросами, поэтому агрегация внутри каждого раздела может быть сама по себе параллелизована. На большой секционированной таблице с GROUP BY, совпадающим с ключом секционирования, это может дать существенный прирост скорости.



Почему по умолчанию выключено


Вот компромисс, объясняющий поведение по умолчанию, и его стоит сформулировать точно, потому что именно по этой причине данный параметр не просто включён, как его «собратья». Когда агрегация проталкивается в разделы, план получает по одному узлу агрегации на раздел — и каждый из этих узлов ограничен параметром work_mem. В документации прямо указано следствие: количество узлов в плане, ограниченных work_mem, может расти линейно с количеством сканируемых разделов. Таблица из ста разделов может превратить один узел агрегации в сто, и если каждый из них имеет право на work_mem, пиковое потребление памяти запросом возрастает в это количество раз. Кроме того, рассмотрение планов с агрегацией по разделам само по себе значительно удорожает планирование с точки зрения CPU и памяти, потому что планировщику приходится оценивать больше альтернативных путей.



Таким образом, возможность выключена по умолчанию не потому, что она редко полезна, а потому, что её глобальное включение было бы «минной ловушкой» для памяти в базах данных с множеством разделов и щедрым work_mem — именно в тех базах данных, где, скорее всего, есть большие секционированные таблицы. Разработчики PostgreSQL решили, что вы должны сознательно выбирать эту возможность для конкретной рабочей нагрузки, убедившись, что она помогает, а не навязывать умножение памяти всем.



Когда это стоит включить


Это один из немногих параметров enable_*, которые вы можете легитимно установить в ненулевое значение в рабочей среде, и логика здесь является зеркальным отражением остального семейства. Вы включаете его — для сессии, для роли или для базы данных, — когда у вас есть большие секционированные таблицы и аналитические запросы, которые группируют по (в идеале) ключу секционирования, и вы подтвердили на реальных данных, что план с проталкиванием работает быстрее. Подтверждение важно: установите enable_partitionwise_aggregate = on, выполните EXPLAIN (ANALYZE) и сравните с планом по умолчанию. Вы ищете узлы HashAggregate/Partial GroupAggregate для каждого раздела и меньшее общее время выполнения — и при этом следите, чтобы стоимость памяти, умноженная на количество ваших разделов, была приемлемой при вашей конкурентности.



Дисциплина применения здесь противоположна диагностическим переключателям. Вместо того чтобы оставлять его включённым и отключать для диагностики, вы оставляете его выключенным и включаете для конкретной рабочей нагрузки, которая выигрывает от этого — классический случай — это роль для отчётности, выполняющая большие группирующие сканирования по секционированной таблице фактов; установите ALTER ROLE reporting SET enable_partitionwise_aggregate = on. Включение его на уровне всего кластера оправдано только в том случае, если вы учли наихудший случай использования памяти для каждого секционированного запроса, который выполняется на сервере, а в смешанной рабочей нагрузке быть в этом уверенным сложнее, чем кажется. Параметр действительно полезен; он выключен по умолчанию, потому что полезность и безопасность по умолчанию — это не одно и то же, и здесь эти понятия расходятся.

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

No comments

The author does not allow comments to this entry

Add Comment

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Form options

Submitted comments will be subject to moderation before being displayed.