Learning Platform
Глоссарий Troubleshooting
Урок 13.04 · 25 мин
Средний
dbt-checkpointdbt_project_evaluatorpre-commitBest practices

dbt-checkpoint и dbt_project_evaluator как pre-commit

sqlfluff проверяет SQL — синтаксис, стиль, анти-паттерны на уровне SQL-запроса. Но в dbt-проекте есть проектный уровень проверок: «у новой модели должны быть тесты», «у источника должна быть freshness», «все public модели имеют description». Это не про SQL, это про структуру dbt-проекта.

Для этого есть два инструмента: dbt-checkpoint (быстрый, для pre-commit hooks) и dbt_project_evaluator (комплексный, как dbt package с моделями-чекерами). Они дополняют друг друга.

Data Governance: quality gates как governance-enforcement

dbt-checkpoint: проектные правила в pre-commit

dbt-checkpoint (бывший pre-commit-dbt) — это коллекция pre-commit hooks, которые читают manifest.json и проверяют отдельные правила. Каждый hook — это одно правило: «есть ли у этой модели тест», «есть ли description».

Что проверяет dbt-checkpoint
Coverage тестовTests: каждая модель должна иметь тесты. Конкретнее — определённый набор колонок (например, primary key). Без тестов модель не приемлема в production.
Documentation coverageDocumentation: каждая модель/source/колонка должна иметь description. Без описаний — это just SQL, не data product.
Source freshnessSource freshness: каждый source с критичной свежестью должен иметь loaded_at_field и freshness config. Без этого нельзя ни сигналить о застрявших pipelines, ни делать gate в production.
Config complianceConfiguration: материализация (table/view/incremental), tags, схемы. Проверка что новые модели идут по конвенции команды.

Установка через pip:

pip install dbt-checkpoint

В .pre-commit-config.yaml:

repos:
  - repo: https://github.com/dbt-checkpoint/dbt-checkpoint
    rev: v2.0.6
    hooks:
      - id: check-model-has-tests
        args: ["--test-cnt", "1", "--"]
      - id: check-model-has-description
      - id: check-source-has-freshness
      - id: check-script-ref-and-source

Как dbt-checkpoint работает

Каждый hook принимает измененные .sql файлы (от pre-commit) и парсит manifest.json. Manifest должен быть свежим.

Типичная проблема: разработчик добавил новую модель, забыл dbt parse. dbt-checkpoint читает старый manifest без этой модели -> ошибки не видит.

Решение — добавить автоматический dbt parse перед запуском hooks:

repos:
  - repo: local
    hooks:
      - id: dbt-parse
        name: dbt parse
        entry: dbt parse
        language: system
        types: [sql]
        pass_filenames: false

  - repo: https://github.com/dbt-checkpoint/dbt-checkpoint
    rev: v2.0.6
    hooks:
      - id: check-model-has-tests
      - id: check-model-has-description

Local hook dbt-parse бежит первым, обновляет target/manifest.json. Потом dbt-checkpoint hooks читают актуальный manifest.

WARNING

Без свежего manifest.json dbt-checkpoint молча скипает новые файлы (они для него «не существуют»). Это особенно опасно: разработчик думает что проверки прошли, на самом деле они не запускались. Всегда вставляйте dbt parse перед hooks.


Детальный разбор ключевых hooks

check-model-has-tests

Проверяет что у модели есть хотя бы N тестов. По умолчанию N=1.

- id: check-model-has-tests
  args: ["--test-cnt", "2"]    # минимум 2 теста на модель

С --test-cnt 2 модель stg_customers.sql должна иметь минимум 2 generic теста в _models.yml:

models:
  - name: stg_customers
    columns:
      - name: customer_id
        data_tests:
          - not_null
          - unique

Если только один тест — pre-commit падает. Это поощряет писать минимум not_null + unique на PK.

check-model-has-tests-by-name

Более конкретно — какие именно тесты должны быть:

- id: check-model-has-tests-by-name
  args:
    - --tests
    - unique
    - not_null

Каждая модель должна иметь оба теста: unique и not_null (хотя бы где-то на колонках).

check-model-has-description

Проверяет что у модели есть description либо в YAML, либо через doc block.

- id: check-model-has-description

Без description hook падает:

Models without description: stg_payments

check-source-has-freshness

Для каждого source проверяет наличие freshness блока:

sources:
  - name: jaffle_shop
    freshness:
      warn_after: {count: 12, period: hour}
      error_after: {count: 24, period: hour}
    loaded_at_field: _loaded_at
    tables:
      - name: orders

