fix(monitoring): доводы приведены к замерам, пары нод больше не расходятся

- Зачем:
  - второй круг ревью нашёл, что текст спорит с собственной приёмкой: окно
    держалось доводом «слияния за минуту-две», а замер даёт двадцать секунд;
    довод панели ошибок опирался на отказ по памяти, которого ADR 0004 на этом
    стенде не обещает.
- Что:
  - «Части» сортируются по таблице: сортировка по числу частей разводила две
    ноды одной таблицы, как только между ними вставала чужая.
  - у «Слияний» столбик шириной в минуту, иначе редкие события выходили
    волосяной линией; из заголовка убран «слой» — фильтр отсекает журналы
    сервера, а не выбирает слои.
  - в cluster.md исправлены довод за окно, ложное «времени в system.parts нет»,
    счёт панелей без нод (их две, не одна) и разрешение, в котором снят замер
    процессора.
  - панель ошибок и её запись в ADR 0002 переписаны под проверенное: отказ на
    шарде воспроизведён принудительным пределом памяти.
  - из queries.md убран абзац про SELECT 1 — он объяснял пол удалённой панели.
- Проверка:
  - make config-test; make smoke 21/21; make check-clickhouse 10/10.
  - все десять панелей отвечают через /api/ds/query в окнах на полчаса, час и
    шесть часов; шаг столбика слияний проверен отдельным запросом.
This commit is contained in:
ddadmin committed 2026-08-21 15:28:03 -04:00
1 parent 42075ff9fb
commit 48bec014cf
4 files changed
+66 -43

No files matched your search

