The Jerusalem PostNova site restricted to memorial events, not celebrations, KKL-JNF says ahead of third anniversaryPunchUS establishes office of religious affairsBollywood HungamaBigg Boss 20: Rhiti Tiwari evicted in surprise mid-week exit after captaincy task? Here’s what we know!Daily MaverickWhen VW sneezes, Nelson Mandela Bay catches a coldBBC BusinessTravelodge failed sex assault victim 'at every stage'الشرقاكتشاف آلية تمهد لتطوير علاج يعتمد على الفيروسات لمكافحة البكتيرياObservador DesportoModelo de IA supera os melhores de jogo de estratégiaDeadlineBAFTA Makes Plans For ‘I Swear’s John Davidson To Attend Scotland Awards After N-Word ScandalLa PresseSénat | Richard Martel n’a pas choisi d’affiliation, mais dit conserver ses valeursAntara NewsNew FM Arrmanatha Nasir vows to continue Prabowo's foreign policyynetבעלי הבית היקר ביותר באוסטרליה: בן של שורד אושוויץ ואשתו היו בטיסת האימהRMF24Kolejny kraj wejdzie do strefy euro? 80 proc. obywateli jest za
The Daily Newsstand · Free, Always
Thursday, October 1, 2026

За пределами EXPLAIN: как увидеть выполнение запроса вживую в распределённой СУБД

Translate

Представьте, что ваш запрос в Greenplum внезапно завис. Есть план выполнения, но что именно сейчас происходит, непонятно. Перекос данных? Spill на диск? В итоге вы перезапускаете запрос наугад: инструментов для живого наблюдения попросту нет. 

Всем привет! Я Алексей Рожок, разработчик ClickHouse в Yandex Cloud. Летом 2026 года я стажировался в Greenplum/Cloudberry и реализовывал проект, который помогает увидеть весь путь запроса. В статье покажу, как достучаться до процессов на всех хостах кластера и почему сбор метрик пришлось вынести с координатора в отдельный сервис YAGPCC. 

Чего не показывает EXPLAIN 

SQL — язык декларативный: вы пишете, что хотите получить, а как именно это сделать, решает СУБД. В PostgreSQL этот выбор можно подсмотреть через EXPLAIN: вот план, вот узлы, вот оценки. Но пока тяжёлый запрос крутится часами, главный вопрос — «На каком шаге мы сейчас и почему так долго?» — остаётся без ответа. EXPLAIN ANALYZE отдаёт статистику только постфактум, а оценки планировщика часто сильно расходятся с реальностью. 

Для ванильного PostgreSQL уже есть решения, которые показывают статистику запроса, пока он выполняется. Но в мире распределённых MPP-СУБД запрос, разбитый на части, может идти параллельно на сотнях узлов кластера. Готовых решений для такого случая нет, поэтому пришлось написать своё. 

Расширение pg_query_state для Apache Cloudberry строит дерево плана по ходу выполнения запроса и периодически обновляет статистику в каждом узле. По сути, мы получаем честный live-observability: видно, чем запрос занят прямо сейчас. Ниже расскажу, как достучаться до процессов запроса на всех сегментах кластера и почему собирать их статистику пришлось отдельным сервисом. 

Как Cloudberry выполняет запросы

Объяснить архитектуру Cloudberry за пару минут сложно, но я попытаюсь. Кластер состоит из координатора и сегментов. Координатор принимает запрос, строит план, рассылает его по сегментам и собирает итоговый результат.

Сегмент — независимый экземпляр СУБД на основе PostgreSQL в архитектуре shared-nothing. Он владеет собственным шардом данных на локальном диске, не имеет доступа к данным других сегментов и получает от координатора план запроса для своей части. Физически сегменты размещаются на сегмент-хостах, обычно по одному на ядро CPU или диск.  

Для примера создадим таблицу test: 

create table test ( 

 id serial primary key, 

 val numeric, 

 txt text, 

 created timestamp default now() 

); 

Выполним простой запрос: 

select * from test a where a.val > 10

Тогда план нашего выполнения в Cloudberry будет таким: 

postgres=# explain select * from test a where a.val > 10;
                                     QUERY PLAN