Без freshness — hook падает. Это критично для production — без freshness вы не знаете, когда данные застряли.

check-script-ref-and-source

Проверяет что все ссылки на другие таблицы в SQL — через ref() или source(), а не hardcoded имя:

-- ANTI-PATTERN
SELECT * FROM raw.jaffle_shop.orders

-- GOOD
SELECT * FROM {{ source('jaffle_shop', 'orders') }}

Hook ругается на первый вариант. Это критично — hardcoded имена ломают:

  • Изменение схемы между dev/prod (одна schema в dev, другая в prod).
  • Lineage DAG (dbt не знает что эта модель зависит от raw.jaffle_shop.orders).
  • Refactor: при переименовании таблицы hardcoded ссылки не находятся.

check-model-materialization-by-childs

Проверяет что тяжёлые модели (с N+ зависимостями от них) имеют materialized: table или incremental. Если такая модель view — она пересчитывается каждый раз когда кто-то её читает.

- id: check-model-materialization-by-childs
  args: ["--child-cnt", "5", "--materialization", "table,incremental"]

«Если у модели больше 5 child models, она должна быть table или incremental, не view».


Performance: —keep-state и manifest

dbt-checkpoint каждый hook парсит manifest.json. На большом проекте это медленно. Оптимизация:

- id: check-model-has-tests
  args: ["--manifest", "target/manifest.json"]

Это явно указывает путь к manifest. Hook не будет искать его сам.

Также можно передать --keep-state чтобы манифест кешировался между runs (для частных запусков локально).


dbt_project_evaluator: полный аудит

dbt_project_evaluator — это dbt package (а не pre-commit hook). Он состоит из dbt-моделей, которые читают manifest.json и graph.gpickle, и материализуют отчёт о нарушениях best practices.

Список проверок (десятки), сгруппированных:

КатегорияПримеры проверок
Modelingstaging_directory_models_use_source, staging_models_use_ref, staging_models_dependent_on_marts
Testingtest_coverage, missing_primary_key_tests
Documentationundocumented_models, undocumented_sources, undocumented_columns
Structuremodels_in_root_directory, model_naming_conventions, model_fanout
Performancechained_views_dependencies, exposure_parents_materializations

Установка

В packages.yml:

packages:
  - package: dbt-labs/dbt_project_evaluator
    version: 0.14.0

Затем:

dbt deps

Запуск

# Запустить все проверки и сохранить результаты
dbt build --select package:dbt_project_evaluator

# Только конкретные проверки
dbt run --select fct_undocumented_models

Каждая проверка — это модель, которая возвращает строки только с нарушениями. Например, fct_undocumented_models — список всех моделей без description.

К каждой модели прикреплён dbt test, который fail-ит если есть нарушения. Поэтому dbt build падает если есть проблемы.

Использовать как pre-commit

Можно интегрировать project_evaluator в pre-commit:

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: dbt-project-evaluator
        name: dbt project evaluator
        entry: bash -c "dbt build --select package:dbt_project_evaluator --fail-fast"
        language: system
        pass_filenames: false
        stages: [push]    # тяжёлый, только при push

Запускается только при git push (не при commit) — потому что тяжёлый, прогоняет dbt build целого пакета.

NOTE

project_evaluator — более тяжёлый чем dbt-checkpoint. dbt-checkpoint — быстрая проверка отдельных правил на staged файлах (миллисекунды). project_evaluator — полный аудит, секунды или минуты. Используйте dbt-checkpoint в pre-commit, project_evaluator в CI как nightly job или на push.


Конфигурация project_evaluator

В dbt_project.yml:

vars:
  # Отключить отдельные проверки
  dbt_project_evaluator:
    # Игнорировать legacy модели
    documentation_coverage_target: 80    # 80% моделей задокументированы

  # Кастомные конвенции
  staging_prefix: 'stg_'
  intermediate_prefix: 'int_'
  marts_prefix: 'fct_,dim_'

  # Какие модели игнорировать (например, snapshots)
  except_directories: ["snapshots"]

Selective enabling

Не все проверки нужно включать сразу. Можно отключить часть:

# dbt_project.yml
models:
  dbt_project_evaluator:
    +enabled: true

    # Отключить конкретную проверку
    marts:
      fct_chained_views_dependencies:
        +enabled: false

Типичный сценарий: postoboard

Команда внедряет CI/CD:

День 1. Установили pre-commit + sqlfluff + dbt-checkpoint минимум:
  - check-model-has-description
  - check-source-has-freshness
  - check-script-ref-and-source

День 7. Прогнали `pre-commit run --all-files`, исправили 50+ моделей (добавили descriptions).

