Learning Platform
Глоссарий Troubleshooting
Урок 02.02 · 25 мин
Средний
namingmigrationdeprecation_datesqlfluffdbt_project_evaluatormodel versions

Naming в production: migrations, deprecation, linters

NOTE

Базовая деривация имён ты знаешь по dbt-i/13: stg_<source>__<entity> для staging, int_<purpose> или int_<domain>__<entity> для intermediate, fct_<entity> / dim_<entity> / bridge_<entity> / agg_<entity> для marts. Двойное подчёркивание __ разделяет source/domain от entity. Колонки <entity>_id, <...>_at, is_<...>. Этот урок не повторяет деривацию — он про то, что происходит с этими именами через год после старта, когда нужно их менять.

Когда вы стартуете dbt-проект, naming — это академическое упражнение: «правильное имя или нет». Когда проект два года в проде, naming — это операция, которая может положить 30 BI-дашбордов за один merge, если делать её неаккуратно.

Этот урок — про три вещи, которые отличают production naming от учебного: как переименовать модель без поломки consumers, как автоматически валидировать конвенции в CI, и как написать style guide, который команда из 10+ человек реально соблюдает.

Migration pattern — три фазы переименования

Самая частая ошибка junior-аналитика — открыть PR с заголовком «Rename: customer_revenue -> fct_customer_revenue» и одним коммитом, который меняет имя файла. На маленьком проекте это работает. На проекте с 5+ downstream consumers — это диверсия.

Правильный rename — трёхфазный, растянут на 4-8 недель, и каждая фаза занимает отдельный PR.

Фаза 1: Add new (week 0)

Создаёте новую модель с правильным именем. Старая остаётся. Обе работают параллельно.

# models/marts/finance/_finance__models.yml
version: 2

models:
  - name: fct_customer_revenue
    description: >
      Revenue per customer per month. Replaces customer_revenue
      (deprecated 2026-06-01, removal 2026-08-01).
    config:
      contract:
        enforced: true
    columns:
      - name: customer_id
        data_type: varchar
        constraints:
          - type: not_null
          - type: primary_key
      - name: revenue_usd
        data_type: decimal(18,2)
        constraints:
          - type: not_null
      - name: month_start_date
        data_type: date
        constraints:
          - type: not_null

  - name: customer_revenue
    description: >
      DEPRECATED 2026-06-01. Use fct_customer_revenue instead.
      Will be removed on 2026-08-01.
    deprecation_date: 2026-08-01
    config:
      tags: ["deprecated"]

Сам SQL-файл fct_customer_revenue.sql обычно сделан так:

-- fct_customer_revenue.sql
with revenue as (
    select * from `{{ ref('int_finance__customer_revenue_calc') }}`
)
select * from revenue

А старая customer_revenue.sql переписывается на тонкий alias:

-- customer_revenue.sql (deprecated alias)
select * from `{{ ref('fct_customer_revenue') }}`

Это критично: старая модель теперь просто пробрасывает данные новой, источник истины — одна модель, не две. Иначе через месяц они расползутся.

Фаза 2: Deprecate (weeks 1-6)

Включаете deprecation_date на старой модели. Это встроенный механизм dbt: когда любой downstream ref’ит модель с deprecation_date ближе чем через 60 дней, dbt выводит warning при dbt parse:

WARNING: Model 'customer_revenue' is deprecated.
It will be removed on 2026-08-01.
References from: dashboards.exposure.executive_summary,
                 dashboards.exposure.cfo_monthly,
                 ml_pipeline.feature_store.user_features

Этот warning попадает в CI-логи каждого PR во всех downstream-проектах. Если у вас dbt Mesh — warning виден в child-проектах автоматически. Это встроенный механизм коммуникации, который не требует Slack-сообщений и email-рассылок.

