Learning Platform
Глоссарий Troubleshooting
Урок 07.04 · 25 мин
Средний
dbt_project_evaluatorBest practicesLintingCode quality

dbt_project_evaluator: правила и exemptions

dbt_project_evaluator — package, который применяет 30+ best practices правил к вашему проекту. Это automated code review для dbt: DAG structure, naming, testing coverage, performance anti-patterns.

На middle-уровне это инструмент continuous quality: запускается в CI, catches regressions до merge. В этом уроке — все категории правил, как читать failures, как настраивать exemptions для legacy кода.

Data Governance: evaluator как governance-инструмент

Setup

# packages.yml
packages:
  - package: dbt-labs/dbt_project_evaluator
    version: [">=0.13.0", "<1.0.0"]
dbt deps
dbt build --select package:dbt_project_evaluator

Этот build создаёт models с результатами проверок:

  • fct_marts_or_intermediate_dependent_on_source — marts shouldn’t ref sources directly.
  • fct_unused_sources — sources, не используемые ни одной model.
  • fct_undocumented_models — модели без description.
  • …и десятки других.

Каждая model — list of violations (rows). Если model empty — все ok. Если есть rows — violations.


Категории правил

1. DAG / Modeling

Правила структуры dependency graph.

  • fct_direct_join_to_source — модель не staging делает ref/source без staging слоя.
  • fct_multiple_sources_joined — staging model joins multiple sources (должна быть один source).
  • fct_root_models — models без upstream (могут быть orphan).
  • fct_rejoining_of_upstream_concepts — A -> B и A -> C, потом B/C joined в D (redundant DAG path).
  • fct_model_fanout — model имеет 5+ downstream (high blast radius).
  • fct_chained_views_dependencies — view -> view -> view (cascading recompute).
-- Run проверки:
dbt build --select package:dbt_project_evaluator --target dev

-- Show violations:
SELECT * FROM dbt_project_evaluator.fct_direct_join_to_source;
SELECT * FROM dbt_project_evaluator.fct_multiple_sources_joined;

2. Testing

Правила coverage тестов.

  • fct_test_coverage — percentage models с tests. Critical для compliance.
  • fct_missing_primary_key_tests — модели без unique / primary key test.
  • fct_documentation_coverage — model description coverage.
SELECT * FROM dbt_project_evaluator.fct_test_coverage
WHERE percent_coverage < 0.8;  -- модели с < 80% column coverage

3. Documentation

  • fct_undocumented_models — models без description.
  • fct_undocumented_source_tables — sources без description.
  • fct_columns_without_descriptions — columns без descriptions.

4. Structure / Naming

  • fct_model_naming_conventions — checks fct_, dim_, stg_, int_ prefixes.
  • fct_source_directories — sources не в правильной директории.
  • fct_staging_directories — staging не в models/staging.
  • fct_model_directories — fct/dim не в models/marts.

5. Performance

  • fct_exposure_parents_materializations — exposure parents не table materialized (view = slow).
  • fct_chained_views_dependencies — view chains.

Reading failures

После build, проверки доступны как regular models. Можно queries:

-- Все undocumented models:
SELECT resource_name, resource_type
FROM dbt_project_evaluator.fct_undocumented_models;

-- Models с rejoining (redundant DAG):
SELECT
    model_name,
    rejoined_concepts
FROM dbt_project_evaluator.fct_rejoining_of_upstream_concepts;

-- Models без PK test:
SELECT model_name
FROM dbt_project_evaluator.fct_missing_primary_key_tests;

Each model documented в dbt_project_evaluator package docs — what it checks, why important.

Summary view

-- Summary всех violations:
SELECT
    'undocumented_models' AS check_name,
    COUNT(*) AS violations
FROM dbt_project_evaluator.fct_undocumented_models
UNION ALL
SELECT 'missing_pk_tests', COUNT(*) FROM dbt_project_evaluator.fct_missing_primary_key_tests
UNION ALL
SELECT 'direct_join_to_source', COUNT(*) FROM dbt_project_evaluator.fct_direct_join_to_source
ORDER BY violations DESC

CI integration

В CI запускайте после dbt build:

# .github/workflows/dbt-quality.yml
- name: Run project evaluator
  run: dbt build --select package:dbt_project_evaluator
  
- name: Check critical violations
  run: |
    dbt show --select fct_undocumented_models --inline "
      SELECT COUNT(*) FROM dbt_project_evaluator.fct_undocumented_models
    " | awk 'NR==4 && $1 > 0 {exit 1}'