День 14. Установили dbt_project_evaluator.

День 14. Запустили `dbt build --select package:dbt_project_evaluator`. Увидели:
  - 12 моделей в root directory (нарушение naming)
  - 5 staging моделей используют ref() от других staging (антипаттерн)
  - 8 fct_/dim_ моделей без primary key теста
  - 15% моделей без description (нужно 80%)

День 14-28. Постепенно чистят.

День 28. project_evaluator проходит. Включают его в CI как required check.

Полный .pre-commit-config.yaml для middle проекта

default_stages: [commit]
fail_fast: false

repos:
  # General hooks
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
        args: ["--allow-multiple-documents"]
      - id: check-merge-conflict
      - id: check-added-large-files
        args: ["--maxkb=1000"]

  # SQL linting
  - repo: https://github.com/sqlfluff/sqlfluff
    rev: 3.4.0
    hooks:
      - id: sqlfluff-lint
        args: ["--dialect=duckdb", "--templater=dbt", "--processes=0"]
        additional_dependencies:
          - dbt-core==1.10.0
          - dbt-duckdb==1.10.0
          - sqlfluff-templater-dbt==3.4.0
      - id: sqlfluff-fix
        args: ["--dialect=duckdb", "--templater=dbt"]
        additional_dependencies:
          - dbt-core==1.10.0
          - dbt-duckdb==1.10.0
          - sqlfluff-templater-dbt==3.4.0

  # Local: refresh dbt parse manifest
  - repo: local
    hooks:
      - id: dbt-parse
        name: dbt parse (refresh manifest)
        entry: dbt parse
        language: system
        types: [sql]
        pass_filenames: false

  # dbt-checkpoint
  - repo: https://github.com/dbt-checkpoint/dbt-checkpoint
    rev: v2.0.6
    hooks:
      - id: check-model-has-tests
        args: ["--test-cnt", "1"]
      - id: check-model-has-description
      - id: check-source-has-freshness
      - id: check-script-ref-and-source
      - id: check-model-tags
        args: ["--tags", "staging,marts,intermediate"]

  # On push only: heavy dbt_project_evaluator
  - repo: local
    hooks:
      - id: dbt-project-evaluator
        name: dbt project evaluator
        entry: bash -c "dbt build --select package:dbt_project_evaluator --fail-fast"
        language: system
        pass_filenames: false
        stages: [push]

Это базовый production config. На реальном проекте обрастает кастомными hooks (например, проверка что новые модели в правильной поддиректории, что используют наш кастомный macro для PK).


Попробуй сам

В вашем dbt-проекте:

  1. Установите dbt-checkpoint:
pip install dbt-checkpoint
  1. Добавьте hook в .pre-commit-config.yaml:
- repo: https://github.com/dbt-checkpoint/dbt-checkpoint
  rev: v2.0.6
  hooks:
    - id: check-model-has-description
  1. Создайте модель без description: models/staging/stg_test.sql:
SELECT id, name FROM {{ source('jaffle_shop', 'customers') }}
  1. Не добавляйте описание в _models.yml. Запустите:
dbt parse
pre-commit run check-model-has-description --files models/staging/stg_test.sql

Получите ошибку: «Models without description: stg_test».

  1. Добавьте description в _models.yml:
models:
  - name: stg_test
    description: "Тестовая staging модель"
  1. Снова запустите — проходит.

Бонус: установите dbt_project_evaluator через packages.yml, запустите dbt build --select package:dbt_project_evaluator, изучите какие нарушения он нашёл в вашем проекте.


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

  1. dbt-checkpoint — коллекция pre-commit hooks для проектных правил dbt (тесты, descriptions, freshness, ref/source). Быстрый, для commit-time.
  2. Каждый hook читает manifest.json. Перед запуском нужен свежий manifest через dbt parse (добавьте в pre-commit local hook).
  3. Ключевые hooks: check-model-has-tests, check-model-has-description, check-source-has-freshness, check-script-ref-and-source.
  4. dbt_project_evaluator — dbt package с десятками проверок (modeling, testing, docs, structure, performance). Тяжёлый — для push-time или nightly CI.
  5. project_evaluator конфигурируется через vars и selective +enabled: false. Запускается через dbt build --select package:dbt_project_evaluator.
  6. Сценарий внедрения: dbt-checkpoint в pre-commit для критичных правил -> project_evaluator на push/CI для полного аудита.
  7. Производительность: указывайте --manifest target/manifest.json, используйте stages: [push] для тяжёлых hooks.
