Зачем бизнесу аналитическое хранилище
Аналитическое хранилище — это место, где данные из десятков систем превращаются в понятные ответы. Без него у каждого отдела своя правда: менеджер видит одно в CRM, маркетолог — другое в таблицах, финансист — третье в Excel.
В какой-то момент наш стек перестал давать данные, которым можно верить. Менеджеры видели показатели только внутри CRM. Связать первое событие пользователя на сайте с покупкой и работой продаж было невозможно. Базовые группировки собирались в админке сайта, отвлекая инженеров от продукта. Данные копились везде: трекер задач, платформа клиентских событий, рассылки, Google Таблицы. Выгрузки рассинхронизировались мгновенно, формулы ломались при изменении структуры. А BI подключался напрямую к операционной БД сайта — тяжёлые аналитические запросы начали конкурировать с транзакциями пользователей. Аналитике потребовался свой изолированный контур.

Почему не стали строить «правильную» архитектуру заранее
Методологии вроде «звезды», «снежинки» или Data Vault предполагают, что метрики заранее известны, требования стабильны, а на проектирование есть время.
У нас этого времени не было. Данные требовались сразу, источники менялись быстрее документации, а какие метрики станут важными через три месяца, заранее никто не знал. Мы решили строить постепенно от реальных запросов, но с инженерной дисциплиной: допускать осознанные компромиссы и понимать, что и почему сможем переделать позже.
Целевая архитектура
Чтобы изолировать аналитику от продуктовой БД, мы выделили хранилище со слоями трансформации: данные из источников попадают в сырой слой (RAW) через ELT-процесс, проходят обработку (Staging, Intermediate, Marts) и отдаются в BI и ad-hoc SQL-запросы. За оркестрацию и сборки отвечает Dagster.

