Troubleshooting — dbt I
База знаний типичных ошибок курса dbt I.
Причина
Забыли запустить `dbt deps` после изменения packages.yml или после клонирования репозитория.
Решение
- Запустите
dbt deps. Это поставит/обновит пакеты из packages.yml в dbt_packages/. Если ошибка осталась — проверьте, что имя macro действительно есть в установленном пакете (например,dbt_utils.star, а неstar). Имя пакета должно быть префиксом: dbt_utils.star, dbt_expectations.expect_column_values_to_be_between.
Причина
В ref('Y') опечатка, или модель Y была удалена, переименована, или лежит в неподключенной директории (model-paths).
Решение
- Проверьте имя модели:
dbt list --select Y. Если пусто — модель не парсится. Проверьте: (1) файлY.sqlсуществует в model-paths, (2) имя файла без расширения совпадает с именем в ref(), (3) нет дублирующихся имён моделей в разных подпапках (dbt требует уникальности).
Причина
Модель ссылается на source через хардкод имени (`from raw_jaffle.orders`) вместо source(). Или target.schema указывает не на ту схему, где лежит raw-таблица.
Решение
- Замените хардкод на
{{ source('jaffle_raw', 'orders') }}и опишите source в YAML. Это даёт portable код и DAG-связь. Если source действительно описан — проверьтеtarget.schemaв profiles.yml иdatabase/schemaв самой source-декларации.
Причина
Default `generate_schema_name` для не-default target суффиксует custom schema: `<target.schema>_<custom_schema>`. В prod target — без суффикса. Это by-design для dev/prod изоляции.
Решение
- Если хочется одинаковое поведение во всех target — override макрос. Создайте
macros/get_custom_schema.sqlс{% macro generate_schema_name(custom_schema_name, node) %}{{ custom_schema_name or target.schema }}{% endmacro %}. Но обычно лучше принять дефолт и иметь dev_marketing для безопасности.
Причина
Колонка реально содержит дубли. Либо в source — но это нарушение контракта; либо в staging после JOIN — JOIN сменил гранулярность.
Решение
- Запустите
dbt test --select test_name --store-failuresи посмотрите на конкретные значения вdbt_test__audit.test_name. Если дубли легитимны (один order_id с двумя статусами при snapshot-логике) — измените unique на (order_id, snapshot_date) или на surrogate key. Если нет — добавьте deduplication в staging через ROW_NUMBER() OVER (PARTITION BY ...).
Причина
Селектор без `+` берёт только модель X. dbt не пытается угадать, что вы хотели — запускает ровно то, что попросили.
Решение
- Используйте граф-операторы:
--select model_X+запустит X и всё ниже по DAG,--select +model_X— X и всё выше,--select +model_X+— и upstream и downstream. Это базовый паттерн при изменении upstream-модели в dev.
Причина
В `dbt_project.yml` указан путь типа `marts: +materialized: table`, но папки `models/marts/` не существует, либо в ней нет моделей.
Решение
- Это warning, не fail. Решение: либо создайте папку и положите туда хотя бы одну модель, либо уберите соответствующий блок из dbt_project.yml. Часто появляется при копировании конфига из чужого проекта.
Причина
В profiles.yml path указан относительный (`./jaffle.duckdb`). dbt резолвит его относительно cwd, а не относительно проекта.
Решение
- Запускайте
dbt runиз корня проекта (там, где dbt_project.yml). Либо используйте абсолютный path. Либоpath: '{{ env_var("DBT_TARGET_DIR", ".") }}/jaffle.duckdb'с DBT_TARGET_DIR=$PWD. Главное — фиксированное место, чтобы DuckDB CLI / DBeaver мог подключиться к тому же файлу.
Причина
`{{ config(...) }}` блок размещён ПОСЛЕ SELECT, или внутри `{% if %}`. dbt парсит config только если он встречается до основного SELECT (парсинг top-of-file).
Решение
- Перенесите
{{ config(materialized='table') }}в самое начало файла, до любого SELECT/CTE. Если хочется условный materialization — используйте Jinja-выражение внутри config:{{ config(materialized=('table' if target.name == 'prod' else 'view')) }}.
Причина
В `dbt_project.yml` указан `profile: jaffle_shop`, но в `~/.dbt/profiles.yml` либо нет такого ключа, либо опечатка, либо файл вообще не существует.
Решение
- Запустите
dbt debug— он покажет, где dbt ищет profiles.yml и какие профили видит. Создайте/исправьте ~/.dbt/profiles.yml и убедитесь, что top-level ключ совпадает сprofile:в dbt_project.yml. На Windows путь —%USERPROFILE%\.dbt\profiles.yml.
Причина
Первый run всегда создаёт таблицу с нуля (is_incremental=False), даже если materialized='incremental'. is_incremental=True только начиная со второго run'а.
Решение
- Это by-design. Логика модели должна работать в обоих режимах: на первом run'е считать все данные, на последующих — только incremental. Внутри
{% if is_incremental() %}пишите WHERE-фильтр для дельты; вне — полный SELECT. Если хотите пересоздать с нуля —dbt run --select model --full-refresh.
Причина
В новых данных есть строки с одним unique_key. dbt пытается одной операцией заmerge'ить две строки с одинаковым ключом, что нарушает контракт MERGE.
Решение
- Дедуплицируйте внутри incremental-блока: WITH new_rows AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY updated_at DESC) rn FROM source WHERE ...) SELECT * FROM new_rows WHERE rn = 1. Альтернатива — переключиться на
incremental_strategy='delete+insert', который сначала всё удалит.
Причина
Для timestamp-стратегии — updated_at в source не изменился (или равен NULL). Для check-стратегии — изменилась колонка, не указанная в check_cols.
Решение
- Для timestamp: проверьте, что в source updated_at действительно обновляется. NULL -> snapshot пропустит строку. Для check: либо расширьте check_cols, либо используйте
check_cols='all'(дорого, но полный). Запуститеdbt snapshot --select snap_nameи проверьте таблицу в DuckDB CLI.
Причина
`dbt docs serve` подаёт статикой artefacts из `target/`. Если перед этим не запустили `dbt docs generate` — catalog.json не существует, и UI пуст.
Решение
- Команда — две стадии: сначала
dbt docs generate(соберёт manifest.json + catalog.json), потомdbt docs serve --port 8080. В CI можно делатьdbt docs generate && aws s3 sync target/ s3://docs-bucket/для публикации.
Причина
В dbt 1.10 шорткат `-m` помечен как deprecated в пользу полного `--select` (или `-s`).
Решение
- Замените
dbt run -m model_nameнаdbt run --select model_nameилиdbt run -s model_name. Если у вас CI-скрипты — обновите их сейчас, чтобы не сломаться при следующем мажоре. -m продолжит работать до 2.0.
Причина
В dbt 1.10 произвольные user-properties в YAML (вне 'meta:') стали warning. До 1.10 dbt их просто игнорировал.
Решение
- Переместите custom property под
meta:: былоmodels: - name: X owners: [alice], сталоmodels: - name: X meta: { owners: [alice] }. То же для sources, snapshots. В 1.10 это warning; в 2.0 станет error.
Причина
Jinja-шаблон не виден напрямую — `ref()`, `source()`, var(), макросы раскрываются динамически.
Решение
- Запустите
dbt compile --select model_nameи откройтеtarget/compiled/<project>/models/.../model_name.sql. Там финальный SQL, готовый к выполнению. Для DDL-обёртки (CREATE TABLE AS) — смотритеtarget/run/.../model_name.sql. Это первое место, куда смотрит junior при дебаге.
Причина
dbt дважды парсит проект: сначала parse-phase (без подключения к warehouse), потом execute-phase. На parse-phase `execute` = False, и run_query возвращает None.
Решение
- Оборачивайте run_query в
{% if execute %}:{% set rows = run_query('select 1') if execute else none %}. Дальше работайте с rows только внутри{% if rows %}. Иначе на parse-phase упадёт'NoneType' object has no attribute 'columns'.
Причина
В YAML тест навешен на алиас, а не на физическую колонку. Либо опечатка в имени. Либо колонка действительно NULL для маленького подмножества (например, для строк до миграции).
Решение
dbt test --select model_name.column_name --store-failuresи посмотрите вdbt_test__audit.<test_name>. Там будут конкретные строки. Если это легитимные NULL'ы — добавьте WHERE-фильтр в тест черезwhere:параметр в YAML, либо severity: warn.
Причина
Сломан indent. Чаще всего таб смешан с пробелами, либо ключ без двоеточия, либо `description: It's a customer` (апостроф ломает scalar).
Решение
- Открой файл в VSCode с YAML extension — он подсветит. Запускай
python -c 'import yaml; yaml.safe_load(open("f.yml"))'для быстрой проверки. Для строк с апострофами/кавычками — обрамляй двойными кавычками:description: "It's a customer". Никогда не используй табы.
Причина
По умолчанию dbt пересчитывает ВСЕ модели. Если их 200, и каждая занимает 8 секунд — это 27 минут.
Решение
- В dev запускайте только то, что меняли:
dbt build --select state:modified+ --defer --state path/to/prod-artifacts/. Slim CI. Для интерактивного дебага:dbt build --select my_model+1— только модель и её прямые downstream. И поднимайте threads до 8-16 в profiles.yml.
Причина
Локально вы запустили `dbt run` без теста, потом `dbt test` — он проверил уже изменённое состояние. В CI после `dbt build` тест запускается на тех же данных, что и в dev — но возможно есть race condition с дублирующимися load'ами.
Решение
- Используйте
dbt buildвсегда (и локально, и в CI) — он гарантирует test-after-run в правильном порядке. Если падает только в CI — проверьте, что raw данные в CI совпадают с локальными (часто CI использует production snapshot, а у вас локальный sample).
Причина
Активирован не тот Python virtualenv, где установлен dbt. Либо dbt установлен глобально, а текущий venv его не видит.
Решение
- Активируйте venv:
source .venv/bin/activate(либо conda env). Проверьте:which dbt— должен указывать на venv/bin/dbt. Если установка слетела:pip install dbt-core dbt-duckdb. Никогда не ставьте dbt системно через sudo — конфликт с системным Python.
Причина
Ephemeral модели не материализуются — они инлайнятся в downstream как CTE. Если downstream — не dbt-модель (а raw SQL в BI или ноутбук), CTE раскрывать некому.
Решение
- Ephemeral можно использовать только из других dbt-моделей. Если данные нужны вовне — поменяйте materialized на
view(дёшево) илиtable(если запрос тяжёлый). Это распространённое заблуждение junior'ов: ephemeral — НЕ table-without-materialization, она строго инлайнится.
Причина
Конвенция: staging = 1:1 к source, без JOIN'ов. JOIN — это уже intermediate или mart. Если в stg_orders приджойнили stg_customers и order_id перестал быть unique — все downstream сломаются.
Решение
- Уберите JOIN из stg_*. В staging оставьте только: rename, cast, deduplication, light transformations (LOWER, COALESCE). JOIN перенесите в
int_orders_enrichedили прямо в mart. Это критическая часть проектной структуры, описанной в dbt best practices.
Причина
Коллега добавил новый пакет в packages.yml, но `dbt deps` не запускается автоматически.
Решение
- Добавьте
dbt depsв свой workflow после каждогоgit pull. Многие команды в pre-commit / Makefile ставятdbt depsкак первую команду перед run. В CI:dbt deps && dbt build. Хорошая привычка — aliasdbt-fresh='dbt clean && dbt deps && dbt seed && dbt build'для полной пересборки.
Причина
Другой процесс держит exclusive lock на файл .duckdb. Часто — открытый DBeaver/SQL-клиент, либо предыдущий `dbt run` упал и оставил процесс.
Решение
- Закройте DBeaver/IDE-подключение. Проверьте процессы:
lsof | grep .duckdb(Mac/Linux),Get-Process | Where-Object Path -like '*duckdb*'(Windows). Убейте лишних. DuckDB — single-writer-per-file, это by design. Для prod-multi-writer нужен MotherDuck.
Причина
DuckDB identifiers case-insensitive, но preserve case. dbt по умолчанию quoting=false для DuckDB — все идентификаторы lowercase'ятся. Если source-CSV имеет колонки `Email`, dbt прочитает их как `email`.
Решение
- Если нужен camelCase — включите quoting: в
dbt_project.yml:quoting: { identifier: true, schema: true, database: true }. Но это редко нужно — convention dbt-сообщества — snake_case везде. Просто переименуйте колонки в staging:"Email" as email.
Причина
dbt seed по умолчанию инферит типы, но при сомнениях фоллбачится на VARCHAR. Если в CSV есть пустая строка вместо NULL, или один не-числовой символ — вся колонка станет VARCHAR.
Решение
- Объявите типы явно в YAML:
seeds: jaffle_shop: products: +column_types: { price: numeric(10,2), id: integer }. Также проверьте качество CSV:headиawk -F, '{print $3}' file.csv | sort -uчтобы найти аномалии в колонке.
Причина
Системный Python на Mac собран без полного SSL-support, либо установка через brew/pyenv не подцепила openssl.
Решение
- Используйте
pyenv install 3.11.Xпослеbrew install openssl@3 readline xz, и пересоздайте virtualenv. Альтернатива — установить Python через python.org installer (бандл со всеми деппами). Не лечитсяpip install— нужен полноценный Python build.
Причина
С dbt 1.8 generic-тесты в YAML переехали с ключа `tests:` на `data_tests:` (под колонкой/моделью). Старое имя ещё работает, но deprecated.
Решение
- Используйте
data_tests:для тестов:columns: - name: id data_tests: [unique, not_null]. Это разделяет data tests от unit tests. В 1.10 — warning при использовании старогоtests:, в 2.0 — будет error.
Причина
Severity теста = warn (по умолчанию для некоторых пакетов). dbt пишет WARN в логи, но exit code остаётся 0 — CI зелёный.
Решение
- Установите severity явно в YAML:
data_tests: - unique: { config: { severity: error } }. Для production-критичных таблиц —errorобязательно.warnоставьте для probabilistic-проверок (например, что строк > 1000), где ложные срабатывания неизбежны.
Причина
В Jinja используется `{{ var('X') }}` без дефолта, и переменная не объявлена ни в `dbt_project.yml`, ни передана через `--vars`.
Решение
- Два варианта. (1) Объявите дефолт в коде:
{{ var('X', 'default_value') }}— никогда не упадёт. (2) Объявите в dbt_project.yml:vars: X: default. CLI может override'ить:dbt run --vars '{X: production_value}'. Дефолт + override — типичный pattern.
Причина
`{{ env_var('SECRET') }}` без дефолта, и переменная окружения не выставлена.
Решение
- Выставьте перед запуском:
export SECRET=my_secret && dbt run. Для постоянного —.envrc(direnv) или.envфайл. В CI — секреты в GitHub Actions secrets / GitLab CI variables. Для опциональных параметров:{{ env_var('SAMPLE_SIZE', '1000') }}— дефолт.
Причина
В модели или profiles.yml используется DuckDB extension (httpfs, parquet, postgres_scanner), не установленная в этом DuckDB-инстансе.
Решение
- В profiles.yml декларируйте extensions:
outputs: dev: type: duckdb extensions: [httpfs, parquet, json]. dbt-duckdb сам установит/загрузит при connect. Альтернатива — вручную:duckdb mydb.duckdb -c 'INSTALL httpfs; LOAD httpfs;'. Для air-gapped: DuckDB поддерживает offline-extension-загрузку через файлы.
Причина
Внутри `{% if is_incremental() %}` нет WHERE-фильтра, или фильтр стоит ПОСЛЕ JOIN'ов (поздно — JOIN уже отсканировал всё).
Решение
- Фильтруйте upstream до JOIN'ов:
WITH new_orders AS (SELECT * FROM {{ source('raw', 'orders') }} {% if is_incremental() %} WHERE updated_at > (SELECT max(updated_at) FROM {{ this }}) {% endif %}) SELECT ... FROM new_orders JOIN .... Profile черезEXPLAIN ANALYZEв DuckDB CLI — увидите, сколько строк реально просканировано.
Причина
`loaded_at_field` указан неправильно (не та колонка), или timezone несовпадает: source в UTC, current_timestamp в локальной TZ.
Решение
- Проверьте:
SELECT MAX(loaded_at_field) FROM source.tableв DuckDB — действительно ли это последний load. Если timezone проблема — приведите явно:loaded_at_field: 'created_at_utc::timestamp_with_time_zone'либо'updated_at AT TIME ZONE \'UTC\''. Параметрыwarn_after/error_after: проверьте интервал —count: 1 period: hourозначает FAIL если последние данные старше часа.
Причина
Дефолтный `dbt run --select stg_orders` запускает ТОЛЬКО stg_orders. Downstream marts остаются на старом snapshot.
Решение
- Используйте
+после имени:dbt run --select stg_orders+— пересчитает всё downstream. Для incremental marts добавьте--full-refreshесли изменилась схема stg_orders:dbt run --select stg_orders+ --full-refresh. В CI всегда полный flow:dbt build --select state:modified+.
Причина
В packages.yml версия указана как `>=0.10.0` (open range). При новой dbt deps ставится последняя версия, которая может быть несовместима с вашим dbt-core.
Решение
- Пиньте версии:
packages: - package: dbt-labs/dbt_utils version: 1.3.0. Для критичных проектов используйтеpackage-lock.yml(с dbt 1.7+) — он коммитится и фиксирует точные версии. После изменения lock'а —dbt deps --upgradeдля обновления.
Причина
Забыли добавить `target/` в .gitignore. В нём manifest.json меняется при каждом run, плюс compiled SQL.
Решение
- Добавьте в .gitignore:
target/,dbt_packages/,logs/,.user.yml,*.duckdb,*.duckdb.wal. Удалите из истории:git rm -r --cached target/ dbt_packages/ logs/и закоммитьте. После этого PR'ы перестанут конфликтовать на сгенерированных артефактах.
Причина
Положили profiles.yml в корень проекта, чтобы команда могла шарить — и забыли что там креды.
Решение
- Никогда. Переписываете historу через
git filter-repo --invert-paths --path profiles.yml, force-push (предварительно предупредив команду). Меняете все пароли, которые были в файле. На будущее: креды только через{{ env_var('PASSWORD') }}, файл profiles.yml в .gitignore, либо вынесен в~/.dbt/.
Причина
profiles.yml содержит `read_only: true`, либо файл .duckdb открыт другим процессом с exclusive lock и текущий процесс получил read-only fallback.
Решение
- Уберите
read_only: trueесли он есть (обычно нужен только для downstream-консьюмеров, не для dbt run). Проверьте, что нет других процессов:lsof file.duckdb(Mac/Linux). Перезапустите dbt — иногда после crash остаётся .wal lock.
Причина
Метод не существует в адаптере dbt-duckdb, либо deprecated в новой версии.
Решение
- Не все методы адаптера портабельны между warehouse'ами. Проверьте документацию dbt-duckdb на GitHub. Для большинства случаев есть universal альтернатива через
run_query('SELECT table_name FROM information_schema.tables WHERE ...'). На junior-уровне это редкий кейс — обычно встречается при попытке копировать макрос из Snowflake-проекта.
Причина
Без CI-state нельзя посчитать `state:modified+`.
Решение
- В CI сначала генерируйте artefacts main-ветки (
dbt compile --target prod), сохраняйтеtarget/manifest.jsonкак artifact. В PR-pipeline: качаете state, иdbt list --select state:modified+ --state path/to/main-state/. Это покажет, что попадёт в slim CI. Для локального дебага:git stash; dbt compile; mv target /tmp/main-state; git stash pop; dbt list --select state:modified+ --state /tmp/main-state/.