Модуль 16. Capacity Planning & Benchmarking: размер, стоимость, нагрузка
Это практическая методика: как спрогнозировать железо/стоимость, поставить стенд, нагенерить 10^8–10^9 строк, разыграть профили нагрузки, снять p95 и read_bytes, учесть мерджи/репликацию/S3, и оформить план масштабирования.
Что считаем успехом
Цель: ещё до продакшена доказать, что выбранная архитектура потянет нужный RPS ingest и одновременные BI-запросы в заданных SLA.
Критерии приёмки (пример):
- Ingest: стабильные N событий/сек (или X млн строк/час) без роста lag и бэклога мерджей.
- Чтение: p95 латентности ключевых запросов ≤ целевых (например, плитки ≤ 1–2 c, отчёты ≤ 5–15 c) при K параллельных пользователях.
- Хранение: «горячее окно» (например, 90 дней) умещается на NVMe, «холод» уехал на S3; размер и стоимость в бюджете.
- Надёжность: мерджи и репликация не отстают; ретро-окна пересчитываются в ночные слоты.
Классификация профилей нагрузки
Read-heavy vs Ingest-heavy vs Mixed
- Read-heavy: десятки–сотни одновременных аналитических запросов, сравнительно небольшой ingest (ежеминутные батчи).
- Ingest-heavy: 1–100k ev/s (Kafka), NRT-витрины, приоритет свежести; чтения вторичны.
- Mixed: оба «горячие» — типично для маркетинга/eCom: NRT KPI + BI-дашборды днём.
Конкурентность
Определяем целевую конкурентность (одновременных запросов) и тип запросов:
- Tile (плитка): короткий, узкий диапазон, GROUP BY 1–2 измерения.
- Report: шире по времени/измерениям.
- Ad-hoc: часто «плохие» планы, но ограничены профилем/квотой.
Sizing: CPU/RAM/NVMe/S3 — принципы и прикидки
CPU и параллелизм
- ClickHouse масштабируется по потокам (max_threads) и частям/шардам.
- Бенч-приём: закладывайте 2–4 vCPU на активного одновременного «тяжёлого» запросчика или ~1 vCPU на 2–3 «плитки», плюс запас под фоновые мерджи/репликацию (обычно +30–50%).
RAM
- RAM нужна для векторных вычислений, хеш-агрегаций, JOIN-ов, сортировок и кэша ОС/FS.
- Правило первого приближения: 4–8 ГБ на vCPU для read-heavy (с запасом на spill), 2–4 ГБ на vCPU для ingest-heavy, но не менее 64–128 ГБ на ноду.
- Если используете filesystem cache под S3 — учитывайте десятки–сотни ГБ на ноду только под кэш.
NVMe (локальное горячее) и S3 (холод)
- «Горячее окно» (последние 30–180 дней) должно помещаться на локальных NVMe.
- Старые партиции — TTL MOVE → S3; при S3-чтениях — filesystem cache.
- Целитесь в средний размер part сотни МБ–единицы ГБ; это экономит метаданные и ускоряет чтение.
Сколько диска нужно? (оценка)
- Посчитайте байты на строку: сумма размеров колонок с учётом типов; примените коэффициент сжатия (обычно 2–6× для чисел/строк со словарями).
- Добавьте индексы/метаданные (5–15%).
- Учтите репликацию (×R) и снапшоты/бэкапы (хотя бы ×1.2 к пику).
Пример: 10 колонок по 8 байт ⇒ 80 байт/строка сырых; сжатие 3× ⇒ 27 байт/строка. На 1e9 строк ⇒ ~27 ГБ + 10% индексы ⇒ ~30 ГБ на одну таблицу «ядра». С репликацией ×2 ⇒ 60 ГБ «горячего».
Горячее окно, TTL и filesystem cache
- Разделите горячие партиции (NVMe) и холод (S3) по storage_policy.
- Пример TTL: 90 дней → warm, 365 дней → S3.
- Для S3 включайте filesystem cache и держите «тиражируемые» отчёты в кэше (часто повторяются).
- Анти-паттерн: держать годами всё «горячим» — мерджи и стоимость взлетят.
Мерджи, репликация и «скрытая» нагрузка
- Мерджи перерабатывают части: для тысяч мелких вставок затраты возрастают нелинейно. Укрупняйте блоки вставок, следите за system.merges.
- Репликация: любая запись делает дополнительную сетевую/дисковую работу. При sizing учитывайте коэффициент репликации (обычно ×2 по IO и хранению).
- Ретро-пересчёты: ночное окно (7–30 дней) — это «спайк» нагрузки. Планируйте CPU/IO-окна.
Методика бенчмарка (TPC-H/TPC-DS-like, mix, concurrency)
Датасет 10^8–10^9 строк (генерация)
Соберём искусственный «широкий факт» + «аггрегаты состояния».
-- 1) Факт на 1e8..1e9 строк CREATE TABLE bench_fact ( event_time DateTime, day Date MATERIALIZED toDate(event_time), shop_id UInt32, sku_id UInt32, user_id UInt64, qty Int32, price Float64, amount Float64 ALIAS qty * price, region LowCardinality(String) ) ENGINE=MergeTree PARTITION BY toYYYYMM(day) ORDER BY (day, shop_id, sku_id, user_id); -- Генерация (пример блоками по 10 млн) INSERT INTO bench_fact SELECT now() - number % 86400 AS event_time, toDate(now() - number % 86400) AS day, (number % 1000) + 1 AS shop_id, (number % 100000) + 1 AS sku_id, cityHash64(number) AS user_id, (rand() % 3) + 1 AS qty, 10 + rand() % 100 AS price, multiIf(rand()%100<50,'NA','EU','APAC') AS region FROM numbers(10000000); -- повторить 10–100 раз → 1e8..1e9 строк
Агрегаты-состояния:
CREATE TABLE bench_minute_state ( minute DateTime, shop_id UInt32, region LowCardinality(String), qty_state AggregateFunction(sum, Int64), rev_state AggregateFunction(sum, Float64), users_state AggregateFunction(uniqCombined, UInt64) ) ENGINE=AggregatingMergeTree PARTITION BY toYYYYMMDD(minute) ORDER BY (minute, shop_id, region); INSERT INTO bench_minute_state SELECT toStartOfMinute(event_time) AS minute, shop_id, region, sumState(qty), sumState(amount), uniqCombinedState(user_id) FROM bench_fact GROUP BY minute, shop_id, region;
Три профиля запросов (набор)
P1: Tile (узкий диапазон, малые группы)
/*tile*/ SELECT day, shop_id, sum(amount) s, sum(qty) q FROM bench_fact WHERE day BETWEEN today()-7 AND today()-1 AND shop_id IN (101,205,309) GROUP BY day, shop_id ORDER BY day, shop_id;
P2: Report (шире окно, больше групп)
/*report*/ SELECT region, toStartOfHour(event_time) AS hh, sum(amount) s, uniqExact(user_id) u FROM bench_fact WHERE event_time >= now()-INTERVAL 3 DAY GROUP BY region, hh ORDER BY hh, region;
В реальном мире uniqExact заменяем на uniq*Merge из rollup — измеряем оба варианта.
P3: NRT (из rollup состояний)
/*nrt*/
SELECT minute, shop_id, region,
sumMerge(qty_state) AS qty,
sumMerge(rev_state) AS rev,
uniqCombinedMerge(users_state) AS users
FROM bench_minute_state
WHERE minute >= now()-INTERVAL 6 HOUR
GROUP BY minute, shop_id, region
ORDER BY minute, shop_id
LIMIT 100000; -- ограничитель ответа
Конкурентность и mix
- Прогоните каждый профиль по отдельности в 1, 4, 8, 16, 32 потоков (параллельные клиенты).
- Затем прогоните mix (например, 60% tile, 30% report, 10% nrt) при той же конкурентности.
Как снимать метрики (read_bytes, p95 latency, parts, merges)
p95 по system.query_log
SELECT extractTextFromHTML(query) AS q, round(quantileExactWeighted(0.95)(query_duration_ms, 1), 0) AS p95_ms, round(sum(read_bytes)/1e9,2) AS read_gb, count() AS n FROM system.query_log WHERE type='QueryFinish' AND event_time >= now()-INTERVAL 15 MINUTE AND (q LIKE '/*tile*/%' OR q LIKE '/*report*/%' OR q LIKE '/*nrt*/%') GROUP BY q ORDER BY q;
Части/мерджи/репликация
-- активные части (top) SELECT table, partition, count() parts, sum(rows) r, sum(bytes_on_disk) b FROM system.parts WHERE active GROUP BY table, partition ORDER BY parts DESC LIMIT 20; -- мерджи, которые долго крутятся SELECT table, elapsed, progress FROM system.merges ORDER BY elapsed DESC LIMIT 20; -- отставание репликации SELECT database, table, count() q, max(create_time) latest FROM system.replication_queue GROUP BY database, table ORDER BY q DESC;
Интерпретация результатов (честно и прагматично)
- Если p95 «прыгает» при росте конкурентности — узкое место: IO (read_bytes растут), CPU (агрегаты/сортировки), мерджи (фон отжирает ресурсы), или S3-кэш (джиттер сети).
- Если ingest держится до X ev/s, а дальше растёт parts/partition и падает свежесть — укрупняйте батчи, уменьшайте консьюмеров, выносите тяжёлую работу из MV.
- Если «тяжёлые» запросы на факте «давят» — включайте rollup и состояния (…State/…Merge), фиксируйте это в дизайне.
Оценка TCO (упрощённый калькулятор)
Влаги: Compute + Storage + Network + Ops
- Compute: Σ(часовая цена VM * 24 * 30) * число нод (включая реплики и Keeper/сервисные).
- Storage: NVMe (горячее окно) + S3 (холод), учесть коэффициент сжатия и репликацию.
- Network: межнодовый трафик репликации + S3 GET/PUT/egress (даже внутри VPC на облаках есть ставка).
- Ops: резерв на сопровождение/наблюдаемость (в процентах от Compute, например 10–15%).
Сценарии сравнения:
- NVMe-only vs NVMe+S3,
- репликация ×1 vs ×2 (стойкость),
- горячее окно 30 vs 90 vs 180 дней.
Выход: таблица «сценарий → p95 / ingest / TCO», рекомендация: «для SLA X и бюджета Y выбираем конфигурацию Z».
План масштабирования (scale-up / scale-out)
- Scale-up: больше vCPU/RAM/NVMe на ноду. Просто, но есть предел.
-
Scale-out: добавляем шарды (распределённые таблицы).
- Выбираем ключ шардинга под типовые WHERE (tenant/region/day).
- Локально аггрегируем на шардах, потом собираем.
- Ребаланс: переставить партиции ALTER TABLE … MOVE PARTITION на новый шард.
Триггеры масштабирования:
- p95 > SLO при типовой конкурентности;
- merges backlog держится длительно;
- «горячее» начинает вытеснять «холод» или наоборот.
Практика: сценарий бенч-дз
- Развернуть кластер: 3–5 нод CH + (опционально) 3 Keeper; NVMe 2–4 ТБ/ноду; включить storage policy с S3.
- Сгенерировать 1e8–1e9 строк в bench_fact, заполнить bench_minute_state.
- Прогнать P1/P2/P3 по конкурентности 1/4/8/16/32, затем MIX.
- Зафиксировать p95, read_bytes, parts, merges, replication_queue.
- Включить TTL MOVE и filesystem cache; повторить P1/P2/P3 (проверить S3-джиттер).
- Провести ретро-пересборку окна (например, 30 дней) и замерить влияние на чтение.
- Подготовить отчёт и TCO.
Отчёт: как оформить выводы
Структура:
- Исходные требования (SLA свежести/латентности, конкурентность).
- Тестовая конфигурация (ядра/память/диски/репликация/S3).
- Результаты (таблицы p50/p95/p99; графики через Grafana/экспорт CSV из query_log).
- Наблюдения (что упёрлось, как лечили).
- Рекомендации (какая конфигурация проходит SLO, где сэкономить TCO, план масштабирования и триггеры).
- Риски и план митигаций.
Чек-лист «годности» стенда и целевые пороги
- Датасет ≥ 1e8 строк, колонки и кардинальность похожи на реальный домен.
- Партиции и ORDER BY соответствуют типовым WHERE.
- Роллап-слой (минуты/часы/дни) есть; «тяжёлые» метрики — в …State/…Merge.
- Конкурентность прогнана (1/4/8/16/32), есть p95 для каждого профиля.
- parts/partition < внутреннего порога (например, <150 активных на партицию).
- merges backlog не растёт в течение бенча, replication_queue — мал.
- TTL MOVE работает, S3-кэш включён, поведение прогнозируемо.
- Описаны ретро-окна и их влияние (в ночной слот укладывается).
- Есть TCO-таблица и план масштабирования (scale-up/out).
Риски и митигации
|
Риск |
Проявление |
Митигация |
|---|---|---|
|
Недооценили мерджи |
parts/partition растёт, свежесть падает |
Укрупняйте батчи, уменьшайте консьюмеров, переносите тяжёлое из MV в батчи/ночь |
|
Репликация «ест» CPU/сеть |
replication_queue длинный, лаг |
Планируйте ×2 по IO, выделяйте CPU на фон, проверяйте сеть/диски |
|
S3-«качели» |
p95 скачет, особенно при кэше miss |
Держать «горячее окно» на NVMe, увеличить filesystem cache, прогрев, избегать случайного сканирования |
|
JOIN «большого на большой» |
таймауты/память |
Предагрегаты, словари dictGet*(), материализация атрибутов |
|
Distinct/квантили «на лету» |
p95 ×5–×10 |
Только …State/…Merge, отдельные rollup’ы |
|
Ретро-окна перекрывают день |
дневные запросы «падают» |
Строго ночной слот, порционное окно (по партициям), лимиты для фоновых джоб |
|
Неверный ORDER BY |
read_bytes ≈ весь объём партиций |
Перепроектировать ключ под реальные WHERE, v2-таблица, прогнать бенч повторно |
Артефакты модуля (что отдаём команде)
- Workbook расчётов (Excel/Markdown): входные SLO/нагрузка → подбор CPU/RAM/NVMe/S3 → TCO сценарии.
- DDL и скрипты бенчей: генерация 1e8–1e9 строк, P1/P2/P3, сценарий MIX, конкаррентные запуски (bash/pytest).
- SQL-репорты метрик: p95/read_bytes из system.query_log, parts/merges/replication.
- Шаблон отчёта: структура, графики, таблицы, выводы/рекомендации.
- Чек-листы: «готовность стенда», «пороговые значения», «план масштабирования».
Вывод
Capacity planning для ClickHouse — это не гадание, а воспроизводимая практика:
- Зафиксировать SLO и профили нагрузки.
- Оценить CPU/RAM/NVMe/S3 и «горячее окно» с TTL.
- Сгенерировать реалистичный объём (≥1e8 строк), поставить бенч P1/P2/P3 + mix.
- Снять p95/read_bytes, parts/merges/replication, проверить S3-кэш.
- Посчитать TCO, выбрать конфигурацию, прописать триггеры масштабирования и план роста.
Arenadata QuickMarts (ADQM) — корпоративная платформа на базе ClickHouse для быстрого слоя витрин и near-real-time аналитики. Решает задачи «быстрых» дашбордов и API с низкой латентностью и высокой конкуррентностью, работает поверх вашего DWH/лейкхауса как serving-уровень. Даёт предсказуемую производительность на терабайтно-петабайтных объёмах за счёт колоночного хранения, компрессии и предагрегатов (Materialized Views, AggregatingMergeTree), подключается к Kafka/S3 и стандартным BI-инструментам по SQL/HTTP. Для корпоративных ИТ ADQM предлагает поддержку и SLA, отказоустойчивые кластеры (HA/DR), безопасность (RBAC, LDAP/OIDC, шифрование трафика и данных), мониторинг и резервное копирование. Платформа хорошо ложится на методологию курса: семантика vw_*, роллап-слои, NRT-ингест, SLO/наблюдаемость и «гвардейки» для BI/API. Итог — быстрый запуск витрин за недели, снижённые риски в проде и предсказуемая стоимость владения.



