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

128 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Дашборд «Кластер»
`infra/grafana/provisioning/dashboards/cluster.json` — семь панелей. Вопрос у
экрана один: что с машиной? Состав панелей и довод за каждую —
[ADR 0002](../../adr/0002-monitoring-scope.md).
Свой вопрос каждая панель называет заголовком прямо на экране. Здесь — только
источники, о них и речь ниже.
| Панель | Откуда берёт |
|---|---|
| Процессор | `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](../../adr/0004-resource-limits.md)). Панель по первой показывала бы
здоровый вид у ноды, которую вот-вот убьют.
Пунктирная «коробка» рядом — предел памяти контейнера, и он тоже читается
живьём, метрикой `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` их не
видно: там проверено, что нода приезжает меткой ряда, а метрика — его именем.