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

Troubleshooting — dbt II

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

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

Причина

prod manifest (--state ./prod-state) не обновлён — модели сравниваются со старым состоянием

Решение

  1. После каждого успешного prod deploy сохраняйте свежий manifest.json в storage (S3/gh-pages/CI artifacts) с тэгом latest. В CI workflow первым шагом скачивайте этот manifest перед dbt build --select state:modified+ --defer --state ./prod-state. Используйте версионирование (commit-sha-tagged), чтобы откатывать на конкретный manifest при инцидентах.

Причина

incremental_strategy='delete+insert' с unique_key, который не уникален в incoming батче или в target таблице, выполняет DELETE FROM target JOIN incoming ON unique_key — каждое совпадение удаляется N раз

Решение

  1. Либо реально гарантируйте уникальность через staging-модель с QUALIFY ROW_NUMBER() = 1, либо используйте составной unique_key (массив), либо переходите на merge-стратегию с merge_update_columns. Test unique_combination_of_columns из dbt-utils в _models.yml ловит это на CI до prod-инцидента.

Причина

MERGE без incremental_predicates сканирует ВСЮ target-таблицу для поиска matching, и без merge_update_columns обновляет все колонки даже если значения те же

Решение

  1. Добавьте incremental_predicates: ['DBT_INTERNAL_DEST.event_date >= dateadd(day, -7, current_date)'] — критично использовать DBT_INTERNAL_DEST. префикс, иначе MERGE silent делает full scan. Дополнительно merge_update_columns: ['amount', 'status'] чтобы не writать неизменяемые колонки.

Причина

Забыли --full-refresh путь: вся логика модели обёрнута в `{% if is_incremental() %}`, и на первом запуске модель возвращает 0 строк

Решение

  1. Структура должна быть: основной SELECT с агрегациями всегда выполняется, а {% if is_incremental() %} WHERE updated_at > (SELECT MAX(updated_at) FROM {{ this }}) {% endif %} только добавляет фильтр. На первом run is_incremental=False, фильтр отсутствует, модель строит полный набор.

Причина

microbatch требует, чтобы event_time был в UTC; column с local-time или TIMESTAMP WITHOUT TIME ZONE вызывает ошибку парсинга батчей

Решение

  1. Каст source-колонки в UTC явно в staging-модели: TIMESTAMP WITH TIME ZONE 'UTC' AT TIME ZONE source_tz или event_at::timestamp at time zone 'UTC'. В yml пометьте event_time как UTC через комментарий. Используйте {{ var('event_time_zone', 'UTC') }} для тестируемости.

Причина

По умолчанию microbatch разрешает full-refresh, и accidental запуск стирает всю историю с заметным billable warehouse usage

Решение

  1. Защитите production-модели через config(materialized='incremental', incremental_strategy='microbatch', full_refresh=false). Если действительно нужен rebuild, временно снимите конфиг или используйте --full-refresh --select fct_orders явно. Для backfill — --event-time-start/--event-time-end без --full-refresh.

Причина

dbt по умолчанию ставит end=now(), что в проде = огромный диапазон -> миллион батчей и многочасовой run

Решение

  1. Всегда указывайте оба флага: dbt run --select fct_events --event-time-start 2024-01-01 --event-time-end 2024-02-01. Запускайте backfill по неделе / месяцу за раз. Мониторьте concurrent_batches на warehouse-pressure.

Причина

Default lookback=1 — пересматривается только один прошлый батч. Если событие приходит с задержкой 2+ дней, оно остаётся вне диапазона

Решение

  1. Увеличьте lookback: 7 (или больше, по реальным observed delays). Альтернатива: периодический backfill через --event-time-start. Метрика для мониторинга: % rows where insertion_timestamp - event_time > batch_size.

Причина

snapshot unique_key не уникален в source — каждый run создаёт по новой строке для каждого дубликата

Решение

  1. Прежде чем дать unique_key в snapshot, валидируйте source: добавьте generic test unique или unique_combination_of_columns. Если естественной уникальности нет — используйте composite key или surrogate (md5 от строки). Snapshot нельзя 'починить retroactive' без потери истории.

Причина

