BI Consult Desktop Logo BI Consult Mobile Logo
  • Russian BI Исследование российских bi
  • Перейти на Fine BI
  • Контакты
  • +7 812 334-08-01
    +7 499 608-13-06
  • Отправить сообщение
  • Главная
  • Продукты Эксперт-BI
    • Дистрибуция
    • Розничная торговля
    • Производство
    • Операторы связи
    • Страхование
    • Банки
    • Лизинг
    • Логистика
    • Нефтегазовый сектор
    • Медицина
    • Сеть ресторанов
    • E-Commerce
    • Сельское хозяйство
    • Энергетика
    • FMCG
    • Девелоперы
    • Маркетплейсы
    • Пищевая промышленность
    • Фармацевтика
    • Построение Data Platform
    • Цифровая трансформация
    • Управление по KPI
    • Финансы
    • Продажи
    • Склад
    • HR
    • Маркетинг
    • Внутренний аудит
    • Категорийный менеджмент
    • S&OP и FP&A
    • Геоаналитика
    • Цепочки поставок (SCM)
    • AutoML
    • Process Mining
    • IBP
    • ИТ (CIO)
    • Закупки
  • Платформы
    • Системы бизнес-анализа (BI)
    • Интегрированное бизнес-планирование (IBP)
    • Хранилища данных (DWH / Lakehouse)
    • Каталоги данных (Data Catalog)
    • Системы ETL и ELT
    • AI / Исскуственный интеллект
    • Шина данных (ESB)
    • Система управления мастер-данными (MDM)
    • Семантический слой
  • Услуги
    • Переход на отечественные BI и DWH системы
    • Консалтинг
    • Пилотный проект
    • Обучение и сертификация
    • Бесплатное обучение
    • Поддержка
    • Технические задания
    • Сбор требований для проекта внедрения BI-системы
    • CI/CD для DWH
    • Аудит BI приложений и DWH
    • Выделенная команда
    • Настойка и поддержка баз данных
    • Разработка BI Стратегии
    • Styleguide для BI-системы
    • Как выбрать BI-систему
  • Курсы
    • Учебный курс Информационная грамотность (Data Literacy)
    • Учебный курс для бизнес-аналитиков
    • Учебный курс для системных аналитиков
    • Учебный курс по Data Governance
    • Учебный курс Как стать CDO
    • Учебный курс Современная архитектура хранилища данных
    • Учебный курс по Fine BI
    • Учебный курс по FineReport
    • Учебный курс по DWH
    • Учебный курс по Data Science (ML, AI)
    • Учебный курс по PostgreSQL
    • Учебный курс по Greenplum
    • Учебный курс по Apache Airflow и NiFi
    • Учебный курс по Open-source BI
    • Учебный курс по ClickHouse
    • Учебный курс по DataLens
    • Учебный курс по Loginom
    • Учебный курс по Modus BI и ETL
    • Учебный курс по Visiology
    • Учебный курс по dbt (Data Build Tool)
  • Компания
    • Руководство
    • Новости
    • Клиенты
    • Карьера
    • Скачать
    • Контакты

BI

  • FineBI
  • FineReport
  • FineDataLink
  • FineChatBI (FineAI)
  • Коннекторы данных из 1С в BI
  • Airflow / Nifi
  • Visiology
  • PIX BI
  • Modus BI
  • Yandex.DataLens
  • Open-source BI: Superset/Metabase
  • Luxms BI
  • AW BI + Alpha BI
  • FlyBI + Форсайт. Аналитическая Платформа
  • Loginom
  • Триафлай
  • AI / Исскуственный интеллект
  • Optimacros
  • Навигатор BI
  • Семантический слой

СУБД

  • Arenadata
  • ClickHouse
  • Greenplum
  • Postgres Professional
  • TData

Другое

  • Построение Data Platform
    • Аналитическое хранилище данных
    • Data Lake и Data Engineering
    • Подробнее про Data Lake
    • Внедрение Lakehouse
      • Apache Doris
      • StarRocks
      • Trino
    • Миграция витрин из пропиетарных DWH на новый стек
    • Учебный курс "Современная архитектура хранилища данных"
Главная » Курсы по системам бизнес-анализа и методологии » Учебный курс по ClickHouse » Курс «Витрины данных на ClickHouse: от архитектуры до SLA» » Модуль 0. ClickHouse как слой витрин: зачем, где он силён, как проектировать, какие риски учесть

Модуль 0. ClickHouse как слой витрин: зачем, где он силён, как проектировать, какие риски учесть

Что такое слой витрин и почему здесь ClickHouse

Витрина данных — это «готовый стол» для отчётов: уже подчищенные, согласованные, часто заранее агрегированные данные «как нужно бизнесу».
ClickHouse чаще всего используется именно здесь, потому что:

  • очень быстро считает срезы и суммы по большим объёмам;
  • выдерживает много одновременных читателей (дашборды, ad-hoc);
  • дешёв в эксплуатации по сравнению с классическими СУБД для таких нагрузок.

 

Важно: витрина — не «источник правды». Истина хранится в CORE (нормализованные таблицы, где «правильно» оформлены сущности, статусы, связи). Витрина — это быстрый, «читабельный» слой сверху.

Поток в целом: Источники → Landing/RAW → STAGE → CORE → MARTS (ClickHouse) → BI/отчёты.
Вы, как аналитик, работаете с MARTS, где всё уже «по-деловому» и быстро.

 

Когда витрина на ClickHouse — уместна, а когда лучше не надо

Подходит, если:

  • основная работа — чтение, группировки, фильтры, а не правки строчек;
  • нужны быстрые отчёты по дням/часам/минутам;
  • у вас много пользователей и параллельных запросов;
  • есть смысл материализовать (заранее посчитать) типовые агрегаты.

 

Осторожно, если:

  • вы ожидаете жёсткие транзакционные операции (много «правок задним числом» каждую минуту);
  • витрина должна заменять собой всю правду (MDM, сложные связи);
  • основной профиль — OLTP (короткие апдейты каждой строки).
    В этих случаях разместите «сложность» в CORE, а в ClickHouse — готовые срезы.

 

Договоренности на берегу: контракт данных CORE → MARTS

Чтобы отчёты были стабильными, мы фиксируем data contract между слоями. В нём есть:

  1. Список полей и их смыслы (связь с «паспортом метрики»).
  2. Ключ и уникальность (как определяем «одну и ту же» запись).
  3. SLA свежести: например, «витрина не старше 15 минут днём, 1 часа ночью».
  4. Окно корректировок: «последние 14 дней могут пересчитываться».
  5. Версии: как меняется схема и формулы (v1 → v2), что это значит для отчётов.
  6. Кто отвечает: бизнес-владелец и тех-владелец.

 

Аналитику это помогает: вы знаете какая метрика, где и с какой задержкой обновится, и кого спрашивать при расхождениях.

 

Как устроены витрины (понятно и по-деловому)

Есть два привычных «стиля»:

  • Широкая витрина (wide): факты + нужные атрибуты прямо в одной таблице.
    Плюс: очень быстро строятся отчёты, мало соединений.
    Минус: атрибуты повторяются, таблица «толстеет».
  • «Звезда»: факты отдельно, справочники (магазин, товар, клиент) отдельно.
    Плюс: гибкость, меньше дублирования атрибутов.
    Минус: отчёты могут быть тяжелее из-за соединений.

 

Обычно вам дают готовые представления (VIEW) поверх того, что выбрали инженеры, — вы работаете с ними одинаково удобно в обоих случаях.

 

Про «материализацию» простыми словами

Материализация — это когда типовой расчёт (например, «продажи по дням × магазинам») мы считаем заранее и храним в отдельной таблице/представлении.
Зачем это вам:

  • отчёт открывается быстрее (суммы уже посчитаны);
  • цифры стабильнее (меньше зависимости от «настроения» запроса в BI).

 

Важно знать две вещи:

  • такие агрегаты обычно пересчитываются кусками (например, «каждые 15 минут за последние 2 часа» + «ночью за последние 30 дней»);
  • если вы смотрите «сегодняшний день», лёгкие колебания возможны из-за опоздавших событий — это нормально, об этом заранее предупреждает окно корректировок.

 

Время, календарь и валюты — главные источники «разъездов»

Календарь. Мы либо живём на обычном календаре (месяцы/кварталы), либо на финансовом (например, 4-5-4 в ритейле). Сравнения «неделя к неделе» делаем в одном и том же календаре.
Валюты. Обычно есть две согласованные логики:

  • в базовой валюте на момент операции (управленка и сверка с «кассой»);
  • пересчёт на дату отчёта (для сравнений между датами).
    Не смешиваем эти подходы: если нужен другой — просим другое представление.

 

Практика для аналитика: в «паспорте метрики» всегда смотрите, какой календарь и какая валюта используются. Если не сходится — первый вопрос именно туда.

 

Что на вашей стороне: семантические представления и паспорта метрик

  • Вам дают VIEW (представления) с понятными колонками: там уже зашиты формулы метрик, правильные статусы, календарь, валюта, и часто — правила доступа (например, «видишь только свой регион»).
  • К каждой ключевой метрике есть паспорт: что считаем, что исключаем, какое «зерно», какая свежесть, кто владелец.

 

Правило №1 для аналитика: строить отчёты только на этих представлениях. Так вы гарантируете, что ваша «Net Sales» = «Net Sales» коллег.

 

Стабильность и качество (что мы проверяем автоматом)

Мы регулярно мониторим:

  • Свежесть витрин (не старше SLA).
  • Баланс с источником (расхождение за вчера ≤ согласованного порога).
  • Дубли по ключам (зерну) — не должны появляться.
  • «Дыры» в плотности данных (дни, где внезапно всё пропало).
  • Инварианты: например, GM не может быть больше Net Sales.

 

Если что-то не так — появляются «сигналы здоровья». Это первый экран, куда имеет смысл заглянуть, если цифры кажутся странными.

 

Что происходит при изменении формул: версии v1 → v2

Метрики живут: поменялись правила, скорректировали статусы — цифры изменились. Это нормально, если:

  • появляется v2 (вторая версия) — и какое-то время обе версии есть параллельно;
  • в паспорте метрики видно, что изменилось и с какой даты;
  • BI переключают на v2 осознанно, а v1 держат для проверок ещё 30–60 дней.

 

