Merge pull request 'Мониторинг: дашборды «Кластер» и «Запросы»' (#123) from feat/116-cluster-queries-dashboards into main
Reviewed-on: #123
This commit was merged in pull request #123.
This commit is contained in:
commit
0340b060f2
7 files changed
+715
-6
No files matched your search
@@ -121,3 +121,23 @@ ClickHouse, Kafka и Airflow, 17 — в дашборде генератора, 1
|
||||
пометку, от которой этот же ADR отговаривает выше, — а реальные сутки на стенде
|
||||
не с чем сравнивать. Довод целиком и остальное устройство дашборда —
|
||||
[docs/architecture/monitoring/data.md](../architecture/monitoring/data.md).
|
||||
|
||||
Тогда же собраны «Кластер» и «Запросы». Довод про `clusterAllReplicas`
|
||||
подтвердился с одной поправкой: очередь распределённых DDL лежит не у ноды, а в
|
||||
keeper, и обе ноды читают её целиком — запрос вернул с каждой одни и те же 124
|
||||
записи. Обёртка там не добавила бы правды, а удвоила бы каждую строку, поэтому
|
||||
единственная панель «Кластера» без неё — эта. Keeper своих системных таблиц не
|
||||
имеет вовсе, и панель по нему приходит из Prometheus.
|
||||
|
||||
«Запросы» получили на две панели больше, чем сказано выше. Разбор запросов по
|
||||
посетителям продиктован [ADR 0007](0007-clickhouse-access.md): своя учётка
|
||||
Grafana заведена там ровно затем, чтобы её запросы были отличимы от
|
||||
человеческих.
|
||||
Отдельная панель ошибок понадобилась потому, что отказ достаётся шарду: в
|
||||
замере с принудительным пределом памяти каждая нода записала свой, и строка
|
||||
шарда видна только без фильтра по инициатору. Здешний сюжет про тяжёлый
|
||||
`GROUP BY` при этом кончается не отказом, а сбросом на диск
|
||||
([ADR 0004](0004-resource-limits.md)) — отказ на стенде приходится вызывать
|
||||
настройкой. Устройство обоих дашбордов —
|
||||
[cluster.md](../architecture/monitoring/cluster.md) и
|
||||
[queries.md](../architecture/monitoring/queries.md).
|
||||
@@ -9,9 +9,9 @@ Prometheus, какие источники данных подготовлены
|
||||
|
||||
**Что здесь есть и чего ещё нет.** Собран пол: свой образ Grafana с плагином
|
||||
ClickHouse, два подготовленных источника данных, экспортёр метрик Kafka и цель
|
||||
для него у Prometheus. Поверх пола стоят дашборды из каталога ниже; «Кластер»
|
||||
и «Запросы» приносят отдельные тикеты. Правил оповещения и серверного журнала
|
||||
`system.text_log` тоже пока нет.
|
||||
для него у Prometheus. Поверх пола стоят все три дашборда ADR 0002, они
|
||||
перечислены ниже. Правил оповещения и серверного журнала `system.text_log`
|
||||
пока нет.
|
||||
|
||||
## Состав
|
||||
|
||||
@@ -69,11 +69,20 @@ Grafana ходит пользователем `grafana` с ролью `monitorin
|
||||
Файлы каталога разбирает `make config-test`, рядом с файлами дагов и по тому же
|
||||
доводу ([testing.md](../testing.md)).
|
||||
|
||||
Порогов и цветовых вердиктов на панелях нет ни на одном дашборде: на них
|
||||
смотрят глазами, красное и зелёное живёт в проверках и дагах DQ.
|
||||
|
||||
Собранные дашборды:
|
||||
|
||||
- **«Данные»** (`data.json`) — с потоком всё в порядке: свежесть, поток, брак,
|
||||
сходимость Kafka с ClickHouse, отставание чтения и дневной слепок заказов.
|
||||
Устройство и разбор панелей — [data.md](data.md).
|
||||
- **«Кластер»** (`cluster.json`) — что с машиной: процессор, память и диски
|
||||
обеих нод, части и слияния, keeper, очередь распределённых DDL. Устройство и
|
||||
разбор панелей — [cluster.md](cluster.md).
|
||||
- **«Запросы»** (`queries.json`) — что выполнялось и во что обошлось: тяжёлые
|
||||
запросы, аппетит по посетителям, ошибки. Устройство и разбор панелей —
|
||||
[queries.md](queries.md).
|
||||
|
||||
## Что проверено
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# Дашборд «Кластер»
|
||||
|
||||
`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` |
|
||||
|
||||
Окно у дашборда — полчаса, обновление — полминуты. Пачка из
|
||||
`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-resource-limits.md)). По первой нода, которую
|
||||
вот-вот убьют, выглядит совершенно здоровой.
|
||||
|
||||
Пунктирный ряд «отведено» — предел памяти контейнера, та самая коробка из
|
||||
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 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).
|
||||
|
||||
Порогов и цветовых вердиктов на панелях нет: на дашборд смотрят глазами,
|
||||
красное и зелёное живёт в проверках и дагах DQ.
|
||||
|
||||
Свой вопрос каждая панель называет заголовком прямо на экране. Здесь — только
|
||||
источники, о них и речь ниже.
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Дашборд «Запросы»
|
||||
|
||||
`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)). Ходи она под
|
||||
аналитиком, панель «Кто спрашивает» показывала бы одну строку на всех.
|
||||
|
||||
## Что отброшено и почему
|
||||
|
||||
Каждый запрос попадает в журнал дважды: строкой начала и строкой конца. В
|
||||
строке начала нет ни длительности, ни памяти, ни прочитанных строк — там их
|
||||
ещё неоткуда взять. Панели считают по строкам конца.
|
||||
|
||||
Распределённый запрос кладёт в журнал не только себя. Инициатор пишет свою
|
||||
строку, а каждый шард — свою, про доставшийся ему кусок. Две панели из трёх
|
||||
считают только запросы инициатора: иначе один запрос человека выглядел бы
|
||||
тремя, а прочитанные строки сложились бы дважды.
|
||||
|
||||
Панель ошибок, наоборот, куски шардов показывает: отказ случается там, где
|
||||
считали, и строка шарда — единственное место, где эта нода названа. В замере с
|
||||
принудительным пределом памяти `clickhouse-01` записал свой отказ, а
|
||||
`clickhouse-02` — свой, и вторая строка видна только без фильтра по
|
||||
инициатору.
|
||||
|
||||
## Пустой пользователь
|
||||
|
||||
Часть строк журнала приходит без имени пользователя: так записана собственная
|
||||
работа сервера — чтение топиков движком Kafka, вставки материализованных
|
||||
представлений, обслуживание. Панели подписывают такие строки «фоновая работа
|
||||
сервера»: пустая клетка в таблице читается как поломка, а это не она.
|
||||
|
||||
## Что проверено
|
||||
|
||||
Дашборд проверен на живом стенде 21 августа 2026 года.
|
||||
|
||||
- Дашборд поднимается из каталога сам, все три панели отвечают через
|
||||
`/api/ds/query` в окне на час. Пустых нет.
|
||||
- Тяжёлый запрос виден. `GROUP BY` по `ods.event_dist` от учётки `analyst`
|
||||
приехал в панель со своими числами: 741 мс, 53,32 МиБ памяти, 701 994
|
||||
прочитанные строки.
|
||||
- Ошибки видны и различаются по природе: отказ по правам (`bi` без `SELECT` на
|
||||
`ods`) и ошибка в тексте запроса (несуществующая колонка) легли в панель
|
||||
разными строками, каждая с полным текстом.
|
||||
- Строка шарда в панели ошибок настоящая. Принудительный предел памяти на
|
||||
распределённом `GROUP BY` дал две записи: `clickhouse-01` с
|
||||
`is_initial_query = 1` и `clickhouse-02` с нулём. Вторая пропала бы, стой на
|
||||
панели тот же фильтр, что на соседних.
|
||||
- Панель «Кто спрашивает» разделила шесть учёток — `grafana`, `analyst`, `etl`,
|
||||
`dict`, `bi`, `default` — и фоновую работу сервера. Больше всех прочитала
|
||||
`grafana`: 11,5 млн строк за час, своими же панелями.
|
||||
@@ -0,0 +1,318 @@
|
||||
{
|
||||
"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\n AND database != 'system'\nGROUP BY \"Таблица\", \"Нода\"\nORDER BY \"Таблица\", \"Нода\"\n"
|
||||
}
|
||||
],
|
||||
"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": 12,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
},
|
||||
"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 database AS db,\n sum(length(merged_from)) AS \"частей съедено\"\nFROM clusterAllReplicas('clickstream_cluster', system.part_log)\nWHERE $__timeFilter(event_time)\n AND event_type = 'MergeParts'\n AND database != 'system'\nGROUP BY t, db\nORDER BY t, db"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"displayName": "${__field.labels.db}",
|
||||
"min": 0,
|
||||
"custom": {
|
||||
"drawStyle": "bars",
|
||||
"fillOpacity": 60,
|
||||
"stacking": {
|
||||
"mode": "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"interval": "1m"
|
||||
},
|
||||
{
|
||||
"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,135 @@
|
||||
{
|
||||
"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": "table",
|
||||
"title": "Кто спрашивает: сколько запросов и какой у них аппетит",
|
||||
"datasource": {
|
||||
"type": "grafana-clickhouse-datasource",
|
||||
"uid": "clickhouse"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"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(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": 8,
|
||||
"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