04 Занятие 04: Доступ к данным / Параллельный доступ
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: qpt_04_am_parallels.html Archive: QPT-handouts-16.zip Страниц PDF: 28
Page 1: Доступ к данным / Параллельный доступ
Авторские права
© Postgres Professional, 2019–2024 Авторы: Егор Рогов, Павел Лузанов, Павел Толмачев, Илья Баштанов Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Параллельные планы
- Размер пула процессов
- Параллельное последовательное сканирование
- Параллельный индексный доступ
Page 3: Параллельные планы
{#source-figure-003 caption=“Source slide page 3: Параллельные планы”}
Ведущий процесс
- выполняет последовательную часть плана
- запускает рабочие процессы и получает от них данные
- Рабочие процессы
- одновременно работают над параллельной частью плана
Комментарии преподавателя
PostgreSQL поддерживает параллельное выполнение запросов. Идея состоит в том, что ведущий процесс, выполняющий запрос, порождает (с помощью postmaster, конечно) несколько рабочих процессов, которые одновременно выполняют одну и ту же «параллельную» часть плана. Результаты этого выполнения передаются ведущему процессу, который собирает их в узле Gather.
Если рабочие процессы не успевают загрузить ведущего данными, ведущий процесс тоже подключается к выполнению того же самого параллельного плана.
Разумеется, запуск процессов и пересылка данных требуют определенных ресурсов, поэтому далеко не каждый запрос выполняется параллельно.
Кроме того, даже при параллельном выполнении не все шаги плана запроса могут быть распараллелены. Часть операций может выполняться ведущим процессом в одиночку, последовательно.
https://postgrespro.ru/docs/postgresql/16/parallel-query
Заметим, что в PostgreSQL нет другого теоретически возможного режима распараллеливания, при котором несколько процессов составляют конвейер для обработки данных (грубо говоря, отдельные узлы плана выполняются отдельными процессами). Разработчики PostgreSQL сочли такой режим неэффективным.
Page 4: Размер пула процессов
{#source-figure-004 caption=“Source slide page 4: Размер пула процессов”}
Комментарии преподавателя
Параллельным выполнением управляет довольно много параметров.
Сначала рассмотрим те, что ограничивают число рабочих процессов.
Вообще механизм рабочих процессов используется не только для параллельного выполнения запросов. Например, рабочие процессы задействованы в логической репликации, ими могут пользоваться расширения. Рабочие процессы можно использовать в прикладном коде (подробнее об этом — в теме «Фоновые процессы» курса DEV2). Общее число одновременно выполняющихся рабочих процессов ограничено параметром max_worker_processes (по умолчанию 8).
Число одновременно выполняющихся рабочих процессов, занимающихся параллельными планами, ограничено параметром max_parallel_workers (по умолчанию тоже 8).
Число одновременно выполняющихся рабочих процессов, обслуживающих один ведущий процесс, ограничено параметром max_parallel_workers_per_gather (по умолчанию 2).
Значения этих параметров следует изменить в зависимости от возможностей аппаратуры, объема данных и загруженности системы. Например, даже если в базе данных есть большие таблицы и запросы могли бы выиграть от распараллеливания, но при этом в системе нет свободных ядер, то параллельное выполнение не будет иметь смысла.
Page 5: Parallel Seq Scan
Параллельное последовательное сканирование
- агрегация
- параллельная агрегация
- Число рабочих процессов
- Ограничения
Page 6: Parallel Seq Scan
{#source-figure-006 caption=“Source slide page 6: Parallel Seq Scan”}
страницы читаются последовательно, но разными процессами
Комментарии преподавателя
Примером узла, выполняющегося в параллельном режиме является Parallel Seq Scan — «параллельное последовательное сканирование».
Название звучит противоречиво, но, тем не менее, отражает суть операции. Страницы таблицы читаются последовательно, в том же самом порядке, в котором они читались бы при обычном последовательном сканировании. Однако запросы на чтение выдаются несколькими параллельно работающими процессами. Процессы синхронизируются между собой, чтобы их запросы шли в правильном порядке.
Преимущество такого сканирования проявляется в том, что параллельные процессы одновременно обрабатывают свои страницы. Чтобы эффект оправдал дополнительные накладные расходы на пересылку данных от процесса к процессу, обработка должна быть достаточно ресурсоемкой. Хорошим примером является агрегация данных, поскольку для нее необходимы ресурсы процессора, а пересылать требуется только одно итоговое число. В таком случае параллельное выполнение запроса может занять существенно меньше времени, чем последовательное.
Page 7: Агрегация
Рассмотрим запрос с агрегатной функцией на небольшой таблице. Такой запрос выполняется последовательно:
План состоит из двух узлов. Верхний узел Aggregate, в котором происходит вычисление count, получает данные от дочернего узла Seq Scan. Обратите внимание на узел Aggregate: его начальная стоимость практически равна полной. Это означает, что узел не может выдать результат (состоящий из одной строки), пока не обработает все данные — что вполне логично. Разница между оценкой для Aggregate и верхней оценкой для Seq Scan — стоимость работы собственно узла Aggregate. Она рассчитывается исходя из того, что надо будет посчитать каждую строку, выданную узлом Seq Scan; одна элементарная операция оценивается значением параметра cpu_operator_cost:
Параллельное последовательное сканирование
Теперь рассмотрим такой же пример, но с большой таблицей. Здесь в плане запроса увидим параллельное последовательное сканирование:
Все, что находится ниже узла Gather — параллельная часть плана. Она выполняется в каждом из рабочих процессов (которых запланировано два) и, возможно, в ведущем процессе. Узел Gather и все узлы выше выполняются только в ведущем процессе. Это последовательная часть плана. Начнем разбираться снизу вверх. Узел Parallel Seq Scan представляет сканирование таблицы в параллельном режиме. В поле rows показана оценка числа строк, которые обработает один рабочий процесс. Всего их запланировано 2, и еще часть работы выполнит ведущий, поэтому общее число строк делится на 2.4 (доля ведущего процесса уменьшается с ростом числа рабочих процессов).
Page 8: Page 8
По умолчанию ведущий процесс участвует в выполнении параллельной части плана:
Если ведущий процесс становится узким местом, его можно разгрузить:
В этом случае общее число строк делится на число запланированных рабочих процессов (2):
Вернем значение по умолчанию для параметра parallel_leader_participation:
Для оценки узла Parallel Seq Scan компонента ввода-вывода берется полностью (таблицу все равно придется прочитать страница за страницей), а ресурсы процессора делятся между процессами (на 2.4 в данном случае).
Выведем план еще раз:
Page 9: Page 9
Следующий узел — Partial Aggregate — выполняет агрегацию данных, полученных рабочим процессом, то есть в данном случае подсчитывает количество строк. Оценка выполняется уже известным образом (и добавляется к оценке сканирования таблицы):
Следующий узел — Gather — выполняется ведущим процессом. Он отвечает за запуск рабочих процессов и получение от них данных. Запуск процессов и пересылка каждой строки данных оцениваются следующими значениями:
В данном случае пересылается всего одна строка и основная стоимость приходится на запуск. И снова посмотрим на план запроса:
Последний узел — Finalize Aggregate — агрегирует полученные частичные агрегаты. Поскольку для этого надо сложить всего три числа, оценка минимальна.
Page 10: Число рабочих процессов
Равно нулю (параллельный план не строится)
- если размер таблицы < min_parallel_table_scan_size = 8MB
- Фиксировано
- если для таблицы указан параметр хранения parallel_workers
- Вычисляется по формуле
- 1 + ⌊ log 3 (размер таблицы / min_parallel_table_scan_size) ⌋
- Всегда не больше max_parallel_workers_per_gather
Комментарии преподавателя
Сколько рабочих процессов будет использоваться?
Планировщик вообще не будет рассматривать параллельное сканирование, если физический размер таблицы меньше значения параметра min_parallel_table_scan_size.
Обычно число процессов вычисляется по формуле, приведенной на слайде. Она означает, что при увеличении таблицы в три раза будет добавляться очередной процесс. Например, для стандартного значения min_parallel_table_scan_size = 8MB:
таблица процессы таблица процессы
8MB 1 216MB 4
24MB 2 648MB 5
72MB 3 1.9GB 6
Число процессов можно и явно указать в параметре хранения parallel_workers таблицы.
При этом число процессов в любом случае не будет превышать значения параметра max_parallel_workers_per_gather. Если при выполнении запроса доступное число процессов окажется меньше запланированного, будут использоваться только доступные (вплоть до последовательного выполнения, если пул полностью исчерпан).
Page 11: Число рабочих процессов при последовательном сканировании
Планировщик не рассматривает параллельные планы для таблиц размером меньше, чем:
Если запросить информацию из таблицы немного большего размера (flights, 19 Мбайт), будет запланирован один дополнительный процесс:
При запросе данных из большой таблицы (bookings, 105 Мбайт) расчетное количество — три процесса.
Однако запланировано два процесса, так как параметр max_parallel_workers_per_gather ограничивает число процессов параллельного участка плана и сейчас действует значение по умолчанию (2).
Ослабив ограничение, получим план с тремя процессами:
Page 12: Page 12
Для конкретной таблицы параметром хранения parallel_workers можно задать рекомендованное количество процессов:
Если установить параметр хранения в ноль, планировщик всегда будет выбирать последовательное сканирование данной таблицы. Но в любом случае количество запланированных процессов не будет превышать max_parallel_workers_per_gather, независимо от параметра хранения. Восстановим начальные значения параметров:
Page 13: Не распараллеливаются
Запросы на запись
- а также запросы с блокировкой строк
- Курсоры
- в том числе запросы в цикле FOR в PL/pgSQL
- Запросы с функциями PARALLEL UNSAFE
- Запросы в функциях, вызванных из распараллеленного запроса
Комментарии преподавателя
Не каждый запрос может выполняться в параллельном режиме.
Не распараллеливаются запросы, изменяющие или блокирующие данные (UPDATE, DELETE, SELECT FOR UPDATE и т. п.).
Не распараллеливаются запросы, выполнение которых может быть приостановлено — это относится к запросам в курсорах, в том числе в циклах FOR PL/pgSQL.
Не распараллеливаются запросы, содержащие функции, помеченные как PARALLEL UNSAFE (пометки параллельности рассматриваются в теме «Функции»).
Не распараллеливаются запросы, содержащиеся в функциях, которые вызываются из распараллеленного запроса (чтобы не допустить рекурсивного разрастания).
Часть из этих ограничений может быть снята в следующих версиях PostgreSQL.
https://postgrespro.ru/docs/postgresql/16/when-can-parallel-query-be-used
Page 14: Только последовательно
Чтение результатов общих табличных выражений (CTE)
- Чтение результатов нераскрываемых подзапросов
- Обращения к временным таблицам
- Вызовы функций PARALLEL RESTRICTED
- Функции, использующие вложенные транзакции
Комментарии преподавателя
В целом, чем большую часть плана удается выполнить параллельно, тем больший возможен эффект. Однако есть ряд операций, которые не препятствуют распараллеливанию плана, но сами могут выполняться только последовательно в ведущем процессе.
К ним относятся:
● чтение результатов общих табличных выражений (подзапросов в предложении WITH);
● чтение результатов других нераскрываемых подзапросов (которые представляются в плане узлами, например, SubPlan);
● обращения ко временным таблицам (так как они доступны только ведущему процессу);
● вызовы функций, помеченных как PARALLEL RESTRICTED.
Если в запросе вызывается функция, использующая вложенные транзакции (например, функция на PL/pgSQL с обработкой исключений), запрос завершится с ошибкой. Такие функции должны быть помечены как PARALLEL RESTRICTED. Подробнее пометки параллельности рассматриваются в теме «Функции».
https://postgrespro.ru/docs/postgresql/16/parallel-safety
Page 15: Только последовательные операции
Некоторые операции всегда выполняются последовательно, например, обращения к временным таблицам. Для примера создадим временную таблицу tmp_bookings — копию таблицы bookings:
- Используя операцию объединения множеств UNION ALL (без удаления дубликатов), объединим результаты двух запросов — к основной и временной таблицам:
Данные из таблицы bookings читаются параллельно (по умолчанию используется два процесса), а из временной таблицы tmp_bookings — последовательно. Если операция не выполняется параллельно, это не обязательно означает, что она в принципе не распараллеливается: возможно, планировщик счел, что в данном случае последовательное выполнение эффективнее. Параметр debug_parallel_query позволяет проверить, может ли операция выполняться в параллельном режиме. Без агрегации обращение к таблице bookings выполняется последовательно:
Но со включенным параметром мы видим, что в принципе параллельный план возможен:
Параметр служит только для отладки. С ним параллельное выполнение планируется, даже если оно не выгодно, и всегда использует единственный процесс.
Page 16: Parallel Index Scan
Параллельное сканирование индекса
- Параллельное сканирование только индекса
- Параллельное сканирование по битовой карте
- Число рабочих процессов
Page 17: Parallel Index Scan
{#source-figure-017 caption=“Source slide page 17: Parallel Index Scan”}
Комментарии преподавателя
Индексный доступ тоже может выполняться в параллельном режиме. Это происходит в два этапа. Сначала ведущий процесс спускается от корня дерева к листовой странице.
Page 18: Parallel Index Scan
{#source-figure-018 caption=“Source slide page 18: Parallel Index Scan”}
Комментарии преподавателя
Затем рабочие процессы выполняют параллельное чтение листовых страниц индекса, двигаясь по списку.
Процесс, прочитавший индексную страницу, выполняет и чтение необходимых табличных страниц. При этом может получиться так, что одну и ту же табличную страницу прочитают несколько процессов (этот пример показан на слайде: последняя табличная страница содержат строки, ссылки на которые ведут от нескольких индексных страниц, прочитанных разными процессами). Конечно, сама страница будет находиться в буферном кеше в одном экземпляре.
Page 19: Параллельное сканирование индекса
В качестве примера параллельного сканирования индекса найдем общую стоимость всех бронирований с кодами, меньшими 400000 (они составляют примерно одну четверть от общего числа):
Аналогичный план мы уже видели при параллельном последовательном сканировании, но в данном случае данные читаются с помощью индекса узлом Parallel Index Scan. Полная стоимость складывается из стоимостей доступа к индексу и к таблице. Оценка стоимости индексного доступа не делится между процессами, так как индекс читается процессами последовательно, страница за страницей:
Значением параметра cpu_operator_cost оценивается операция сравнения значений («меньше»). Стоимость доступа к 1/4 табличных страниц (поделенных между процессами) оценивается аналогично последовательному сканированию, поскольку строки физически упорядочены по book_ref:
Page 20: Parallel Index Only Scan
{#source-figure-020 caption=“Source slide page 20: Parallel Index Only Scan”}
Комментарии преподавателя
Сканирование только индекса тоже может выполняться параллельно. Это происходит точно так же, как и при обычном индексном сканировании: ведущий процесс спускается от корня к листовой странице, а затем рабочие процессы параллельно сканируют листовые страницы индекса, обращаясь при необходимости к соответствующим страницам таблицы для проверки видимости.
Page 21: Параллельное сканирование только индекса
Рассмотрим похожий пример, в котором сканирование только индекса выполняется параллельно:
Стоимость доступа к таблице здесь учитывает только обработку строк, без ввода-вывода:
Вклад индексного доступа остается прежним.
Page 22: Parallel Bitmap Heap Scan
{#source-figure-022 caption=“Source slide page 22: Parallel Bitmap Heap Scan”}
Комментарии преподавателя
Сканирование по битовой карте может выполняться параллельно.
Первый этап — сканирование индекса и построение битовой карты — всегда выполняется последовательно ведущим процессом.
А второй этап — сканирование таблицы — выполняется рабочими процессами параллельно. Это происходит аналогично параллельному последовательному сканированию.
Page 23: Параллельное сканирование по битовой карте
Для построения битовой карты нам понадобится новый индекс для таблицы bookings:
Сколько бронирований сделано за последний месяц на сумму до 20 тысяч ₽?
Ведущий процесс строит битовую карту в узле Bitmap Index Scan. Сканирование таблицы по построенной битовой карте выполняется параллельно в узле Parallel Bitmap Heap Scan.
Page 24: Число рабочих процессов
Равно нулю (параллельный план не строится)
если размер выборки < min\_parallel\_index\_scan\_size = 512kB
- Фиксировано
если для таблицы указан параметр хранения parallel\_workers
- Вычисляется по формуле
1 + ⌊ log 3 (размер выборки / min\_parallel\_index\_scan\_size) ⌋
- Но не больше, чем max_parallel_workers_per_gather
Комментарии преподавателя
Число рабочих процессов выбирается примерно так же, как и в случае последовательного сканирования. Объем данных, который предполагается прочитать из индекса (определяемый числом индексных страниц), сравнивается со значением параметра min_parallel_index_scan_size (по умолчанию 512kB).
Если в случае последовательного сканирования таблицы объем данных определялся размером всей таблицы, то при индексном доступе планировщик должен спрогнозировать, сколько индексных страниц будет прочитано. Как именно это происходит, рассматривается позже в теме «Базовая статистика».
Если размер выборки меньше, параллельный план не рассматривается оптимизатором. Например, никогда не будет выполняться параллельно доступ к одному значению — в этом случае просто нечего распараллеливать.
Если размер выборки достаточно велик, число рабочих процессов определяется по формуле, если только оно не указано явно в параметре хранения parallel_workers таблицы (не индекса!).
Число процессов в любом случае не будет превышать значения параметра max_parallel_workers_per_gather.
Page 25: Итоги
Параллельные операции используют ресурсы процессора в нескольких рабочих процессах
- PostgreSQL выделяет рабочие процессы из общего пула
- Все методы доступа могут работать в параллельном режиме
Page 26: Практика
Выполните запрос, вычисляющий общее количество бронирований, с различными настройками планировщика и сравните планы выполнения: 1. С настройками по умолчанию. 2. Запретив последовательное сканирование. 3. Дополнительно запретив сканирование только индекса. 4. Разрешив все методы доступа, но отключив параллельность.
Комментарии преподавателя
Речь идет о запросе EXPLAIN (costs off) SELECT count(book_ref) FROM bookings;
Установите параметр enable_seqscan в off.
Установите параметр enable_indexonlyscan в off.
Восстановите значения параметров по умолчанию и установите параметр max_parallel_workers_per_gather в 0.
Page 27: 1. План запроса с настройками по умолчанию
Параллельное последовательное сканирование оказывается наиболее выгодным.
2. Запрет последовательного сканирования
Отключим Seq Scan и повторим запрос:
Индексный доступ медленнее табличного, но в данном случае достаточно просканировать только индекс.
3. Запрет сканирования только индекса
Отключим и Index Only Scan:
Остальные параллельные способы получения данных оказываются невыгодными, лучше вообще не использовать параллельность.
4. Запрет параллельности
Сбросим все параметры и запретим параллельные планы:
Page 28: Page 28
Планировщик выбрал последовательное сканирование как самый выгодный способ получить все строки. Меняя значения конфигурационных параметров, можно исследовать поведение оптимизатора: например, понять, какие альтернативные планы он рассматривал.