Маркетинговые команды больше не используют готовые отчёты из Google Analytics — они работают с собственными data pipeline'ами, где каждое правило написано ими. В 2026 году современный маркетинговый data stack состоит из трёх слоёв: raw source'ы, modeling layer и semantic layer. Эта статья показывает, как построить эти три слоя с помощью dbt + BigQuery, какие типичные ошибки возникают на каждом этапе и как создать устойчивую архитектуру для production.
Source mapping: загрузить данные в BigQuery недостаточно
Вы загрузили GA4, Meta Ads и события sGTM в BigQuery — но это только начало. Source mapping означает преобразование raw таблиц в значимые контракты данных. В dbt определения source'ов живут в .yml файлах:
sources:
- name: raw_ga4
database: roibase-prod
schema: analytics_123456789
tables:
- name: events_*
identifier: events_*
loaded_at_field: event_timestamp
freshness:
warn_after: {count: 12, period: hour}
Это определение выполняет три функции: (1) Data lineage — какая модель использует какую raw таблицу, (2) Freshness check — если последнее событие старше 12 часов, выдаётся предупреждение, (3) Contract — если отсутствует колонка event_timestamp, build падает.
Самая частая ошибка: использование raw schema'ы как есть. Писать SQL с nested array'ем GA4 event_params без flatten'инга означает, что каждый запрос займёт 200+ строк. Logic unnest должна жить в одном месте на этапе source mapping:
-- models/staging/stg_ga4_events.sql
with source as (
select * from {{ source('raw_ga4', 'events_*') }}
),
flattened as (
select
event_date,
event_timestamp,
user_pseudo_id,
(select value.string_value from unnest(event_params) where key = 'session_id') as session_id,
(select value.int_value from unnest(event_params) where key = 'ga_session_number') as session_number
from source
)
select * from flattened
Теперь эта модель вызывается downstream'ом через ref('stg_ga4_events') — синтаксис raw event_params изолирован upstream. Freshness check запускается ежедневно, изменения schema автоматически вызывают ошибки.
Modeling layer: определите метрику один раз, используйте сто раз
После staging layer идёт modeling layer. Здесь разделяются intermediate модели (business logic) и mart модели (aggregation). В маркетинговом data stack самая критичная модель — это session → transaction join:
-- models/marts/mrt_session_metrics.sql
with sessions as (
select * from {{ ref('int_sessions') }}
),
transactions as (
select * from {{ ref('int_transactions') }}
),
joined as (
select
s.session_id,
s.session_date,
s.traffic_source,
s.medium,
s.campaign,
t.transaction_id,
t.revenue,
t.transaction_timestamp
from sessions s
left join transactions t
on s.session_id = t.session_id
)
select
session_date,
traffic_source,
medium,
campaign,
count(distinct session_id) as sessions,
count(distinct transaction_id) as transactions,
sum(revenue) as total_revenue,
safe_divide(count(distinct transaction_id), count(distinct session_id)) as conversion_rate
from joined
group by 1, 2, 3, 4
Эта модель запускается ежедневно в 03:00 (dbt Cloud scheduler), Looker Studio подключается напрямую к этой таблице. Когда требуется изменение, вы меняете SQL в одном месте — все dashboard'ы обновляются автоматически.
Важная деталь: использование safe_divide — если sessions = 0, он не вызывает ошибку деления на ноль, а возвращает null. В production pipeline'е обработка исключений происходит на этом уровне.
dbt tests: автоматическая проверка качества данных
При определении метрик в modeling layer одновременно пишут тесты:
# models/marts/schema.yml
models:
- name: mrt_session_metrics
columns:
- name: session_date
tests:
- not_null
- name: sessions
tests:
- not_null
- dbt_utils.expression_is_true:
expression: ">= 0"
- name: conversion_rate
tests:
- dbt_utils.expression_is_true:
expression: "<= 1"
Команда dbt test запускает эти правила. Если conversion rate > 1 (в SQL ошибка), build падает и в Slack уходит alert. Вместо ручной QA — автоматизированная проверка качества данных. На этом основании строится весь остальной data stack.
Semantic layer: определите метрику, а не запрос
С dbt v1.6+ semantic layer вышел из beta. Теперь метрика определяется не в SQL, а в .yml файле:
# models/semantic/metrics.yml
metrics:
- name: total_revenue
label: Total Revenue
model: ref('mrt_session_metrics')
type: sum
sql: total_revenue
timestamp: session_date
time_grains: [day, week, month]
- name: roas
label: Return on Ad Spend
type: ratio
numerator: total_revenue
denominator: total_ad_spend
Это определение используется в трёх местах: (1) Looker Studio, (2) Slack bot через dbt Cloud discovery API для запроса метрик, (3) Airflow DAG в качестве входа для downstream ML pipeline.
Преимущество: метрика потребляется без написания SQL. Marketing analyst просто пишет "Show me ROAS by campaign, last 7 days", и semantic layer автоматически компилирует запрос. SQL logic находится в модели, определение метрики — в semantic layer. Они независимы, изменения изолированы.
Внимание: semantic layer всё ещё молод — не все BI инструменты имеют native интеграцию. В production stack Roibase используется гибридный подход: критические метрики в semantic layer, custom анализ через SQL exposure'ы.
Exposures: документируйте downstream зависимости
Exposures показывают, где за пределами dbt используется модель:
# models/exposures.yml
exposures:
- name: looker_studio_performance_dashboard
type: dashboard
url: https://lookerstudio.google.com/...
depends_on:
- ref('mrt_session_metrics')
- ref('mrt_campaign_performance')
owner:
name: Marketing Analytics Team
email: [email protected]
Это определение визуализируется в dbt docs — видно, какой dashboard зависит от какой модели, на кого нужно подписать изменения schema. В production, когда нужно сделать breaking change, команда dbt run --select +mrt_session_metrics+ показывает все downstream зависимости.
Реальный сценарий: в GA4 key page_location в event_params переименован на page_url. Благодаря exposure'ам мы нашли 3 затронутых dashboard'а и 1 Airflow DAG, миграция заняла 2 часа. Без exposure'ов dashboard'ы молча сломались бы, об ошибке узнали бы от users.
Incremental models: не делайте полный rebuild 2TB данных ежедневно
В маркетинговых данных daily partition'ы достигают терабайтов. Каждый dbt run с полным refresh неприемлем — стоимость BigQuery и время выполнения будут недопустимы. Используйте incremental модель:
-- models/marts/mrt_user_journey.sql
{{
config(
materialized='incremental',
partition_by={'field': 'event_date', 'data_type': 'date'},
cluster_by=['user_pseudo_id', 'traffic_source'],
incremental_strategy='insert_overwrite'
)
}}
select
event_date,
user_pseudo_id,
traffic_source,
-- ...
from {{ ref('stg_ga4_events') }}
{% if is_incremental() %}
where event_date >= date_sub(current_date(), interval 3 day)
{% endif %}
Эта конфигурация делает три вещи: (1) создаёт partition в BigQuery — добавляет новые дни, не трогая старые, (2) cluster_by улучшает производительность запросов, (3) стратегия insert_overwrite удаляет последние 3 дня и пересчитывает (для late arriving data).
Разница в стоимости: 365 дней данных, full refresh = 2.5 TB scan ($12.5), incremental = 3 GB scan ($0.015). Для ежедневного pipeline'а годовая разница ~$4500 vs ~$5. Поэтому incremental модель — основа production stack'а.
Связать data stack с механизмом принятия решений
dbt + BigQuery создают инфраструктуру, но реальная ценность проявляется в маркетинговых решениях. Типичный сценарий — метрики из semantic layer в Slack bot:
- Marketing manager пишет в Slack
/metric roas last_30_days campaign=brand - Slack app вызывает dbt Cloud semantic layer API
- API запрашивает таблицу
mrt_session_metrics, вычисляет ROAS - Результат возвращается в Slack: "Brand campaign ROAS: 4.2x"
Для этого потока нужны semantic layer + custom Python middleware. В production stack Roibase Airflow DAG'ей делает ежедневный snapshot semantic layer, Looker Studio и internal app'ы используют этот snapshot — без проблем с API rate limit.
Альтернативный подход: в сервисе First-Party Data & Ölçüm Architecture используется гибридный stack — dbt semantic layer + Cube.js. Cube.js добавляет слой кеширования, улучшает BI производительность. Выбор зависит от объёма данных и паттерна запросов.
Production checklist: перед deployment dbt stack'а
dbt работает локально — перед production нужны эти проверки:
- CI/CD: dbt Cloud или GitHub Actions должны запускать
dbt build --select state:modified+на каждый commit - Freshness monitoring: для критичных source'ов определите
warn_afterиerror_after - Alerting: webhook dbt Cloud → Slack интеграция; при fail build'а команда узнает в течение 5 минут
- Documentation:
dbt docs generateзапускается автоматически, artifact'ы push'ятся в S3/GCS - Cost monitoring: BigQuery slot reservation или on-demand alert — установите threshold $500/день на неожиданные spike'ы
- Backup strategy: snapshot таблица критичных моделей — если обновление сломало данные, сможете откатиться
Самое критичное правило: в production нет manual dbt run. Всё выполняется только через scheduler (dbt Cloud, Airflow, Prefect). Manual run нарушает data lineage, при ошибке невозможно найти root cause.
dbt + BigQuery — позвоночник современного маркетингового data stack'а. Source mapping связывает raw данные с контрактом, modeling layer централизует определение метрики, semantic layer позволяет потреблять метрики без SQL. В production incremental модели и test coverage делают pipeline'ы устойчивыми. Следующий слой — связать данные с real-time activation'ом: CDP, audience sync, incrementality measurement. Но это — отдельная история data stack'а.