В этой же фазе:

  1. Notify consumers явно. Email + Slack-message в #data-platform со списком dashboards, которые ref’ят старую модель. Список берётся из dbt ls --select +customer_revenue --output json плюс exposures.
  2. Track usage. Если ваш warehouse даёт query logs (Snowflake QUERY_HISTORY, BigQuery INFORMATION_SCHEMA.JOBS_BY_PROJECT) — мониторьте, кто реально читает старую модель. Если за 4 недели читателей не убавляется, deadline сдвигается.
  3. Не ускорять deadline. Соблазн «никто не возражает, удалим раньше» — типичный путь к падению prod. Кто-то всегда возражает, просто молча.

Фаза 3: Remove (week 8)

В deadline-день: удаляете SQL-файл старой модели, удаляете YAML-запись, делаете один PR с понятным заголовком Remove deprecated: customer_revenue. Этот PR идёт в обычное CI, не требует особых проверок — все консьюмеры уже мигрированы.

Трёхфазная миграция имени

Add new (week 0) -> deprecate с date (weeks 1-6) -> remove (week 8). Каждая фаза — отдельный PR. Между фазами consumers мигрируют, dbt warnings подсвечивают зависимости.

Week 0: AddPR 1: создаём fct_customer_revenue, переписываем customer_revenue как alias. Обе модели работают, source of truth одна — новая.
2 weeks
Week 2: DeprecatePR 2: ставим deprecation_date: 2026-08-01 на старую. dbt parse начинает выдавать warning в CI всех downstream проектов. Slack notification consumers.
6 weeks
Week 8: RemovePR 3: удаляем SQL и YAML старой модели. dbt parse чистый. Никто не сломан, потому что все ref'ы за 6 недель мигрированы.

Versions — параллельный механизм для breaking changes

dbt-i: базовые конвенции именования

deprecation_date хорош для rename. Но что если вы не переименовываете модель, а меняете её колонки? Например, fct_orders.total (без валюты) должна стать fct_orders.total_amount_usd. Тут rename файла не помогает — имя модели то же.

Здесь работают dbt model versions. Идея: одна логическая модель, две физические версии, переход постепенный.

# models/marts/finance/_finance__models.yml
models:
  - name: fct_orders
    latest_version: 2
    config:
      contract:
        enforced: true
    columns:
      - name: order_id
      - name: customer_id
      - name: total_amount_usd
        data_type: decimal(18,2)
      - name: order_date

    versions:
      - v: 2
        defined_in: fct_orders_v2

      - v: 1
        deprecation_date: 2026-08-01
        defined_in: fct_orders_v1
        columns:
          - include: '*'
            exclude: ['total_amount_usd']
          - name: total
            data_type: decimal(18,2)

Что происходит:

  • ref('fct_orders') без версии -> берёт latest_version: 2, то есть fct_orders_v2.
  • ref('fct_orders', v=1) -> старая версия, ещё работает.
  • ref('fct_orders', v=2) -> новая версия, явно.

Downstream-команды мигрируют по версиям, а не по rename:

-- старый dashboard.sql (постепенно обновляется)
select
    order_id,
    sum(total) as revenue
from `{{ ref('fct_orders', v=1) }}`

-- новый dashboard.sql после миграции
select
    order_id,
    sum(total_amount_usd) as revenue
from `{{ ref('fct_orders', v=2) }}`

Versions — это infrastructure, которая позволяет вам сделать breaking change без коммуникационного chaos. Каждая команда мигрирует на v=2 в своём темпе, dbt парсер видит, кто ещё на v=1, deprecation_date выводит на этой версии warning.

TIP

Используйте versions только для публичных моделей (marts с access: public). Versions имеют overhead: два физических файла, два набора тестов, два места для документации. На internal intermediate-моделях это не окупается. Эмпирическое правило: если модель ref’ит хотя бы один exposure (BI-дашборд, ML-пайплайн, Reverse ETL) — кандидат на versions при breaking change. Если только intermediate из вашего же домена — просто переименуйте, обновите ref’ы синхронно.

Linters в CI — sqlfluff и project_evaluator

dbt-iii: model versions в Mesh — миграция v1 -> v2 между проектами

Конвенции, которые человек должен помнить, через год нарушаются в 30% случаев. Конвенции, которые проверяет CI, нарушаются в 0%.

Два инструмента, которые покрывают 95% naming-проверок: sqlfluff для SQL-конвенций, dbt_project_evaluator для structure/naming на уровне dbt.

