Cloud Cost Insights
Cost Insights — компонент Cloud / Studio, который связывает warehouse-расходы с конкретными dbt-моделями и Jobs. Это FinOps-инструмент: вместо абстрактного “Snowflake stoit 4k/month и её можно оптимизировать”.
dbt-iii: Performance at Scale — глубокий разбор стоимости materializationsВ 2026 году, после merger c Fivetran, Cost Insights также покрывает Fivetran-стоимости в Enterprise-тарифе. Этот урок — про dbt-часть.
Зачем атрибуция расходов
Без атрибуции отдел data smotрит на Snowflake bill в 30k, но точечно нет понимания, где tradeoff.
С атрибуцией:
- Модель X стоит $4k/month — анализируем, может ли быть incremental вместо table
- Модель Y стоит $1.5k и запускается 4 раза в день — может, можно реже?
- 80% расходов идёт на 10 моделей (Парето) — фокусируем optimization там
Это превращает FinOps из “уменьшить bill в принципе” в “оптимизируй конкретные 10 моделей”.
Как работает Cost Insights технически
Cloud собирает данные о warehouse-usage через две точки:
Snowflake: через QUERY_TAG
В Snowflake dbt автоматически проставляет QUERY_TAG для каждого запроса. Формат:
{
"app": "dbt",
"dbt_version": "1.10.21",
"project_name": "ecommerce",
"target_name": "prod",
"node_id": "model.ecommerce.fct_orders",
"node_name": "fct_orders",
"node_alias": "fct_orders",
"node_package_name": "ecommerce",
"node_original_file_path": "models/marts/fct_orders.sql",
"node_database": "ANALYTICS",
"node_schema": "MARTS",
"node_materialized": "table",
"node_meta": {...},
"run_started_at": "2026-05-19T06:00:00Z",
"invocation_id": "abc-123-xyz"
}
Cloud собирает QUERY_HISTORY join’ит с этим query_tag и получает per-model costs:
-- Упрощённый запрос Cost Insights
SELECT
QUERY_TAG:node_id::string AS model_id,
SUM(CREDITS_USED_CLOUD_SERVICES + CREDITS_USED) AS total_credits,
COUNT(*) AS query_count,
AVG(TOTAL_ELAPSED_TIME) AS avg_duration_ms
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE QUERY_TAG:app::string = 'dbt'
AND START_TIME >= DATEADD(DAY, -7, CURRENT_DATE)
GROUP BY 1
ORDER BY total_credits DESC;
Cloud берёт credits, умножает на cost-per-credit (стандартный rate 4.00 для most accounts) и показывает в долларах.
BigQuery: через labels
В BigQuery dbt проставляет labels:
dbt_invocation_id: abc-123
dbt_node_id: model_ecommerce_fct_orders
dbt_project: ecommerce
dbt_target: prod
Cloud join’ит INFORMATION_SCHEMA.JOBS_BY_PROJECT (или JOBS_BY_ORGANIZATION для billing-уровня) с labels:
SELECT
labels.value AS model_id,
SUM(total_slot_ms) / (1000 * 60 * 60) AS slot_hours,
SUM(total_bytes_billed) / POWER(10, 12) AS tib_billed,
COUNT(*) AS query_count
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
, UNNEST(labels) AS labels
WHERE labels.key = 'dbt_node_id'
AND creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
GROUP BY 1
ORDER BY slot_hours DESC;
BigQuery cost model — flat-rate (per-slot) или on-demand (per-TB). Cloud делит по обоим, показывает в долларах.
Другие warehouses
- Redshift / Databricks — поддержка частичная, требует Enterprise tier и специфичной конфигурации.
- Postgres — не поддерживается (warehouse-без-credits модель).
- DuckDB — не поддерживается в Cloud, потому что Cloud в принципе не работает с DuckDB.
Что показывает Cost Insights UI
Дашборд имеет несколько разделов:
Top expensive models
Ranked list моделей по cost за выбранный период (7d / 30d / 90d / custom). Колонки:
- Model name
- Total cost ($)
- Run count
- Avg cost per run ($)
- Trend (рост / стабильно / падение)
Это первое, что вы смотрите при FinOps-обзоре. Парето обычно работает: топ-10 моделей = 60-80% от всех затрат.
Cost per Job
То же самое, но в разрезе Jobs. Полезно, чтобы понять, какой job самый дорогой:
- Daily prod deploy: $1200/week
- Hourly partial refresh: $800/week
- CI jobs: $200/week
- Ad-hoc runs: $50/week
Часто оказывается, что CI jobs дороже, чем интуитивно ожидается — потому что они запускаются часто.
Trends по времени
График cost per day за выбранный период. Видны:
- Регулярные паттерны (выше будни / ниже weekends)
- Спайки (тот день, когда кто-то запустил
dbt build --full-refresh) - Тренды (рост на 10% в неделю — данные растут вместе с бизнесом)
Спайки требуют расследования: что произошло, и кто запустил, и не повторится ли.
Per-environment breakdown
Разбивка по environment:
- Production: основной cost
- CI: cost от PR-валидаций
- Staging: pre-prod testing
- Development: dev-experimentation
Часто CI cost оказывается 15-30% от prod cost — это significant и optimizable.
Как использовать для optimization: practical playbook
Cost Insights не оптимизирует за вас — даёт visibility. Что делать с visibility:
Step 1: identify top 10 expensive models
Открываете Cost Insights -> Top expensive models -> 30d period. Записываете список топ-10.
Типичный результат для команды на Snowflake:
fct_orders_history— $4200/monthmart_customer_360— $2800/monthint_events_enriched— 500/month
- Tail: 80 моделей суммарно $3000/month
Парето на лицо: top 3 = 50% бюджета.
Step 2: классифицировать причины
Для каждой топ-модели смотрите:
- Materialization: table / incremental / view?
- Run frequency: сколько раз в день/неделю?
- Rows processed: миллионы / миллиарды?
- Recent execution time trend: растёт ли?
Категории проблем:
A. Full table rebuild каждый раз. Модель materialized='table' пересчитывается полностью, хотя данные incremental. Fix: переключить на incremental со стратегией insert_overwrite или merge.
B. Too frequent. Модель mart_customer_360 строится каждый час, но downstream BI обновляется раз в день. Fix: уменьшить частоту до раз в день.
C. Wide scan. fct_orders_history сканирует всю историю каждый run, потому что нет clustering / partitioning. Fix: добавить cluster_by / partition в config, добавить incremental_predicates для prune’инга.
D. Expensive join. Модель делает CROSS JOIN или join без proper keys. Fix: переписать SQL, добавить broadcast hint, использовать microbatch.
E. Dependency: cheap upstream rebuild. Сама модель дешёвая, но триггерит rebuild дорогих upstream. Fix: использовать state:modified+1 или state:n_plus 1 чтобы избежать вертикальной перезаписи.
Step 3: prioritize wins
Сортируете кандидатов по estimated win × effort. Часто оптимальные wins:
| Optimization | Effort | Estimated win |
|---|---|---|
| Table -> incremental на 1 модели | 2-4 часа | 3k/month |
| Reduce frequency 4 -> 1 раз в день | 0.5 часа | 500/month |
| Add cluster_by на large table | 1 час | 30-50% query reduction |
| Move CI to defer + state:modified+ | 1-2 дня (если ещё не сделано) | 800/month |
| Disable source freshness в CI | 0.5 часа | 100/month |
Делаете 3-5 wins за спринт. Через месяц — Cost Insights показывает падение на 30-50%.
Step 4: monitor regression
После optimization включаете regression monitoring: алерт, если total cost растёт быстрее, чем data volume. Cloud Enterprise может сделать через AnomalyDetection в Notifications.
Типичные находки на реальных проектах
Из опыта аудитов dbt-проектов после внедрения Cost Insights:
1. “Snapshot in dev” trap. Команда забыла настроить target.name == 'prod' гард на snapshots. Каждый dev-run пересчитывал snapshots в dev. Cost: $800/month избыточно.
2. Тесты дороже моделей. На широких таблицах unique тест на колонку без index’а делает full table scan. Cost тестов был 40% от cost моделей. Fix: severity = warn для not-critical + индексы / cluster_by.
3. CI без defer и state:modified. Каждый CI job запускал dbt build всего проекта. На команде из 5 dev’ов и 200 моделей это 200.
4. Source freshness в каждом step deploy. dbt source freshness запускался первым шагом в каждом из 12 daily jobs. Cost — $300/month избыточно. Достаточно одного раза в день.
5. Full refresh on Mondays. Кто-то добавил cron-job, который запускает dbt run --full-refresh каждый понедельник “для cleanup”. Cost — $4k/month на одной модели. Удалили cron — никто не заметил.
6. Materialized view с rebuild на каждый source change. Modern data stack feature — materialized views на dbt (через materialized='materialized_view'). На Snowflake / BigQuery MV пересоздаётся automatically при изменениях. Если source с миллионами updates per day — MV перестраивается чаще чем нужно. Cost: $2k+/month.
Cost Insights в Core: что доступно
Без Cloud вы можете построить эквивалент сами:
-
Snowflake: запросы к
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORYс фильтром наQUERY_TAG. Соберите данные в отдельную dbt-модель (мета-модель). dbt-snowflake adapter автоматически проставляет query_tag, вам надо только парсить его. -
BigQuery: запросы к
INFORMATION_SCHEMA.JOBS_BY_PROJECTс join’ом по labels. Аналогично. -
dbt_artifacts package (от Brooklyn Data Co): парсит run_results.json в таблицы, дополняет warehouse-метриками. Это open-source аналог Cost Insights.
-
Elementary package: data observability tool, есть free и paid-tier (Elementary Cloud). Включает cost tracking как opt-in.
Cloud Cost Insights — это удобная UI-обёртка над тем, что технически доступно в любом warehouse. Если у вас Core и есть инженерный ресурс — построить эквивалент за 1-2 спринта вполне реально.
Что middle-инженер должен уметь
- Объяснить, как Cost Insights технически связывает warehouse query history с dbt-моделями (QUERY_TAG в Snowflake, labels в BigQuery).
- Найти топ-10 expensive models в проекте и классифицировать причины (full rebuild / too frequent / wide scan / expensive join / dependency).
- Спроектировать optimization plan: 3-5 wins с оценкой effort и payoff.
- Знать про эквивалент через open-source (dbt_artifacts package, прямые запросы к QUERY_HISTORY).
- Понимать, что typical wins — это переход table -> incremental, reduction frequency, cluster_by / partitioning, Slim CI с defer.
Попробуй сам
Если у вас Snowflake (с правами на ACCOUNT_USAGE) — запустите следующий запрос:
SELECT
PARSE_JSON(QUERY_TAG):node_name::string AS model_name,
SUM(CREDITS_USED_CLOUD_SERVICES + CREDITS_USED) AS total_credits,
COUNT(*) AS run_count,
AVG(TOTAL_ELAPSED_TIME) / 1000 AS avg_duration_seconds
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE QUERY_TAG ILIKE '%"app":"dbt"%'
AND START_TIME >= DATEADD(DAY, -7, CURRENT_DATE)
GROUP BY 1
ORDER BY total_credits DESC
LIMIT 10;
Это покажет, какие модели стоят больше всего. Для BigQuery аналогичный запрос на JOBS_BY_PROJECT.
Если warehouse — Postgres / DuckDB — Cost Insights не применим (нет credit-модели). Но можно собрать stats по execution time через pg_stat_statements (Postgres) или system tables (DuckDB) и аппроксимировать.