После добавления колонки в check_cols snapshot считает ВСЕ existing rows changed (новая колонка vs missing -> diff)

Решение

  1. Используйте timestamp strategy с надёжной updated_at — она устойчивее к schema changes. Если check strategy обязательна, при добавлении колонки сделайте explicit dbt snapshot --select my_snapshot --vars '{check_cols_changed: true}' (custom flag) ИЛИ временно удалите колонку из check_cols пока история не нагонит.

Причина

Дефолт `hard_deletes: ignore` — если строка исчезает из source, snapshot её не закрывает (dbt_valid_to=NULL навсегда)

Решение

  1. Включите hard_deletes: invalidate в snapshot config — при отсутствии строки в source dbt закроет dbt_valid_to=now(). Для аудиторного следа: hard_deletes: new_record добавляет marker-строку с dbt_is_deleted=true. DuckDB: hard_deletes пока не поддерживается, нужна Postgres/Snowflake.

Причина

Запросы вида `WHERE date_col BETWEEN dbt_valid_from AND dbt_valid_to` возвращают пустоту для текущей версии (NULL > date -> False)

Решение

  1. Включите dbt_valid_to_current: '9999-12-31' в snapshot config — для открытых версий dbt подставит этот sentinel. Все BI-запросы становятся date_col BETWEEN dbt_valid_from AND dbt_valid_to без NULL-обработки. Доступно с dbt-core 1.9+.

Причина

Команда оставила severity на default или скопировала warn — failures не блокируют merge

Решение

  1. Audit _models.yml: для бизнес-критичных тестов (uniqueness PK, FK integrity, not_null) — обязательно severity=error. Для soft-проверок (cardinality, range) — severity: warn + error_if: '>X'. Запустите grep для аудита: grep -r 'severity: warn' models/.

Причина

`store_failures: true` создаёт таблицы в `<project>_dbt_test__audit.<test_name>` без cleanup; на больших проектах — сотни таблиц с failure-данными

Решение

  1. В on-run-end установите cleanup: DROP TABLE IF EXISTS <project>_dbt_test__audit.<table> CASCADE для тестов старше N дней. Альтернатива: store_failures_as: view (1.9+) — view не хранит данные, lazy-eval. Для прод-инцидентов сохраняйте failures как partitioned table.

Причина

Дефолтный relationships тест делает LEFT JOIN ON foreign_key и считает orphans — на >10M rows это full scan + сортировка

Решение

  1. Замените на dbt_utils.relationships_where с filter на recent partition: where: 'created_at >= dateadd(day, -7, current_date)'. Альтернатива: severity=warn + custom тест с EXISTS subquery. Регулярно: добавьте index/partition на FK-колонку в warehouse.

Причина

Запустили `--select state:modified` без +, изменённая модель собралась, но её downstream не пересобрались. Merge в main вызвал break-разрыв

Решение

  1. Всегда используйте state:modified+ в Slim CI. Плюс расширяет селекцию на ВСЕ downstream-зависимости. Для cross-project — state:modified+,+state:modified (вверх и вниз). Тест: запустите CI, проверьте, что в output есть не только сами модели, но и их fct_/dim_ потребители.

Причина

На CI с --defer ref на не-modified модели резолвится в prod-схему. Если prod-данные мутируют между двумя CI runs, тесты могут пройти/упасть по-разному

Решение

  1. Защитите prod-схемы от мутаций во время CI: либо clone схемы (Snowflake zero-copy) в CI startup, либо снимок prod-данных в test fixtures. Альтернатива: --defer только для models, тесты — на CI-collected data. Документируйте flaky-tests в run_results, отслеживайте patterns.

Причина

`dbt source freshness` не входит в production job, daily fail остаётся незамеченным — модели строятся на устаревшем источнике

Решение

  1. В prod-deploy workflow первым шагом: dbt source freshness || (notify Slack + exit 1). Используйте threshold error_after (не warn_after) в _sources.yml для бизнес-критичных. Для CI достаточно warn_after — не блокирует merge. Для prod — error_after обязательно.

Причина

Команда установила `pre-commit-config.yml` но забыла `pre-commit install` — хуки прописаны декларативно, но не зарегистрированы в .git/hooks