Ваша польза: вы заранее знаете, чего ожидать («GM% вырастет на 1–2% из-за исключения тестовых транзакций») и не теряете историю.

 

Доступ и безопасность: почему вам дают «только VIEW»

  • В представлениях уже замаскированы PII (персональные поля), настроены политики видимости (например, «регион видит только себя»).
  • На «сырые» таблицы витрин и тем более CORE BI обычно не имеет прав — это сделано, чтобы избежать случайных утечек и «самодельных» формул.

 

Если вам нужен дополнительный срез/показатель — просите инженеров добавить его в семантическое представление. Так это станет «правильной» частью общего слоя.

 

Стоимость и хранение (почему иногда «старое» открывается медленнее)

Данные делятся на:

  • «горячие» (последние дни/недели) — хранятся на быстрых дисках, отчёты летают;
  • «холодные» (архив) — могут лежать в более дешёвом хранилище (объектное/облако). Там скорость ниже, но это экономит бюджет.

 

Если отчёт на год «тяжелее» — это не баг, это так задумано. Для регулярной отчётности чаще используют готовые агрегаты по периодам, которые тоже быстро открываются.

 

Типовые сценарии и на что смотреть

Розница / eCom

  • Отчёт: «продажи по дням × магазин × категория».
  • На что смотреть: календарь (финансовый/обычный), статусы «оплачено/возврат», окно корректировок (возвраты часто приходят «задним числом»).

 

Веб/приложение

  • Отчёт: «события, конверсия, топ каналов, p95 задержки».
  • На что смотреть: уникальные пользователи (как считаем «активного»), атрибуция каналов (last/first, U-shape и т. п.).

 

Финансы/финтех

  • Отчёт: «остатки/балансы по дням, выручка, комиссии».
  • На что смотреть: валюта (на дату операции или отчёта), окно ретро-пересчётов (чарджбеки, уточнения), согласование с «главной книгой».

 

Типовые «почему не сходится» и что делать

  • Цифры «вчера» сегодня изменились.
    Скорее всего, сработало окно корректировок (дозалили возвраты/опоздавшие события). Посмотрите SLA и окно; для «официальной» выгрузки берите дату, вне окна.
  • Разъехались с отчётом коллег из другой команды.
    Проверьте: одинаковый ли календарь (финансовая неделя ≠ обычная), одна ли валюта, одна ли версия метрики.
  • Дашборд стал медленным.
    Возможно, выбран слишком длинный период или «тяжёлый» срез. Проверьте рекомендуемые фильтры/лимиты в паспорте метрики. Если это новый кейс — попросите команду витрин сделать отдельную агрегированную витрину.
  • Появился новый статус/категория и всё «в UNKNOWN».
    Это нормально на пару дней: статус попал в отчёт «на разбор». Сообщите владельцу метрики — добавят в маппинг.

 

Шпаргалка аналитика (коротко)

Перед тем как строить отчёт:

  1. Откройте паспорт метрики: формула, календарь, валюта, зерно, окно корректировок.
  2. Убедитесь, что вы используете правильное представление (VIEW).
  3. Проверьте свежесть витрины (SLA) и версию метрики (v1/v2).
  4. Держите в голове: «последние N дней» могут меняться, потому что данные дозаливаются.
  5. Если «не сходится» — сначала календарь/валюта/версия, потом — сигнал «здоровья» витрины (freshness, баланс).

 

Что даёт такой подход

  • Единые цифры для всех отчётов.
  • Прозрачность (всегда понятно, что и откуда посчитано).
  • Предсказуемость (изменения через версии, а не «тихо ночью»).
  • Скорость (тяжёлое посчитано заранее, отчёты открываются быстро).
  • Безопасность (без «лишних» доступов и утечек PII).

 

Подробнее обо всем:

 

Введение без маркетинга: зачем вообще выделять слой витрин

В классическом хранилище (DWH) мы делим ответственность по слоям: RAW/LANDING (как прилетело), STAGE (очистка/приведение типов), CORE/DDS (нормализованная, «правдивая» модель), MARTS/Serving (готовые под запросы бизнеса представления и агрегаты).
ClickHouse чаще всего попадает именно в последний слой — слой витрин. Причины просты:

  • Скорость: колонночное хранение, векторизированное исполнение, сжатие — дешёвые сканы и агрегации на миллиардах строк.
  • Стоимость: на «железе» средней ценовой категории ClickHouse даёт SLA интерактивной аналитики, который в реляционных OLTP-СУБД потребовал бы несоразмерных ресурсов.
  • Простая материализация агрегатов: Materialized View, AggregatingMergeTree, SummingMergeTree, Projection (в ряде случаев) — быстро готовим «отчётные» таблицы.
  • Хорошо уживается с потоками: Kafka/S3/JDBC источники плюс MVs — и у нас near real-time витрины.

 

Важно: ClickHouse почти никогда не является единственным источником правды. Исторически он слабее в части «тяжёлых» транзакций, строгих ACID, сложной ссылочной целостности и тонкой работы с SCD на уровне концептуальной модели (хотя SCD-паттерны внедряются). Поэтому практическая архитектура: истина хранится в CORE (Postgres/Greenplum/Iceberg/Delta/… или даже другой ClickHouse-кластер как DDS), а ClickHouse — быстрый слой витрин.

 

Архитектурная роль ClickHouse как слоя витрин

Как вписывается в общую картину

Типичный поток:
Sources (OLTP/Apps/Logs) → Landing/RAW → STAGE → CORE(DDS) → MARTS(ClickHouse) → BI/Приложения.

  • CORE обеспечивает единые определения сущностей и метрик на уровне логики бизнеса (нормализованные модели, MDM, проверки качества).
  • MARTS на ClickHouse — это денормализованные, предагрегированные, подогнанные под запросы таблицы, которые:
    • гарантируют низкую латентность (секунды) для отчётов/дашбордов,
    • разгружают CORE от тяжёлых аналитических запросов,
    • дают стабильные SLA: время ответа, свежесть данных (freshness), целостность и полноту.

 

Контракты между CORE и MARTS

Перед тем как строить витрину из CH, фиксируем data contract (минимум):

  1. Список полей и их смысл (с обязательной ссылкой на «паспорт метрики»).
  2. Ключи и уникальность (какой surrogate/business key, где dedup).
  3. Границы латентности: «в MARTS попадает не старше N минут/часов от факта».
  4. Стратегия изменений схемы: backward/forward compatibility, версия в имени или в колонке.
  5. Ответственность за DQ: что тестируется в CORE, а что — на входе MARTS.
  6. Сценарии отката и ретро-пересчёта: side-by-side таблицы, переключение BI.

 

Где ClickHouse уместен (и где нет)

ClickHouse уместен, если:

  • Преобладают чтения и агрегации; нужна низкая латентность на больших объёмах.
  • Запросы сканируют много строк и сводят их (SUM/COUNT/AVG, percentiles, top-k).
  • Есть много одновременных читателей (аналитики/дашборды/ад-hoc).
  • Витрины можно материализовать и обновлять инкрементально (CDC/микробатчи).
  • Приемлема eventual consistency внутри короткого окна (секунды-минуты).

 

Осторожнее/неуместно, если:

  • Нужны жёсткие транзакции с множеством взаимосвязанных апдейтов/удалений.
  • Высокая интенсивность point updates с жёсткой консистентностью.
  • Модели требуют строгой ссылочной целостности и сложных каскадных операций.
  • Основной профиль — OLTP (короткие point-select/insert/update).
  • Витрина должна быть единственным источником правды без альтернативной «золотоносной» базы — рискованно.

 

База ClickHouse, важная для витрин

Колонночное хранение, партиции, части

  • Данные хранятся колонками — выигрываем на сканах/агрегациях.
  • Таблица разбита на партиции (обычно по дате/месяцу/неделе) и части (parts).
  • Фоновый процесс merge объединяет маленькие части в крупные.
  • Риск part explosion (тысячи маленьких частей) — боль для диска и планировщика.

 

MergeTree-семейство (базовый выбор для витрин)

  • MergeTree: базовый движок (без встроенной логики «сворачивания» изменений).
  • ReplacingMergeTree(version): хранит записи с одинаковым ключом, «побеждает» с максимальной version.
  • SummingMergeTree: суммирует числовые колонки по ключу (осторожно с повторной агрегацией!).
  • AggregatingMergeTree: хранит состояния агрегатных функций (avgState, uniqState), а при чтении делаем …Merge/…Final.
  • CollapsingMergeTree(sign): схлопывает пары +(insert)/–(delete) по ключу (аккуратно с порядком).
  • VersionedCollapsingMergeTree: расширенный вариант collapsing с version.

 

ORDER BY vs PRIMARY KEY

В ClickHouse PRIMARY KEY = префикс ORDER BY.
ORDER BY определяет физический порядок данных в частях — ключевая настройка для диапазонных фильтров и пропуска чтения (skip indices). Проектирование ORDER BY — половина успеха витрины.

 

Индексы-скип-лист и Bloom

  • Skip indices (minmax, set, bloom_filter) позволяют пропускать чтение гранул, где условие заведомо ложно.
  • Bloom-фильтр полезен для поиска подстрок/LIKE/IN больших множеств (но не панацея).

 

Materialized Views и Projections

  • MV — потоковая/батчевая материализация: читаем из источника → пишем в витрину/агрегат.
  • Projections — альтернативные локальные «представления» с другим ORDER BY/агрегацией (хитрый инструмент, требует аккуратной эксплуатации и понимания планировщика).

 

Базовые паттерны проектирования витрин

Широкая денормализованная витрина (Wide Table)

Под BI-приложения, которым не хочется много JOIN’ов:

  • Храним факты + дескриптивные атрибуты измерений прямо в строке факта (на дату среза).
  • Проще запросы, меньше JOIN — быстрее ответы.
  • Нужно уметь поддерживать актуальность атрибутов (SCD1/SCD2 snapshot).

 

Пример (продажи по чекам, денормализация на дату транзакции):