Проверка знанийKnowledge check
В команде внедрили dbt-checkpoint hook \`check-source-has-freshness\` в pre-commit. Через 2 недели обнаружили, что в production некоторые sources всё ещё без freshness — pre-commit их пропустил. Почему и как починить?
ОтветAnswer
Самые вероятные причины:\n\n1. **Существующие sources без freshness уже были в коде.** pre-commit запускается на **staged** файлах. Если разработчик не менял `_sources.yml` файл — hook не получил его на вход. Существующие нарушения проскользнули.\n\n2. **Manifest.json устарел.** Если перед `pre-commit run` нет `dbt parse` — hook читает старый manifest, в котором ещё не появилось новых sources. \n\n3. **Hook применяется только к .sql файлам.** Sources декларируются в YAML. Если hook настроен `types: [sql]` — он YAML файлы не получает.\n\n**Починка:**\n\n1. **Прогнать на всём репо разово**:\n\n```bash\ndbt parse\npre-commit run check-source-has-freshness --all-files\n```\n\nЭто выявит все существующие нарушения. Исправить вручную.\n\n2. **Добавить dbt-parse local hook перед dbt-checkpoint hooks** в .pre-commit-config.yaml (чтобы manifest всегда был свежим).\n\n3. **Настроить hook на YAML файлы тоже:**\n\n```yaml\n- id: check-source-has-freshness\n files: \\.(yml|yaml|sql)$\n```\n\n4. **Дублировать проверку в CI** (`pre-commit run --all-files` в GitHub Actions). CI ловит то, что прошло мимо локального pre-commit (например, если кто-то закоммитил с `--no-verify`).\n\n5. **Использовать dbt_project_evaluator** как safety net на push — он не зависит от staged файлов, прогоняет полный аудит всего проекта.\n\n**Главный урок** — pre-commit не покрывает «уже существующие нарушения». Это инструмент **prevention новых проблем**, а не cleanup старых. Для cleanup — разовый `--all-files` + регулярный аудит через project_evaluator.
Проверка знанийKnowledge check
Команда подключила dbt_project_evaluator. На запуске \`dbt build --select package:dbt_project_evaluator\` получила 200+ нарушений на legacy-проекте. Команда хочет включить project_evaluator в CI как required check, но 200 нарушений за один раз не починят. Какие стратегии внедрения?
ОтветAnswer
**Не делать**: включить весь project_evaluator в required CI разом — CI будет красным месяцами, команда отключит проверку.\n\n**Правильные стратегии (по возрастанию строгости):**\n\n**1. Baseline + delta.** Зафиксировать существующие 200 нарушений как baseline. CI fail-ит только если число нарушений увеличилось.\n\n```bash\n# В CI:\ndbt build --select package:dbt_project_evaluator\nNEW_COUNT=$(dbt run-operation count_violations)\nif [ $NEW_COUNT -gt 200 ]; then\n echo "New violations introduced"\n exit 1\nfi\n```\n\nКоманда продолжает работать, новых нарушений не добавляет, старые постепенно чистит.\n\n**2. Selective enabling.** Включить только критичные категории. Остальные через `+enabled: false`:\n\n```yaml\nvars:\n dbt_project_evaluator:\n # Только docs + testing\nmodels:\n dbt_project_evaluator:\n structure:\n +enabled: false # пока выключено\n performance:\n +enabled: false\n```\n\nЧерез месяц включают `structure`, ещё через — `performance`. Постепенная enforcement.\n\n**3. Warning vs Error.** Запустить project_evaluator в **warn** режиме. Результаты пишутся в PR comment как информация, но CI не падает.\n\n```bash\ndbt build --select package:dbt_project_evaluator --warn-error-options '{"include":[]}'\n```\n\nЧерез 3 месяца, когда нарушений стало 50 — включают в error.\n\n**4. Quota по категориям.** «Документирование — 100% обязательно. Структура — 80% соответствие. Performance — 60%». CI fail-ит если падаем ниже квоты по конкретной категории.\n\n**Реалистичный план для команды:**\n\nМесяц 1: project_evaluator в warn-режиме, постят результаты в PR как комментарий.\nМесяц 2: фиксируют baseline (200 нарушений). CI блочит увеличение.\nМесяц 3-6: команда чистит самые критичные категории (documentation, testing).\nМесяц 6+: включают строгий режим (`fail_fast: true`) на чистых категориях.\n\n**Главный принцип** — gradual enforcement. Не пытаться enforce всё сразу, иначе команда саботирует процесс.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Чем dbt-checkpoint принципиально отличается от sqlfluff?

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

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

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

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