CI/CD для dbt: slim CI и проверки в PR

Каждый pull request с правкой моделей должен сам проверять, что ничего не сломалось.

Slim CI — практика, при которой в pull request dbt строит и тестирует только изменённые модели и их потомков (через state:modified+), а не весь проект — быстро и дёшево.

Зачем CI для данных

Раз модели — это код в git, к ним применима та же дисциплина, что и к приложениям: ни одна правка не попадает в прод без автоматической проверки. CI (continuous integration) на каждый pull request собирает затронутые модели и прогоняет тесты. Если коллега случайно сломал ключ или испортил джойн — это видно в PR, до мёржа, а не в проде.

Почему именно slim, а не «прогнать всё»

В большом проекте моделей тысячи. Строить их все на каждый PR — долго и дорого. Логично проверять лишь то, что изменилось в этом PR, и то, что от него зависит (потомков). Это и есть slim CI на основе state:modified+:

# В CI: сравнить с прод-манифестом и собрать только затронутое
dbt build --select state:modified+ --state ./prod_artifacts

dbt сравнит текущий код с эталонным manifest.json прода, найдёт изменённые модели, добавит их потомков и построит+протестирует только этот подграф.

Типичный CI-конвейер

Pull request открыт:
  1. dbt deps           (поставить пакеты)
  2. dbt build          (только state:modified+)
        --> упал тест? PR красный, мёрж заблокирован
  3. dbt docs generate  (проверить, что docs собираются)

Мёрж в main --> деплой:
  1. dbt build --target prod   (полный прогон)
  2. dbt docs generate --target prod  (обновить документацию)

Пример шага в GitHub Actions

# .github/workflows/dbt_ci.yml (фрагмент)
jobs:
  dbt_ci:
    runs-on: ubuntu-latest
    steps:
      - run: pip install dbt-postgres
      - run: dbt deps
      - run: dbt build --select state:modified+ --state ./prod_artifacts
        env:
          DBT_USER: "{{ secrets.DBT_USER }}"
          DBT_PASSWORD: "{{ secrets.DBT_PASSWORD }}"

Как работает под капотом

Ключ к slim CI — артефакт manifest.json с прошлого прод-прогона. dbt сравнивает текущее состояние проекта с этим эталоном и помечает изменённые узлы как state:modified. Оператор + добавляет потомков. CI обычно собирает изменения в отдельной CI-схеме хранилища (часто временной), чтобы не трогать прод. После мёржа уже отдельный пайплайн делает полный dbt build --target prod.

Частые ошибки

  • Строить весь проект на каждый PR. Медленно и дорого — используйте state:modified+.
  • Не блокировать мёрж при красном CI. Защита ветки должна требовать зелёный dbt build.
  • Прогонять CI прямо в прод-схему. Используйте отдельную CI-схему, чтобы не задеть продовые данные.

Итоги

  • CI прогоняет dbt build на каждый PR и блокирует мёрж при падении тестов.
  • Slim CI строит только изменённое и его потомков через state:modified+ и эталонный manifest.
  • CI работает в отдельной схеме; полный прод-прогон идёт после мёржа.
Проверьте себя
1. В чём идея slim CI для dbt?
AСтроить весь проект на каждый pull request
BСтроить и тестировать только изменённые модели и их потомков (state:modified+)
CНе запускать тесты вовсе
DТестировать только подключение
2. Какой артефакт нужен dbt, чтобы понять, что изменилось в PR?
Aprofiles.yml
Bmanifest.json предыдущего (прод) прогона для сравнения
CCSV-файл
Dpackages.yml