CREATE TABLE mart_sales_wide
(
    tx_date Date,
    shop_id UInt32,
    shop_name LowCardinality(String),
    region_id UInt16,
    region_name LowCardinality(String),
    sku_id UInt32,
    sku_name String,
    sku_brand LowCardinality(String),
    qty Int32,
    amount Decimal(12,2),
    currency FixedString(3),
    customer_id UInt64,
    segment LowCardinality(String)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(tx_date)
ORDER BY (tx_date, shop_id, sku_id);

 

Почему так ORDER BY: почти все отчёты в рознице фильтруют по дате, затем по магазину/товару.

 

Звезда (star schema) для устойчивых JOIN

Если BI-инструмент активно комбинирует измерения, а denormalize = слишком толстые строки:

  • Факт (grain: чек-позиция, транзакция, день) + измерения (магазин, товар, клиент, календарь).
  • Стороны медальки:
    • Плюс — гибкость, меньше дублей атрибутов;
    • Минус — JOIN в ClickHouse стоит памяти/времени; важно проектировать ключи и порядок.

 

Пример (упрощённо):

CREATE TABLE d_shop (
    shop_id UInt32,
    shop_name String,
    region_id UInt16,
    region_name String,
    scd_from DateTime,
    scd_to   DateTime,
    is_current UInt8
) ENGINE = MergeTree
ORDER BY (shop_id, scd_from);
CREATE TABLE f_sales (
    tx_datetime DateTime,
    shop_id UInt32,
    sku_id UInt32,
    qty Int32,
    amount Decimal(12,2)
) ENGINE = MergeTree
PARTITION BY toYYYYMM(tx_datetime)
ORDER BY (tx_datetime, shop_id, sku_id);

 

Далее строим витрины с pre-join (см. ниже) либо выполняем JOIN в запросе (см. модуль 5).

 

Pre-join и pre-aggregate

Часто выгоднее раз в минуту/час собрать предагрегат, чем каждый раз гонять ад-hoc.
Пример: ежедневные продажи на уровне магазина и бренда:

CREATE TABLE agg_sales_daily
(
    sales_date Date,
    shop_id UInt32,
    brand LowCardinality(String),
    qty_sum Int64,
    amount_sum Decimal(14,2)
)
ENGINE = SummingMergeTree((qty_sum, amount_sum))
PARTITION BY toYYYYMM(sales_date)
ORDER BY (sales_date, shop_id, brand);

 

И заполняем либо INSERT INTO … SELECT … по расписанию, либо через MV:

CREATE MATERIALIZED VIEW mv_sales_to_daily
TO agg_sales_daily
AS
SELECT
    toDate(tx_datetime) AS sales_date,
    shop_id,
    sku_brand AS brand,
    sum(qty) AS qty_sum,
    sum(amount) AS amount_sum
FROM mart_sales_wide
GROUP BY sales_date, shop_id, brand;

 

Замечания:

  • Для устойчивого Summing — следи за повторной агрегацией (перезалив дублей приведёт к удвоению).
  • Если возможны корректировки задним числом — лучше AggregatingMergeTree + sumState/sumMerge, либо Replacing + версия.

 

Семантический слой метрик: «паспорт метрики» как контракт

Самая частая причина «разъезжания» отчётов — разные формулы/фильтры в разных витринах. Решение — паспорт метрики:

  • Идентификатор и название (GM, NetSales, ARPU).
  • Определение: формула, агрегаты, где считать (факт/агрегат), по каким колонкам.
  • Фильтры/исключения: «не учитывать возвраты SPIKE», «только оплаченные чек-позиции».
  • Границы: единицы измерения, валюты, курс пересчёта.
  • Срок годности (окно событий): «пересчитываем последнюю неделю каждые 15 мин».
  • Слой материализации: MV в CH, batch overlay, пост-обработка.
  • Версионирование: как меняется формула во времени.

 

Пример (кратко, для NetSales):

  • Формула: SUM(amount) - SUM(returns_amount) на зерне tx_date, shop, sku.
  • Фильтр: status IN ('paid','captured').
  • Валюта: локальная; пересчёт в базовую валюту делается в отдельной витрине *_fx.
  • Окно: «скользящая неделя + ежедневный ретро-пересчёт 30 дней».
  • Материализация: mv_sales_to_daily (см. выше), ретро-пересчёт через overlay.

 

Инкрементальные обновления витрин (без полного пересчёта)

Витрины ценны, когда обновляются быстро. Основной инструментарий:

  • Idempotent upsert (вставка «поверх» старого с логикой выбора версии): ReplacingMergeTree(version)
  • Схлопывание сигналов: CollapsingMergeTree(sign) для потоков c +/–.
  • Агрегат-состояния: AggregatingMergeTree + …State/…Merge для устойчивого накопления.
  • Side-by-side стратегия при больших ретро-пересчётах: строим table_v2, переключаем BI.

 

Пример: денормализованная витрина продаж с заменой по версии:

CREATE TABLE mart_sales_wide_v
(
    tx_id UInt64,
    tx_date Date,
    shop_id UInt32,
    sku_id UInt32,
    qty Int32,
    amount Decimal(12,2),
    version UInt64,            -- монотонно растущая версия события/правки
    updated_at DateTime
)
ENGINE = ReplacingMergeTree(version)
PARTITION BY toYYYYMM(tx_date)
ORDER BY (tx_date, shop_id, sku_id, tx_id);

 

Вставляем новые/исправленные строки из CORE — в чтении используем FINAL (или гарантируем отсутствие конкурирующих версий на срезе). Для больших таблиц FINAL дорог — поэтому на отчётных витринах стараемся обеспечивать консистентность upstream, чтобы FINAL не требовался.

 

Практический кейс №1: розница (ежедневные витрины продаж)

Задача: дашборд по продажам за день с разрезами по магазину, бренду, категории, с метриками NetSales, Qty, Margin, конверсия по акциям.

Шаги:

  1. Контракт CORE→MARTS: формат фактов чеков/позиций, ключи, статусы.
  2. Витрина mart_sales_wide: денормализация атрибутов магазина/товара на дату транзакции (snapshot атрибутов).
  3. Материализация:
    • Ежечасно инкремент за последние 2 дня (вдруг опоздавшие события/возвраты).
    • Ежедневный ретро-пересчёт за последние 30 дней (агрегаты пересчитались с учётом исправлений).
  4. Агрегаты: agg_sales_daily (shop × brand), agg_sales_daily_cat (shop × category).
  5. DQ-контроли: баланс сумм с CORE (±0.1%), дублей по tx_id, плотность данных по магазинам.

 

Типичные риски и как их избежать:

  • Часы «тишины» в кассах → «нулевые» продажи. Решение: строить полную матрицу дат × магазинов и явно проставлять нули (и это отличное место для materialized view c join на календарь/справочник магазинов).
  • Опоздавшие события/возвраты → разъезды ежедневных агрегатов. Решение: вести overlay-таблицу корректировок и применять их в «окне» X дней; либо AggregatingMergeTree с дневными state и ретро-…Merge при чтении для окна.
  • Удвоение сумм в SummingMergeTree при переигрывании загрузки. Решение: работать через таблицу-источник и пересобирать целевой Summing из чистого INSERT … SELECT … WHERE date BETWEEN … (или использовать AggregatingMergeTree).

 

Практический кейс №2: веб-аналитика (события)

Задача: near real-time витрины по событиям сайта/приложения (просмотры, клики, транзакции), отчёт по воронке и retention.

Паттерн:

  • Источник — Kafka (Debezium/логгер). В CH таблица с Kafka Engine + MV в MergeTree.
  • Широкая витрина mart_events_wide с нормализованными полями: event_time, user_id, session_id, event_type, channel, campaign, device, referrer, is_purchase, order_id, amount.
  • Агрегаты agg_events_minute/agg_events_hour по каналам/кампаниям.
  • Согласование таймзон, дробление по event_time (партиции toYYYYMM(event_time)).

 

SQL-набросок (упрощённо):

CREATE TABLE events_kafka
(
    event_time DateTime,
    user_id UInt64,
    session_id UUID,
    event_type LowCardinality(String),
    channel LowCardinality(String),
    campaign LowCardinality(String),
    is_purchase UInt8,
    amount Decimal(12,2)
)
ENGINE = Kafka
SETTINGS
    kafka_broker_list = 'kafka:9092',
    kafka_topic_list = 'events',
    kafka_format = 'JSONEachRow',
    kafka_num_consumers = 4;
CREATE TABLE mart_events_wide
(
    event_time DateTime,
    user_id UInt64,
    session_id UUID,
    event_type LowCardinality(String),
    channel LowCardinality(String),
    campaign LowCardinality(String),
    is_purchase UInt8,
    amount Decimal(12,2)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id, session_id);
CREATE MATERIALIZED VIEW mv_events_ingest
TO mart_events_wide
AS
SELECT * FROM events_kafka;
Дальше — агрегаты:
sql
КопироватьРедактировать
CREATE TABLE agg_events_minute
(
    ts_minute DateTime,
    channel LowCardinality(String),
    campaign LowCardinality(String),
    views UInt64,
    clicks UInt64,
    purchases UInt64,
    revenue Decimal(14,2)
)
ENGINE = SummingMergeTree((views, clicks, purchases, revenue))
PARTITION BY toYYYYMM(ts_minute)
ORDER BY (ts_minute, channel, campaign);
CREATE MATERIALIZED VIEW mv_events_to_minute
TO agg_events_minute
AS
SELECT
    toStartOfMinute(event_time) AS ts_minute,
    channel,
    campaign,
    sum(event_type = 'view') AS views,
    sum(event_type = 'click') AS clicks,
    sum(is_purchase) AS purchases,
    sumIf(amount, is_purchase = 1) AS revenue
FROM mart_events_wide
GROUP BY ts_minute, channel, campaign;

 

Риски:

  • Мелкие блоки из Kafka, часть взрывается, мерджи зашиваются. Решение: настроить kafka_max_block_size, партиции по времени, микробатчи через буферную таблицу.
  • Дубликаты событий после рестартов консюмера. Решение: в mart_events_wide завести уникальный event_id и использовать ReplacingMergeTree(version) или явную дедупликацию на STAGE.
  • Скачки нагрузки в прайм-тайм. Решение: увеличить kafka_num_consumers, горизонтально масштабировать шарды и распределённые таблицы, готовить предагрегаты.

 

Производительность витрин: с чего начать

Короткий «первый набор» техник, который окупается:

  1. Спроектируйте правильный ORDER BY под реальные фильтры. Если 90% запросов фильтруют по date, затем shop_id, делайте ORDER BY (date, shop_id, …).
  2. Выберите корректный движок под обновление:
    • «Вставляем и забываем» — MergeTree/SummingMergeTree.
    • «Исправляем задним числом» — ReplacingMergeTree(version) или AggregatingMergeTree.
    • «Сигналы +/–» — CollapsingMergeTree(sign) (но с дисциплиной порядка).
  3. Избегайте лишнего FINAL на чтении — обеспечивайте консистентность upstream.
  4. Собирайте разумные партиции — месячные для продаж, дневные для событий высокого трафика.
  5. Контролируйте число частей — укрупняйте партиции, ограничивайте частоту вставок, используйте буферные таблицы.
  6. Профилируйте запросы (EXPLAIN PLAN/PIPELINE), смотрите query_log (прочитано строк/байт, время, стадии).

 

Антипаттерны слоя витрин (и быстрые фиксы)

  • «Витрина = копия CORE»: без денормализации и агрегатов смысла мало.
    Фикс: делайте pre-join и pre-aggregate, убирайте тяжелые JOIN из BI.
  • ORDER BY не отражает фильтры: читаем лишние данные, тормоза.
    Фикс: переопределите порядок, переложите партиции, сделайте миграцию через v2-таблицу.
  • Повсюду FINAL: красиво, но больно дорого.
    Фикс: добивайтесь уникальности и стабильности на записи.
  • Summing без дисциплины: удвоение сумм при повторных заливках.
    Фикс: rebuild из чистого источника или переход на AggregatingMergeTree.
  • Мелкие партиции/части: перегрев мерджей.
    Фикс: буферизация вставок, настройка размеров блоков, укрупнение партиций.

 

Мини-чек-лист «Готовим первую витрину в ClickHouse»

  1. Опишите метрику (паспорт) и зафиксируйте контракт CORE→MARTS.
  2. Выберите модель витрины (wide vs star), зерно и ключи.
  3. Спроектируйте партиционирование и ORDER BY под реальные фильтры.
  4. Решите, что материализовать (MV/agg) и в каком окне ретро-пересчётов жить.
  5. Запланируйте DQ-контроли (балансы, дубли, плотность).
  6. Подготовьте runbook: «что делаем, если задержка/дубли/расхождения».
  7. Заложите наблюдаемость (query_log, part_log, метрики merges/parts, лаги).

 

Набросок CI/CD для витрин (пригодится уже сейчас)

  • Git: SQL-скрипты схем (v1/v2), код MVs, DQ-тесты.
  • PR-pipeline: прогон lint/SQL-тестов, smoke-прогоны на стейдже, деплой миграций.
  • Миграции без простоя: side-by-side таблицы *_v2, двойная запись (при необходимости), переключение BI.
  • Мониторинг релизов: сравнение агрегатов до/после, контроль латентности.

 

Коротко об оценке стоимости (TCO) слоя витрин

  • Хранилище: NVMe для «горячих» партиций (N=30–90 дней), S3/объектные диски для холодных, TTL для ротации.
  • CPU: учитывайте пики BI-нагрузки; горизонтально масштабируйте Distributed-слой.
  • Сеть: внимание к S3 latency (кэширование), перекладывайте «тяжёлые» расчёты в ночные окна.
  • Оптимизация: чем больше pre-aggregate — тем меньше «живых» тяжёлых запросов.

 

Паттерны материализации: выбор движка и стратегии обновления

Правильный движок и стратегия обновления определяют, как вы будете поддерживать витрины при инкрементах, ретро-пересчётах и корректировках задним числом.

 

MergeTree (базовый, «insert and forget»)

  • Когда применять: факты стабильно не меняются после записи; витрина — «широкая» таблица, собираем её batch-ом или через MV, но без апдейтов.
  • Плюсы: предсказуемая производительность, простая эксплуатация.
  • Минусы: любые исправления требуют overlays (накладок) или пересборки партиций.

 

Шаблон:

CREATE TABLE mart_orders
(
    order_date Date,
    order_id   UInt64,
    customer_id UInt64,
    shop_id    UInt32,
    amount     Decimal(12,2)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(order_date)
ORDER BY (order_date, shop_id, order_id);

 

Риск: приходят «поздние» корректировки (chargeback, отмены).
Митигация: отдельная таблица корректировок + nightly overlay INSERT … SELECT … на окно N дней.

 

ReplacingMergeTree(version) (upsert по версии)

  • Когда применять: приходит несколько версий одной записи (по business_key), нужна идемпотентность и выбор «последней» версии.
  • Как работает: хранит все версии; фоновый merge оставляет запись с максимальной version. На чтении FINAL обеспечивает консистентный срез (дорогая операция).
  • Важно: старайтесь не использовать FINAL на частых BI-запросах — обеспечьте консистентность upstream или читайте только «свежие» партиции, где нет конкурирующих версий.

 

Шаблон:

CREATE TABLE mart_sales_upsert

(

    tx_id    UInt64,

    tx_date  Date,

    shop_id  UInt32,

    sku_id   UInt32,

    qty      Int32,

    amount   Decimal(12,2),

    version  UInt64,          -- монотонно растущая версия

    updated_at DateTime

)

ENGINE = ReplacingMergeTree(version)

PARTITION BY toYYYYMM(tx_date)

ORDER BY (tx_date, shop_id, sku_id, tx_id);

 

Риск: неконтролируемые многократные версии → FINAL становится «обязательным».
Митигация: на входе дедуп по ключу+версии, дисциплина источника, короткое окно «переигрываний».

 

CollapsingMergeTree(sign) и VersionedCollapsing

  • Когда применять: поток сигналов типа +1/-1 для одной сущности (вставки/удаления), которые потом схлопываются.
  • Collapsing: пары одинаковых ключей с sign=+1 и sign=-1 взаимно уничтожаются при merge.
  • VersionedCollapsing: добавляет version, упрощает логику при несколь­ких апдейтах.

 

Шаблон:

CREATE TABLE mart_positions_collapse
(
    id       UInt64,
    sign     Int8,  -- +1/-1
    qty      Int32,
    amount   Decimal(12,2)
)
ENGINE = CollapsingMergeTree(sign)
ORDER BY (id);

 

Риски: нарушение порядка сигналов, «непарные» записи, тяжёлые FINAL.
Митигация: применять аккуратно; чаще ReplacingMergeTree(version) проще сопровождать.

 

SummingMergeTree (устойчивые суммы)

  • Когда применять: нужна накопительная сумма по ключу, а строки однозначно «добавочные» (без переигрываний).
  • Плюсы: дешёвые агрегаты; great для «счётчиков».
  • Минусы: повторная загрузка удвоит сумму.

 

Шаблон:

CREATE TABLE agg_daily
(
    day Date,
    shop_id UInt32,
    amount_sum Decimal(14,2),
    qty_sum    Int64
)
ENGINE = SummingMergeTree((amount_sum, qty_sum))
PARTITION BY toYYYYMM(day)
ORDER BY (day, shop_id);

 

Риск: переиграли загрузку за день → удвоение.
Митигация: rebuild «с нуля» из источника (INSERT SELECT с фильтром по дате), либо переход на AggregatingMergeTree.

 

AggregatingMergeTree (состояния агрегатов)

  • Когда применять: нужно устойчивое накопление агрегатов (в т. ч. uniq*, quantile*, avg без искажения при повторной заливке).
  • Как работает: в таблицу пишем state: sumState(x), uniqCombinedState(y), а на чтении делаем sumMerge/uniqCombinedMerge.
  • Плюсы: отсутствие «удвоения» при повторном прогоне; гибкие агрегаты (перцентили, uniq).
  • Минусы: чуть сложнее в запросах, нужен дисциплинированный слой чтения (…Merge).

 

Шаблон:

CREATE TABLE agg_sales_state
(
    day Date,
    shop_id UInt32,
    amount_state AggregateFunction(sum, Decimal(14,2)),
    customers_state AggregateFunction(uniqCombined, UInt64)
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (day, shop_id);
-- Заполнение:
INSERT INTO agg_sales_state
SELECT
  toDate(tx_datetime) AS day,
  shop_id,
  sumState(amount),
  uniqCombinedState(customer_id)
FROM mart_sales_wide
GROUP BY day, shop_id;
-- Чтение:
SELECT
  day, shop_id,
  sumMerge(amount_state) AS amount_sum,
  uniqCombinedMerge(customers_state) AS customers_uniq
FROM agg_sales_state
GROUP BY day, shop_id;

 

Риск: забыли …Merge → получите бинарные состояния, а не числа.
Митигация: оборачивайте чтение в вьюху или параметризованный шаблон.

 

Materialized Views и буферизация

  • Stream MV: из Kafka/логов в витрины. Настраивайте размер блоков на входе, чтобы не плодить мелкие части.
  • Batch MV: из промежуточной таблицы «сырья» в агрегаты. Используйте буферные таблицы и короткие окна.
  • Антипаттерн: MV, которая при каждом сообщении пересчитывает огромный агрегат. Делите по времени и ключам.

 

Projections (в помощь, но осторожно)

  • Идея: альтернативная физическая организация внутри той же таблицы — «предагрегированная/переупорядоченная» копия.
  • Когда помогaют: когда большинство запросов — однотипные и совпадают с проекцией.
  • Риски: усложнение эксплуатации, неочевидный выбор плана запроса планировщиком.
  • Рекомендация: начинайте без projections; вводите точечно, измеряя до/после.

Пример:

CREATE TABLE f_tx
(
  dt Date,
  shop_id UInt32,
  sku_id UInt32,
  amount Decimal(12,2)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(dt)
ORDER BY (dt, shop_id, sku_id)
SETTINGS allow_experimental_projection_optimization = 1;
ALTER TABLE f_tx
ADD PROJECTION p_daily_shop
(
  SELECT dt, shop_id, sum(amount) AS amount_sum
  GROUP BY dt, shop_id
);

 

ORDER BY, индексы и физическая организация данных

Как выбрать ORDER BY

  • Оперируйте реальными фильтрами BI: если 90% запросов начинаются с WHERE day BETWEEN … AND … AND shop_id IN (…), то логика ORDER BY (day, shop_id, …) — рациональна.
  • Сразу заложите селективные поля в префикс: дата/временная гранулярность, гео/магазин/канал, сегмент.
  • Не переусердствуйте: слишком длинный ключ → избыточная кардинальность и накладные расходы.

 

Skip-индексы (secondary data-skipping indexes)

  • minmax: по числам/датам; надежный «молоток».
  • bloom_filter: для подстрок/IN по большим множествам; экспериментируйте с настройками granularity и bloom_filter_size.
  • set: для небольших наборов значений.

 

Пример:

CREATE TABLE mart_orders_idx
(
    day Date,
    shop_id UInt32,
    customer_id UInt64,
    amount Decimal(12,2),
    status LowCardinality(String),
    INDEX idx_status status TYPE set(1000) GRANULARITY 4
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (day, shop_id, customer_id);

 

Принцип: индекс ускорит пропуск гранул, но не заменит продуманное ORDER BY.

 

Партиционирование и «part explosion»

  • Партиции — логическая группировка (обычно по месяцу/неделе/дню).
  • Слишком мелкие партиции → много частей → перегретые merges.
  • Буферизуйте вставки: вставляйте крупными блоками (десятки-сотни тысяч строк).

 

Полезные настройки и приёмы:

  • Вставляйте через INSERT SELECT с батчами.
  • Сведите мелкие партии в промежуточную таблицу и периодически переливайте в целевую.
  • Контролируйте метрики: число parts на партицию, merge backlog.

 

Миграции схем и side-by-side стратегии

Изменения в витринах неизбежны (новые атрибуты, уточнение формул, смена ключей). Цель — без простоя BI.

Side-by-side (v1 → v2)

  1. Создайте новую таблицу *_v2 с нужной схемой, ORDER BY, движком.
  2. Двойная запись (если возможно): всё новое пишем и в v1, и в v2.
  3. Ретро-пересчёт v2 за историю (окно N месяцев/лет).
  4. Сравнение агрегатов (контрольные суммы, «серебряный» датасет).
  5. Переключение BI (view/alias) на v2.
  6. Заморозка v1 и план удаления.

 

SQL-набросок:

-- 1. v2
CREATE TABLE mart_sales_v2 ( ... )
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (day, shop_id);
-- 2-3. Перекладка истории:
INSERT INTO mart_sales_v2
SELECT ... FROM source
WHERE day BETWEEN '2024-01-01' AND today();
-- 4. Сравнение:
SELECT sum1, sum2, sum1 - sum2 AS diff FROM (
  SELECT sum(amount) AS sum1 FROM mart_sales_v1 WHERE day>=today()-30
), (
  SELECT sumMerge(amount_state) AS sum2 FROM mart_sales_v2 WHERE day>=today()-30
);
-- 5. VIEW/замена
CREATE OR REPLACE VIEW mart_sales AS SELECT * FROM mart_sales_v2;

 

Миграции колонок

  • Добавление — безопасно (backward-compatible).
  • Удаление/rename — делайте через v2.
  • Смена типа — v2 + перелив.

 

Пересборка партиций

Если требуется массовая перекладка:

  • Снимайте бэкап партиций.
  • Пересборка по окну (месяц/неделя) с контролем агрегатов.
  • В ночные окна/low-traffic периоды.

 

Observability: логи, метрики, алерты

Хорошая витрина — это не только SQL. Это наблюдаемость: чтоб заранее знать о проблемах.

Системные логи ClickHouse

  • system.query_log — факт выполнения запросов (включайте логирование завершённых и, по возможности, выбрасывайте на долгоживущие SELECT).
  • system.part_log — операции с частями (создание, мердж, удаление).
  • system.trace_log — низкоуровневые профилировки (опционально).
  • system.metric_log / system.asynchronous_metrics — счётчики и «асинхронные» метрики.

 

Первые запросы:

-- Топ тяжёлых запросов за сутки
SELECT
  query_kind, query,
  sum(read_rows) AS rows, sum(read_bytes) AS bytes,
  avg(query_duration_ms) AS ms
FROM system.query_log
WHERE event_time >= now() - INTERVAL 1 DAY
  AND type = 'QueryFinish'
GROUP BY query_kind, query
ORDER BY bytes DESC
LIMIT 50;
-- Динамика merges/parts
SELECT event_type, count()
FROM system.part_log
WHERE event_time >= now() - INTERVAL 1 DAY
GROUP BY event_type;

 

Метрики Prometheus/Grafana (или аналоги)

Отслеживайте:

  • Число партиций/частей на таблицу и их динамику.
  • Merge backlog и время мерджей.
  • Репликационный лаг (если реплицируете).
  • Латентность обновления витрин (SLA freshness).
  • Ошибки в MV/ingestion (например, лаг чтения из Kafka, отвал консьюмеров).
  • Объём диска по хранилищам/политикам (NVMe/S3), TTL-переносы.

 

Алерты:

  • parts > порога (например, >20k) по таблице/партиции.
  • merges backlog растёт > X минут.
  • freshness витрины > SLA (например, >15 мин).
  • репликация отстаёт > N секунд/минут.

 

Безопасность и доступ

RBAC (роли и права)

  • Создавайте роли: reader_bi, developer_marts, admin_dwh.
  • Гранты уровня БД/таблиц/вьюх.
  • Привязывайте пользователей к ролям, не раздавайте права напрямую.

 

CREATE ROLE reader_bi;
GRANT SELECT ON db_marts.* TO reader_bi;
CREATE USER analyst1 IDENTIFIED BY '***';
GRANT reader_bi TO analyst1;

 

Row-Level Security (политики строк)

Ограничивайте доступ по регионам/подразделениям.

-- Дать пользователю видеть только строки его региона
CREATE ROW POLICY rp_sales_region
ON db_marts.mart_sales_wide
FOR SELECT
USING region_id = currentSetting('region_id');  -- или через mapping-таблицу + JOIN во VIEW
ALTER USER analyst1 SETTINGS region_id = 77;

 

Практика: для сложных фильтров удобнее создавать VIEW с фильтрами и раздавать доступ к ним.

 

Маскирование колонок

  • В ClickHouse нет «универсальной» встроенной сложной маскировки как в некоторых СУБД, поэтому:
    • Используйте VIEW, где PII/секреты трансформируются функциями (SHA256, substring, if) или зануляются.
    • Гранты только на VIEW, не на исходную таблицу.
    • В сложных случаях — вынос PII в отдельные хранилища/таблицы и join-по-требованию.

 

Прикладной кейс №3: FinTech (книга транзакций и витрины балансов)

Контекст: транзакции клиентов (дебет/кредит), комиссии, возвраты; нужны витрины остатков по дням и отчёты по действительной выручке (после уточнений).

 

Модель и слои

  • CORE (вне CH): нормализованные таблицы t_transaction (ledger), справочники клиентов, продуктов, тарифов.
  • MARTS (CH):
    • mart_tx_wide — денормализация транзакций (snapshot тарифов на дату).
    • agg_balance_daily_state — AggregatingMergeTree с состояниями баланса.
    • agg_revenue_daily_state — агрегаты выручки/комиссий.

 

Таблицы

CREATE TABLE mart_tx_wide
(
  tx_dttm DateTime,
  account_id UInt64,
  product_id UInt32,
  tx_type LowCardinality(String), -- debit/credit/fee/refund
  amount Decimal(18,2),
  currency FixedString(3),
  exchange_rate Decimal(12,6),    -- snapshot на момент tx
  amount_base Decimal(18,2)       -- amount * rate
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(tx_dttm)
ORDER BY (tx_dttm, account_id, product_id);

 

Ежедневные балансы (sum по знаку):

CREATE TABLE agg_balance_daily_state
(
  day Date,
  account_id UInt64,
  balance_state AggregateFunction(sum, Decimal(18,2))
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (day, account_id);
INSERT INTO agg_balance_daily_state
SELECT
  toDate(tx_dttm) AS day,
  account_id,
  sumState(
    if(tx_type IN ('credit','refund'), amount_base, -amount_base)
  ) AS balance_state
FROM mart_tx_wide
GROUP BY day, account_id;

 

Чтение баланса за период с накоплением (running total на стороне BI/SQL):

SELECT
  day, account_id,
  sumMerge(balance_state) AS delta_day
FROM agg_balance_daily_state
WHERE day BETWEEN '2025-06-01' AND '2025-06-30'
GROUP BY day, account_id
ORDER BY account_id, day;

 

Далее считаем кумулятивно в BI или SQL-окном.

 

Выручка (комиссии/fee) по дням/продуктам:

CREATE TABLE agg_revenue_daily_state
(
  day Date,
  product_id UInt32,
  revenue_state AggregateFunction(sum, Decimal(18,2))
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (day, product_id);
INSERT INTO agg_revenue_daily_state
SELECT
  toDate(tx_dttm) AS day,
  product_id,
  sumState(if(tx_type='fee', amount_base, 0))
FROM mart_tx_wide
GROUP BY day, product_id;

 

Риски и митигации

  • Корректировки задним числом (chargeback/refund через дни/недели).
    → Митигация: AggregatingMergeTree + ночной ретро-прогон последних N дней; side-by-side для больших правок.
  • Валютные пересчёты (курсы меняются).
    → Митигация: фиксируйте snapshot курса в mart_tx_wide и отдельно делайте витрину «пересчёт в базовую валюту» при отчётах на нужную дату.
  • Согласование с GL (главной книгой).
    → Митигация: DQ-тесты балансов (дебет=кредит), контрольные суммы по дням/продуктам.

 

DQ-контроли (минимум)

  • Баланс входа/выхода (сверка с GL).
  • Наличие «дней тишины» для активных аккаунтов.
  • Наличие отрицательных/аномальных сумм вне допустимых бизнес-правил.
  • Согласованность количества транзакций с источником (± допуск).

 

Прикладной кейс №4: Telecom (CDR/события сети)

Контекст: миллиарды CDR (Call Detail Records), KPI по качеству сети, загрузке сот. Витрины — почасовые/поминутные агрегаты по сотам/тарифам/регионам.

 

Модель и ingestion

  • Источник: Kafka (сырые CDR) → Kafka Engine + MV → cdr_wide.
  • Витрины: agg_cdr_minute, agg_cdr_hour, перцентили задержек/длительностей.

 

 

 

CREATE TABLE cdr_kafka
(
  event_time DateTime,
  cell_id UInt64,
  msisdn UInt64,
  call_duration_ms UInt32,
  latency_ms UInt32,
  result LowCardinality(String)   -- success/fail
)
ENGINE = Kafka
SETTINGS kafka_broker_list='kafka:9092', kafka_topic_list='cdr', kafka_format='JSONEachRow', kafka_num_consumers=6;
CREATE TABLE cdr_wide
(
  event_time DateTime,
  cell_id UInt64,
  msisdn UInt64,
  call_duration_ms UInt32,
  latency_ms UInt32,
  result LowCardinality(String)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, cell_id, msisdn);
CREATE MATERIALIZED VIEW mv_cdr_ingest TO cdr_wide AS
SELECT * FROM cdr_kafka;

 

Поминутные агрегаты (состояния):

CREATE TABLE agg_cdr_minute_state
(
  ts_minute DateTime,
  cell_id UInt64,
  calls_state AggregateFunction(sum, UInt64),
  ok_state    AggregateFunction(sum, UInt64),
  latency_q95_state AggregateFunction(quantileTDigest(0.95), UInt32),
  duration_avg_state AggregateFunction(avg, UInt32)
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(ts_minute)
ORDER BY (ts_minute, cell_id);
CREATE MATERIALIZED VIEW mv_cdr_to_minute TO agg_cdr_minute_state AS
SELECT
  toStartOfMinute(event_time) AS ts_minute,
  cell_id,
  sumState(1) AS calls_state,
  sumState(result='success') AS ok_state,
  quantileTDigestState(0.95)(latency_ms) AS latency_q95_state,
  avgState(call_duration_ms) AS duration_avg_state
FROM cdr_wide
GROUP BY ts_minute, cell_id;

 

Чтение:

SELECT
  ts_minute, cell_id,
  sumMerge(calls_state) AS calls,
  sumMerge(ok_state) AS ok,
  round(100.0 * ok / calls, 2) AS success_rate,
  quantileTDigestMerge(0.95)(latency_q95_state) AS p95_latency,
  avgMerge(duration_avg_state) AS avg_duration
FROM agg_cdr_minute_state
WHERE ts_minute >= now() - INTERVAL 1 DAY
GROUP BY ts_minute, cell_id
ORDER BY ts_minute, cell_id;

 

Риски и митигации

  • Бурстовая нагрузка вечером → тысячи маленьких вставок.
    → Митигация: настроить kafka_max_block_size, микробатчи, буферные таблицы, контроль parts.
  • Дубликаты CDR после рестартов консьюмера.
    → Митигация: event_id + ReplacingMergeTree на этапе cdr_wide или upstream-дедуп.
  • Нестабильная латентность S3/объектного диска (если храните «холодные» партиции там).
    → Митигация: кэширование, вынесение холодных периодов, отделение hot-window (N часов/дней) на NVMe.

 

BI-отчётность

  • Дашборды по успешности (success_rate) в разрезе сот/регионов, p95-латентность, средняя длительность, alarm-панель (алерты по SLA).
  • Drill-down: регион → город → соты → «спайки» по минутам.

 

Практики «устойчивых» витрин

  • Стабилизация схемы: изменения через v2, view-перекидку, «медленные» миграции в окна.
  • Стабилизация метрик: паспорт, версионирование формул, changelog.
  • Стабилизация запросов: вьюхи с …Merge, чтобы BI не ошибался.
  • Стабилизация обновления: договорённость с CORE — «в окне последних N дней возможны корректировки», всё старше — только overlays.
  • Стабилизация стоимости: TTL в холод, S3-policy, «горячее окно» на NVMe.

 

Мини-runbooks (что делать, если…)

A) Витрина стала обновляться с лагом

  1. Проверить lag ingestion (Kafka/S3), отставание MV.
  2. Проверить merges backlog и число parts; при необходимости — OPTIMIZE TABLE … FINAL по конкретным партициям (точечно!).
  3. Временно сузить окно ретро-пересчёта, отключить тяжёлые overlay.
  4. Алерты: свежесть > SLA → уведомление + полуавтоматические шаги.

 

B) Дубли/расхождения в агрегатах

  1. Снять снимки контрольных сумм (SUM(amount)) по окну, сравнить CORE↔MARTS.
  2. Найти источник дублей (повторные заливки, offsets Kafka).
  3. Для Summing — пересобрать целевые партиции из чистого источника; для Aggregating — дописать состояния и перечитать …Merge.

 

C) «Память закончилась» на BI-JOIN

  1. Проверить план (EXPLAIN PIPELINE), уменьшить ширину результата/колонок.
  2. Разбить запрос на два (pre-aggregate), использовать wide-витрину.
  3. Настроить лимиты и распределённые стадии (если кластер).

 

Практические советы по настройкам (точечно, как чек-лист)

  • Вставки: старайтесь в один INSERT подавать крупные блоки (сотни тысяч/миллионы строк), избегайте «дроби».
  • MVs: разбивайте агрегаты по времени (минуты/часы/дни), не пересчитывайте «всё» на каждую запись.
  • Партиции: для high-traffic событий — дневные; для продаж — месячные (часто достаточно).
  • ORDER BY: отражает частые фильтры. «Дата → ключ разреза → доп. поля».
  • Индексы: ставьте только те, что дают измеримый эффект.
  • SLA: фиксация fresh­ness, completeness, и «алгоритм деградации» (что отключаем при перегрузе).
  • DQ: балансные проверки, дубликаты, плотность данных; не блокировать прод, а сигналить и лечить.

 

Часто задаваемые вопросы

Нужно ли делать звезду, если у нас wide-витрины быстрые?
— Делайте то, что простое и предсказуемое. Если wide-таблица закрывает 90% сценариев и не бьёт по объёму — это нормальный выбор. Введите «тематические» витрины (by product, by customer) вместо универсальной «гигантской».

 

Когда FINAL допустим?
— Точечно: ад-hoc диагностика, маленькие партиции, редкие отчёты. Для постоянных BI-дашбордов добивайтесь консистентности без FINAL.

 

Projections — спасут?
— Иногда да, но это инструмент точечной оптимизации, а не серебряная пуля. Начинайте без них.

 

Где хранить истину по метрикам?
— В паспортe метрик (артефакт) + вьюхи/слой sema­ntic. Согласуйте с бизнесом и BI.

 

В этой части мы разложили ключевые техники ClickHouse-витрин: выбор движков (Replacing/Collapsing/Summing/Aggregating), грамотный ORDER BY, индексы, миграции без простоя, наблюдаемость, безопасность, и две отраслевые практики с SQL и планами митигаций. Это «рабочий чемодан» архитектора/инженера витрин.

 

Cookbook: типовые симптомы, как диагностировать и что делать

Ниже сгруппированы реальные ситуации. Формат: Симптом → Диагностика → Фикс → Профилактика. Команды даны коротко, отталкивайтесь от своих схем/таблиц.

 

Вставки, части, мерджи

A1. Много мелких частей (part explosion), мерджи «задыхаются».

  • Диагностика
    • Проверить число частей по таблице/партициям:
SELECT table, partition, count() AS parts
FROM system.parts
WHERE active AND database='db_marts' AND table='mart_sales_wide'
GROUP BY table, partition
ORDER BY parts DESC;

 

  • Лог мерджей:
SELECT event_type, count()
FROM system.part_log
WHERE event_time >= now()-INTERVAL 1 HOUR
GROUP BY event_type;

 

  • Фикс
    • Временно буферизуйте вставки: пишите в промежуточную таблицу батчами (INSERT SELECT раз в N минут).
    • Точечно укрупните партиции: OPTIMIZE TABLE ... PARTITION ... FINAL (не злоупотреблять).
  • Профилактика
  • Увеличить размер блоков на входе (Kafka/JDBC), переход на микробатчи.
  • Пересмотреть партиционирование (дни вместо часов/минут при продажах).

 

A2. Мерджи «висят» часами, растёт merge backlog.

  • Диагностика
    • Метрики merges/threads, background_pool_size, активные мерджи:
SELECT * FROM system.merges WHERE elapsed > 60;
SELECT name, value FROM system.asynchronous_metrics
WHERE name LIKE '%Merge%';

 

  • Фикс
    • Снизить скорость генерации новых частей (замедлить ingestion или объединять upstream).
    • Увеличить background_pool_size, при необходимости вертикально усилить CPU/IO.
  • Профилактика
  • Планировать «тяжёлые» пересчёты в ночные окна.
  • Свести частоту вставок к разумной (десятки/сотни тысяч строк за батч).

 

A3. «Память закончилась» во время массового OPTIMIZE … FINAL.

  • Диагностика
    • По логу запросов и системным ошибкам (OOM, memory limit exceeded).
  • Фикс
  • Делать OPTIMIZE по одной партиции, уменьшить параллелизм.
  • Временно увеличить max_memory_usage (осторожно).
  • Предотвращать part explosion, проводить профилактические OPTIMIZE малых партиций заранее.
  • Профилактика

 

Запросы и производительность BI

B1. Запросы BI стали в 5–10 раз медленнее «со вчера».

  • Диагностика
    • Сравнить планы: EXPLAIN PIPELINE/PLAN.
    • Топ-запросы по байтам/строкам/времени:
SELECT query, sum(read_rows) r, sum(read_bytes) b, avg(query_duration_ms) ms
FROM system.query_log
WHERE event_time >= now()-INTERVAL 1 DAY AND type='QueryFinish'
GROUP BY query ORDER BY b DESC LIMIT 20;

 

  • Фикс
    • Проверить селективность WHERE и соответствие ORDER BY; добавить/подправить skip-индексы.
    • Пересобрать «проблемные» партиции (сильно фрагментированные) точечным OPTIMIZE.
  • Профилактика
  • Контролировать изменения схем/ORDER BY через PR-процессы и нагрузочные тесты.
  • Договориться с BI о лимитах/фильтрах по умолчанию (не «SELECT * за год»).

 

B2. JOIN «съедает» память, падает по лимиту.

  • Диагностика
    • План запроса, какая таблица «раздувает» хэш-таблицу.
  • Фикс
  • Разбить запрос на два (pre-aggregate → join агрегатов).
  • Перейти на wide витрину с нужными атрибутами.
  • Ограничить набор колонок и кардинальность.
  • Для CH-витрин оптимальнее минимизировать JOIN на чтении: делать pre-join при материализации.
  • Профилактика

 

B3. FINAL в витрине стал обязательным — всё тормозит.

  • Диагностика
    • Проверить количество конкурирующих версий/«грязных» записей.
  • Фикс
  • Навести порядок upstream: дедуп на источнике или на STAGE, ограничить окно версий.
  • Перейти на AggregatingMergeTree/batch пересчёт.
  • Жёсткий data contract на уникальность ключей + версионирование событий.
  • Профилактика

 

Материализованные представления и агрегаты

C1. MV «жует» CPU — пересчитывает слишком много.

  • Диагностика
    • Посмотреть SQL MV, есть ли группировка без ограничения по времени/партиции.
  • Фикс
  • Делить MV по времени (минуты/часы/дни), агрегировать только свежие окна.
  • Вынести тяжёлые пересчёты в batch (INSERT SELECT по расписанию).
  • Не делать «одну MV, которая считает весь мир». Мелкие, целевые, по окнам.
  • Профилактика

 

C2. SummingMergeTree удвоил суммы после «повторной» загрузки.

  • Диагностика
    • Сверить контрольные суммы по окну, найти дубли в источнике.
  • Фикс
  • Пересобрать целевой период из чистого источника (перезапись партиций).
  • Или перейти на AggregatingMergeTree (…State/…Merge).
  • Жёсткая дисциплина загрузок: «повторная заливка» только через пересборку.
  • Профилактика

 

Репликация, Keeper, Distributed

D1. Реплика «отстаёт», lag растёт.

  • Диагностика
    • Метрики replication queue, system.replication_queue.
  • Фикс
  • Увеличить фоновые потоки, проверить IO/сеть, починить «битые» задания в очереди.
  • Равномерный шард/реплік layout, не перегружать одну ноду.
  • Профилактика

 

D2. Keeper (ZooKeeper/CH Keeper) «шалит»: висят DDL, рассинхрон.

  • Диагностика
    • Логи keeper, сессии, состояния нод.
  • Фикс
  • Починить кластер keeper (кворум), переразвернуть, переиграть DDL через Distributed DDL.
  • Выделенные ресурсы под keeper, мониторинг сессий/лагов, не хранить «всё» в ZK.
  • Профилактика

 

D3. Distributed-таблица «тормозит», локальные быстрые.

  • Диагностика
    • Проверить preferred_block_size_bytes, балансировку, сеть.
  • Фикс
  • Сужать запросы (пушдаун WHERE к шардам), настраивать локальные агрегации.
  • Правильный sharding key (обычно по дате/идентификатору разреза).
  • Профилактика

 

S3/объектные диски и TTL

E1. SELECT по «холодным» партициям на S3 внезапно медленные.

  • Диагностика
    • Проверить кэш, сеть, облачный endpoint.
  • Фикс
  • Включить локальный кэш, подогрев горячих участков, разнести «горячее окно» на NVMe.
  • Чёткий tiering: горячий горизонт (N дней/недель) — локально, остальное — S3.
  • Профилактика

 

E2. TTL «вынес» не те партиции/колонки.

  • Диагностика
    • Проверить правила TTL и фактические действия в part_log.
  • Фикс
  • Исправить TTL, вернуть партиции из бэкапа, временно отключить правила.
  • Тестировать TTL на стейдже, не накатывать сразу на весь «прод».
  • Профилактика

 

DQ, консистентность, опоздавшие события

F1. BI видит расхождения с CORE (±1–3%).

  • Диагностика
    • Балансы по окну, сравнение контрольных сумм:
-- MARTS
SELECT toDate(tx_datetime) d, sum(amount) s FROM mart_sales_wide
WHERE tx_datetime >= today()-7 GROUP BY d;
-- CORE (подставьте источник)

 

  • Фикс
    • Выявить окно опоздавших, доиграть корректировки (overlay).
    • Устранить дубли на пути ingestion.
  • Профилактика
  • Договориться об окне ретро-пересчёта (напр., «последние 14 дней»), автоматический nightly reconcile.

 

F2. Много дублей событий после рестарта Kafka-консьюмера.

  • Диагностика
    • Сравнить offsets, посмотреть повторяющиеся ключи.
  • Фикс
  • Дедуп по event_id на STAGE или ReplacingMergeTree(version) в витрине.
  • Exactly-once недостижим — стройте идемпотентность на ключах.
  • Профилактика

 

Релизы, миграции, CI/CD

G1. Изменили схему — BI «упал».

  • Диагностика
    • Что изменилось: rename/drop/тип?
  • Фикс
  • Вернуть обратно или срочно переключить BI на v2-view, где совместимая схема.
  • Только additive изменения в проде; breaking-changes через v2 + alias/view.
  • Профилактика

 

G2. Пересборка витрины заняла всю ночь, отчёты не готовы.

  • Диагностика
    • Объём пересчёта, IO/CPU, конкуренция с мерджами.
  • Фикс
  • Делить пересборку по партициям, параллелить по окнам, переносить «глубокую историю» заранее.
  • Side-by-side миграции, «прокладка» v2 в фоне, переключение BI в нужный момент.
  • Профилактика

 

Безопасность и доступ

H1. Случайно раскрыли PII в широких витринах.

  • Диагностика
    • Проверить колонки, кому был дан прямой SELECT.
  • Фикс
  • Срочно ограничить доступ, создать VIEW с маскировкой, раздать гранты только на VIEW.
  • Политика: PII никогда не отдаём напрямую из витрин; всегда через «обезличивающие» представления.
  • Профилактика

 

Шаблоны артефактов (копируй и используй)

27.1 Паспорт метрики (шаблон)

Идентификатор: NET_SALES

Название: Чистые продажи (Net Sales)

 

Бизнес-определение:

  Сумма оплаченных продаж без возвратов и отмен,

  пересчитанная в базовую валюту по курсу на момент транзакции.

 

Формула (SQL/псевдо):

  SUM(amount_base) - SUM(returns_amount_base)

 

Гранулярность:

  День × Магазин × Категория SKU

 

Фильтры/исключения:

  status IN ('paid', 'captured') AND source NOT IN ('test', 'fraud')

 

Единицы и валюты:

  amount_base в валюте 'RUB'; для мультивалютных витрин — отдельная витрина *_fx.

 

Окно ретро-пересчёта:

  Скользящая неделя (7 дней) каждые 15 минут + ночной пересчёт 30 дней.

 

Слой материализации:

  agg_sales_daily_state (AggregatingMergeTree) + VIEW для чтения (…Merge).

 

Версионирование:

  v1 от 2025-07-01; v2 (меняем исключения) планируется 2025-09-01.

 

Тесты DQ:

  - Баланс с CORE ±0.1% в окне 7 дней

  - Отсутствие дублей по ключу (day, shop_id, category_id)

  - Нулевые значения только при явной «тишине»

 

Контакты/ответственность:

  Владелец метрики: Head of FP&A

  Технический владелец: DWH Architect

  Канал изменений: PR в репозитории marts-metrics

 

SLA витрины (шаблон)

Витрина: agg_sales_daily_state

Назначение: Дашборды продаж (оперативные и управленческие)

 

Показатели SLA:

  - Freshness: не старше 15 минут от факта (08:00–23:00), не старше 1 часа (ночью)

  - Availability: 99.5% в месяц

  - Consistency: расхождение с CORE ≤ 0.2% на окне 7 дней

 

Окна обслуживания:

  Ночной ретро-пересчёт 00:30–02:00; тяжёлые миграции — воскресенье 02:00–04:00

 

Алерты:

  - Freshness > 15 мин → PagerDuty: BI-OnCall

  - parts > 20k/партиция → уведомление в #dwh-alerts

 

Degradation policy:

  При перегрузе отключаем minute-level агрегаты, оставляем hourly-level.

 

Чек-лист DQ для витрины

  • Баланс суммы с CORE на окне N дней в допуске.
  • Дубликаты ключей (grain) отсутствуют.
  • Плотность данных (нет «дыр» там, где не должно быть).
  • Домены значений (статусы/категории) валидны.
  • Неотрицательные суммы/количества, если бизнес-правило таково.
  • Единицы измерения и валюты соответствуют контракту.
  • Регрессионные тесты метрик (после изменений) проходят.

 

Регламент релизов/миграций (в конспекте)

  1. Любые breaking-changes — через v2 + alias/view.
  2. PR: схемы, MV, тесты DQ, миграции.
  3. Стейдж: нагрузочные прогоны, сравнение агрегатов v1 vs v2 на окне.
  4. Прод: двойная запись (если нужно), ретро-пересчёт истории, переключение BI.
  5. Пост-мониторинг (freshness, ошибки запросов, parts/merges).

 

Runbook (шаблон «что делать, если…»)

Сценарий: Витрина отстала по свежести > SLA

 

Шаги:

  1. Проверить lag ingestion (Kafka/S3), состояние MV

  2. Посмотреть merges backlog, число parts

  3. При необходимости временно:

     - сузить окно ретро-пересчёта

     - отключить тяжелые overlay-процессы

  4. Точечный OPTIMIZE проблемной партиции (если фрагментация)

  5. Сообщить бизнесу о деградации (если длительно) + ETA

 

Критерии восстановления:

  Freshness < 15 мин; merges backlog < порога; parts стабилизированы

Ответственный:

  DWH OnCall (смена); эскалация: DWH Lead

 

Маршрут запуска витрины «с нуля за 2–3 дня»

День 0 (подготовка)

  • Уточнить use-case, метрики, источники, окно свежести.
  • Зафиксировать паспорт метрики и data contract CORE→MARTS.
  • Проверить доступы, учётки, хостинг (кластер CH, Kafka/S3 при необходимости).

 

День 1 (схема и базовые таблицы)

  1. База и роли:
CREATE DATABASE db_marts;
CREATE ROLE reader_bi; GRANT SELECT ON db_marts.* TO reader_bi;
CREATE USER bi_ro IDENTIFIED BY '***'; GRANT reader_bi TO bi_ro;

 

  1. Витрина (wide) и агрегаты-состояния:
CREATE TABLE db_marts.mart_sales_wide
(
  tx_datetime DateTime,
  day Date MATERIALIZED toDate(tx_datetime),
  shop_id UInt32, shop_name LowCardinality(String),
  sku_id UInt32,  category_id UInt32,
  qty Int32, amount Decimal(12,2),
  currency FixedString(3),
  customer_id UInt64
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (day, shop_id, sku_id);

 

  1. Агрегат-состояния (устойчивые):
CREATE TABLE db_marts.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);

 

  1. Первичная загрузка (история):
INSERT INTO db_marts.mart_sales_wide
SELECT ... FROM core.sales
WHERE tx_datetime >= now()-INTERVAL 90 DAY;

 

  1. Первичное наполнение агрегатов:
INSERT INTO db_marts.agg_sales_daily_state
SELECT
  toDate(tx_datetime) day, shop_id, category_id,
  sumState(amount) AS amount_state,
  sumState(qty)    AS qty_state
FROM db_marts.mart_sales_wide
GROUP BY day, shop_id, category_id;

 

День 2 (материализация, DQ, OBS)

  1. VIEW для удобного чтения (…Merge):
CREATE OR REPLACE VIEW db_marts.vw_agg_sales_daily AS
SELECT
  day, shop_id, category_id,
  sumMerge(amount_state) AS amount_sum,
  sumMerge(qty_state)    AS qty_sum
FROM db_marts.agg_sales_daily_state
GROUP BY day, shop_id, category_id;

 

  1. Инкремент (каждые 15 минут) — cron/Airflow:
-- псевдо: инкремент за последние 2 часа
INSERT INTO db_marts.mart_sales_wide
SELECT ... FROM core.sales
WHERE tx_datetime >= now()-INTERVAL 2 HOUR;
INSERT INTO db_marts.agg_sales_daily_state
SELECT
  toDate(tx_datetime), shop_id, category_id,
  sumState(amount), sumState(qty)
FROM db_marts.mart_sales_wide
WHERE tx_datetime >= now()-INTERVAL 2 HOUR
GROUP BY toDate(tx_datetime), shop_id, category_id;

 

  1. DQ-контроли (ночью и на инкремент):
-- Баланс vs CORE за 1 день
SELECT
  (SELECT sum(amount) FROM db_marts.vw_agg_sales_daily WHERE day= yesterday()) AS marts_sum,
  (SELECT sum(amount) FROM core.sales WHERE toDate(tx_datetime)=yesterday()) AS core_sum,
  abs(marts_sum-core_sum)/NULLIF(core_sum,0) AS diff_rel;
Алерт, если diff_rel > 0.002.

 

  1. Observability: включить query_log/part_log, настроить экспорт в Prometheus/Grafana (метрики merges/parts, freshness, ошибки MV/джобов).

 

День 3 (безопасность, BI, ретро-окно, релизы)

  1. Безопасность:
  • Доступ BI — только к vw_* вьюхам; PII маскируется в VIEW.
  • RLS (если нужно) по регионам/подразделениям.

 

  1. Подключение BI:
  • Тест отчётов, фильтры по умолчанию, лимиты.
  • SLA-монитор: плитка «свежесть витрины», «health» мерджей/частей.

 

  1. Ретро-пересчёт (окно 30 дней):
  • Ночной джоб INSERT SELECT по дням, где были корректировки.
  • Для Summing — rebuild; для Aggregating — дописываем состояния.

 

  1. Регламент релизов:
  • Внести в репозиторий схемы, VIEW, джобы, DQ-тесты.
  • PR-проверки, стейдж-прогон, потом — прод.

 

Результат к концу Дня 3: рабочая витрина с SLA, DQ, Observability, безопасностью и подключенным BI.

 

Быстрые рекомендации по сайзингу слоя витрин (на старте)

  • CPU: 16–32 vCPU на ноду (3+ ноды) — для начала интерактива; масштабировать горизонтально.
  • RAM: 64–128 ГБ на ноду (зависит от JOIN/агрегатов и concurrency).
  • Диск: NVMe для «горячего окна» (N дней/недель), остальное — S3/object storage + кэш.
  • Сеть: 10–25 Gbit/s внутренняя; выделенная полоса к S3/объектному хранилищу.
  • Шардинг: по дате/разрезу (shop/region), чтобы запросы «сужались» на шард.
  • Репликация: 2 реплики на шард для HA, Keeper — выделенные ресурсы.

 

Частые вопросы (коротко)

Q: Можно ли жить только на SummingMergeTree?
A: Можно, если никогда не переигрываете окна. На практике — используйте AggregatingMergeTree для устойчивости.

Q: Когда FINAL безболезнен?
A: На малых партициях/разово. Для прод-дашбордов — лучше без FINAL.

Q: Нужны ли projections?
A: Только после профилирования и там, где дают заметный прирост. Это не «обязательный» инструмент.

Q: Что хранить в CH, а что — нет?
A: В CH — витрины/агрегаты под чтение. Истина/MDM/тонкие транзакции — в CORE.

 

Итоги модуля

  • ClickHouse — идеальный слой витрин: быстрые агрегации, дешёвые сканы, near real-time.
  • Ключ к успеху — контракты CORE→MARTS, правильный ORDER BY/партиции, устойчивые материализации (Aggregating/Replacing + дисциплина), наблюдаемость и регламент релизов.
  • Cookbook, шаблоны и маршрут дают «скелет» для быстрого и безопасного старта.

 

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. Итог — быстрый запуск витрин за недели, снижённые риски в проде и предсказуемая стоимость владения.

 

Узнать стоимость решенияЗапросить видео презентацию

Следующая статья →
Модуль 1. Бизнес-метрики и семантический слой витрин для ClickHouse
Запросить видео презентацию Запросить доступ к демо стенду online Узнать стоимость лицензий

Задать вопрос

loading...

Решения

Анализировать ФинансыУвеличивайте ПродажиОптимальный Склад и ЛогистикаМаркетинговые Метрики

Клиенты
  • ГК «Акрон Холдинг», одно из крупнейших в России промышленно-металлургических предприятий, запустил проект по модернизации управления данными. В качестве целевого решения для анализа ключевых данных компания выбрала систему PIX BI. В компании уже более 100 пользователей PIX BI, и в этом году в планах увеличить их число в два раза.

  • ПАО «Ростелеком» — российский провайдер цифровых услуг и сервисов. Предоставляет услуги широкополосного доступа в Интернет, интерактивного телевидения, сотовой связи, местной и дальней телефонной связи и др. Занимает лидирующие позиции на российском рынке высокоскоростного доступа в интернет, платного ТВ, хранения и обработки данных, а также кибербезопасности

  • ЭГИС - международная фармацевтическая компания, основанная в 1907 году в Венгрии. Компания имеет представительства более чем в 60 странах мира, в том числе в России. Компания ЭГИС является одним из ведущих производителей дженерических лекарственных средств в Центральной и Восточной Европе. Её деятельность охватывает все звенья производственно-сбытовой фармацевтической цепочки.

  • Авиакомпания NordStar (АО «АК «НордСтар») – работает под данным брендом с 2008 г. и сейчас входит в топ-15 крупнейших российских авиакомпаний (данные Росавиации) с пассажирооборотом более 1 млн человек в год. АО «АК «НордСтар» выполняет и внутренние, и внешние рейсы, а ее основные хабы - Домодедово, Пулково и Емельяново. С 2021 года компания является базовым перевозчиком аэропорта Норильск.

  • Решения
    • Дистрибуция
    • Розничная торговля
    • Производство
    • Операторы связи
    • Страхование
    • Банки
    • Лизинг
    • Логистика
    • Нефтегазовый сектор
    • Медицина
    • Сеть ресторанов
    • E-Commerce
    • Энергетика
    • Фармацевтика
  • Услуги
    • Переход на отечественные BI и DWH
    • Консалтинг
    • Пилотный проект
    • Обучение и сертификация
    • Бесплатное обучение
    • Техническая поддержка
    • Технические задания
    • Сбор требований для проекта внедрения BI-системы
    • CI/CD для DWH
    • Аудит BI приложений
    • Выделенная команда
    • Настойка и поддержка баз данных
    • Разработка BI Стратегии
    • Styleguide для BI-системы
    • Как выбрать BI-систему
  • Платформы
    • FineBI
    • FineReport
    • FineDataLink
    • Коннекторы данных из 1С в BI
    • Airflow + NiFi
    • Visiology
    • Luxms BI
    • Modus BI
    • PIX BI
    • Arenadata
    • ClickHouse
    • Greenplum
    • Postgres Professional
    • Open-source BI: Superset/Metabase
    • Loginom
    • Yandex.DataLens
    • AI / Исскуственный интеллект
    • Optimacros
    • Шины данных
  • Курсы
    • Учебный курс Информационная грамотность
    • Учебный курс для бизнес-аналитиков
    • Учебный курс для системных аналитиков
    • Учебный курс по Data Governance
    • Учебный курс Как стать CDO
    • Учебный курс Современная архитектура хранилища данных
    • Учебный курс по Fine BI
    • Учебный курс по FineReport
    • Учебный курс по DWH
    • Учебный курс по Data Science (ML, AI)
    • Учебный курс по PostgreSQL
    • Учебный курс по Apache Airflow и NiFi
    • Учебный курс по Open-source BI
    • Учебный курс по ClickHouse
    • Учебный курс по DataLens
    • Учебный курс по Loginom
    • Учебный курс по Modus BI и ETL
    • Учебный курс по Visiology
    • Учебный курс по dbt
  • Функциональные решения
    • Создание Data Lake
    • Цифровая трансформация
    • Управление по KPI
    • Финансы
    • Продажи
    • Склад
    • HR
    • Маркетинг
    • Внутренний аудит
    • Категорийный менеджмент
    • S&OP и прогнозная аналитика
    • Геоаналитика
    • Цепочки поставок (SCM)
    • AutoML
    • Process Mining
    • Сквозная аналитика
  • Компания
    • О нас
    • Руководство
    • Новости
    • Клиенты
    • Скачать
    • Контакты
    • Политика конфиденциальности
RutubeVkontakteLinkedInYouTube
ООО "Би Ай Консалт",
ИНН: 7811437757,
ОГРН: 1097847154184
199178, Россия,
Санкт-Петербург,
6-ая линия В.О., Д. 63, 4 этаж
Тел: +7 (812) 334-08-01
Тел: +7 (499) 608-13-06
E-mail: info@biconsult.ru

 

 

 

 

 

×

Пользуясь сайтом, вы соглашаетесь с использованием cookies и политикой конфиденциальности.