Это fails CI если есть undocumented models. Кастомизируйте per project — какие violations block vs warn.

Альтернатива: использовать dbt tests, которые fail при violation:

# models/_evaluator_tests.yml
models:
  - name: fct_undocumented_models
    data_tests:
      - dbt_utils.expression_is_true:
          expression: "COUNT(*) = 0"
          config:
            severity: error
            warn_if: ">0"
            error_if: ">10"

CI runs dbt test — failures блокируют merge.


Exemptions для legacy

Реальный сценарий: проект 2 года, evaluator finds 200 violations. Из них 50 real issues, 150 — legacy, которые fix-ить не приоритет.

Решение — exemptions config в dbt_project.yml:

vars:
  dbt_project_evaluator:
    # Список моделей, освобождённых от specific checks
    
    # Exempt из undocumented check
    undocumented_models_exclude:
      - 'fct_legacy_orders'
      - 'dim_legacy_customers'
    
    # Exempt из missing_pk_tests
    missing_primary_key_tests_exclude:
      - 'fct_audit_log'  # audit log doesn't need PK
    
    # Exempt из direct_join_to_source
    direct_join_to_source_exclude:
      - 'fct_legacy_*'  # wildcards возможны

Documented exemptions:

# dbt_project.yml
vars:
  dbt_project_evaluator:
    undocumented_models_exclude:
      # Legacy models pre-2024. Documentation backfill — Q3 2026.
      - 'fct_legacy_orders'
      - 'dim_legacy_customers'
      # Internal helper — no business meaning, no docs needed.
      - 'int_debug_temp'

Comments объясняют why. Иначе future devs не понимают legacy carve-outs.


Custom checks

Можно добавить custom evaluator checks. Создайте model в models/data_quality/:

-- models/data_quality/fct_models_without_grants.sql
{{ config(
    materialized='table',
    tags=['evaluator', 'data_quality']
) }}

SELECT name AS model_name
FROM {{ ref('all_models') }}  -- evaluator helper model
WHERE configurations NOT LIKE '%grants%'
  AND model_type = 'table'  -- only tables, не views

Этот check finds tables без +grants config. После build:

SELECT * FROM analytics.fct_models_without_grants;

И в CI:

data_tests:
  - dbt_utils.expression_is_true:
      expression: "(SELECT COUNT(*) FROM analytics.fct_models_without_grants) = 0"

Extending evaluator с project-specific rules.


Production rollout

Большой проект, evaluator добавляется впервые. 200 violations.

Phase 1 — Assessment (Week 1):

dbt build --select package:dbt_project_evaluator

Document each violation type, count:

SELECT * FROM analytics.fct_evaluator_summary;

Categorize:

  • Critical (block CI): missing PK tests на fact tables, undocumented marts.
  • High priority (fix in 1-2 sprints): documentation gaps, direct source joins.
  • Low priority (backlog): naming conventions, rejoining concepts.
  • Won’t fix: legacy exemptions.

Phase 2 — Setup (Week 2):

# dbt_project.yml
vars:
  dbt_project_evaluator:
    # Initial exemptions: всё что не critical
    undocumented_models_exclude:
      - 'fct_legacy_*'
    missing_primary_key_tests_exclude:
      - 'audit_*'

CI tests с warn для critical, info для остальное.

Phase 3 — Fix critical (Weeks 3-4):

Address все critical violations. Update CI to error на these.

Phase 4 — Backlog work (ongoing):

Каждый sprint — fix 5-10 high priority. Reduce exemptions list. Remove legacy exemptions when fixed.

Phase 5 — New code prevention:

CI configured to block new violations. Existing exemptions remain. Goal: net new code 100% compliant.

После года: exemptions list — short, well-documented. Project quality measurably better.


Common violations patterns

”direct_join_to_source”

-- BAD: fct_orders.sql
SELECT * FROM {{ source('app', 'orders') }}  -- source без staging слоя

Fix: создать staging слой:

-- models/staging/stg_app__orders.sql — new
SELECT * FROM {{ source('app', 'orders') }}
-- models/marts/fct_orders.sql — updated
SELECT * FROM {{ ref('stg_app__orders') }}

Bénéfit: testing, documentation, изоляция source schema changes.

”missing_primary_key_tests”

# BEFORE: fct_orders.yml без PK test
models:
  - name: fct_orders
    columns:
      - name: order_id

Fix:

