# Дашборд «Кластер» `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` их не видно: там проверено, что нода приезжает меткой ряда, а метрика — его именем.