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-enforcementdbt-checkpoint: проектные правила в pre-commit
dbt-checkpoint (бывший pre-commit-dbt) — это коллекция pre-commit hooks, которые читают manifest.json и проверяют отдельные правила. Каждый hook — это одно правило: «есть ли у этой модели тест», «есть ли description».
Установка через 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.
Без свежего 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.
Список проверок (десятки), сгруппированных:
| Категория | Примеры проверок |
|---|---|
| Modeling | staging_directory_models_use_source, staging_models_use_ref, staging_models_dependent_on_marts |
| Testing | test_coverage, missing_primary_key_tests |
| Documentation | undocumented_models, undocumented_sources, undocumented_columns |
| Structure | models_in_root_directory, model_naming_conventions, model_fanout |
| Performance | chained_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 целого пакета.
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-проекте:
- Установите dbt-checkpoint:
pip install dbt-checkpoint
- Добавьте hook в
.pre-commit-config.yaml:
- repo: https://github.com/dbt-checkpoint/dbt-checkpoint
rev: v2.0.6
hooks:
- id: check-model-has-description
- Создайте модель без description:
models/staging/stg_test.sql:
SELECT id, name FROM {{ source('jaffle_shop', 'customers') }}
- Не добавляйте описание в
_models.yml. Запустите:
dbt parse
pre-commit run check-model-has-description --files models/staging/stg_test.sql
Получите ошибку: «Models without description: stg_test».
- Добавьте description в
_models.yml:
models:
- name: stg_test
description: "Тестовая staging модель"
- Снова запустите — проходит.
Бонус: установите dbt_project_evaluator через packages.yml, запустите dbt build --select package:dbt_project_evaluator, изучите какие нарушения он нашёл в вашем проекте.
Ключевые выводы
- dbt-checkpoint — коллекция pre-commit hooks для проектных правил dbt (тесты, descriptions, freshness, ref/source). Быстрый, для commit-time.
- Каждый hook читает
manifest.json. Перед запуском нужен свежий manifest черезdbt parse(добавьте в pre-commit local hook). - Ключевые hooks:
check-model-has-tests,check-model-has-description,check-source-has-freshness,check-script-ref-and-source. - dbt_project_evaluator — dbt package с десятками проверок (modeling, testing, docs, structure, performance). Тяжёлый — для push-time или nightly CI.
- project_evaluator конфигурируется через
varsи selective+enabled: false. Запускается черезdbt build --select package:dbt_project_evaluator. - Сценарий внедрения: dbt-checkpoint в pre-commit для критичных правил -> project_evaluator на push/CI для полного аудита.
- Производительность: указывайте
--manifest target/manifest.json, используйтеstages: [push]для тяжёлых hooks.