models:
  - name: fct_orders
    columns:
      - name: order_id
        data_tests:
          - unique
          - not_null

Single column PK. For composite:

data_tests:
  - dbt_utils.unique_combination_of_columns:
      combination_of_columns: [order_id, line_item_id]

“rejoining_of_upstream_concepts”

stg_customers ─┬──> dim_customers
               └──> int_customer_stats ──> fct_customer_metrics

stg_customers дважды consumed: once для dim, once через int_customer_stats который join’ится с dim. Redundancy.

Fix: dim_customers должен быть единственный consumer stg_customers downstream of dim:

stg_customers ──> dim_customers ──> int_customer_stats ──> fct_customer_metrics

Linear chain. Cleaner DAG.

”chained_views_dependencies”

stg_orders (view) ──> int_enriched (view) ──> fct_orders (view)

3-level view chain. Каждый SELECT в downstream — recompute всех views. Performance death.

Fix: materialize middle layer as table:

-- models/intermediate/int_enriched.sql
{{ config(materialized='table') }}  -- было view
SELECT ...

Compute happens once на dbt run. Downstream queries fast.


DuckDB specifics

dbt_project_evaluator работает на DuckDB. Особенности:

  1. Performance: evaluator builds 30+ models. На большом проекте — 5-15 minutes.
  2. Memory: anali graf проекта — нужно memory_limit setting.
  3. Schemas: evaluator создаёт schema dbt_project_evaluator. На DuckDB single-database — этим занят отдельный namespace.

Альтернативы

dbt-checkpoint (модуль 12) — pre-commit hooks для quality checks. Эквивалентно evaluator, но runs on PR vs after build.

sqlfluff (модуль 12) — SQL linting, не dbt-specific.

evaluator + sqlfluff + dbt-checkpoint — full quality stack. evaluator для DAG / structural, sqlfluff для SQL syntax, dbt-checkpoint для simple convention checks.


Попробуй сам

В labs:

  1. Install dbt_project_evaluator, run dbt build --select package:dbt_project_evaluator.
  2. Identify violations через queries:
    SELECT * FROM dbt_project_evaluator.fct_undocumented_models;
    SELECT * FROM dbt_project_evaluator.fct_missing_primary_key_tests;
  3. Fix one violation per category:
    • Add description to undocumented model.
    • Add PK test to model.
    • Add staging layer для model с direct source join.
  4. Setup exemptions для legacy models:
    vars:
      dbt_project_evaluator:
        undocumented_models_exclude: ['legacy_old_model']
  5. CI integration: add dbt test step that fails on critical violations.

Ключевые выводы

  1. dbt_project_evaluator — automated dbt code review. 30+ best practices правил.
  2. Категории: DAG modeling, testing, documentation, structure/naming, performance.
  3. Output — models с violations. Empty = ok, rows = issues.
  4. CI integration: dbt test с expression_is_true, или CLI checks output.
  5. Exemptions в dbt_project.yml для legacy. Document why в comments.
  6. Custom checks через models в проекте — extends evaluator с project-specific rules.
  7. Rollout phases: assess -> setup -> fix critical -> backlog -> prevent new.
  8. Combination с sqlfluff + dbt-checkpoint для full quality stack.
