dbt: структура проекта и слои на собеседовании Data Engineer
user_id, status) из таблицы orders. Какой запрос верный?Содержание:
Почему структуру dbt спрашивают
dbt позволяет написать одну гигантскую модель с десятью джойнами и агрегациями — и она даже заработает. Проблема в том, что через полгода в таком проекте невозможно ничего найти, переиспользовать или протестировать. Поэтому на собесе Data Engineer вопрос про структуру проекта проверяет не знание dbt как инструмента, а инженерную зрелость: умеете ли вы разбивать трансформацию на слои, где у каждого своя ответственность.
Стандарт, который продвигает dbt Labs и который ждут почти на любом собесе, — три слоя: staging, intermediate и marts. Данные текут снизу вверх: сырьё чистится, обогащается и превращается в витрины для бизнеса. Разберём каждый слой и зачем он нужен.
Слоёная структура
Типичная раскладка каталога models/ в dbt-проекте:
models/
staging/ — лёгкая очистка сырья, 1:1 с источниками
intermediate/ — переиспользуемые джойны и логика
marts/ — витрины для бизнесаИдея в разделении ответственности: staging приводит источники к порядку, intermediate готовит переиспользуемые заготовки, marts отдаёт готовые витрины в BI и ML. Каждый слой опирается только на предыдущий, поэтому DAG остаётся читаемым, а изменение источника не приходится править в двадцати местах.
Staging-модели
Именование: stg_<source>__<table>.sql (двойное подчёркивание отделяет источник от таблицы).
Назначение — лёгкая очистка данных прямо из источника, без бизнес-логики. Здесь делают ровно три вещи:
- Переименовывают колонки к единому стилю (например, всё в
snake_case,id→user_id). - Приводят типы (
CASTстроки к дате, текста к числу). - Ставят лёгкие фильтры вроде отсева удалённых записей (
deleted = false).
SELECT
id AS user_id,
email,
CAST(created_at AS TIMESTAMP) AS created_at
FROM {{ source('app', 'users') }}
WHERE deleted = FALSEПравило: одна staging-модель на одну таблицу источника, никаких джойнов и агрегаций. Staging — это тонкий слой-адаптер между сырьём и остальным проектом; обычно его материализуют как view, чтобы не хранить лишние копии. Как только источник переименовал колонку — правку делаем в одном месте, и весь проект дальше её не замечает.
Intermediate
Именование: int_<purpose>.sql.
Назначение — переиспользуемые джойны и промежуточная логика, которую не хочется отдавать напрямую в BI. Это «рабочие заготовки» между staging и marts.
-- int_user_with_orders.sql
SELECT
u.user_id,
u.email,
COUNT(o.id) AS orders_count
FROM {{ ref('stg_app__users') }} u
LEFT JOIN {{ ref('stg_app__orders') }} o ON u.user_id = o.user_id
GROUP BY 1, 2Смысл слоя — DRY. Если один и тот же джойн «пользователи + заказы» нужен в трёх разных витринах, его выносят в intermediate и переиспользуют через ref(), а не копируют трижды. Intermediate обычно не показывают аналитикам напрямую и часто материализуют как ephemeral или view — это внутренняя кухня трансформации.
Marts
Именование: fct_<grain>.sql, dim_<entity>.sql или mart_<subject>__<grain>.sql.
Назначение — витрины для бизнеса. Именно на marts смотрят дашборды, отчёты и ML-фичи. Здесь собирается финальная бизнес-логика: агрегации, метрики, готовые к употреблению таблицы фактов и измерений.
-- fct_orders_daily.sql
SELECT
DATE,
country,
SUM(revenue) AS revenue,
COUNT(*) AS orders_count
FROM {{ ref('int_orders_enriched') }}
GROUP BY 1, 2Marts почти всегда материализуют как таблицы (а не view): по ним постоянно ходят дашборды, и пересчитывать логику на каждый запрос дорого. Организуют marts обычно по бизнес-доменам — финансы, маркетинг, продукт, — чтобы каждая команда работала со своим набором витрин.
Соглашения по именованию
Предсказуемые имена — это не эстетика, а навигация: по названию модели сразу понятно, что это и на каком она слое.
sources: raw_app.users
staging: stg_app__users
intermediate: int_users_enriched
marts:
- dim_* — измерения (сущности: пользователи, товары)
- fct_* — факты (события: заказы, платежи)
- mart_* — широкие витрины под конкретный отчётЕдиная схема именования помогает и людям, и инструментам: новый человек в команде за минуту понимает граф зависимостей, а автогенерация документации и поиск по проекту работают предсказуемо. На собесе умение внятно объяснить эту схему ценится выше, чем знание экзотических фич dbt.
Как это спрашивают на собесе
Как звучат вопросы вживую и что за ними стоит:
- «Опишите структуру своего dbt-проекта.» Ждут три слоя и внятное объяснение ответственности каждого, а не пересказ документации.
- «Куда положить джойн, который нужен в трёх витринах?» В intermediate — это прямая проверка на понимание DRY.
- «Чем fct отличается от dim?» fct — таблица фактов (события, метрики, много строк), dim — измерение (сущности, атрибуты). Классика Кимбалла, её ждут почти всегда.
- «Как материализуете слои?» staging и intermediate — обычно view/ephemeral, marts — table. Проверяют, понимаете ли вы trade-off между стоимостью хранения и скоростью чтения.
- «Зачем вообще staging, если можно джойнить из source напрямую?» Чтобы изолировать проект от изменений источника и не переписывать логику по всему проекту при переименовании колонки.
Частые ошибки
- Джойны и агрегации в staging. Staging должен быть 1:1 с источником. Как только туда заезжает бизнес-логика, слой перестаёт быть переиспользуемым адаптером.
- Отсутствие intermediate. Если из staging сразу лепить marts, одинаковые джойны расползаются по витринам копипастом. Первое же изменение логики придётся вносить в пяти местах.
- Ссылки на source в обход ref(). Прямой доступ к сырью из marts ломает граф зависимостей и lineage — dbt перестаёт видеть порядок построения.
- Хаотичное именование.
orders_final_v2,test_new— и через месяц проект нечитаем. Единая схемаstg_/int_/fct_/dim_экономит часы навигации. - Всё материализуется как table. Тяжёлые таблицы на каждом слое раздувают хранилище и время прогона. staging и intermediate почти всегда должны быть view.
Связанные темы
- dbt на собесе DE
- dbt sources и macros для DE
- DWH stages для DE
- Data mart design для DE
- Подготовка к собесу Data Engineer
FAQ
Зачем разбивать проект на три слоя, а не писать одну модель?
Слои разделяют ответственность: staging чистит источник, intermediate переиспользует логику, marts отдаёт витрины бизнесу. Это делает проект читаемым, тестируемым и устойчивым к изменениям — при переименовании колонки в источнике правка нужна в одном месте, а не по всему проекту.
Чем отличаются fct- и dim-таблицы?
fct (fact) — таблица фактов: события и метрики, обычно много строк (заказы, платежи, клики). dim (dimension) — измерение: сущности и их атрибуты (пользователи, товары, даты). Это терминология размерного моделирования Кимбалла, marts традиционно строят именно в ней.
Как материализовать слои?
staging и intermediate чаще всего делают view или ephemeral — они лёгкие и не хранят данные лишний раз. marts материализуют как table, потому что по витринам постоянно ходят дашборды и пересчитывать логику на каждый запрос дорого. Тяжёлые marts нередко делают incremental.
Где ставить тесты на качество данных?
И на staging (поймать проблемы источника раньше), и на marts (гарантировать контракт для BI). На staging обычно unique/not_null/relationships, на marts — проверки бизнес-логики и корректности агрегаций.
Это официальная информация?
Нет. Статья основана на best practices dbt Labs и опыте кандидатов. Конкретные соглашения и раскладка слоёв зависят от команды и проекта.
Тренируйте Data Engineering — откройте тренажёр с 1500+ вопросами для собесов.