Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

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

Дата публикации: 01-10-2026 07:01:21

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

Основное содержимое страницы с новостью.

Время на прочтение7 мин

Охват и читатели7K

9d24d7e84509b5838054d7eb4e60c09b.png

Представьте, что ваш запрос в 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)

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

568080553667933669023584680b8760.png

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Представим крупный продакшн-кластер: 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 на каждом хосте 2c875cc7a94c8f2fceeee0a4a008ac7f.png

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

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

1633e6d0de5588f45a3ba473b60855d9.png

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

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

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

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

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

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

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

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

5d46446cd90e24045efc3cd2037f5dc8.pngd51b7d3e6ade298db297a882f55c2405.pngЧто дальше

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

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

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Как на ровном месте сэкономить 1000+ ядер, или Куда на самом деле уходили 80% CPU09.1208-09-2026
2Миграция без права на ошибку: как перенести 70 кластеров MongoDB в 7 и не сломать продакшен010.6502-09-2026
3Go SDK для YDB: уменьшаем количество запросов к СУБД для интерактивных транзакций08.813-08-2026
4Купили двухсокетный сервер на 128 ядер, а база данных стала ...09.4901-10-2026
5Купили двухсокетный сервер на 128 ядер, а база данных стала ...09.4901-10-2026
6xk6-sip: мониторинг нагрузочного тестирования VoIP/SIP-звонков017.2126-09-2026
7Миллион контейнеров и быстрый откат релиза: эволюция деплоя в RTC07.4918-08-2026
8IDM на максималках: как управлять доступами к 1500 систем Яндекса и не стать бутылочным горлышком08.3204-09-2026
9xk6-sip: SIP-телефония как код013.0525-09-2026
10Доверить сервер ИИ-агенту и не пожалеть: как спать спокойно без SSH012.5826-09-2026

Классификация: Мнения. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 6.98. Источник: habr.com.