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
@@ -121,3 +121,12 @@ ClickHouse, Kafka и Airflow, 17 — в дашборде генератора, 1
|
|||||||
пометку, от которой этот же ADR отговаривает выше, — а реальные сутки на стенде
|
пометку, от которой этот же ADR отговаривает выше, — а реальные сутки на стенде
|
||||||
не с чем сравнивать. Довод целиком и остальное устройство дашборда —
|
не с чем сравнивать. Довод целиком и остальное устройство дашборда —
|
||||||
[docs/architecture/monitoring/data.md](../architecture/monitoring/data.md).
|
[docs/architecture/monitoring/data.md](../architecture/monitoring/data.md).
|
||||||
|
|
||||||
|
Тогда же собраны «Кластер» и «Запросы». Довод про `clusterAllReplicas`
|
||||||
|
подтвердился с одной поправкой: очередь распределённых DDL лежит не у ноды, а в
|
||||||
|
keeper, и обе ноды читают её целиком — запрос вернул с каждой одни и те же 124
|
||||||
|
записи. Обёртка там не добавила бы правды, а удвоила бы каждую строку, поэтому
|
||||||
|
единственная панель «Кластера» без неё — эта. Keeper своих системных таблиц не
|
||||||
|
имеет вовсе, и панель по нему приходит из Prometheus. Устройство обоих
|
||||||
|
дашбордов — [cluster.md](../architecture/monitoring/cluster.md) и
|
||||||
|
[queries.md](../architecture/monitoring/queries.md).
|
||||||
@@ -9,9 +9,9 @@ Prometheus, какие источники данных подготовлены
|
|||||||
|
|
||||||
**Что здесь есть и чего ещё нет.** Собран пол: свой образ Grafana с плагином
|
**Что здесь есть и чего ещё нет.** Собран пол: свой образ Grafana с плагином
|
||||||
ClickHouse, два подготовленных источника данных, экспортёр метрик Kafka и цель
|
ClickHouse, два подготовленных источника данных, экспортёр метрик Kafka и цель
|
||||||
для него у Prometheus. Поверх пола стоят дашборды из каталога ниже; «Кластер»
|
для него у Prometheus. Поверх пола стоят все три дашборда ADR 0002, они
|
||||||
и «Запросы» приносят отдельные тикеты. Правил оповещения и серверного журнала
|
перечислены ниже. Правил оповещения и серверного журнала `system.text_log`
|
||||||
`system.text_log` тоже пока нет.
|
пока нет.
|
||||||
|
|
||||||
## Состав
|
## Состав
|
||||||
|
|
||||||
@@ -69,11 +69,20 @@ Grafana ходит пользователем `grafana` с ролью `monitorin
|
|||||||
Файлы каталога разбирает `make config-test`, рядом с файлами дагов и по тому же
|
Файлы каталога разбирает `make config-test`, рядом с файлами дагов и по тому же
|
||||||
доводу ([testing.md](../testing.md)).
|
доводу ([testing.md](../testing.md)).
|
||||||
|
|
||||||
|
Порогов и цветовых вердиктов на панелях нет ни на одном дашборде: на них
|
||||||
|
смотрят глазами, красное и зелёное живёт в проверках и дагах DQ.
|
||||||
|
|
||||||
Собранные дашборды:
|
Собранные дашборды:
|
||||||
|
|
||||||
- **«Данные»** (`data.json`) — с потоком всё в порядке: свежесть, поток, брак,
|
- **«Данные»** (`data.json`) — с потоком всё в порядке: свежесть, поток, брак,
|
||||||
сходимость Kafka с ClickHouse, отставание чтения и дневной слепок заказов.
|
сходимость Kafka с ClickHouse, отставание чтения и дневной слепок заказов.
|
||||||
Устройство и разбор панелей — [data.md](data.md).
|
Устройство и разбор панелей — [data.md](data.md).
|
||||||
|
- **«Кластер»** (`cluster.json`) — что с машиной: процессор, память и диски
|
||||||
|
обеих нод, части и слияния, keeper, очередь распределённых DDL. Устройство и
|
||||||
|
разбор панелей — [cluster.md](cluster.md).
|
||||||
|
- **«Запросы»** (`queries.json`) — что выполнялось и во что обошлось: тяжёлые
|
||||||
|
запросы, поток запросов, аппетит по пользователям, ошибки. Устройство и
|
||||||
|
разбор панелей — [queries.md](queries.md).
|
||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
|
|||||||
@@ -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` их не
|
||||||
|
видно: там проверено, что нода приезжает меткой ряда, а метрика — его именем.
|
||||||
@@ -4,9 +4,6 @@
|
|||||||
экран. Вопрос у экрана один: с потоком всё в порядке? Состав панелей и довод за
|
экран. Вопрос у экрана один: с потоком всё в порядке? Состав панелей и довод за
|
||||||
каждую — [ADR 0002](../../adr/0002-monitoring-scope.md).
|
каждую — [ADR 0002](../../adr/0002-monitoring-scope.md).
|
||||||
|
|
||||||
Порогов и цветовых вердиктов на панелях нет: на дашборд смотрят глазами,
|
|
||||||
красное и зелёное живёт в проверках и дагах DQ.
|
|
||||||
|
|
||||||
Свой вопрос каждая панель называет заголовком прямо на экране. Здесь — только
|
Свой вопрос каждая панель называет заголовком прямо на экране. Здесь — только
|
||||||
источники, о них и речь ниже.
|
источники, о них и речь ниже.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Дашборд «Запросы»
|
||||||
|
|
||||||
|
`infra/grafana/provisioning/dashboards/queries.json` — четыре панели поверх
|
||||||
|
одной таблицы, `system.query_log`. Вопрос у экрана один: что выполнялось и во
|
||||||
|
что это обошлось? Состав панелей и довод за каждую —
|
||||||
|
[ADR 0002](../../adr/0002-monitoring-scope.md).
|
||||||
|
|
||||||
|
Свой вопрос каждая панель называет заголовком прямо на экране. Здесь — только
|
||||||
|
то, чего в заголовке не скажешь.
|
||||||
|
|
||||||
|
Обе ноды пишут журнал каждая свой, поэтому все четыре панели читают его через
|
||||||
|
`clusterAllReplicas` — то же правило, что и на [«Кластере»](cluster.md).
|
||||||
|
|
||||||
|
Окно — час, обновление — полминуты. Час взят потому, что сюда приходят с
|
||||||
|
вопросом о прошлом: «почему у меня всё стояло десять минут назад». Полминуты —
|
||||||
|
чтобы только что запущенный тяжёлый запрос не пришлось ждать: в журнал он
|
||||||
|
попадает через несколько секунд после конца.
|
||||||
|
|
||||||
|
## Первым в списке видно сам мониторинг
|
||||||
|
|
||||||
|
Grafana ходит в ClickHouse обычным пользователем, и её запросы ложатся в
|
||||||
|
журнал наравне с человеческими. На спокойном стенде верх списка тяжёлых
|
||||||
|
запросов занимает `grafana`: панель свежести с дашборда «Данные» читает по
|
||||||
|
полмиллиона строк раз в минуту.
|
||||||
|
|
||||||
|
Это не помеха, а первое, что дашборд рассказывает: мониторинг тоже стоит
|
||||||
|
денег, и цену видно там же, где и всё остальное. Скрывать свои запросы
|
||||||
|
фильтром означало бы прятать эту цену — и заодно сделать панель «Кто
|
||||||
|
спрашивает» неполной ровно на самого частого посетителя.
|
||||||
|
|
||||||
|
Различать пользователей дашборд умеет потому, что у Grafana своя учётка со
|
||||||
|
своей ролью ([ADR 0007](../../adr/0007-clickhouse-access.md)). Ходи она под
|
||||||
|
аналитиком, панель «Кто спрашивает» показывала бы одну строку на всех.
|
||||||
|
|
||||||
|
## Что отброшено и почему
|
||||||
|
|
||||||
|
Каждый запрос попадает в журнал дважды: строкой начала и строкой конца. В
|
||||||
|
строке начала нет ни длительности, ни памяти, ни прочитанных строк — там их
|
||||||
|
ещё неоткуда взять. Панели считают по строкам конца.
|
||||||
|
|
||||||
|
Распределённый запрос кладёт в журнал не только себя. Инициатор пишет свою
|
||||||
|
строку, а каждый шард — свою, про доставшийся ему кусок. Три панели из четырёх
|
||||||
|
считают только запросы инициатора: иначе один запрос человека выглядел бы
|
||||||
|
тремя, а прочитанные строки сложились бы дважды.
|
||||||
|
|
||||||
|
Панель ошибок, наоборот, куски шардов показывает. Отказ по памяти случается
|
||||||
|
там, где считали, — на шарде; инициатор в своей строке передаст только текст
|
||||||
|
ошибки, а нода, на которой всё случилось, останется неназванной.
|
||||||
|
|
||||||
|
## Пустой пользователь
|
||||||
|
|
||||||
|
Часть строк журнала приходит без имени пользователя: так записана собственная
|
||||||
|
работа сервера — чтение топиков движком Kafka, вставки материализованных
|
||||||
|
представлений, обслуживание. Панели подписывают такие строки «фоновая работа
|
||||||
|
сервера»: пустая клетка в таблице читается как поломка, а это не она.
|
||||||
|
|
||||||
|
## Что проверено
|
||||||
|
|
||||||
|
Дашборд проверен на живом стенде 21 августа 2026 года.
|
||||||
|
|
||||||
|
- Дашборд поднимается из каталога сам, все четыре панели отвечают через
|
||||||
|
`/api/ds/query` в окне на час. Пустых нет.
|
||||||
|
- Тяжёлый запрос виден. `GROUP BY` по `ods.event_dist` от учётки `analyst`
|
||||||
|
приехал в панель со своими числами: 741 мс, 53,32 МиБ памяти, 701 994
|
||||||
|
прочитанные строки.
|
||||||
|
- Ошибки видны, и обе стороны различаются: отказ по правам (`bi` без `SELECT`
|
||||||
|
на `ods`) и ошибка в тексте запроса (несуществующая колонка) легли в панель
|
||||||
|
разными строками с полным текстом обеих.
|
||||||
|
- Панель «Кто спрашивает» разделила семь посетителей: `grafana`, `analyst`,
|
||||||
|
`etl`, `dict`, `bi`, `default` и фоновую работу сервера. Больше всех
|
||||||
|
прочитала `grafana` — её же панели, 11,5 млн строк за час.
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
{
|
||||||
|
"uid": "cluster",
|
||||||
|
"title": "Кластер",
|
||||||
|
"timezone": "utc",
|
||||||
|
"schemaVersion": 42,
|
||||||
|
"refresh": "30s",
|
||||||
|
"time": {
|
||||||
|
"from": "now-30m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Процессор: сколько ядер занимает нода",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "timeseries",
|
||||||
|
"format": 0,
|
||||||
|
"rawSql": "SELECT\n $__timeInterval(event_time) AS t,\n hostName() AS node,\n -- Процессорное время самого сервера. Метрики с именами OS* сняты с машины\n -- целиком, и в них обе ноды показывали бы одно и то же число.\n sum(ProfileEvent_OSCPUVirtualTimeMicroseconds) / 1e6 / $__interval_s AS \"ядер\"\nFROM clusterAllReplicas('clickstream_cluster', system.metric_log)\nWHERE $__timeFilter(event_time)\nGROUP BY t, node\nORDER BY t, node"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"displayName": "${__field.labels.node}",
|
||||||
|
"min": 0,
|
||||||
|
"decimals": 2
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Память: сколько нода занимает и сколько ей отведено",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "timeseries",
|
||||||
|
"format": 0,
|
||||||
|
"rawSql": "SELECT\n $__timeInterval(event_time) AS t,\n hostName() AS node,\n -- Занятое меряется резидентной памятью: именно её ядро сравнивает с\n -- пределом контейнера, когда решает, убить ли ноду.\n maxIf(value, metric = 'MemoryResident') AS \"занято\",\n maxIf(value, metric = 'CGroupMemoryTotal') AS \"коробка\"\nFROM clusterAllReplicas('clickstream_cluster', system.asynchronous_metric_log)\nWHERE $__timeFilter(event_time)\n AND metric IN ('MemoryResident', 'CGroupMemoryTotal')\nGROUP BY t, node\nORDER BY t, node"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"displayName": "${__field.name} · ${__field.labels.node}",
|
||||||
|
"min": 0,
|
||||||
|
"unit": "bytes"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byRegexp",
|
||||||
|
"options": "коробка.*"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.lineStyle",
|
||||||
|
"value": {
|
||||||
|
"fill": "dash",
|
||||||
|
"dash": [
|
||||||
|
10,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.fillOpacity",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Диски: сколько нода читает и пишет",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 16,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "timeseries",
|
||||||
|
"format": 0,
|
||||||
|
"rawSql": "SELECT\n $__timeInterval(event_time) AS t,\n hostName() AS node,\n -- Байты, прошедшие через обращения к файлам: чтение из страничного кэша\n -- тоже видно. Счётчики блочного устройства на вставке молчат.\n sum(ProfileEvent_OSReadChars) / $__interval_s AS \"чтение\",\n sum(ProfileEvent_OSWriteChars) / $__interval_s AS \"запись\"\nFROM clusterAllReplicas('clickstream_cluster', system.metric_log)\nWHERE $__timeFilter(event_time)\nGROUP BY t, node\nORDER BY t, node"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"displayName": "${__field.name} · ${__field.labels.node}",
|
||||||
|
"min": 0,
|
||||||
|
"unit": "Bps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"title": "Части: сколько их у таблицы и по сколько строк в каждой",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT\n concat(database, '.', table) AS \"Таблица\",\n hostName() AS \"Нода\",\n count() AS \"Частей\",\n sum(rows) AS \"Строк\",\n round(sum(rows) / count()) AS \"Строк в части\",\n sum(bytes_on_disk) AS \"На диске\"\nFROM clusterAllReplicas('clickstream_cluster', system.parts)\nWHERE active\nGROUP BY \"Таблица\", \"Нода\"\nORDER BY \"Частей\" DESC, \"Таблица\"\nLIMIT 15"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "На диске"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "bytes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"title": "Слияния: сколько частей они съели и чего это стоило",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT\n concat(database, '.', table) AS \"Таблица\",\n hostName() AS \"Нода\",\n sum(length(merged_from)) AS \"Частей съело\",\n count() AS \"Слияний\",\n sum(rows) AS \"Строк переварило\",\n round(sum(duration_ms) / 1000, 1) AS \"Заняло, с\",\n max(peak_memory_usage) AS \"Пиковая память\"\nFROM clusterAllReplicas('clickstream_cluster', system.part_log)\nWHERE $__timeFilter(event_time)\n AND event_type = 'MergeParts'\nGROUP BY \"Таблица\", \"Нода\"\nORDER BY \"Частей съело\" DESC, \"Таблица\"\nLIMIT 15"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Пиковая память"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "bytes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Keeper: сколько узлов в дереве",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prometheus"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "ClickHouseAsyncMetrics_KeeperZnodeCount",
|
||||||
|
"legendFormat": "узлов в дереве",
|
||||||
|
"range": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"title": "Распределённый DDL: что прошло по кластеру и чем кончилось",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 16,
|
||||||
|
"x": 8,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT\n query_create_time AS \"Создан\",\n entry AS \"Запись\",\n host AS \"Нода\",\n status AS \"Состояние\",\n query_duration_ms AS \"Заняло, мс\",\n exception_text AS \"Ошибка\",\n query AS \"Запрос\"\nFROM system.distributed_ddl_queue\nORDER BY entry DESC, \"Нода\"\nLIMIT 20"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
{
|
||||||
|
"uid": "queries",
|
||||||
|
"title": "Запросы",
|
||||||
|
"timezone": "utc",
|
||||||
|
"schemaVersion": 42,
|
||||||
|
"refresh": "30s",
|
||||||
|
"time": {
|
||||||
|
"from": "now-1h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"title": "Тяжёлые запросы: что прочитало больше всех",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 10,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT\n event_time AS \"Когда\",\n hostName() AS \"Нода\",\n if(empty(user), 'фоновая работа сервера', user) AS \"Кто\",\n query_duration_ms AS \"Заняло, мс\",\n memory_usage AS \"Память\",\n read_rows AS \"Прочитано строк\",\n result_rows AS \"Отдано строк\",\n query AS \"Запрос\"\nFROM clusterAllReplicas('clickstream_cluster', system.query_log)\nWHERE $__timeFilter(event_time)\n -- Начало запроса записано отдельной строкой, и в ней ещё нет ни времени,\n -- ни памяти. Куски распределённого запроса отброшены: считает их тот, кто\n -- запрос затеял.\n AND type != 'QueryStart'\n AND is_initial_query\nORDER BY \"Прочитано строк\" DESC\nLIMIT 20"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Память"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "bytes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Поток запросов: сколько выполнено и сколько упало",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "timeseries",
|
||||||
|
"format": 0,
|
||||||
|
"rawSql": "SELECT\n $__timeInterval(event_time) AS t,\n countIf(type = 'QueryFinish') AS \"выполнено\",\n countIf(type IN ('ExceptionBeforeStart', 'ExceptionWhileProcessing')) AS \"упало\"\nFROM clusterAllReplicas('clickstream_cluster', system.query_log)\nWHERE $__timeFilter(event_time)\n AND is_initial_query\nGROUP BY t\nORDER BY t"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"title": "Кто спрашивает: сколько запросов и какой у них аппетит",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 16,
|
||||||
|
"x": 8,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT\n if(empty(user), 'фоновая работа сервера', user) AS \"Кто\",\n count() AS \"Запросов\",\n sum(read_rows) AS \"Прочитано строк\",\n max(read_rows) AS \"Самый читающий, строк\",\n max(memory_usage) AS \"Самый прожорливый, память\",\n round(quantile(0.9)(query_duration_ms)) AS \"Девять из десяти быстрее, мс\"\nFROM clusterAllReplicas('clickstream_cluster', system.query_log)\nWHERE $__timeFilter(event_time)\n AND type != 'QueryStart'\n AND is_initial_query\nGROUP BY \"Кто\"\nORDER BY \"Прочитано строк\" DESC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Самый прожорливый, память"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "bytes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"title": "Ошибки: на чём запросы споткнулись",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT\n event_time AS \"Когда\",\n hostName() AS \"Нода\",\n if(empty(user), 'фоновая работа сервера', user) AS \"Кто\",\n exception AS \"Чем кончилось\",\n query AS \"Запрос\"\nFROM clusterAllReplicas('clickstream_cluster', system.query_log)\nWHERE $__timeFilter(event_time)\n -- Здесь куски распределённого запроса нужны: отказ по памяти случается\n -- на шарде, а не у того, кто спрашивал.\n AND type IN ('ExceptionBeforeStart', 'ExceptionWhileProcessing')\nORDER BY \"Когда\" DESC\nLIMIT 20"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in new issue
Block a user