Files
clickstream-data-platform/docs/architecture/monitoring/cluster.md
T
ddadmin 7ffb4606f1 feat(monitoring): собраны дашборды «Кластер» и «Запросы»
- Зачем:
  - ADR 0002 обещает три дашборда; собран был один, а симптом инфраструктуры
    как следствие решения человека показать было негде.
- Что:
  - cluster.json — семь панелей: процессор, память с пределом коробки, диски,
    части по таблицам, слияния, keeper, очередь распределённых DDL.
  - queries.json — четыре панели поверх system.query_log: тяжёлые запросы,
    поток запросов, аппетит по пользователям, ошибки.
  - системные таблицы читаются через clusterAllReplicas; очередь DDL — без
    обёртки, она живёт в keeper и у обеих нод одна.
  - устройство обоих дашбордов записано в docs/architecture/monitoring/,
    правило про пороги поднято из data.md в README набора.
- Проверка:
  - make config-test; make smoke 21/21; make check-clickhouse 10/10.
  - все одиннадцать панелей отвечают через /api/ds/query.
2026-08-21 14:52:58 -04:00

9.8 KiB
Raw Blame History

Дашборд «Кластер»

infra/grafana/provisioning/dashboards/cluster.json — семь панелей. Вопрос у экрана один: что с машиной? Состав панелей и довод за каждую — ADR 0002.

Свой вопрос каждая панель называет заголовком прямо на экране. Здесь — только источники, о них и речь ниже.

Панель Откуда берёт
Процессор system.metric_log
Память system.asynchronous_metric_log
Диски system.metric_log
Части system.parts
Слияния system.part_log
Keeper экспортёр keeper через Prometheus
Распределённый DDL system.distributed_ddl_queue

Окно у дашборда — полчаса, обновление — полминуты, и это выбрано под нагрузку. Вставка на стенде занимает секунды, а оседание после слияний — минуту-другую; в часовом окне и то и другое схлопнулось бы в один столбик. Урок здесь — форма кривой, и окно подобрано так, чтобы форма была видна.

Обе ноды на одной панели

Системная таблица у каждой ноды своя: system.metric_log на clickhouse-01 знает только про clickhouse-01. Запрос без обёртки показал бы половину правды и ничем бы себя не выдал — вторая нода просто не появилась бы в легенде. Поэтому запросы идут через clusterAllReplicas, а hostName() подписывает, чья это строка.

Исключение одно — очередь распределённых DDL. Она лежит не у ноды, а в keeper, и обе ноды читают из него одну и ту же очередь целиком, вместе со строками про соседа. Обёртка здесь не добавила бы правды, а удвоила бы каждую строку. Правило поэтому звучит так: clusterAllReplicas нужен там, где у каждой ноды своя правда.

Пол на графиках — журналы самого сервера

ClickHouse ведёт около десятка собственных журналов: metric_log, trace_log, text_log, query_log и соседние. Все они — таблицы MergeTree, и пишутся они постоянно, даже когда на стенде не происходит ничего. Отсюда три следствия, которые иначе выглядят поломкой:

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

Мера отсюда простая: своя нагрузка читается как подъём над полом, а не как число само по себе.

Панели этот пол не вычитают, и не по недосмотру. Процессор и диски меряются счётчиками процесса — в них нет колонки «чья это работа», и вычесть журналы оттуда нечем. Отфильтруй мы system в тех двух панелях, где это технически возможно, они разошлись бы с остальными, и разбираться пришлось бы уже с расхождением.

Что именно меряют процессор, память и диски

Процессор считается по времени самого сервера (ProfileEvent_OSCPUVirtualTimeMicroseconds), а не по загрузке машины. Метрики с именами OS* сняты с хоста целиком: обе ноды показывали бы в них одно и то же число, и панель на два ряда потеряла бы смысл.

Память показана резидентной (MemoryResident), а не той, что сервер считает своей (MemoryTracking). Разница не косметическая: на праздной ноде MemoryTracking — 113 МиБ при 846 МиБ резидентной памяти, а ядро, решая, кого убить за нехватку памяти, смотрит на вторую величину (ADR 0004). Панель по первой показывала бы здоровый вид у ноды, которую вот-вот убьют.

Пунктирная «коробка» рядом — предел памяти контейнера, и он тоже читается живьём, метрикой CGroupMemoryTotal. Впиши мы 4 ГиБ числом в JSON, панель молча соврала бы в тот день, когда предел поменяют.

Диски меряются обращениями к файлам (OSReadChars, OSWriteChars), а не блоками, дошедшими до устройства (OSReadBytes). Блочное чтение на стенде почти всегда ноль: части лежат в страничном кэше, и слияние перечитывает их оттуда. Работа при этом делается настоящая, и панель по блокам показывала бы, что её нет.

Keeper и очередь DDL — то, что живёт вне нод

Keeper — отдельный сервис, системных таблиц у него нет, и панель по нему единственная на дашборде приходит из Prometheus.

Показывает она размер дерева. В keeper лежит журнал репликации, а в нём — запись на каждую часть: пока поток идёт, дерево растёт, а когда части домержились и старые записи убраны, оседает. Замер за полчаса живого потока: 9 078 узлов на подъёме, 11 512 на пике, 10 308 через десять минут после остановки потока.

Очередь распределённых DDL — вторая половина того же сюжета: через keeper проходит каждый запрос ON CLUSTER. Панель показывает последние двадцать записей независимо от окна дашборда: в спокойные полчаса очередь пуста, и пустая панель ничего бы не рассказала. Повод заглянуть — не «сколько там сейчас», а «доехал ли мой ON CLUSTER до обеих нод и что ответила каждая».

Что проверено

Дашборд проверен на живом стенде 21 августа 2026 года.

  • Дашборд поднимается из каталога сам, все семь панелей отвечают через /api/ds/query в окне на полчаса. Пустых нет.
  • Обе ноды видны на каждой панели по системной таблице. Единственная панель без clusterAllReplicas — очередь DDL, и обёртка ей действительно не нужна: запрос с ней вернул 124 записи с clickhouse-01 и те же 124 с clickhouse-02.
  • Под нагрузкой. make generate-batch (50 626 событий одной пачкой): процессор поднялся с 0,06–0,07 ядра до 0,37–0,38, запись — с 300 КиБ/с до 1,1 МиБ/с, активных частей стало 145 против 131. Через полминуты после конца вставки процессор вернулся к 0,02–0,07 ядра, части осели до 135.
  • Панель частей показала ods.order_rep по 136 строк в части против 11 171 у ods.event_rep — заказы приезжают дневным слепком, события потоком.
  • Панель слияний за полчаса: stg.hits_raw_rep съела 430 частей, ods.event_rep — 401, и рядом с ними системные журналы с сопоставимыми числами.
  • Очередь DDL показала CREATE MATERIALIZED VIEW ... ON CLUSTER в состоянии Finished и временем по каждой ноде: make generate-batch поднимает clickhouse-init, и тот заново прогоняет DDL.

Подписи рядов (${__field.labels.node}) и правило, по которому пунктирной становится только «коробка», сверены по документации Grafana через MCP Context7. Оба применяются на стороне браузера, и через /api/ds/query их не видно: там проверено, что нода приезжает меткой ряда, а метрика — его именем.