Проверка знанийKnowledge check
dbt_project_evaluator finds 300 violations в legacy проекте. Команда испугалась 'ничего не работает корректно'. Что делать?
ОтветAnswer
Это **typical scenario** при first install evaluator на mature проект. 300 violations не означает 'броken project' — это **list improvements** в order of priority.\n\nApproach: triage + gradual rollout, не panic-fix all at once.\n\n**Step 1: Categorize violations.**\n\nGet summary:\n\n```sql\nSELECT * FROM dbt_project_evaluator.fct_evaluator_summary;\n```\n\nGroup by check type. Skip 'whats important now' from list. Example:\n\n- 100 undocumented_models — documentation debt.\n- 50 missing_primary_key_tests — testing gaps.\n- 30 direct_join_to_source — DAG structure.\n- 80 columns_without_descriptions — documentation debt.\n- 20 rejoining_of_upstream_concepts — DAG redundancy.\n- 20 chained_views_dependencies — performance.\n\n**Step 2: Prioritize.**\n\nMatrix: impact × effort:\n\n| Impact | Effort | Action |\n|---|---|---|\n| High impact, low effort | Now (1-2 sprints) | missing_primary_key_tests on critical facts |\n| High impact, high effort | Backlog | DAG restructure |\n| Low impact, low effort | Quick wins | columns_without_descriptions for visible models |\n| Low impact, high effort | Won't fix | Legacy structures с deprecated downstream |\n\nCritical = data quality / blocking governance.\nHigh impact = obvious / frequent downstream issues.\nLow impact = cosmetic / documentation gaps.\n\n**Step 3: Initial exemptions.**\n\nDo not try to fix 300 violations at once. Add **exemptions для всё что не fix-ите this sprint**:\n\n```yaml\n# dbt_project.yml\nvars:\n dbt_project_evaluator:\n # Initial cleanup state — to be reduced over sprints\n \n # Legacy models, fix planned Q3 2026\n undocumented_models_exclude:\n - 'fct_legacy_*'\n - 'dim_legacy_*'\n \n # Audit / internal — no PK needed by design\n missing_primary_key_tests_exclude:\n - 'audit_*'\n - 'int_debug_*'\n```\n\nWith exemptions, evaluator shows только **non-excluded violations**. Manageable list.\n\n**Step 4: Sprint plan.**\n\nSprint 1 (2 weeks):\n- Fix all missing PK tests on fct_ models (critical для data quality).\n- Quick wins: add descriptions для top 10 most-used models.\n- Result: 30 fewer violations.\n\nSprint 2:\n- Tackle direct_join_to_source (DAG hygiene). Build staging layer for 10 most-egregious cases.\n- Result: 10 fewer violations.\n\nSprint 3-6:\n- Documentation backfill.\n- Performance issues (chained views -> materialize intermediate).\n\nPace: 5-10 violations / sprint. После 6 months — список значительно меньше.\n\n**Step 5: Prevent regressions.**\n\nMost important: **new code must be compliant**.\n\nCI configuration:\n\n```yaml\n# CI: fail если **new** undocumented models или missing PK tests\n- name: Check для new violations\n run: |\n # Compare current vs baseline (main branch)\n NEW_VIOLATIONS=$(dbt show ... --inline \"\n SELECT COUNT(*) FROM dbt_project_evaluator.fct_undocumented_models\n WHERE resource_name NOT IN ({{ exempted_models | tojson }})\n \")\n \n if [ $NEW_VIOLATIONS -gt $BASELINE ]; then\n echo \"New violations introduced. Fix or add to exemptions with justification.\"\n exit 1\n fi\n```\n\nKey: **trend should be down**. Existing legacy — backlog, but new code clean.\n\n**Step 6: Communication к team.**\n\nDon't present 300 violations як failure. Frame як improvement opportunity:\n\n- 'We installed evaluator. It identified 300 potential improvements.'\n- 'We've categorized: 50 critical, 100 high priority, 150 cosmetic.'\n- 'Critical 50 — fix this quarter. Rest — backlog or exempted.'\n- 'New code must comply. Existing — improve gradually.'\n\nTeam buy-in critical. If everyone sees 300 violations as 'broken project' — demoralized. Frame as forward-looking, gradual improvement.\n\n**Step 7: Measure progress.**\n\nKey metric: 'open violations trend over time'. Should go down:\n\n```\nMonth 1: 300\nMonth 3: 270 (sprint 1-2)\nMonth 6: 200 (sprint 3-6)\nMonth 12: 100\n```\n\nIf flat — process broken (no investment в improvement).\nIf going up — regressions seeping in (CI not catching new violations).\n\nDashboard: weekly chart of (violations vs time). Visibility drives action.\n\n**Bottom line**: 300 violations not catastrophic. It's **inventory of improvements**. Triage, exempt non-critical, fix critical first, prevent new. Year 2 — substantially better project.
Проверка знанийKnowledge check
evaluator finds chained_views_dependencies: stg_orders (view) -> int_orders_enriched (view) -> fct_orders (view). Performance issues. Какое решение?
ОтветAnswer
Chained views — typical anti-pattern. Each SELECT downstream recomputes entire chain. Solution: **materialize intermediate layer as table**.\n\n**Понимание проблемы**:\n\nView в SQL — это **stored query**, не stored data. Каждый SELECT view re-runs underlying query.\n\nЦепочка views:\n\n```\nstg_orders (view) — SELECT * FROM raw_orders WHERE ...\nint_orders_enriched (view) — SELECT *, JOIN stg_customers, ... FROM stg_orders\nfct_orders (view) — SELECT *, calculations FROM int_orders_enriched\n```\n\nКогда BI/downstream запрашивает fct_orders:\n\n1. fct_orders view executes its SELECT.\n2. Which references int_orders_enriched view — executes its SELECT.\n3. Which references stg_orders view — executes its SELECT.\n4. Which finally hits raw_orders table.\n\nKaжdy query от downstream = full recomputation chain. Slow на больших data.\n\n**Fix strategies**:\n\n**1. Materialize critical middle layer as table.**\n\n```sql\n-- models/intermediate/int_orders_enriched.sql\n{{ config(materialized='table') }} -- was view\n\nSELECT\n o.*,\n c.tier AS customer_tier,\n c.country AS customer_country,\n p.category AS product_category\nFROM {{ ref('stg_orders') }} o\nLEFT JOIN {{ ref('stg_customers') }} c ON o.customer_id = c.customer_id\nLEFT JOIN {{ ref('stg_products') }} p ON o.product_id = p.product_id\n```\n\nNow int_orders_enriched computed once на dbt run, stored as table. Downstream views fast (просто read from table).\n\nTrade-off: storage cost (extra table). Refresh latency (data fresh as last dbt run, not real-time).\n\n**2. Materialize the final fact as table.**\n\nЕсли вся chain heavy:\n\n```sql\n-- models/marts/fct_orders.sql\n{{ config(materialized='table') }} -- was view\n\nSELECT *, calculations FROM {{ ref('int_orders_enriched') }}\n```\n\nFct_orders pre-computed. BI dashboards instant.\n\nKeep intermediate as view (only one consumer = fct_orders). Stg as view (one consumer = int).\n\n**3. Hybrid: incremental на fact level.**\n\nЕсли fct_orders large + slow recompute:\n\n```sql\n{{ config(\n materialized='incremental',\n unique_key='order_id'\n) }}\n\nSELECT *, calculations\nFROM {{ ref('int_orders_enriched') }}\n{% if is_incremental() %}\nWHERE updated_at > (SELECT MAX(updated_at) FROM {{ this }})\n{% endif %}\n```\n\nOnly new/changed orders processed. Best performance, но requires unique_key + updated_at tracking.\n\n**Decision tree**:\n\n- Если final fact small (менее 1M rows) -> materialized='table' for fact. Intermediate / staging as views.\n- Если final fact large (> 1M rows) -> materialized='incremental' for fact + materialized='table' for intermediate.\n- Если intermediate **also** queried directly downstream -> table at intermediate too.\n\n**Когда **view chain** OK**:\n\n- Small data (менее 100K rows).\n- Simple transformations.\n- Real-time freshness required (view always reads current data).\n- Storage budget tight (no extra tables).\n\nFor middle-level production projects (millions of rows, BI dashboards, batch processing): **avoid view chains**. Tabularize critical layers.\n\n**Configuration по дефолту**:\n\nProject-level в dbt_project.yml:\n\n```yaml\nmodels:\n myproject:\n staging:\n +materialized: view # staging — lightweight, view OK\n intermediate:\n +materialized: table # intermediate — usually table\n marts:\n +materialized: table # marts — almost always table\n facts:\n +materialized: incremental # facts — incremental если large\n```\n\nDefault by layer. Override per model when needed.\n\n**Measuring impact**:\n\nBefore fix:\n\n```bash\ntime dbt run --select fct_orders\n# 45 seconds (recomputes whole chain)\n```\n\nAfter fix (intermediate as table):\n\n```bash\ntime dbt run --select fct_orders\n# First run: ~50 seconds (computes int_orders_enriched + fct_orders)\n# Subsequent runs: ~2 seconds для fct_orders (only fct, int unchanged)\n```\n\nDownstream query (BI):\n\n```bash\n# Before:\ntime psql -c \"SELECT * FROM fct_orders LIMIT 1000\"\n# 20 seconds (recomputes view chain on each query)\n\n# After:\n# 50ms (reads from table)\n```\n\nDramatic difference for BI experience.\n\n**Related evaluator checks**:\n\n- **fct_chained_views_dependencies** — finds these view chains.\n- **fct_exposure_parents_materializations** — finds BI exposures whose parents are views (slow).\n- **fct_materialization_chain_consistency** — ensures matching materialization strategies.\n\nUse evaluator output to identify which chains to fix first. Usually top 10 evaluator hits = 80% of performance pain.

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

Результат: 0 из 0
Аналитический
Вопрос 1 из 6. dbt_project_evaluator finds 300 violations в legacy project. Команда испугалась 'всё broken'. Что делать?

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

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

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

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