Модуль 2. Моделирование витрин под ClickHouse
(wide vs star, зерно и ключи, SCD0/1/2, pre-join, инкременты/CDC, агрегаты, ORDER BY/партиции, миграции, кейсы)
Куда «ложится» моделирование витрин
Слой витрин (MARTS) — интерфейс между «правдой» (CORE) и потребителями (BI, аналитики, сервисы). Моделирование отвечает на 6 вопросов:
- Зерно (grain): какая одна строка — какой факт/срез?
- Срезы/измерения: какие атрибуты нужны и где они жить будут?
- Материализация: что посчитать заранее (агрегаты, состояния)?
- Инкременты: как довозить изменения без полного пересчёта?
- Физика ClickHouse: партиции, ORDER BY, движок (MergeTree-семейство), индексы.
- Жизненный цикл: миграции v1→v2 без простоя, тесты, регламенты.
Зерно витрины: ставим «точку опоры»
Правило: сначала зерно, потом всё остальное. Типичные зерна:
- Транзакционное: чек-позиция, платеж, событие.
- Снапшот: «состояние на конец дня/часа» (остатки, активные подписки).
- Агрегат: день × магазин × категория (предрассчитанный слой).
Чек-лист выбора зерна
- Какой вопрос отвечает витрина? («сколько продали по дням × магазинам»)
- Какая минимальная дробность нужна BI? (день/час/мин)
- Какие фильтры будут стоять в 90% запросов? (дата → магазин → категория)
- Как будете обновлять (инкремент/ретро)?
- Какая цена ошибки дубля? (для платежей — критично)
Риск: «размытое зерно» (в строке и факт, и агрегат) → путаница и дубли.
Митигация: отдельно витрина фактов и отдельно агрегаты.
Модель: wide vs star vs гибрид
Wide (широкая, денормализованная)
Одна строка содержит факт и нужные атрибуты измерений «на дату среза».
Когда: отчёты с простыми разрезами, высокая нагрузка чтения, нежелательны JOIN на лету.
Плюсы: быстро, стабильно, мало соединений.
Минусы: дубли атрибутов, обновления атрибутов сложнее (SCD-логика).
Пример:
CREATE TABLE mart_sales_wide ( tx_datetime DateTime, day Date MATERIALIZED toDate(tx_datetime), shop_id UInt32, shop_name LowCardinality(String), region_id UInt16, sku_id UInt32, sku_name String, brand LowCardinality(String), category_id UInt32, qty Int32, amount Decimal(12,2), currency FixedString(3), status_canon LowCardinality(String), customer_id UInt64 ) ENGINE = MergeTree PARTITION BY toYYYYMM(day) ORDER BY (day, shop_id, sku_id);
Star (звезда)
Факты отдельно, измерения отдельно (d_shop, d_sku…), связь по ключам.
Когда: много сочетаний измерений, нужна гибкость, частые изменения справочников.
Плюсы: меньше дублирования, независимая эволюция измерений.
Минусы: JOIN дороже; надо проектировать ключи и ORDER BY под запросы.
Компромисс (часто лучший вариант): хранить в факте ключи измерений + несколько «часто используемых» атрибутов, а редкие/тяжёлые атрибуты подтягивать pre-join в агрегатах или в отдельные тематические wide-витрины.
SCD для измерений в ClickHouse
Типы SCD
- SCD0: «заморозили» значение — не меняется.
- SCD1: «переписали» на новое (историю не храним).
- SCD2: ведём версии (period from/to, current_flag) — «каким было на дату X».
Где вести SCD
- В идеале — в CORE, а в MARTS уже «снимки» на дату факта.
- Если SCD нужно вести прямо в MARTS: используем ReplacingMergeTree(version) для версий или «периодизацию» (from/to) в измерении.
SCD2-измерение (упрощённо):
CREATE TABLE d_shop_scd2 ( shop_id UInt32, shop_name String, region_id UInt16, valid_from DateTime, valid_to DateTime, is_current UInt8 ) ENGINE = MergeTree ORDER BY (shop_id, valid_from);
Присоединение «на дату транзакции»:
SELECT f.*, d.shop_name, d.region_id FROM f_sales f JOIN d_shop_scd2 d ON d.shop_id = f.shop_id AND f.tx_datetime >= d.valid_from AND f.tx_datetime < d.valid_to
Для скорости — материализуем snapshot атрибутов в широкую витрину при загрузке.
Риск: JOIN по интервалу без индекса может быть тяжелым.
Митигация: pre-join в процессе ETL и записать результат; либо хранить ежедневные snapshot-версии ключевых атрибутов.
Ключи и уникальность
Бизнес-ключи vs суррогатные
- Бизнес-ключ (order_id, (shop_id, receipt_no)) — понятен домену, но может меняться/пересекаться.
- Суррогат (UInt64/UUID) — удобен для дедупа/распределения.
Рекомендация: хранить оба: суррогат для техпроцессов, бизнес-ключ — для аудит-следа.
Уникальность
В ClickHouse жёсткой уникальности нет — вы её обеспечиваете дисциплиной записи и периодическими проверками.
Для идемпотентности: ReplacingMergeTree(version) или предварительный дедуп на STAGE.
Риск: тихие дубли «накручивают» суммы в Summing-агрегатах.
Митигация:
- тест «дубликатов зерна» по окну;
- для сумм — использовать AggregatingMergeTree (состояния), устойчивые к повторной заливке.
Инкременты, «поздние» события и ретро-пересчёты
Инкрементальные паттерны
- Append-only: «вставили и забыли» (события).
- Upsert: пришла новая версия записи → заменить старую (Replacing).
- Сигналы +/-: вставка/удаление (Collapsing/VersionedCollapsing).
- Агрегат-состояния: …State/…Merge (Aggregating).
Поздние события / ретро-окно
Фиксируем окно late events — напр., «14 дней дозаливаем». В джобах:
- инкремент: «каждые 15 минут за последние 2 часа»;
- ночной ретро-пересчёт: «за 30 дней».
Риск: бесконечный полнопересчёт.
Митигация: side-by-side пересборка (v2-таблица) и переключение; оконные ретро-пересчёты, а не «вся история».
Материализация: pre-join и pre-aggregate
Pre-join
Дорогие/частые JOIN делаем заранее (в широкую витрину или агрегат). Особенно — SCD-атрибуты «на дату факта», статусы, валюты.
Pre-aggregate
Частые группировки (день×магазин×категория, минута×канал) считаем и храним. Выбор движка:
- SummingMergeTree — если не будет переигровок (повторных заливок).
- AggregatingMergeTree — устойчивые состояния (sumState, uniq*State, quantile*State) → на чтении …Merge.
- Projections — только после профилирования, точечно.
Риск: Summing + повторная заливка = удвоение.
Митигация: Aggregating (состояния) или пересборка целевого окна из «чистого» источника.
Физика ClickHouse: партиционирование, ORDER BY, индексы
Партиционирование
- По времени: toYYYYMM(day) / toYYYYMMDD(day) (месяц/день).
- Выбор: события с высоким трафиком — дневные; продажи — месячные.
- Слишком мелко → part explosion. Слишком крупно → неудобный ретро-пересчёт.
ORDER BY (префикс PRIMARY KEY)
ORDER BY — главный рычаг. Он определяет физический порядок в частях. Делайте под типовые фильтры: дата → разрез → идентификатор.
Примеры:
- Продажи: (day, shop_id, sku_id)
- События: (event_time, user_id[, session_id])
- Балансы: (day, account_id)
Индексы-скипы
- minmax по числам/датам;
- bloom_filter для LIKE/IN больших списков;
- set для маленьких доменов.
Риск: длинный ORDER BY по высококардинальным полям → рост метаданных, слабый эффект.
Митигация: держите префикс коротким и соответствующим фильтрам.
Дедупликация и идемпотентность
ReplacingMergeTree(version)
Хранит все версии; при merge остаётся запись с максимальной version.
Паттерн:
- источник даёт монотонную version (ts/event_id_seq);
- на чтении не использовать FINAL в BI → дисциплина upstream (не плодить версии) + чтение «закрытых» партиций.
Collapsing/VersionedCollapsing
Подходит для логов +/-, где удаление моделируется отдельной записью со знаком. Требует строгой дисциплины порядка.
Дедуп на STAGE
Лучше всего — дедуп перед записью в витрину (по ключу+версии), а Replacing — как страховка.
Риск: привыкание к FINAL для консистентности.
Митигация: архитектурно обеспечить уникальность в окне → BI без FINAL.
Моделирование измерений: справочники, словари, маппинги
- d_calendar: календарь (григорианский и/или 4-5-4), единый для всех.
- d_status_map: маппинг статусов источников → канонические (PAID, REFUND…).
- dict_fx: курсы валют (на дату).
- d_uom / dict_uom: единицы измерения и коэффициенты.
Риск: «UNKNOWN» статусы «портят» цифры.
Митигация: алерт «новые статусы», ежедневный разбор, строгий контракт «откуда берём истину».
Типовые шаблоны таблиц (копируй и адаптируй)
Факт продаж (wide)
CREATE TABLE mart_sales_wide ( tx_id UInt64, tx_datetime DateTime, day Date MATERIALIZED toDate(tx_datetime), shop_id UInt32, shop_name LowCardinality(String), region_id UInt16, sku_id UInt32, category_id UInt32, brand LowCardinality(String), qty Int32, amount Decimal(12,2), currency FixedString(3), status_canon LowCardinality(String), customer_id UInt64 ) ENGINE = MergeTree PARTITION BY toYYYYMM(day) ORDER BY (day, shop_id, sku_id, tx_id);
Агрегат «день × магазин × категория» (устойчивый)
CREATE TABLE agg_sales_daily_state ( day Date, shop_id UInt32, category_id UInt32, amount_state AggregateFunction(sum, Decimal(14,2)), qty_state AggregateFunction(sum, Int64) ) ENGINE = AggregatingMergeTree PARTITION BY toYYYYMM(day) ORDER BY (day, shop_id, category_id);
Справочник статусов
CREATE TABLE d_status_map ( src_system LowCardinality(String), src_status LowCardinality(String), canonical_status LowCardinality(String) ) ENGINE = MergeTree ORDER BY (src_system, src_status);
Миграции схем и эволюция витрин
Additive-изменения
ADD COLUMN — безопасно; DROP/RENAME — через v2-таблицу.
Side-by-side (v1→v2)
- создаём *_v2 (новое ORDER BY/схема/движок),
- двойная запись «вперёд» (если возможно),
- переливаем историю по окнам,
- сверяем агрегаты,
- переключаем BI (VIEW/alias),
- замораживаем v1 → удаляем позже.
Пересборка партиций
Только по окнам, ночью, с бэкапом/снапшотом, c контролями.
Риск: «ночью всё пересобрали, утром — пусто».
Митигация: чек-листы: бэкап, dry-run на стейдже, пост-мониторинг.
Производительность: выбор ORDER BY, размер блоков, merges
Быстрые выигрыши:
- ORDER BY под реальные фильтры.
- Партиции под частоту запросов (месяц/день).
- Крупные батчи вставок (сотни тысяч/миллионы строк).
- Избегайте FINAL в BI.
- Мониторьте system.query_log, system.part_log, merges backlog.
Антипаттерны:
- ORDER BY «как попало» (не отражает WHERE) → читаете лишнее.
- Слишком частые мелкие вставки → тысячи parts, merges «задыхаются».
- Summing на данных, которые «переигрывают» → удвоение сумм.
Кейс 1 (Retail): продажи и GM на wide-витринах
Задача: дашборды «день × магазин × категория», Net Sales, GM, GM%.
Модель:
- mart_sales_wide — факт с нормализованными статусами и суммой в базовой валюте.
- agg_retail_daily_state — суммы и GM как состояния.
- VIEW vw_retail_daily — sumMerge + расчет GM%.
Риски:
- Возвраты «задним числом» → ретро-окно 14–30 дней.
- Себестоимость меняется → хранить cost_per_unit снэпшотом на день, ретро-пересчитать окно.
- Длинные отчёты «за год» → хранить месячные агрегаты отдельно.
Кейс 2 (FinTech): транзакции, остатки, выручка
Задача: остатки по дням (полуаддитивная метрика), комиссионная выручка.
Модель:
- mart_tx_wide — факт (дебет/кредит/fee/refund), amount_base.
- agg_balance_daily_state — sumState(+/- amount) по дню и счёту.
- agg_revenue_daily_state — sumState(fee) по продукту.
Риски:
- Чарджбеки → ретро-окно пересчёта.
- Валюты → фиксируйте правило «на дату операции» vs «на дату отчёта».
- Сверка с GL (главная книга) → nightly баланс-тест.
Кейс 3 (Events/Telecom): поминутные KPI, перцентили
Задача: p95 latency, success_rate, объёмы по минутам × сотам.
Модель:
- ingest из Kafka → events_wide.
- agg_minute_state с sumState(ok/all) и quantileTDigestState(0.95).
- VIEW агрегирует …Merge и считает долю.
Риски:
- Бурсты и мелкие вставки → настраивать батчи, буферные таблицы.
- Дубликаты событий → dedup по event_id до mart; Replacing как страховка.
«Политика» агрегатов и слоёв
- Факты (wide/star) — первичный слой витрин: быстрое чтение, минимум JOIN.
- Агрегаты-состояния — «рабочие лошади» под BI (устойчивость к повторной заливке).
- Вьюхи — интерфейс для потребителя: …Merge, семантика, маскирование, RLS.
- Агрегаты по периодам (неделя/месяц) — отдельные таблицы для регулярной отчётности.
Проверки качества (вокруг моделирования)
- Нет дублей зерна в фактических витринах (причина — неверная уникальность/ingest).
- Баланс MARTS vs CORE на окне (±допуск).
- Плотность рядов по ключевым срезам (нет «дыр»).
- Стабильность схем: additive-изменения, breaking — только через v2.
- Guardrails производительности: ограничение периодов в BI, дефолтные фильтры.
Антипаттерны моделирования и быстрые фиксы
-
Витрина копирует CORE (никаких pre-join/агрегатов).
Фикс: денормализуйте под сценарии BI, материализуйте частые группировки. -
ORDER BY не по фильтрам.
Фикс: переопределить под реальные WHERE, переложить v2-таблицей. -
Повсюду FINAL в чтении.
Фикс: дисциплина записи/дедуп upstream, использование Aggregating-состояний. -
Summing на данных с ретро-правками.
Фикс: Aggregating (state/merge) или rebuild окна. -
Мелкие партиции, много parts.
Фикс: буферизация вставок, укрупнение партиций, контроль merges. -
JOIN «толстых» измерений на лету.
Фикс: pre-join атрибутов «на дату факта» в широкую витрину/агрегат.
Практические «рецепты» (SQL-наброски)
Выбор ORDER BY через «трассировку» запросов
- Соберите из system.query_log топ WHERE-фильтров.
- Выделите общий префикс (обычно дата → разрез).
- Спроектируйте ORDER BY именно под этот префикс.
Стратегия «двух окон» обновления
- Near-real-time: каждые N минут дозаливаем NOW()-2h..NOW().
- Ночь: пересчитываем NOW()-30d..NOW()-1d (ретро-корректировки).
Side-by-side миграция ORDER BY
- создать *_v2 с новым ORDER BY;
- переливать партиции «месяц за месяцем» с контролем суммы;
- переключить VIEW на v2;
- v1 хранить 30–60 дней.
«Человеческая» стратегия выбора движка
- MergeTree — если вставили и забыли, без правок.
- ReplacingMergeTree(version) — если бывают апдейты по ключу (но избегайте FINAL).
- SummingMergeTree — простые накопительные суммы без переигровок.
- AggregatingMergeTree — когда важна устойчивость к повторной заливке или нужны uniq/quantile/avg с правильной агрегацией.
- Collapsing/VersionedCollapsing — для сигналов +/- (дисциплина порядка!).
Мини-регламент проектирования новой витрины
- Сформулируйте вопрос и зерно (что в строке).
- Определите разрезы (какие атрибуты реально нужны в 90% запросов).
- Выберите модель: wide / star / гибрид.
- Спроектируйте партиции и ORDER BY под реальные фильтры.
- Решите, что материализовать (pre-join, агрегаты состояния).
- Определите инкремент и ретро-окно.
- Добавьте DQ-тесты и мониторинг (freshness, parts/merges).
- Пропишите миграции (как будете менять схему/формулы в будущем).
- Оформите паспорт метрик и VIEW (семантика для BI).
Ответы на частые вопросы
Нам нужна и ширина, и гибкость. Что делать?
— Делайте wide-витрины по доменам (продажи, веб-события) + тематические агрегаты; звезду держите в CORE и/или как вторую «служебную» модель для сложных расчётов.
Когда использовать проекции (projections)?
— Только после измерений «до/после». Это оптимизация, а не основа модели.
Как убрать FINAL, если данные «грязнятся»?
— Введите дедуп upstream (ключ+версия), ограничьте окно конкурирующих версий, переливайте «закрытые» партиции батчем, используйте Aggregating-состояния.
Сколько партиций делать?
— События с большим потоком — дневные; продажи — месячные. Дальше смотрите на merges backlog и SLA ретро-пересчётов.
Итог
Моделирование витрин под ClickHouse — это про баланс: удобство и скорость чтения ↔ управляемость изменений ↔ устойчивость к инкрементам.
Ключевые опоры:
- Ясное зерно и осмысленная модель (wide/star/гибрид).
- Материализации (pre-join, агрегаты-состояния) вместо тяжёлых запросов в BI.
- Инкременты и ретро-окна вместо полных пересчётов.
- Физика под запросы (партиции, ORDER BY, батчи).
- Миграции v1→v2 без простоя и DQ/observability — как часть конструкции.
И дополнительные материалы:
1) Чек-лист проектирования витрины (версия 1.0)
Заполняется при старте любой новой витрины или при планировании v2.
Формат: да/нет + заметки + владелец.
A. Назначение и потребители
- Бизнес-вопрос(ы), на которые отвечает витрина (2–3 конкретных запроса).
- Основные потребители (BI/аналитики/сервисы), критичные дашборды.
- Требуемая гранулярность: транзакция / минута / час / день / неделя / месяц.
- Ожидаемые топ-срезы (до 5): пример — день → магазин → категория.
B. Контракты и семантика
- Паспорт ключевых метрик (формулы, фильтры, календарь, валюта, зерно).
- Календарь: григорианский / финансовый (4-5-4/4-4-5), TZ.
- Валюта: «на дату операции» / «на дату отчёта» (нужны обе? отдельные VIEW).
- Маппинг статусов источников → канонические статусы.
- Окно корректировок (late events), дни: …; кто владелец решения.
C. Зерно, модель и ключи
- Зерно vitrine-fact (что в 1 строке) описано однозначно.
- Модель: wide / star / гибрид (обоснование выбора).
- Ключи: бизнес-ключ и суррогат (нужно оба? зачем?).
- SCD по измерениям: SCD0/1/2 и где живёт (CORE или MARTS).
D. Материализация
- Какие JOIN делаем заранее (pre-join) и что остаётся для чтения.
- Какие агрегаты считаем заранее (pre-aggregate), где будут храниться.
- Механика устойчивости к повторным заливкам (Aggregating/…State/…Merge).
E. Физика ClickHouse
- Партиционирование: месяц/день/неделя (обосновано нагрузкой).
- ORDER BY соответствует типовым WHERE (реальный паттерн фильтров).
- Вторичные индексы (data-skipping): minmax / bloom / set — только при доказанной пользе.
- Движок таблицы: MergeTree / Replacing(version) / Summing / Aggregating — и почему.
F. Инкременты и ретро
- Инкремент: как часто дозаливаем (каждые N минут/часов).
- Ночной ретро-пересчёт: глубина окна (N дней/недель) и график.
- Стратегия пересборки больших периодов (side-by-side).
G. SLA, DQ, Observability
- Freshness SLA (днём/ночью), Availability, Consistency-порог (например, 0,2% с CORE).
- Тесты DQ: баланс, дубли зерна, «дыры» в плотности, инварианты метрик.
- Мониторинг: свежесть витрин, parts/merges, ошибки ingestion/MV.
- Границы по умолчанию в BI (лимиты периода, дефолтные фильтры).
H. Доступ и безопасность
- BI → только vw_* (семантические VIEW), прямой доступ к таблицам закрыт.
- RLS по региону/тенанту (где и как реализовано).
- Маскирование PII на уровне VIEW.
I. Релизы и версии
- Процедура v1→v2: side-by-side, сравнение на окне, дата переключения, откат.
- Документация: паспорт метрики, changelog, ссылки на дашборды.
2) Аудит текущих витрин: как быстро найти точки роста
Ниже — последовательность шагов, которые можно выполнить за 1–3 дня и получить план улучшений.
Сбор «рабочей нагрузки»
Цель: понять реальные фильтры/срезы и «тяжёлые» запросы.
-
Выгрузить из system.query_log:
- топ запросов по read_bytes/read_rows/duration,
- частые WHERE/GROUP BY паттерны,
- частоту использования FINAL (тревожный маркер).
- Классифицировать по витринам: какие поля реально фильтруются первыми.
Выход: матрица «витрина → топ-фильтры → соответствует ли текущему ORDER BY».
Состояние таблиц и вставок
Цель: найти part-explosion, болезненные merges.
- Для каждой таблицы: активные parts по партициям, merges backlog, средний размер блока вставок.
- Доля «мелких» вставок, число MV-пересчётов и их «дороговизна».
Выход: список таблиц-кандидатов на:
- укрупнение партиций / буферизацию вставок,
- точечный OPTIMIZE или переразбиение по ORDER BY.
Качество и стабильность
Цель: понять, где отчёты «гуляют».
- Freshness vs SLA, баланс MARTS vs CORE (на окне), дубли зерна, «дыры» по датам.
- Новые «сырые» статусы, не попавшие в канонические.
Выход: список обязательных DQ-фиксов и владельцы.
3) Предложения v2: общий конвейер решений
Выбор нового ORDER BY
Принцип: префикс ключа = самый частый «селективный» фильтр. Обычно:
- события: (event_time, user_id[, …]);
- продажи: (day, shop_id, sku_id) или (day, shop_id) — если SKU уходит в агрегат;
- балансы: (day, account_id).
Алгоритм:
- Из query-лога — топ WHERE-паттернов на 80% трафика.
- Сформировать 2–3 кандидата ORDER BY (короткие префиксы).
- Прогнать «What-If» (на стейдже): время типовых запросов до/после на тестовом окне.
- Взять победителя, подготовить v2-таблицу и side-by-side миграцию.
Риски: слишком длинный ключ, много высококардинальных полей → рост метаданных и слабый эффект.
Как избежать: держать префикс коротким (2–3 поля), ориентироваться на реальный WHERE.
Выделение агрегатов (pre-aggregate)
Цель: разгрузить BI и стабилизировать формулы.
- Частые группировки («день × магазин × категория») вынести в AggregatingMergeTree в виде состояний (sumState/uniq*/quantile*).
- На чтении давать VIEW с …Merge — BI делает лёгкий SELECT без FINAL.
Когда делать отдельные агрегаты:
- отчёт строится чаще 1–2 раз в день;
- запросы сканируют «пол-таблицы» без фильтра;
- считаются uniq/quantile — дорогие «на лету».
Переход с SummingMergeTree на AggregatingMergeTree
Зачем: устойчивость к повторной заливке и ретро-пересчётам.
- Summing удваивает суммы при повторной загрузке периода.
- Aggregating хранит состояния; повторная заливка не «накручивает» метрику — вы всегда «мерджите» состояния.
План перехода:
- Создать *_v2 на AggregatingMergeTree.
- Наполнить историю батчем INSERT … SELECT из «чистого» источника.
- Переключить инкремент: писать states.
- Создать VIEW с …Merge.
- Сравнить v1 vs v2 на окне, переключить BI, v1 — в read-only 30–60 дней.
Ретро-окно: как выбрать глубину и расписание
Подход:
- Постройте распределение задержек поступления событий: разница между «временем факта» и «временем прихода в MARTS».
- Возьмите квантили 95/99% → это разумные «границы» окна.
- Зафиксируйте: «оперативный инкремент — каждые 15 минут за ~2 часа; ночной ретро — за N дней; глубокая коррекция — по тикету и в выходные окна».
Рекомендованный старт:
- eCom / события: 7–14 дней
- Финансы / биллинг: 14–30 дней
- Telecom / CDR: 1–3 дня (часто хватает), но глубже при пост-коррекциях
Шаблоны аудит-проверок и вспомогательных запросов
Ниже — «что именно смотреть», чтобы принять решения. (Сами SQL-наброски простые, подставите свои БД/таблицы.)
A. Частые фильтры и тяжёлые запросы
- Топ по read_bytes, read_rows, duration_ms.
-
Распарсить WHERE и GROUP BY для 80% запросов.
Цель: сформировать кандидатов ORDER BY и понять, какие агрегаты выделять.
B. Состояние частей и мерджей
-
parts по партициям (сортировка по убыванию), system.part_log событий мерджа за сутки, system.merges долго живущие операции.
Цель: найти таблицы с part-explosion, определить необходимость буферизации вставок и укрупнения партиций.
C. FINAL-детектор
-
Доля запросов с FINAL по каждой таблице/VIEW.
Цель: если FINAL нужен «для жизни» — обязательно переход на дисциплину записи или Aggregating.
D. DQ-срез
- Баланс с CORE на «вчера»/на 7 дней.
- Дубликаты grain (ключевые поля витрины).
-
Плотность (матрица дат × ключ — нет ли пропусков).
Цель: убрать систематические источники «гуляющих» цифр до оптимизаций.
5) Типовые «рецепты v2» (примерные Before/After)
Кейc 1: Продажи (eCom/Retail)
Before
- Таблица mart_sales_wide с ORDER BY (shop_id, sku_id, day); Summing-агрегат agg_sales_daily (удваивается при повторной загрузке); BI часто фильтрует по day BETWEEN … и shop_id IN (…).
After (v2)
- Переложить ORDER BY на (day, shop_id, sku_id) — под реальный WHERE.
- Заменить Summing на Aggregating с sumState(amount), sumState(qty);
- Дать VIEW vw_sales_daily с sumMerge и календарём 4-5-4;
- Ввести ретро-окно 14 дней: ночной пересчёт последних 14 дней, инкремент 15 минут за 2 часа;
- В BI запретить FINAL (линтер), дефолтный фильтр «последние 28 дней».
Ожидаемый эффект: −40–70% времени на типовых отчётах, нулевая чувствительность к повторным заливкам, исчезновение «удвоений».
Кейc 2: События (продукт/маркетинг)
Before
- ORDER BY (user_id, event_time); частые фильтры по времени; тяжёлые uniq/quantile на лету; много мелких частей из Kafka.
After (v2)
- ORDER BY (event_time, user_id);
- Выделить agg_events_minute_state с uniqCombinedState(user_id) и quantileTDigestState(0.95)(latency);
- Настроить ingestion на микробатчи (размер блока ↑), буферную таблицу;
- VIEW vw_kpi_minute — …Merge + rolling окна;
- Ретро-окно 1–3 дня, ночной пересчёт, алерты «мелкие parts».
Ожидаемый эффект: стабильные p95/DAU, снижение латентности дашбордов ×2–5 раз.
6) Пошаговая миграция (side-by-side) — шаблон
- Создать v2-таблицу с новым ORDER BY/движком.
- Перелить историю по партициям (месяц/неделя), сверяя агрегаты.
- Включить двойную запись на период (если возможно): новые данные → v1 и v2.
- Построить VIEW для чтения из v2 (семантика неизменна по колонкам).
- Сравнить v1 vs v2 на окне (дельты в допуске), провести нагрузочный тест.
- Переключить BI на v2 (alias/view swap), v1 → read-only.
- Пост-мониторинг (freshness, ошибки, топ запросы), через 30–60 дней — удалить v1.
Guardrails:
- никакого DROP до завершения окна мониторинга;
- бэкапы/снапшоты партиций;
- план отката (alias назад + временно отключить инкремент в v2).
7) Как выбрать глубину ретро-окна (короткая методика)
- Возьмите последнее 1–3 месяца логов поступления данных.
- Постройте гистограмму «задержка прибытия» (0–1 день, 1–2, 2–3, …).
-
Выберите квантиль:
- 95-й процентиль → «операционный» ретро (ночной),
- 99-й → «глубокий» ретро (раз в неделю/в выходные).
- Зафиксируйте в паспорте метрик и в расписании джобов.
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. Итог — быстрый запуск витрин за недели, снижённые риски в проде и предсказуемая стоимость владения.



