Files
clickstream-data-platform/docs/architecture/monitoring/cluster.md
T
ddadmin 42075ff9fb fix(monitoring): панели уточнены по ревью, приёмка снята под нагрузкой
- Зачем:
  - слияния в верхних строках показывали только журналы самого сервера, а
    предел в 15 строк рвал пару нод пополам: панели отвечали не на свой вопрос.
- Что:
  - «Слияния» стали столбиками по слоям: журналы сервера отделены, ряды ods и
    stg видны своими.
  - «Части» показывают все таблицы слоёв целиком, без предела и без обрезанных
    пар.
  - у «Запросов» убран «Поток запросов» — его пол составляли пробники
    состояния; у «Кто спрашивает» убрана лишняя колонка.
  - ряд «коробка» назван «отведено» — как в заголовке панели.
  - доводы дописаны в cluster.md и queries.md, отступления от ADR 0002
    записаны в сам ADR.
- Проверка:
  - make config-test; make smoke 21/21; make check-clickhouse 10/10.
  - все десять панелей отвечают через /api/ds/query в окнах на полчаса, час и
    шесть часов.
2026-08-21 15:11:21 -04:00

11 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.parts — опись живых частей, времени в ней нет вовсе. Очередь DDL показывает последние двадцать записей: в спокойные полчаса она пуста, а пустая панель ничего бы не рассказала.

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

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

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

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

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

Вычесть этот пол на процессоре и дисках нечем: их счётчики знают, сколько работы сделал процесс, и не знают, ради какой таблицы. Своя нагрузка на этих двух панелях читается как подъём над полом, а не как число само по себе.

У частей и слияний пол отделим: в system.parts и system.part_log у каждой строки написано, какой таблице она принадлежит. Обе панели поэтому спрашивают только про слои стенда. Довод в размере: за спокойные полчаса слияния съели 6 492 части в системных журналах против 219 в ods и stg вместе. Оставь мы журналы в кадре, ряды слоёв прижались бы к нулю, и панель рассказывала бы про уборку за сервером вместо ваших вставок. Сколько эта уборка стоит, видно этажом выше, на процессоре и дисках.

Слияния показаны по слоям, а не по нодам и не по таблицам. Шарды сливают симметрично, и второй ряд на слой удвоил бы легенду, ничего не добавив. Какая именно таблица держит части, отвечает соседняя панель.

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

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

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

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

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

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

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

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

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

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

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

  • Дашборд поднимается из каталога сам, все семь панелей отвечают через /api/ds/query в окне на полчаса. Пустых нет.
  • Обе ноды видны на всех панелях по системным таблицам, кроме одной. Без clusterAllReplicas осталась очередь DDL, и обёртка ей действительно не нужна: запрос с ней вернул 124 записи с clickhouse-01 и те же 124 с clickhouse-02.
  • Под нагрузкой. make generate-batch (50 626 событий одной пачкой): процессор поднялся с 0,04–0,18 ядра до 0,54 и 0,64, запись — с 400 КиБ/с до 2,1 и 4,5 МиБ/с. Следующие десять секунд обе величины уже вернулись к прежним: на таком объёме подъём и оседание укладываются в полминуты.
  • Слияния под той же нагрузкой отозвались столбиком: два слияния в stg, 15 съеденных частей.
  • Блочное чтение действительно молчит: за минуту вокруг вставки OSReadBytes был нулевым в десяти десятисекундных окнах из четырнадцати, а обращения к файлам шли непрерывно во всех.
  • Память: около 1 ГиБ занятого против 4 ГиБ отведённого на каждой ноде.
  • Панель частей показала ods.order_rep по 142 строки в части против 13 046 у ods.event_rep — заказы приезжают дневным слепком, события потоком. Все таблицы слоёв поместились целиком, каждая обеими нодами: восемь строк.
  • Очередь DDL показала CREATE MATERIALIZED VIEW ... ON CLUSTER в состоянии Finished и со временем по каждой ноде: make generate-batch поднимает clickhouse-init, и тот заново прогоняет DDL.

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