- Зачем: - свежий main добавил мониторинг и проявил устаревшие доводы, мёртвые настройки и слишком короткий тайм-аут DDL. - Что: - документация приведена к текущему жизненному циклу мира и составу стенда; - удалены лишняя проверка и неиспользуемый YAML-якорь; - ожидание ClickHouse возвращено к 300 секундам, а отказы и параметры DockerOperator пояснены. - Проверка: - `make config-test`; - `make lint`; - `make smoke` — 21/21.
13 KiB
Дашборд «Кластер»
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 |
Окно у дашборда — полчаса, обновление — полминуты. Пачка из
make generate-batch проходит за двадцать секунд, и на получасовой оси это
узкий всплеск в пару столбиков. Шире окно не берут, чтобы всплеск не выродился
в точку; уже — чтобы не промахнуться мимо него, заглянув через минуту, и чтобы
рядом осталось спокойное время: подъём читается только над полом. Кто хочет
разглядеть форму вплотную, растягивает окно мышью по самому всплеску.
Панель частей окна не знает вовсе. 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, и обёртка ей действительно не нужна: запрос с ней вернул сclickhouse-01и сclickhouse-02одно и то же число записей — на день замера 124 и 124. - Под нагрузкой.
make generate-batch(50 626 событий одной пачкой), замер в десятисекундных вёдрах: процессор поднялся с 0,04–0,18 ядра до 0,54 на одной ноде и 0,64 на другой, запись — с 400 КиБ/с до 2,1 и 4,5 МиБ/с. Ведро тут часть ответа: в тридцатисекундном тот же прогон даёт 0,29 и 0,21 ядра. Следующие десять секунд обе величины уже вернулись к прежним: на таком объёме подъём и оседание укладываются в полминуты. - Слияния под той же нагрузкой отозвались столбиком в ряду
stgвысотой в 15 съеденных частей. - Блочное чтение действительно молчит: за минуту вокруг вставки
OSReadBytesбыл нулевым в десяти десятисекундных окнах из четырнадцати, а обращения к файлам шли непрерывно во всех. - Память: около 1 ГиБ занятого против 4 ГиБ отведённого на каждой ноде.
- Панель частей показала
ods.order_repпо 142 строки в части против 13 046 уods.event_rep— заказы приезжают дневным слепком, события потоком. Строк вышло восемь: четыре таблицы, каждая обеими нодами. Таблиц ошибок среди них нет, и это правда состояния — частей у них пока не появилось. - Очередь DDL показала
CREATE MATERIALIZED VIEW ... ON CLUSTERв состоянииFinishedи со временем по каждой ноде. Новые безопасные записи для показа оставляетmake check-clickhouse: цель создаёт и удаляет временные таблицыON CLUSTER. Канонический DDL появляется в очереди при создании мира дагомworld_initialize.
Подписи рядов (${__field.labels.node}) и правило, по которому пунктирным
становится только ряд «отведено», сверены по документации Grafana через MCP
Context7. И то и другое применяется на стороне браузера, через
/api/ds/query их не видно: там проверено только, что нода приезжает меткой
ряда, а метрика — его именем.