Стек инструментов
Инструмент | Назначение |
Загрузчик данных (Python-dlt) | Извлекает данные из источников, поддерживает инкрементальную загрузку и идемпотентность |
Реляционное хранилище | Единая база для всех слоёв данных |
dbt | Трансформация данных: очистка, логика, витрины с тестами и документацией |
Оркестратор (Dagster) | Управляет графом зависимостей, расписаниями и нагрузкой на серверы |
Эволюция хранилища: как росла система
Хранилище росло вместе с запросами бизнеса — каждый новый слой появлялся, когда предыдущий переставал справляться.
1. Сырой слой (RAW)
На старте всё было просто: данные из MySQL-платформы, трекера задач, CRM, Google Таблиц и CDP складывались в Postgres как есть, без общей схемы. Загрузчик переносил данные, сохраняя структуру источников. Задача была одна: свести всё в одно место и уйти от ручных выгрузок.
2. Staging
Сырые данные постоянно менялись: схемы, типы полей, названия колонок. Чтобы защитить от этого хаоса всё, что строится выше, мы выделили слой staging (stg_{источник}__{сущность}). Это копии сырых таблиц 1:1, но с приведением типов, индексами для быстрых соединений и нормализацией имён полей.
3. Intermediate (View)
Когда staging-моделей стало много, логика начала дублироваться: одни и те же связки (заказы + пользователи + тарифы) кочевали из запроса в запрос. Мы вынесли их в промежуточный слой intermediate. Разместили их во view сознательно: логика на этом уровне часто меняется, а хранить её в материализованных таблицах и пересобирать при каждом изменении слишком дорого.
4. Marts (Таблицы)
Когда главным потребителем стал BI, витрины превратились в жесткий контракт. Появились таблицы с индексами, только скалярными типами и явным описанием зерна (grain) — что именно хранит одна строка (один заказ, один день × материал и т.д.). Без зафиксированного зерна агрегаты ломаются.
Ограничения BI задали дизайн витрин: многозначные атрибуты превратили в CSV-строки и таблицы-мосты, иерархии — в root/parent/depth, JSON — в отдельные распакованные таблицы. Каждый mart получил первичный ключ, тесты качества и описание.
5. Точечная история изменений
Мы не стали строить глобальный CDC/SCD для всех сущностей — это увеличивает сложность и стоимость поддержки. История фиксируется точечно, только там, где бизнес реально задаёт вопросы «что и когда изменилось» (например, статусы задач или настройки лидов).
6. Оркестрация (Dagster)
С ростом числа моделей вручную управлять запуском стало невозможно. Задачи разделили по ресурсам: лёгкие запускаются часто в течение дня, тяжёлые — в ночные окна. Оркестратор отслеживает граф зависимостей и запускает сборку по готовности данных, а не по календарю. Параллелизм ограничили, чтобы dbt-сборки не конфликтовали при записи.
Три практических урока
Таймауты операционной базы. Инкрементуальная загрузка попадала на поиск по неиндексированным полям источника, уходила в full scan и падала по таймауту. Решили это инкрементальными курсорами с отступом, хинтами для тяжёлых запросов и выносом проблемных таблиц в отдельные запуски.
Таблицы с тяжёлыми колонками. Полная выгрузка таблицы с большими текстами каждые полчаса ломала стабильность. Мы вынесли её в отдельный процесс предфильтрации и снизили частоту обновления до раза в сутки. Свежесть уступила место стабильности.
Изменяющиеся схемы источников. Постоянные новые поля ломали жёсткую схему на входе. Мы сделали сырой слой терпимым к изменениям, а контроль качества унесли на уровень трансформаций и тестов.
Главный компромисс: сырой grain против ранних агрегатов
Подход | Плюсы | Минусы | Когда применять |
Сырой grain (каждое событие) | Абсолютная гибкость, срез по любой метрике, детализация для поиска | Больше диска, дольше пересчёт, выше стоимость | Старт проекта, требования быстро меняются, KPI не зафиксированы |
Ранние агрегаты (суммы по дням/месяцам) | Высокая скорость дашбордов, минимальный размер БД | Потеря деталей, нельзя пересчитать отчёт назад | Зрелый продукт, метрики жёстко зафиксированы, объёмы терабайты в сутки |
На старте выбор агрегатов — рискованный шаг. Если через месяц бизнес попросит срез по часам, агрегированные данные пришлось бы перестраивать с нуля. Поэтому мы выбрали максимальную детализацию (до уровня события или дня). Когда модели стабилизируются, их всегда можно безболезненно свернуть в агрегаты на верхних слоях.
Что это дало в цифрах
Замеры из аудиторского прогона на продовом DWH:
Прогон | Строк за ран | Wall | Пик памяти |
Учебная платформа, ночная полная | 2,8 млн и 2,5 млн | 149–162 с | 0,7–1,0 ГБ |
CDP, агрегаты событий | 2,2 млн | 577 с | 0,5 ГБ |
Учебная платформа, дневная инкрементальная | 22 | 91 с | 0,5 ГБ |
CRM | 3,1 тыс. | 57 с | 0,5 ГБ |
Онлайн-таблицы | 20–53 тыс. | 5–20 с | 0,5–0,6 ГБ |
Сборка dbt, 262 узла | — | 380 с | 0,3 ГБ |
Контраст между ночной и дневной загрузкой показывает смысл инкремента: ночью прокачиваются миллионы строк, днём — пары десятков. Сборка dbt при этом в основном ждёт БД (процессорного времени на весь прогон уходит 22 секунды).
По хранению самые большие таблицы — 64,8 млн строк в staging клиентских событий и 55,6 млн в витрине поверх него. Память утилизируется скромно (около 1 ГБ RSS), так как вычисления происходят внутри базы.
Технический долг и планы
Сейчас сделано не всё: модели описаны неравномерно, часть справочников обновляется вручную, а формулы метрик живут в SQL витринах, а не в отдельном семантическом слое.
Следующий шаг — переход от реактивной разработки моделей к якорным таблицам вокруг ключевых сущностей (заказы, пользователи, курсы, задачи). Каждая якорная таблица получит строгий контракт: зафиксированное зерно, первичный ключ, владельца, SLA по свежести, обязательные тесты и правила изменений. Это создаст базовый слой доверия, поверх которого витрины и отчёты смогут меняться без риска сломать логику.
Шпаргалка: когда такой подход имеет смысл
Стадия | Подход | Когда подходит |
MVP / ранняя стадия | Запросы к прод-базе, ручные таблицы | До 3–5 аналитиков, небольшие объёмы, проверка гипотез |
Рост (наш этап) | DWH + dbt + оркестратор + BI | Несколько источников, нужна изоляция нагрузки и единые термины |
Зрелая экосистема | DWH + dbt + оркестратор + семантический слой + стриминг | Сотни источников, реалтайм, ML на графе данных |
Мы не строили идеальное хранилище с первого дня. Главное — понимать текущие ограничения, фиксировать контракты моделей и честно видеть принимаемые компромиссы. Хорошее хранилище решает текущие задачи бизнеса, не блокирует развитие и растёт вместе с требованиями.
Вместо заключения
Эта статья — скорее концептуальная карта нашего пути, чем пошаговый технический мануал. За каждым коротким абзацем про слои хранилища или выбор материализации скрываются месяцы повседневной инженерной работы: сотни коммитов, отладка упавших по таймауту тасок, вечера за оптимизацией dbt-моделей и постоянный поиск баланса между свежестью данных и нагрузкой на базы.
Мы сознательно не стали зарываться в детали реализации конкретных коннекторов или расписывать узкие бизнес-кейсы — задача была передать именно логику архитектурного взросления системы и показать, как компромиссы превращаются в рабочие решения.
Если какой-то из блоков — от практики настройки Dagster до тонкостей проектирования контрактов данных — покажется вам интересным для глубокого погружения, дайте знать. С удовольствием разберём его в деталях со всеми техническими подробностями в отдельном материале.