--------------------------------------------------------------------------------------
 Gather Motion 4:1  (slice1; segments: 4)  (cost=0.00..1620.53 rows=4999950 width=57)
   ->  Seq Scan on test a  (cost=0.00..667.21 rows=1249988 width=57)
         Filter: (val > '10'::numeric)
 Optimizer: GPORCA
(4 rows)

На кластере из четырёх сегментов это выглядит так:

Каждый сегмент последовательно сканирует свою часть таблицы (Seq Scan) и применяет фильтр. Узел Gather Motion собирает результаты с четырёх сегментов на координаторе, поэтому в плане 4:1.

Слайс — это горизонтальный срез плана выполнения, ограниченный точками обмена данными (Motion nodes). Когда оптимизатор строит план для распределённого запроса, он разбивает его на слайсы — логические единицы работы. Слайс выполняется параллельно на всех сегментах в рамках выделенного процесса (QE, Query Executor). Внутри слайса сегменты работают независимо друг от друга и обрабатывают только свои локальные данные.

Между слайсами данные передают Motion-узлы (Gather, Redistribute, Broadcast): они пересылают кортежи по сети между сегментами или на координатор. Слайсы пронумерованы, в плане запроса их границы отмечены отступами и пометками sliceN.

Как достучаться до сегментов

Координатор знает, на каких сегментах выполняется запрос. Но координатор и сегменты — отдельные процессы, которые физически могут находиться на разных машинах. При этом сами сегменты тоже могут быть раскиданы по разным сегмент-хостам.

Чтобы собрать статистику во время выполнения запроса, её нужно снять с каждого активного процесса и куда-то отправить. Возникают как минимум два вопроса: как заставить сегменты собирать данные и куда их отправлять. 

В PostgreSQL на одной машине до процесса можно достучаться обычным сигналом прерывания. Здесь это не сработает: сигнал не дойдёт до процесса на другом хосте. Поэтому я использовал диспетчеризацию — механизм, которым координатор рассылает SQL-команды на сегменты: 

  1. Отправляем сигнал координатору. В ответ он возвращает массив пар (segid, pid) для всех активных сегментов. 

  2. Запускаем SQL-запрос на всех сегментах и передаём ему этот массив аргументом.

  3. Внутри каждого сегмента этот запрос выполняет отдельный новый процесс. Он оставляет из массива только нужные пары — те, где segid совпадает с номером текущего сегмента. 

  4. Локальный бэкенд рассылает сигналы всем активным процессам в пределах текущего сегмент-хоста. 

Таким образом, мы смогли прервать процессы на всех нужных сегментах прямо с координатора. 

Почему координатор не справится в одиночку

Остаётся второй вопрос: куда отправлять статистику. Первым напрашивается координатор, но посчитаем, во что это обойдётся на большом кластере. 

Представим крупный продакшн-кластер: 300 сегментов на нескольких десятках физических хостов. Аналитик запускает сложный отчётный запрос: цепочка из нескольких JOIN, пара подзапросов, оконные функции, агрегации с GROUP BY. Оптимизатор разбивает план на 25 слайсов. Значит, одновременно на сегментах работают 300 × 25 = 7500 процессов — каждый обслуживает ровно один слайс на своём сегменте. 

Допустим, мы собираем runtime-статистику с каждого такого процесса. Внутри одного процесса строится собственное дерево плана: пусть в нём порядка 20 узлов, как в типовом плане с несколькими Scan, Join, Aggregation и Motion-узлами. Для каждого узла нужно передать около 32 полей: фактическое число полученных и отброшенных строк, время ожидания ввода-вывода, количество spill-файлов, объём использованной памяти, прогноз до завершения и другие. Не умаляя общности, будем считать, что каждое поле — это 8-байтовое число с плавающей точкой.

Тогда наши гипотетические данные таковы: 

  • размер одного узла: 32 поля × 8 байт = 256 байт; 

  • размер дерева с одного процесса: 20 узлов × 256 байт = 5120 байт (5 КБ); 

  • суммарный объём данных со всего кластера за один сбор: 7500 процессов × 5 КБ ≈ 38 МБ. 