Решение

  1. После клона репо каждый разработчик ОБЯЗАН запустить pre-commit install. Документируйте в README + добавьте pre-commit install в make-target (make bootstrap). Альтернатива: централизованный setup через scripts/install-hooks.sh, который проверяет version и предупреждает.

Причина

sqlfluff без `dbt` templater читает `{{ ref('foo') }}` как SQL-выражение, выдаёт linting errors

Решение

  1. В .sqlfluff обязательно templater = dbt + [sqlfluff:templater:dbt] project_dir = .. Проверьте, что dbt-core доступен в env. Для CI: запускайте sqlfluff через sqlfluff lint --templater=dbt models/. Альтернативно: использовать sqlfluff-templater-dbt отдельный пакет.

Причина

В CI workflow не передан `--target ci` или `--target prod`, dbt берёт default из profiles.yml (обычно dev) -> CI пишет в dev schema

Решение

  1. Всегда явно: dbt build --target ci --profiles-dir ./profiles/. В profiles.yml для CI отдельный target с уникальным schema (schema: dbt_ci_pr{{ env_var('GITHUB_PR_NUMBER') }}). Post-CI hook: DROP SCHEMA dbt_ci_pr{number} CASCADE после merge.

Причина

`{{ env_var('SOMETHING') }}` без второго аргумента: если SOMETHING не задано, parse fail с 'Env var required but not provided'

Решение

  1. Для всех optional env_var всегда давайте default: {{ env_var('FEATURE_FLAG', 'false') }}. Для required — оставьте без default и зафиксируйте в README/CI required env_vars. Хорошая практика: в начале dbt_project.yml вызвать env_var('CRITICAL_VAR') для fail-fast.

Причина

В dbt_project.yml забыли указать `dispatch: - macro_namespace: dbt_utils, search_order: ['my_project', 'dbt_utils']`. dbt берёт первую попавшуюся реализацию (часто из dbt-utils)

Решение

  1. Явно укажите search_order в dbt_project.yml. Для каждого macro_namespace (dbt, dbt_utils): сначала ваш проект, потом packages. Тестируйте через dbt run-operation my_macro — если выполнилась версия из package, search_order настроен неверно.

Причина

Override без `if target.name == 'prod'` — даже на prod target dbt именует схемы с суффиксом (analytics_marts вместо marts)

Решение

  1. Канонический override: {% if target.name == 'prod' %}{{ custom_schema_name | trim }}{% else %}{{ target.schema }}_{{ custom_schema_name | trim }}{% endif %}. На prod кастомное имя как-есть, на остальных — суффикс. Тестируйте: dbt compile --target prod -> проверьте target/compiled/ paths.

Причина

Comment длиннее warehouse-limit (Snowflake 16K, BigQuery 1K), warehouse silent обрезает или fail — но dbt продолжает run

Решение

  1. Аудит yml-описаний: для длинных используйте {% docs %} блоки + doc() reference (короткий handle в COMMENT). Для warehouse без поддержки column-comments отключите: persist_docs: {relation: true, columns: false}.

Причина

На warehouses без real constraint enforcement (BigQuery, Snowflake до 2023) constraints записываются как metadata-only — данные могут нарушать констрейнт, dbt этого не заметит

Решение

  1. Добавьте explicit data tests на критичные constraints: unique, not_null в _models.yml. Contract enforced проверяет только columns + types, не data integrity. Для PK uniqueness — dbt_utils.unique_combination_of_columns. CI должен запускать оба слоя.

Причина

В yml model versions нет `latest_version: 1` -> `ref('model')` без v= резолвится в максимальную доступную версию. Когда выкатили v2 (несовместимую), все downstream сломались

Решение

  1. Всегда явно latest_version в yml. Когда выкатывается v2: пишите её с defined_in: model_v2.sql, оставляйте latest_version=1, postupenno мигрируйте downstream на ref('model', v=2). После миграции переключите latest_version=2.

Причина

`grants: {select: ['analyst_role']}` без `+` (cumulative) на каждом run перезаписывает grants. Любой ручной GRANT, добавленный вне dbt, теряется