sqlfluff: SQL и naming rules

sqlfluff — линтер для SQL, понимающий Jinja и dbt. Конфигурация через .sqlfluff в корне проекта:

[sqlfluff]
templater = dbt
dialect = duckdb
exclude_rules = L016, L029

[sqlfluff:rules:capitalisation.keywords]
capitalisation_policy = lower

[sqlfluff:rules:references.consistent]
single_table_references = consistent

[sqlfluff:rules:references.qualification]
unqualified_references = false

[sqlfluff:templater:dbt]
project_dir = .
profiles_dir = ~/.dbt

Запуск в CI:

sqlfluff lint models/ --format github-annotation-native
sqlfluff fix models/ --force  # автофикс safe-нарушений

Для naming-конвенций моделей sqlfluff умеет проверять регулярные выражения на имена файлов. Создайте models/.sqlfluff:

[sqlfluff:rules:references.special_chars]
# имена колонок только snake_case
allow_space_in_identifier = false
quoted_identifiers_policy = none

[sqlfluff:rules:capitalisation.identifiers]
extended_capitalisation_policy = lower

dbt_project_evaluator: structure rules

Пакет от dbt Labs, который запускается как dbt-модели в вашем проекте и проверяет:

  • Все staging-модели named stg_<source>__<entity>.
  • Все mart-модели в marts/ имеют префикс fct_/dim_/bridge_/agg_.
  • Нет ref на mart из staging (direction-rule).
  • Нет staging-моделей с join на другую staging.
  • Все source-таблицы используются хотя бы одной staging-моделью.
  • Все модели имеют description в YAML.
  • Все primary keys имеют test unique + not_null.

packages.yml:

packages:
  - package: dbt-labs/dbt_project_evaluator
    version: [">=0.13.0", "<0.14.0"]

В CI:

dbt deps
dbt build --select package:dbt_project_evaluator

Если есть нарушения — модели fct_marts_or_intermediate_dependent_on_source, fct_undocumented_models и др. содержат строки с конкретными violations. CI настраивается так, чтобы dbt test --select package:dbt_project_evaluator падал на любых нарушениях.

Линтеры в CI pipeline

PR проходит через два уровня: sqlfluff (SQL и naming syntax) и dbt_project_evaluator (структура и dependencies). Оба должны зелёные, чтобы PR merge'нулся.

PR openedРазработчик пушит ветку. GitHub Actions / GitLab CI стартует pipeline.
sqlfluff lintПроверяет SQL-style, naming в колонках, capitalisation. Падает на любом нарушении. Можно auto-fix safe-правила через 'sqlfluff fix'.
project_evaluatorПроверяет: префиксы моделей, structure rules, direction-rule, undocumented models, missing PK tests. Падает с конкретным списком нарушений.
mergeОба зелёные -> reviewer аппрувит -> merge. Naming-нарушения физически не могут попасть в main.

Real anti-patterns из production

Список того, что реально встречается в проектах, доросших до middle-уровня без conventions. Каждый — реальный пример из dbt-аудитов.

final_v2_final.sql — копия final_v2.sql, потому что аналитик не доверял версии 2 и сделал «финальную финальную». Через год их три: final_v2_final, final_v2_final_real, final_v2_final_USE_THIS. Лечение — model versions с latest_version.

tmp_test.sql в models/, не в analysis/. Это staging для какой-то одноразовой проверки, забытый три месяца назад. Сейчас один dashboard ref’ит её, и аналитик боится удалить. Лечение — dbt_project_evaluator падает на моделях без description, не давая такому добраться до prod.

analytics_kpis_2024.sql — год в имени. В 2026 году будет analytics_kpis_2025.sql и analytics_kpis_2024_archive. Year-stamping — антипаттерн: модель должна быть временно-нейтральна, а год должен быть колонкой данных. Лечение — sqlfluff regex-правило ^(stg|int|fct|dim|bridge|agg)_[a-z_]+$ без цифр в имени файла.