+6 -3
View File
@@ -133,8 +133,11 @@ keeper, и обе ноды читают её целиком — запрос в
посетителям продиктован [ADR 0007](0007-clickhouse-access.md): своя учётка
Grafana заведена там ровно затем, чтобы её запросы были отличимы от
человеческих.
Отдельная панель ошибок понадобилась под здешний же сюжет про тяжёлый
`GROUP BY`: отказ по памяти случается на шарде, и текст ошибки — единственное
место, где написано, на каком. Устройство обоих дашбордов —
Отдельная панель ошибок понадобилась потому, что отказ достаётся шарду: в
замере с принудительным пределом памяти каждая нода записала свой, и строка
шарда видна только без фильтра по инициатору. Здешний сюжет про тяжёлый
`GROUP BY` при этом кончается не отказом, а сбросом на диск
([ADR 0004](0004-resource-limits.md)) — отказ на стенде приходится вызывать
настройкой. Устройство обоих дашбордов —
[cluster.md](../architecture/monitoring/cluster.md) и
[queries.md](../architecture/monitoring/queries.md).
+47 -30
View File
@@ -17,14 +17,17 @@
| Keeper | встроенные метрики keeper через Prometheus |
| Распределённый DDL | `system.distributed_ddl_queue` |
Окно у дашборда — полчаса, обновление — полминуты, и это выбрано под нагрузку.
Вставка на стенде занимает секунды, а слияния разбирают её следы за минуту-две;
в часовом окне и подъём, и оседание схлопнулись бы в один столбик. Урок здесь —
форма кривой, и окно подобрано так, чтобы форма была видна.
Окно у дашборда — полчаса, обновление — полминуты. Пачка из
`make generate-batch` проходит за двадцать секунд, и на получасовой оси это
узкий всплеск в пару столбиков. Шире окно не берут, чтобы всплеск не выродился
в точку; уже — чтобы не промахнуться мимо него, заглянув через минуту, и чтобы
рядом осталось спокойное время: подъём читается только над полом. Кто хочет
разглядеть форму вплотную, растягивает окно мышью по самому всплеску.
Две панели окна не знают, и обе по делу. «Части» показывают состояние сейчас:
`system.parts` — опись живых частей, времени в ней нет вовсе. Очередь DDL
показывает последние двадцать записей: в спокойные полчаса она пуста, а пустая
Панель частей окна не знает вовсе. `system.parts` — опись живых частей, и
вопрос к ней про сейчас: колонки со временем в ней есть, но ни одна не годится
в ось. Очередь DDL окна тоже не знает, но по другой причине: она показывает
последние двадцать записей, потому что в спокойные полчаса пуста, а пустая
панель ничего бы не рассказала.
## Обе ноды на одной панели
@@ -35,11 +38,15 @@
Поэтому запросы идут через `clusterAllReplicas`, а `hostName()` подписывает,
чья это строка.
Исключение одно — очередь распределённых DDL. Она лежит не у ноды, а в keeper,
и обе ноды читают из него одну и ту же очередь целиком, вместе со строками про
соседа. Обёртка здесь не добавила бы правды, а удвоила бы каждую строку.
Правило поэтому звучит так: `clusterAllReplicas` нужен там, где у каждой ноды
своя правда.
Исключений два, и они разной природы. Очередь распределённых DDL лежит не у
ноды, а в keeper, и обе ноды читают из него одну и ту же очередь целиком,
вместе со строками про соседа. Обёртка здесь не добавила бы правды, а удвоила
бы каждую строку — это единственная панель дашборда без неё. Правило поэтому
звучит так: `clusterAllReplicas` нужен там, где у каждой ноды своя правда.
Второе исключение мягче. Панель слияний читает обе ноды той же обёрткой, но
складывает их в один ряд: шарды сливают симметрично, и разделение удвоило бы
легенду ради двух одинаковых кривых.
## Пол на графиках — журналы самого сервера
@@ -53,16 +60,22 @@ ClickHouse ведёт около десятка собственных журн
двух панелях читается как подъём над полом, а не как число само по себе.
У частей и слияний пол отделим: в `system.parts` и `system.part_log` у каждой
строки написано, какой таблице она принадлежит. Обе панели поэтому спрашивают
только про слои стенда. Довод в размере: за спокойные полчаса слияния съели
строки написано, какой таблице она принадлежит. Обе панели поэтому показывают
всё, кроме журналов сервера, — и у каждой на это своя причина.
Столбик у слияний шириной в минуту, а не в шаг панели. Слияния — события
редкие: на своём шаге столбик выродился бы в волосяную линию, и четыре события
за полчаса пришлось бы искать глазами.
Слияниям фильтр иначе не пережить по размеру: за спокойные полчаса они съели
6 492 части в системных журналах против 219 в `ods` и `stg` вместе. Оставь мы
журналы в кадре, ряды слоёв прижались бы к нулю, и панель рассказывала бы про
уборку за сервером вместо ваших вставок. Сколько эта уборка стоит, видно этажом
выше, на процессоре и дисках.
уборку за сервером вместо приехавших данных. Сколько эта уборка стоит, видно
этажом выше, на процессоре и дисках.
Слияния показаны по слоям, а не по нодам и не по таблицам. Шарды сливают
симметрично, и второй ряд на слой удвоил бы легенду, ничего не добавив. Какая
именно таблица держит части, отвечает соседняя панель.
Частям фильтр нужен для другого: без него в таблице десятки строк, а значит
нужен предел — и предел рвёт пару нод пополам, оставляя таблицу с одной нодой в
кадре. С фильтром помещаются все, и предел не нужен.
## Что именно меряют процессор, память и диски
@@ -109,23 +122,27 @@ Keeper — отдельный сервис, системных таблиц у
- Дашборд поднимается из каталога сам, все семь панелей отвечают через
`/api/ds/query` в окне на полчаса. Пустых нет.
- Обе ноды видны на всех панелях по системным таблицам, кроме одной. Без
`clusterAllReplicas` осталась очередь DDL, и обёртка ей действительно не
нужна: запрос с ней вернул 124 записи с `clickhouse-01` и те же 124 с
`clickhouse-02`.
- Под нагрузкой. `make generate-batch` (50 626 событий одной пачкой): процессор
поднялся с 0,04–0,18 ядра до 0,54 и 0,64, запись — с 400 КиБ/с до 2,1 и
4,5 МиБ/с. Следующие десять секунд обе величины уже вернулись к прежним: на
таком объёме подъём и оседание укладываются в полминуты.
- Слияния под той же нагрузкой отозвались столбиком: два слияния в `stg`,
- Обе ноды видны на четырёх панелях из пяти, что читают системные таблицы нод.
Пятая — слияния, там ноды сложены намеренно. Без `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` — заказы приезжают дневным слепком, события потоком. Все
таблицы слоёв поместились целиком, каждая обеими нодами: восемь строк.
`ods.event_rep` — заказы приезжают дневным слепком, события потоком. Строк
вышло восемь: четыре таблицы, каждая обеими нодами. Таблиц ошибок среди них
нет, и это правда состояния — частей у них пока не появилось.
- Очередь DDL показала `CREATE MATERIALIZED VIEW ... ON CLUSTER` в состоянии
`Finished` и со временем по каждой ноде: `make generate-batch` поднимает
`clickhouse-init`, и тот заново прогоняет DDL.
+9 -7
View File
@@ -27,10 +27,6 @@ Grafana ходит в ClickHouse обычным пользователем, и
остальное. Спрячь мы свои запросы фильтром — цена ушла бы с глаз, а панель «Кто
спрашивает» недосчиталась бы самого частого посетителя.
Там же, в «Кто спрашивает», стоит `default` с сотнями запросов и одной
прочитанной строкой на каждый: это `SELECT 1` от проверки состояния
контейнера, она ходит к каждой ноде раз в несколько секунд.
Различать посетителей дашборд умеет потому, что у Grafana своя учётка со своей
ролью ([ADR 0007](../../adr/0007-clickhouse-access.md)). Ходи она под
аналитиком, панель «Кто спрашивает» показывала бы одну строку на всех.
@@ -46,9 +42,11 @@ Grafana ходит в ClickHouse обычным пользователем, и
считают только запросы инициатора: иначе один запрос человека выглядел бы
тремя, а прочитанные строки сложились бы дважды.
Панель ошибок, наоборот, куски шардов показывает. Отказ по памяти случается
там, где считали, — на шарде; инициатор в своей строке передаст только текст
ошибки, а нода, на которой всё случилось, останется неназванной.
Панель ошибок, наоборот, куски шардов показывает: отказ случается там, где
считали, и строка шарда — единственное место, где эта нода названа. В замере с
принудительным пределом памяти `clickhouse-01` записал свой отказ, а
`clickhouse-02` — свой, и вторая строка видна только без фильтра по
инициатору.
## Пустой пользователь
@@ -69,6 +67,10 @@ Grafana ходит в ClickHouse обычным пользователем, и
- Ошибки видны и различаются по природе: отказ по правам (`bi` без `SELECT` на
`ods`) и ошибка в тексте запроса (несуществующая колонка) легли в панель
разными строками, каждая с полным текстом.
- Строка шарда в панели ошибок настоящая. Принудительный предел памяти на
распределённом `GROUP BY` дал две записи: `clickhouse-01` с
`is_initial_query = 1` и `clickhouse-02` с нулём. Вторая пропала бы, стой на
панели тот же фильтр, что на соседних.
- Панель «Кто спрашивает» разделила шесть учёток — `grafana`, `analyst`, `etl`,
`dict`, `bi`, `default` — и фоновую работу сервера. Больше всех прочитала
`grafana`: 11,5 млн строк за час, своими же панелями.