Решение

  1. Для аддитивных prod-сценариев: grants: {+select: ['bi_role', 'analyst_role']}. Плюс делает GRANT cumulative — старые grants сохраняются. Для строгого декларативного управления: оставьте без +, но запретите ручные GRANTs (RBAC через Terraform/Snowflake DDL).

Причина

В моделях прописано `WHERE env = 'prod'` вместо `WHERE env = '{{ target.name }}'` — на dev запросы возвращают пусто

Решение

  1. Используйте {{ target.name }} или {{ var('environment', target.name) }} для маппинга. Для тестов: dbt unit tests с overrides: {target.name: 'prod'}. Аудит: grep -rn "'prod'\|'dev'" models/ ловит hard-coded.

Причина

На dev `dbt snapshot --full-refresh` или dropping/recreating snapshot schema стирает историю — теряется SCD2 state

Решение

  1. Snapshots должны жить в durable schema даже на dev. В profiles.yml: snapshots target schema = dbt_snapshots_dev (стабильная). Никогда --full-refresh на snapshots. Если нужно начать заново — explicit DROP конкретного snapshot, не reset всей dev схемы.

Причина

Default `materialized=view` забыт; через копипасту/codegen все модели table. Warehouse cost растёт линейно от количества моделей

Решение

  1. Аудит: dbt list --resource-type model --output json | jq '.[] | select(.config.materialized=="table") | .name'. Для intermediate — ephemeral (если простые) или view. Только fct_/dim_ финальные — table/incremental. Большие — incremental с partition.

Причина

Регулярный prod-job запускает `dbt test` целиком — тысячи тестов на не-изменившихся моделях, warehouse cost растёт

Решение

  1. Разделите тесты: critical (uniqueness, not_null) — каждый run после build, остальное — daily/weekly job с --select tag:slow. Используйте where: filter в тестах для ограничения scope (last 7 days). Для freshness — отдельный job (5-min cron).

Причина

На Snowflake Iceberg table dbt 1.11 поддерживает только view + table, не incremental. Build падает с 'Iceberg unsupported for incremental'

Решение

  1. Wait for 1.12+ official support OR использовать workaround: external table + post-hook INSERT INTO. Альтернатива — Native Snowflake table (не Iceberg) для incremental, periodic snapshot в Iceberg через на separate job. Trackайте dbt-snowflake issues.

Причина

В microbatch включили concurrent_batches=true, но модель не идемпотентна (например, ROW_NUMBER() OVER без partition by event_time) — race condition даёт inconsistent state

Решение

  1. Включайте concurrent_batches=true только для агрегаций без order-dependency. Для SCD/cumulative/window-functions — оставьте concurrent_batches: false. Проверка: запустите N раз с concurrent=true vs concurrent=false -> результаты должны совпадать.

Причина

`{% docs my_block %}` определён в `models/docs.md`, но `dbt docs generate` не запускался — references к doc('my_block') в yml fail с 'doc not found'

Решение

  1. Включите dbt docs generate как первый шаг CI (или в pre-commit). Без него parse fail на doc(). Альтернативно: явный test dbt parse в CI — он валидирует doc references на parse-time без полного docs build.

Причина

Обновили `packages.yml`, но не запустили `dbt deps` — модели берут старую версию package из `dbt_packages/`, новые макросы не доступны

Решение

  1. В CI/dev обязательно: dbt deps перед каждым dbt run. В pre-commit hook: чекать, что packages.yml не изменился без обновления package-lock.yml. В Makefile: make build зависит от make deps.

Причина

`incremental_strategy='merge'` требует unique_key (для ON-условия). Без него dbt fail на parse phase с 'merge requires unique_key'

Решение

  1. Добавьте unique_key='order_id' (single column) или unique_key=['order_id', 'item_id'] (composite). Если естественной уникальности нет — surrogate key через dbt_utils.generate_surrogate_key(['col1', 'col2']) в модели.

Причина

microbatch стратегия by-design без unique_key — она работает через DELETE+INSERT per батч. unique_key игнорируется и вызывает config validation error

Решение

  1. Уберите unique_key из microbatch config. Гарантию идемпотентности обеспечивайте через event_time + batch_size (DELETE по диапазону событий). Если внутри батча возможны дубли — фильтруйте на staging (QUALIFY ROW_NUMBER()).