vasily_dashboard.sql — имя аналитика в имени модели. Через 2 года Василий уйдёт, и модель никто не тронет «потому что это Васина». Лечение — model groups с owner: [email protected], никаких personal names в model paths.

fct_orders_FINAL_USE_THIS.sql — UPPERCASE-emphasis. Сигнал, что в проекте есть fct_orders_old, fct_orders_legacy, fct_orders_deprecated, и команда не уверена, какая правильная. Лечение — deprecation_date + version migration вместо «название кричит, какая правильная».

stg_stripe_charges.sql (одинарное подчёркивание вместо двойного) — точечное нарушение конвенции. Один файл из 100. Через год их 5, через два — 15. Лечение — sqlfluff regex на имя файла, падает в CI.

int_stripe_charges_dedup.sql — source в имени intermediate. Intermediate должен быть business-aware, не source-aware. stripe_charges это уже staging-слой; intermediate, который пере-дедупит charges — это staging с дополнительной логикой, должен либо быть в staging как stg_stripe__charges (если dedup универсальный), либо в intermediate как int_finance__payments_canonical (если это бизнес-логика). Лечение — code review плюс dbt_project_evaluator правило int_models_naming.

mart_orders.sql вместо fct_orders.sql — «mart» это слой, а не префикс. Каждая модель в marts/ это mart по определению, префикс должен описывать тип (fact / dim / bridge / agg), не повторять название слоя. Лечение — sqlfluff regex на имена файлов в models/marts/.

Org-wide style guide — один документ, версионированный

Все эти конвенции должны жить в одном месте: docs/dbt-style-guide.md в репозитории проекта. Не в Confluence, не в Notion, не «в голове у Васи».

Структура хорошего style guide:

  1. Naming derivation — формулы для каждого слоя, ссылки на dbt Labs guide.
  2. Anti-patterns — конкретный список того, что нельзя, с примерами.
  3. Migration patterns — как rename, как breaking change, как deprecate.
  4. CI rules — какие linters запускаются, на что они падают.
  5. Exceptions — где можно отступить от convention и почему. Эта секция критична: без неё guide становится догмой, и команда начинает его обходить.

Версионирование guide. Style guide живёт в git, изменения через PR. Каждое крупное изменение — добавление anti-pattern, изменение конвенции, исключение — проходит обычный review. В commit message записывается rationale изменения: почему добавили, какой incident это решает.

# git log docs/dbt-style-guide.md
2026-04-12 Add anti-pattern: year-stamping in model names
2026-03-08 Loosen rule on int_ prefix for one-off finance models
2026-02-15 Add deprecation_date workflow with 60-day notice
2026-01-03 Forbid CamelCase identifiers (incident SE-2024-009)

Команда из 10 человек смотрит на guide раз в неделю при review PR — и это работает только если он короткий (не больше 5 страниц), конкретный (примеры, не абстракции) и актуальный (последний commit не старше 3 месяцев).

TIP

Plant a check: раз в квартал run dbt_project_evaluator на чужом проекте (партнёрском, например, проекте соседней команды). Если ваш guide неверен или устарел — соседи это покажут «у вас не так как у нас, а лучше или хуже?». Это лучший способ держать guide живым.

Один шаблон на team / company

Когда внутри компании несколько dbt-проектов, style guide должен быть company-wide, не per-project. Иначе при ротации аналитика между командами он сталкивается с пятью разными «как мы тут пишем staging».

Технически это решается через shared dbt package с конвенциями:

# packages.yml в каждом проекте компании
packages:
  - git: "https://github.com/mycompany/dbt-style-pack.git"
    revision: "1.4.0"

В пакете лежат: .sqlfluff config, custom dbt_project_evaluator overrides, shared macros для cents-to-dollars, type-cast helpers. Style guide сам — Markdown в репозитории пакета, ссылка из README каждого проекта.

Когда команда меняет конвенцию (например, переходит на domain-based intermediate), она обновляет пакет до 1.5.0, и все проекты обновляют packages.yml. Это не мгновенно — команды мигрируют в своём темпе, — но даёт единый источник истины для всей компании.

