Перейти к содержанию
Learning Platform
Глоссарий
Troubleshooting

Troubleshooting — dbt I

База знаний типичных ошибок курса dbt I.

Показано 44 из 44 ошибок

Причина

Забыли запустить `dbt deps` после изменения packages.yml или после клонирования репозитория.

Решение

  1. Запустите 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).

Решение

  1. Проверьте имя модели: dbt list --select Y. Если пусто — модель не парсится. Проверьте: (1) файл Y.sql существует в model-paths, (2) имя файла без расширения совпадает с именем в ref(), (3) нет дублирующихся имён моделей в разных подпапках (dbt требует уникальности).

Причина

Модель ссылается на source через хардкод имени (`from raw_jaffle.orders`) вместо source(). Или target.schema указывает не на ту схему, где лежит raw-таблица.

Решение

  1. Замените хардкод на {{ 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 изоляции.

Решение

  1. Если хочется одинаковое поведение во всех 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 сменил гранулярность.

Решение

  1. Запустите 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 не пытается угадать, что вы хотели — запускает ровно то, что попросили.

Решение

  1. Используйте граф-операторы: --select model_X+ запустит X и всё ниже по DAG, --select +model_X — X и всё выше, --select +model_X+ — и upstream и downstream. Это базовый паттерн при изменении upstream-модели в dev.

Причина

В `dbt_project.yml` указан путь типа `marts: +materialized: table`, но папки `models/marts/` не существует, либо в ней нет моделей.

Решение

  1. Это warning, не fail. Решение: либо создайте папку и положите туда хотя бы одну модель, либо уберите соответствующий блок из dbt_project.yml. Часто появляется при копировании конфига из чужого проекта.

Причина

В profiles.yml path указан относительный (`./jaffle.duckdb`). dbt резолвит его относительно cwd, а не относительно проекта.

Решение

  1. Запускайте 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).

Решение

  1. Перенесите {{ 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` либо нет такого ключа, либо опечатка, либо файл вообще не существует.

Решение

  1. Запустите 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'а.

Решение

  1. Это by-design. Логика модели должна работать в обоих режимах: на первом run'е считать все данные, на последующих — только incremental. Внутри {% if is_incremental() %} пишите WHERE-фильтр для дельты; вне — полный SELECT. Если хотите пересоздать с нуля — dbt run --select model --full-refresh.

Причина

В новых данных есть строки с одним unique_key. dbt пытается одной операцией заmerge'ить две строки с одинаковым ключом, что нарушает контракт MERGE.

Решение

  1. Дедуплицируйте внутри 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.

Решение

  1. Для 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 пуст.

Решение

  1. Команда — две стадии: сначала 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`).

Решение

  1. Замените 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 их просто игнорировал.

Решение

  1. Переместите 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(), макросы раскрываются динамически.

Решение

  1. Запустите 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.

Решение

  1. Оборачивайте 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 для маленького подмножества (например, для строк до миграции).

Решение

  1. 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).

Решение

  1. Открой файл в VSCode с YAML extension — он подсветит. Запускай python -c 'import yaml; yaml.safe_load(open("f.yml"))' для быстрой проверки. Для строк с апострофами/кавычками — обрамляй двойными кавычками: description: "It's a customer". Никогда не используй табы.

Причина

По умолчанию dbt пересчитывает ВСЕ модели. Если их 200, и каждая занимает 8 секунд — это 27 минут.

Решение

  1. В 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'ами.

Решение

  1. Используйте dbt build всегда (и локально, и в CI) — он гарантирует test-after-run в правильном порядке. Если падает только в CI — проверьте, что raw данные в CI совпадают с локальными (часто CI использует production snapshot, а у вас локальный sample).

Причина

Активирован не тот Python virtualenv, где установлен dbt. Либо dbt установлен глобально, а текущий venv его не видит.

Решение

  1. Активируйте 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 раскрывать некому.

Решение

  1. 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 сломаются.

Решение

  1. Уберите JOIN из stg_*. В staging оставьте только: rename, cast, deduplication, light transformations (LOWER, COALESCE). JOIN перенесите в int_orders_enriched или прямо в mart. Это критическая часть проектной структуры, описанной в dbt best practices.

Причина

Коллега добавил новый пакет в packages.yml, но `dbt deps` не запускается автоматически.

Решение

  1. Добавьте dbt deps в свой workflow после каждого git pull. Многие команды в pre-commit / Makefile ставят dbt deps как первую команду перед run. В CI: dbt deps && dbt build. Хорошая привычка — alias dbt-fresh='dbt clean && dbt deps && dbt seed && dbt build' для полной пересборки.

Причина

Другой процесс держит exclusive lock на файл .duckdb. Часто — открытый DBeaver/SQL-клиент, либо предыдущий `dbt run` упал и оставил процесс.

Решение

  1. Закройте 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`.

Решение

  1. Если нужен 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.

Решение

  1. Объявите типы явно в 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.

Решение

  1. Используйте 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.

Решение

  1. Используйте 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 зелёный.

Решение

  1. Установите severity явно в YAML: data_tests: - unique: { config: { severity: error } }. Для production-критичных таблиц — error обязательно. warn оставьте для probabilistic-проверок (например, что строк > 1000), где ложные срабатывания неизбежны.

Причина

В Jinja используется `{{ var('X') }}` без дефолта, и переменная не объявлена ни в `dbt_project.yml`, ни передана через `--vars`.

Решение

  1. Два варианта. (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') }}` без дефолта, и переменная окружения не выставлена.

Решение

  1. Выставьте перед запуском: 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-инстансе.

Решение

  1. В 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 уже отсканировал всё).

Решение

  1. Фильтруйте 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.

Решение

  1. Проверьте: 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.

Решение

  1. Используйте + после имени: 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.

Решение

  1. Пиньте версии: 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.

Решение

  1. Добавьте в .gitignore: target/, dbt_packages/, logs/, .user.yml, *.duckdb, *.duckdb.wal. Удалите из истории: git rm -r --cached target/ dbt_packages/ logs/ и закоммитьте. После этого PR'ы перестанут конфликтовать на сгенерированных артефактах.

Причина

Положили profiles.yml в корень проекта, чтобы команда могла шарить — и забыли что там креды.

Решение

  1. Никогда. Переписываете 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.

Решение

  1. Уберите read_only: true если он есть (обычно нужен только для downstream-консьюмеров, не для dbt run). Проверьте, что нет других процессов: lsof file.duckdb (Mac/Linux). Перезапустите dbt — иногда после crash остаётся .wal lock.

Причина

Метод не существует в адаптере dbt-duckdb, либо deprecated в новой версии.

Решение

  1. Не все методы адаптера портабельны между warehouse'ами. Проверьте документацию dbt-duckdb на GitHub. Для большинства случаев есть universal альтернатива через run_query('SELECT table_name FROM information_schema.tables WHERE ...'). На junior-уровне это редкий кейс — обычно встречается при попытке копировать макрос из Snowflake-проекта.

Причина

Без CI-state нельзя посчитать `state:modified+`.

Решение

  1. В 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/.