Причина

Из project_b сделали `ref('project_a', 'model')`, но в project_a `access: protected` — Mesh boundary не позволяет

Решение

  1. В upstream project_a explicit пометьте модель access: public в yml. Согласуйте через group + access — каноничный паттерн: модели в group=public-api с public access, остальные protected. Документируйте public API в exposures.

Причина

В semantic_models забыли primary entity (или поставили только foreign). MetricFlow не может построить JOIN-граф для metric, выдаёт 'no path between entities'

Решение

  1. Каждая semantic_model должна иметь ровно один type: primary entity. Foreign entities ссылаются на primary в других models. Запустите mf validate-configs — он проверяет, что все entity references resolvable.

Причина

В ratio metric numerator и denominator на разных semantic_models с разной cardinality — MetricFlow делает Cartesian-style JOIN

Решение

  1. Убедитесь, что numerator и denominator имеют общую entity, через которую корректно агрегируются. Для AOV: revenue (sum amount FROM orders) / order_count (count FROM orders) — оба per-order, JOIN на customer_id (или order_id) гарантирован. Тестируйте через mf query --explain.

Причина

`window: 7 days` не учитывает дни без событий — нулевые дни выпадают, running sum 'прыгает'

Решение

  1. Добавьте date_spine seed/model (последовательность дат) и LEFT JOIN к нему. Альтернатива в MetricFlow: grain_to_date режим с явной нулевой защитой. Тестируйте на спарс-данных (период без событий).

Причина

В CLI запустили `mf query --saved-query my_query` (только вывод), но не `mf export` (материализация в warehouse)

Решение

  1. Для материализации: mf export --saved-query my_query. Можно поместить в schedule через GitHub Actions или dbt Cloud job. Export пишет в схему как table (default) или view, тип контролируется конфигом saved_query.

Причина

actionlint строго парсит expression-syntax, иногда false positives на dynamic expressions с `${{ secrets.FOO }}` внутри shell-команд

Решение

  1. Используйте # actionlint-shellcheck exclude комменты для known-false-positives. Для shell-команд: вынесите в отдельный .sh файл, в workflow вызывайте через ./script.sh. Tests: actionlint -ignore '...'.

Причина

В CI workflow `--state` указан на gh-pages URL, но `curl` fail из-за CORS / private repo / неверного пути

Решение

  1. Для public repo: curl -sf https://owner.github.io/repo/manifest.json -o prod-state/manifest.json. Для private — используйте GitHub artifact download через gh run download или раздельный bucket S3 c IAM. Тестируйте манифест на каждом deploy.

Причина

Хук `check-script-has-no-table-name` фейлит, потому что модель ещё не в manifest (новый файл, не было dbt parse)

Решение

  1. Добавьте в pre-commit-config: args: [--manifest-path, target/manifest.json] и в make pre-commit запускайте сначала dbt parse. Альтернатива: skip hook for new files через # noqa: dbt-checkpoint.

Причина

Unit test `given:` fixture ссылается на input model, но input model изменилась (новые колонки) — unit test становится несовместимым

Решение

  1. Каждый раз при изменении output input-модели — обновляйте все unit test fixtures, которые её используют. Используйте dbt parse локально для catch early. CI: dbt test --select test_type:unit обязательно перед merge.

Причина

`incremental_strategy='merge'` падает с 'MERGE not supported' на DuckDB <1.4. dbt-duckdb 1.10 + DuckDB 1.1 — без MERGE

Решение

  1. Обновите DuckDB до 1.4+ (через pip install duckdb>=1.4) ИЛИ замените на delete+insert (с unique_key для гарантии уникальности). Для production: всегда фиксируйте конкретную версию DuckDB в requirements.txt.

Причина

CI cache `~/.dbt` копит установленные packages, runs артефакты между job runs — занимает гигабайты

Решение

  1. Кэшируйте только dbt_packages/ (после dbt deps) и target manifest. Cleanup: find ~/.dbt -name 'logs/*' -mtime +1 -delete. В GitHub Actions: actions/cache@v4 с явным path dbt_packages/ + key: dbt-${{ hashFiles('packages.yml') }}.