Dev vs Prod: паттерн переключения через target.name
Финальный урок модуля — практический. Мы поставим вместе всё, что изучили: var, env_var, target, {% if %}, и научимся делать модели, которые корректно работают в dev (быстро, на маленьком объёме) и в prod (полная история, корректные схемы, full performance).
Это тот самый паттерн, который вам нужен для запуска dbt-проекта в production. Без него — либо разработка медленная (на полном объёме), либо есть риск что-то сломать в prod при экспериментах.
Зачем разделять dev и prod
Главный механизм: target
target в dbt — это именованная конфигурация подключения к warehouse. Объявляется в profiles.yml. Один профиль может иметь несколько targets (dev, ci, prod), переключение через --target.
# ~/.dbt/profiles.yml
jaffle_shop:
target: dev # default target, если --target не передан
outputs:
dev:
type: duckdb
path: "{{ env_var('DBT_DUCKDB_PATH', 'dev.duckdb') }}"
schema: "dbt_{{ env_var('USER', 'dev') }}"
threads: 4
ci:
type: duckdb
path: "/tmp/ci_{{ env_var('CI_BUILD_ID', 'local') }}.duckdb"
schema: dbt_ci
threads: 8
prod:
type: postgres
host: "{{ env_var('PROD_PG_HOST') }}"
user: "{{ env_var('PROD_PG_USER') }}"
password: "{{ env_var('PROD_PG_PASS') }}"
port: "{{ env_var('PROD_PG_PORT', '5432') | int }}"
dbname: analytics
schema: dbt_prod
threads: 8
Запуск:
# Dev (default)
$ dbt run
# CI
$ dbt run --target ci
# Prod
$ dbt run --target prod
Каждый target — изолированная конфигурация. Switch — одной флагой.
target.X в Jinja
Из любого шаблона можно прочитать атрибуты текущего target:
{{ target.name }} -- 'dev', 'ci', 'prod'
{{ target.type }} -- 'duckdb', 'postgres', ...
{{ target.schema }} -- 'dbt_alice', 'dbt_ci', 'dbt_prod'
{{ target.database }} -- название БД (для DuckDB — путь к файлу)
{{ target.threads }} -- 4, 8, ...
{{ target.user }} -- имя пользователя (Postgres)
target.name — это главное условие переключения. С его помощью модели меняют поведение.
Паттерн 1: фильтр в dev на N дней
Самый частый. В prod — полная история, в dev — последние 7 дней.
-- models/staging/stg_jaffle__orders.sql
select *
from {{ source('jaffle', 'raw_orders') }}
where 1=1
{% if target.name == 'dev' %}
and order_date >= current_date - interval 7 day
{% endif %}
Compile в dev:
select *
from "jaffle_shop"."main"."raw_orders"
where 1=1
and order_date >= current_date - interval 7 day
Compile в prod:
select *
from "analytics"."raw"."raw_orders"
where 1=1
Время сборки моделей в dev падает с минут до секунд.
Вариация: переменная lookback
Часто dev на 7 дней мало (нужно хотя бы 30 для теста по месячной агрегации). Сделаем параметрическим:
{% set is_dev = target.name == 'dev' %}
{% set lookback_days = var('lookback_days', 7 if is_dev else none) %}
select *
from {{ source('jaffle', 'raw_orders') }}
{% if lookback_days %}
where order_date >= current_date - interval '{{ lookback_days }}' day
{% endif %}
В dev — WHERE с 7 днями. В prod — нет WHERE. Можно override:
# Dev с 30 днями
$ dbt run --target dev --vars '{lookback_days: 30}'
# Prod backfill с явным WHERE
$ dbt run --target prod --vars '{lookback_days: 365}'
Паттерн 2: отдельные схемы для каждого dev-разработчика
В команде из 5 разработчиков никто не хочет работать в общей dbt_dev схеме — конфликтуют таблицы, ломают друг другу runs.
Решение: схема включает имя пользователя.
# profiles.yml
jaffle_shop:
target: dev
outputs:
dev:
type: duckdb
path: dev.duckdb
schema: "dbt_{{ env_var('USER', 'dev') }}"
В dbt_project.yml:
models:
jaffle_shop:
+schema: '' # без дополнительного префикса
Что произойдёт:
- Alice запускает
dbt run-> схемаdbt_alice. - Bob запускает
dbt run-> схемаdbt_bob. - В прod target -> схема
dbt_prod.
Каждый работает в своей изолированной схеме. Нет конфликтов.
custom schema через generate_schema_name
dbt позволяет переопределить логику генерации имён схем через macro generate_schema_name:
-- macros/generate_schema_name.sql
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- set default_schema = target.schema -%}
{%- if target.name == 'prod' -%}
{# В prod: target.schema + custom (если указано в config) #}
{%- if custom_schema_name is none -%}
{{ default_schema }}
{%- else -%}
{{ custom_schema_name | trim }}
{%- endif -%}
{%- else -%}
{# В dev: всё в одну dev-схему, игнорируем custom #}
{{ default_schema }}
{%- endif -%}
{%- endmacro %}
Это самый частый pattern overrides в реальных проектах. Объяснение:
- В prod: модель с
config(schema='marketing')попадёт вmarketing(илиdbt_prod_marketing— зависит от логики). - В dev: ВСЕ модели попадают в
dbt_alice, custom schema игнорируется.
Без этого в dev получите 10 разных схем (по числу custom-конфигов), и нужно помнить, что куда смотреть.
Паттерн 3: разное materialization для dev/prod
Table-материализация в dev замедляет итерации (после каждого dbt run пересоздание таблицы).
{{ config(
materialized=('view' if target.name == 'dev' else 'table')
) }}
select * from ...
В dev — view (быстро). В prod — table (быстрый downstream запрос).
Это работает для всех materializations. Например, в dev incremental может быть table (с --full-refresh каждый раз), в prod — incremental. Зависит от кейса.
Паттерн 4: явный target check в hooks
В dbt_project.yml:
on-run-end:
- "{% if target.name == 'prod' %}grant select on all tables in schema {{ target.schema }} to looker_role{% endif %}"
Hook выполнится только в prod. В dev grants не нужны (вы один работаете в своей схеме).
Полный пример: production-ready конфиг проекта
# dbt_project.yml
name: 'jaffle_shop'
version: '1.0.0'
profile: 'jaffle_shop'
vars:
# Default — для prod
lookback_days: 365
include_pii: false
enable_experimental_models: false
models:
jaffle_shop:
staging:
+materialized: view
intermediate:
+materialized: ephemeral
marts:
+materialized: table
# Конфигурация для dev
+on-schema-change: append_new_columns
on-run-start:
- "{% do log('Running in ' ~ target.name ~ ' target', info=true) %}"
# profiles.yml
jaffle_shop:
target: dev
outputs:
dev:
type: duckdb
path: "{{ env_var('DBT_DUCKDB_PATH', 'dev.duckdb') }}"
schema: "dbt_{{ env_var('USER', 'dev') }}"
threads: 4
prod:
type: postgres
host: "{{ env_var('PROD_PG_HOST') }}"
user: "{{ env_var('PROD_PG_USER') }}"
password: "{{ env_var('PROD_PG_PASS') }}"
port: "{{ env_var('PROD_PG_PORT', '5432') | int }}"
dbname: analytics
schema: dbt_prod
threads: 8
-- models/staging/stg_jaffle__orders.sql
{{ config(
materialized='view'
) }}
select *
from {{ source('jaffle', 'raw_orders') }}
{% if target.name == 'dev' %}
where order_date >= current_date - interval '{{ var("lookback_days", 7) }}' day
{% else %}
where order_date >= current_date - interval '{{ var("lookback_days", 365) }}' day
{% endif %}
-- macros/generate_schema_name.sql
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- set default_schema = target.schema -%}
{%- if target.name == 'prod' and custom_schema_name is not none -%}
{{ custom_schema_name | trim }}
{%- else -%}
{{ default_schema }}
{%- endif -%}
{%- endmacro %}
Запуски:
# Разработка: 7 дней, схема dbt_alice
$ export USER=alice
$ dbt run --target dev
# Тот же код, но 30 дней:
$ dbt run --target dev --vars '{lookback_days: 30}'
# CI: отдельная схема, 90 дней
$ DBT_TARGET=ci dbt run --vars '{lookback_days: 90}'
# Prod: полная история, схема dbt_prod
$ dbt run --target prod
Дополнительные паттерны
Заводить только нужные модели в dev
Через --select и tags:
# В _models.yml
models:
- name: heavy_aggregation_model
config:
tags: ['heavy']
# Dev: всё, кроме heavy
$ dbt run --target dev --exclude tag:heavy
# Prod: всё
$ dbt run --target prod
dev-only test severity
В dev тесты как warning, в prod — error:
columns:
- name: total_amount
data_tests:
- not_null:
config:
severity: "{{ 'warn' if target.name == 'dev' else 'error' }}"
В dev разработчик может пропустить failing test (warn) и продолжить экспериментировать. В prod — error блокирует.
Отключение моделей в dev/prod
{{ config(
enabled=(target.name == 'prod')
) }}
-- Эта модель собирается ТОЛЬКО в prod
Полезно для прод-only моделей: например, экспорт в external storage (parquet), который dev не нужен.
Распространённые ошибки
1. Забыли default для prod в {% if target.name == 'dev' %}.
{# Только dev обрабатывает, prod падает на parse #}
{% if target.name == 'dev' %}
where order_date >= current_date - 7
{% endif %}
{# Если в prod не нужен фильтр — OK, ничего не выводится. Но если нужен — обязательно else #}
2. Хардкод схемы в SQL.
{# Bad: хардкод #}
from analytics.raw_orders
{# Good: через source/ref #}
from {{ source('jaffle', 'raw_orders') }}
Хардкод сломается, когда схема меняется между dev и prod.
3. target.name in [...] с одним target.
{% if target.name in ['dev', 'ci'] %}
-- одинаковая логика для двух targets
{% endif %}
Чище, чем два {% if %} блока.
4. Запутаться, где target.schema, где var(‘schema_prefix’).
target.schema — из profiles.yml. var() — из dbt_project.yml. Не путать.
Попробуй сам
Соберите production-ready setup своего проекта:
- profiles.yml с двумя targets (dev, prod). Dev — DuckDB локально, prod — DuckDB на другом пути (имитация prod).
- dev схема включает
$USERчерез env_var. - Staging-модель фильтрует на 7 дней в dev и не фильтрует в prod.
- Mart-модель в dev —
view, в prod —table. - Hook в
on-run-start, печатающийtarget.name.
Запустите оба:
$ dbt run --target dev
$ dbt run --target prod
Проверьте через DuckDB CLI, что в dbt_$USER схеме — 7 дней данных и views, а в dbt_prod — полная история и tables.
Итоги
- target — именованная конфигурация подключения. Объявляется в
profiles.yml, переключается через--targetили env varDBT_TARGET. - target.name, target.schema, target.type, target.threads — атрибуты текущего target. Используются в Jinja для условной логики.
- Главные паттерны dev vs prod:
- Filter в dev на N дней:
{% if target.name == 'dev' %} where date >= 7 days ago {% endif %}. - Per-developer схемы:
schema: "dbt_{{ env_var('USER', 'dev') }}". - Override generate_schema_name: в dev игнорировать custom schema, в prod — применять.
- Materialization по target: dev — view (быстро), prod — table (производительность downstream).
- Filter в dev на N дней:
- Дополнительные: dev-only tags, conditional hooks, severity по target.
- Антипаттерны: хардкод схем, забыть else, путаница var vs target.schema.
Этот паттерн — основа production-ready dbt-проекта. С ним вы развиваете без боли в dev, безопасно деплоите в prod. Курс dbt I завершён. Дальше — dbt II (middle): продвинутые темы, performance, complex incremental, CI/CD, и реальный production opsec.
target.name в production: conditional logic per environment на реальных кейсах GitHub Flow — workflow, в котором dbt-модели проходят dev -> prod