БСН 22 09
Как мы строили аналитику, когда нельзя было остановиться и все спроектировать

Как мы строили аналитику, когда нельзя было остановиться и все спроектировать

В статье — весь путь хранилища: от «просто складываем все в Postgres» до слоистой архитектуры с dbt и оркестратором. Рассказываем, почему не стали выбирать методологию заранее, что дали контракты моделей и явное зерно, почему витрины принимают форму своего потребителя и чему нас научили три продакшн-грабли.

2,1 тыс. просмотров89 открытий

Зачем бизнесу аналитическое хранилище

Аналитическое хранилище — это место, где данные из десятков систем превращаются в понятные ответы. Без него у каждого отдела своя правда: менеджер видит одно в 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 до тонкостей проектирования контрактов данных — покажется вам интересным для глубокого погружения, дайте знать. С удовольствием разберём его в деталях со всеми техническими подробностями в отдельном материале.

Информации об авторе

Этот пост написан блогером Трибуны. Вы тоже можете начать писать: сделать это можно .

Начать дискуссию
ГлавнаяПодписка