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.
This commit is contained in:
1 parent
272660ff66
commit
7ffb4606f1
7 files changed
+708
-6
No files matched your search
@@ -0,0 +1,127 @@
|
||||
# Дашборд «Кластер»
|
||||
|
||||
`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` их не
|
||||
видно: там проверено, что нода приезжает меткой ряда, а метрика — его именем.
|
||||
Reference in new issue
Block a user