Попробуй сам

  1. Откройте свой dbt-проект и найдите хотя бы один anti-pattern из списка выше. Запишите его.
  2. Установите dbt_project_evaluator через packages.yml, запустите dbt deps && dbt build --select package:dbt_project_evaluator. Посмотрите, сколько violations найдено.
  3. Установите sqlfluff локально, запустите sqlfluff lint models/. Посмотрите количество warnings.
  4. Выберите одну модель, которую вы хотите переименовать, и запишите trёхфазный план: даты PR1, PR2, PR3, список consumers, кому пойдёт notification.
  5. Создайте файл docs/dbt-style-guide.md в проекте, даже если он на одну страницу. Запишите туда naming-конвенции, которые уже соблюдаются. Это первая версия — дальше она будет расти.
Проверка знанийKnowledge check
У вас в проде модель fct_subscriptions с колонкой 'mrr' (без явной валюты). Финансовая команда обнаружила, что половина данных в USD, половина в EUR — баг был месяц назад, исправили в источнике, но downstream все думают, что mrr это USD. Вам нужно: (а) переименовать колонку в mrr_usd для ясности, (б) пересчитать historical данные с учётом валюты, (в) не сломать 12 BI-дашбордов и 3 ML-пайплайна. Опишите план миграции на 6-8 недель.
ОтветAnswer
План на 8 недель. Week 0 — Add new version. Через model versions добавляете fct_subscriptions v=2. В v=2 колонка переименована в mrr_usd (с явным data_type contract), и SQL-логика учитывает валютный пересчёт через staging-слой (stg_*__exchange_rates джойнится в intermediate, и mrr_usd считается как mrr_native * rate_to_usd). v=1 остаётся старая логика — historical данные не пересчитаны, всё в неоднозначной валюте, в YAML описание прямо говорит 'mrr column has mixed-currency bug, see incident SE-2026-014, fixed in v=2'. YAML: latest_version: 2, v=1 deprecation_date: 2026-08-01 (через 8 недель). Создаёте exposure-listing через 'dbt ls --select +fct_subscriptions --resource-type exposure' — получаете список из 12 dashboards и 3 ML-пайплайнов, сохраняете в файл. Week 1 — Notification. Slack-message в каждый channel-owner exposures: 'Migration plan, deadline 2026-08-01, новая колонка mrr_usd, старая mrr deprecated, в v=2 валютный пересчёт корректный'. CI всех downstream проектов начинает выдавать warning при parse, потому что deprecation_date < 60 дней (отсчёт начнётся через 4 недели). Weeks 2-6 — Active migration. Каждую неделю проверяете query logs warehouse: какие BI-инструменты ещё читают v=1. Если кто-то не двинулся — escalate. Параллельно владелец fct_subscriptions помогает migration — пишет PR в BI-репозиторий (если monorepo) или открывает issue в чужом repo. Каждый смигрированный consumer фиксируется в issue tracking как done. Week 7 — Audit. За неделю до deadline берёте dbt-cloud usage analytics или warehouse query_history. Если 0 ссылок на v=1 за последние 7 дней — готовы удалять. Если есть один забытый dashboard — двигаете deadline на 2 недели, не делаете тихий remove. Week 8 — Remove. PR с удалением SQL-файла v=1 и YAML-записи. CI зелёный (никто не ref'ит). Merge, prod без падений. Что критично не делать: (1) не пересчитывать v=1 — это переписывание истории, downstream-метрики поедут, чарты в дашбордах изменятся задним числом, доверие к данным упадёт. (2) не удалять старую mrr-колонку из v=2 как 'для совместимости' — это путь к перманентной двусмысленности. (3) не сдвигать deadline без явного incident-постмортема (если у вас deadline всегда сдвигается, deprecation теряет силу). Альтернативный путь без model versions — три фазы: add new column mrr_usd рядом со старой mrr в той же fct_subscriptions, deprecate старую через description, через 8 недель удалить. Проще, но менее надёжно: contract enforcement на mixed-state модели слабее, dbt parse не выдаёт warning per-column.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Почему dbt Labs рекомендует двойное подчёркивание в naming staging-моделей: stg_stripe__charges вместо stg_stripe_charges?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4