Раз в секунду, а то и чаще, координатор: 

  1. Принимает 7500 входящих сообщений. Это 7500 системных вызовов read(), прерываний и переключений контекста, которые крадут процессорное время у основного цикла обработки запросов. 

  2. Десериализует каждое сообщение и восстанавливает из него 20-узловое дерево. 

  3. Сливает 7500 частичных деревьев в одно общее и агрегирует статистику по каждому узлу плана. Сложность слияния — O(N × M), где N — число процессов, M — число узлов. При 7500 процессах и 20 узлах это 150 000 операций слияния за один такт сбора. 

И это только один запрос. В час пик на реальном кластере одновременно могут выполняться два-три таких отчётных запроса и десятки более лёгких. Тогда 7500 процессов легко превращаются в 15 000–20 000, а объём данных за один сбор переваливает за 100 МБ. В таком режиме координатор полностью занят задачей, для которой его не проектировали. Задержки растут, пропускная способность падает, кластер деградирует — и всё ради красивого дашборда.

Поэтому я отказался от прямой доставки статистики на координатор и вынес сбор и агрегацию в отдельный сервис YAGPCC, изолированный от СУБД. 

Решение: агенты YAGPCC на каждом хосте 

На каждом хосте кластера работает написанный на Go агент. Он слушает UDS, и процессы сегментов сбрасывают туда сырую статистику без предобработки. Агенты на сегмент-хостах временно хранят данные в памяти; у хранилищ есть фоновая сборка мусора и экспорт метрик в Prometheus. 

Собирают статистику по требованию через HTTP-ручку, по протоколу gRPC с Protobuf. Мастер-агент агрегирует всё в одно дерево плана и обновляет его атомарно. Сбор обходится дёшево: если пакет потерялся, просто перезапрашиваем его. 

Нагрузка на YAGPCC-мастер начинает зависеть исключительно от количества хостов — величины, которая на два-три порядка меньше числа процессов. Асимптотически это O(N), где N — число хостов в кластере. 

Что даёт сбор через агентов? 

  • Горизонтальное масштабирование больше не ломает observability. Если добавить 100 новых сегментов на существующие хосты, нагрузка на мастер-агент не изменится. Добавили новые хосты? Нагрузка выросла линейно и предсказуемо, а не мультипликативно. 

  • Сложность запроса перестала быть проблемой. Раньше 30 слайсов вместо пяти означали шестикратный рост нагрузки на сборщик. Теперь число слайсов влияет только на локальные агенты на каждом хосте, а до мастер-агента это не доходит. 

  • Система стала предсказуемой. Вместо скачкообразной нагрузки, зависящей от того, сколько и каких запросов одновременно выполняется в кластере, YAGPCC-мастер получает стабильный поток из N сообщений за такт. 

  • Координатор БД изолирован. Он не тратит ресурсы на сбор телеметрии, не получает лавину сообщений и продолжает выполнять свою основную работу — планировать, диспетчеризовать и собирать финальные результаты запросов. 

По сути мы перенесли вычислительную сложность агрегации с центрального сборщика, который стал бы бутылочным горлышком, на агентов: их много, и каждый работает только со своими данными. Это классический паттерн map-reduce наоборот: reduce выполняется локально на каждом хосте, а мастер-агенту остаётся только сделать финальный merge небольшого числа уже агрегированных результатов.

Дерево плана в реальном времени 

Я записал видео, на котором видно, как это выглядит.

На скриншотах видно дерево плана выполнения: прогресс каждого узла обновляется в реальном времени. Любой узел можно раскрыть и посмотреть статистику по каждому сегменту. 

Что дальше

Интеграция pg_query_state в Apache Cloudberry и YAGPCC показала, что live-observability в распределённых СУБД — решаемая инженерная задача. Сейчас фича только начинает свой путь, и ей ещё предстоит пережить испытания в проде. Именно там, в боях с реальными нагрузками, мы поймём, насколько хорошо архитектура выдерживает большие объёмы данных, и какие узкие места ещё предстоит оптимизировать.

Если вы тоже следите за запросами в Greenplum или Cloudberry, расскажите в комментариях, какими инструментами пользуетесь и чего вам в них не хватает. А если вам интересно следить за тем, что мы делаем внутри Yandex Cloud — присоединяйтесь к нашему каналу! 

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.