Learning Platform
Глоссарий Troubleshooting
Урок 14.02 · 25 мин
Средний
manifest.jsonSlim CIArtifact storageS3GitHub Pages

Manifest storage: где хранить production manifest для Slim CI

Slim CI (которую разберём в следующем уроке) работает на сравнении: «что в моей feature branch отличается от production?». Для этого нужно знать, что есть в production. Эта информация — в файле target/manifest.json, который генерируется при каждом dbt parse / dbt run / dbt build.

Manifest production-build’а нужно где-то хранить между запусками. Это центральный паттерн в dbt CI. В этом уроке разберём три способа: S3 (или совместимое объектное хранилище), GitHub Pages, GitHub Artifacts. У каждого свои plus и минусы.

dbt-iii: manifest.json изнутри — структура и содержимое

Что такое manifest.json и зачем его хранить

После каждого dbt build (или dbt parse) в target/manifest.json лежит полная картина проекта: все модели, их колонки, конфигурации, hashes, зависимости.

Простая иллюстрация:

{
  "metadata": {
    "dbt_version": "1.10.0",
    "generated_at": "2026-05-19T08:30:00Z",
    "invocation_id": "abc-123",
    "project_name": "jaffle_shop"
  },
  "nodes": {
    "model.jaffle_shop.stg_customers": {
      "raw_code": "SELECT id, name FROM ...",
      "checksum": {"name": "sha256", "checksum": "abc123..."},
      "config": {"materialized": "view"},
      "columns": {"id": {...}, "name": {...}}
    },
    ...
  }
}

Slim CI сравнивает manifest.json от текущего PR с production manifest.json. По разности checksums определяет какие модели изменились -> запускает только их.

Поэтому production manifest должен быть:

  1. Persistent — переживать между запусками.
  2. Versioned — нужен последний прошедший production build.
  3. Доступен из CI — для fetch в feature branch.

Три варианта storage

Где хранить production manifest
S3 (object storage)S3 / R2 / GCS / Azure Blob. Самый production-ready вариант. Простой fetch через AWS CLI. Versioning через S3 versioning. Underlying — Snowflake/BigQuery команды используют именно это.
GitHub PagesGitHub Pages — статика бесплатно. Простой для open source / маленькие команды. Manifest коммитится в orphan ветку gh-pages. Доступен по public URL — это и плюс и минус (нужно сделать репо приватным или manifest не должен содержать секретов).
GitHub ArtifactsGitHub Artifacts — встроенный artifact storage. Простой для CI-only сценариев. Не требует external storage. Минус — retention 90 дней (можно настроить), доступен только через GitHub API.

Вариант 1: S3 (или R2/GCS/Azure Blob)

Принцип: prod-build загружает manifest в bucket, PR-build скачивает.

prod workflow

# .github/workflows/dbt-prod.yml
name: dbt Prod Build

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 5 * * *'   # daily 5am

jobs:
  build-prod:
    runs-on: ubuntu-latest
    env:
      DBT_PROFILES_DIR: .
      SNOWFLAKE_ACCOUNT: ${'{{'} secrets.SNOWFLAKE_ACCOUNT {'}}'}
      SNOWFLAKE_USER: ${'{{'} secrets.SNOWFLAKE_USER_PROD {'}}'}
      SNOWFLAKE_PASSWORD: ${'{{'} secrets.SNOWFLAKE_PASSWORD_PROD {'}}'}

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install dbt-snowflake==1.10.0

      - name: dbt build prod
        run: |
          dbt deps
          dbt build --target prod

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${'{{'} secrets.AWS_ACCESS_KEY_ID {'}}'}
          aws-secret-access-key: ${'{{'} secrets.AWS_SECRET_ACCESS_KEY {'}}'}
          aws-region: us-east-1

      - name: Upload manifest to S3
        run: |
          aws s3 cp target/manifest.json s3://my-dbt-state/prod/manifest.json
          # Сохранить timestamped версию для troubleshooting
          aws s3 cp target/manifest.json "s3://my-dbt-state/prod/manifest-$(date +%Y%m%d-%H%M%S).json"

После каждого успешного prod build:

  • s3://my-dbt-state/prod/manifest.json — latest.
  • s3://my-dbt-state/prod/manifest-20260519-053012.json — timestamped backup.

PR workflow (использование)

# .github/workflows/dbt-ci.yml
jobs:
  dbt-build-pr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${'{{'} secrets.AWS_ACCESS_KEY_ID {'}}'}
          aws-secret-access-key: ${'{{'} secrets.AWS_SECRET_ACCESS_KEY {'}}'}
          aws-region: us-east-1

      - name: Download prod manifest
        run: |
          mkdir -p ./prod-state/
          aws s3 cp s3://my-dbt-state/prod/manifest.json ./prod-state/manifest.json

      - name: dbt build slim CI
        run: |
          dbt build --target ci \
            --select state:modified+ \
            --defer \
            --state ./prod-state/

Ключевое — --state ./prod-state/ указывает на скачанный manifest. dbt сравнит его с текущим и определит, что изменилось.


Вариант 2: GitHub Pages

Если у вас open source или нет S3 / нежелательно платить — manifest можно хранить в orphan ветке gh-pages того же репо.

prod workflow

- name: Publish manifest to gh-pages
  uses: peaceiris/actions-gh-pages@v4
  with:
    github_token: ${'{{'} secrets.GITHUB_TOKEN {'}}'}
    publish_dir: ./target
    publish_branch: gh-pages
    keep_files: false
    destination_dir: prod/

После этого https://<your-org>.github.io/<repo>/prod/manifest.json — публичный URL.

PR workflow

- name: Download prod manifest
  run: |
    mkdir -p ./prod-state/
    curl -L -o ./prod-state/manifest.json \
      https://your-org.github.io/your-repo/prod/manifest.json

- name: dbt build slim CI
  run: dbt build --select state:modified+ --defer --state ./prod-state/
WARNING

GitHub Pages публичный по умолчанию. Manifest.json содержит структуру вашего проекта (имена моделей, колонки, конфиги, иногда комментарии-метаданные). Если этого не должны видеть посторонние — либо сделайте repo private + Pages private (требует GitHub Pro/Team), либо используйте другой вариант (S3 с private bucket, GitHub Artifacts).

Versioning

GitHub Pages не умеет versioning из коробки. Workaround — публиковать с timestamped имена:

- name: Publish manifest with timestamp
  run: |
    mkdir -p ./gh-pages-content/
    cp target/manifest.json ./gh-pages-content/manifest-latest.json
    cp target/manifest.json "./gh-pages-content/manifest-$(date +%Y%m%d-%H%M%S).json"
    # ... затем publish

В PR использовать manifest-latest.json для slim CI, timestamped — для historical analysis.


Вариант 3: GitHub Artifacts

Артефакты GitHub Actions — это файлы, которые сохраняются после workflow run и доступны через GitHub API. Подходит для CI-only сценариев.

prod workflow

- name: Upload manifest artifact
  uses: actions/upload-artifact@v4
  with:
    name: prod-manifest
    path: target/manifest.json
    retention-days: 90        # default 90, можно настроить

PR workflow

- name: Download latest prod manifest
  uses: dawidd6/action-download-artifact@v9
  with:
    name: prod-manifest
    workflow: dbt-prod.yml
    workflow_conclusion: success
    branch: main
    path: ./prod-state/

- run: dbt build --select state:modified+ --defer --state ./prod-state/

dawidd6/action-download-artifact — community action для скачивания артефактов из другого workflow (вне родного actions/download-artifact, который умеет только в рамках одного workflow).

Ограничения

  • Retention 90 дней (по умолчанию). Старые манифесты автоматически удаляются.
  • Размер artifact — до 10GB per artifact, 20-50 GB total на репо.
  • Доступны только через GitHub API (не через прямой URL).

Для большинства проектов 90 дней достаточно — manifest нужен только последний для Slim CI.


Сравнение в таблице

ПараметрS3GitHub PagesGitHub Artifacts
Стоимость$0.023/GB/месбесплатнобесплатно (с лимитами)
Setup сложностьсредняянизкаянизкая
Privacyprivate (default)public (default)private
Versioningда (S3 versioning)manualretention 90d
Cross-repoдаданет (только в рамках GH)
Production-readyдаусловнода

Versioning стратегии

Latest + timestamped

s3://bucket/prod/manifest.json                          # latest, всегда обновляется
s3://bucket/prod/history/manifest-20260519-053012.json  # immutable backup
s3://bucket/prod/history/manifest-20260518-053012.json
s3://bucket/prod/history/manifest-20260517-053012.json

Slim CI использует latest. History для troubleshooting («что было неделю назад?»).

По git commit SHA

s3://bucket/prod/manifest-abc123.json   # main commit abc123
s3://bucket/prod/manifest-def456.json   # main commit def456
s3://bucket/prod/manifest-latest.json   # symlink/alias к самому свежему

Преимущество — точная атрибуция: «manifest от commit abc123». Удобно для archaelogical investigation.

По release tag

s3://bucket/prod/v1.5.0/manifest.json
s3://bucket/prod/v1.6.0/manifest.json
s3://bucket/prod/latest/manifest.json

Если у вас релизный процесс (теги по semver). Slim CI всегда от latest.


Stale manifest: критическая гочча

Что если prod build падал последние 3 дня, но не починили? Manifest устаревший — отражает состояние от 3 дней назад. Slim CI с этим манифестом будет неверным.

DANGER

Stale manifest — самая частая гочча Slim CI. Если prod build не прогонялся (упал, отключили cron, забыли смержить deploy), manifest не обновляется. Slim CI начинает сравнивать с состоянием недельной давности — пропускает реальные изменения или считает изменением то, что уже в проде. Результат — ломаются prod modeled таблицы.

Мониторинг свежести manifest

В CI добавить проверку:

- name: Check manifest freshness
  run: |
    python -c "
    import json
    from datetime import datetime, timezone, timedelta
    with open('./prod-state/manifest.json') as f:
      m = json.load(f)
    generated = datetime.fromisoformat(m['metadata']['generated_at'].replace('Z', '+00:00'))
    age = datetime.now(timezone.utc) - generated
    if age > timedelta(days=2):
      print(f'ERROR: prod manifest is {age.days} days old')
      exit(1)
    print(f'Manifest age: {age.days} days {age.seconds // 3600}h')
    "

Если manifest старше 2 дней — workflow падает с понятной ошибкой. Команда знает что нужно перезапустить prod build.


fallback: если нет state, делаем full build

- name: Try download prod manifest
  id: download
  continue-on-error: true
  run: aws s3 cp s3://my-dbt-state/prod/manifest.json ./prod-state/manifest.json

- name: dbt build (slim if manifest available, full otherwise)
  run: |
    if [ -f ./prod-state/manifest.json ]; then
      echo "Using Slim CI with state"
      dbt build --target ci --select state:modified+ --defer --state ./prod-state/
    else
      echo "No manifest — full build"
      dbt build --target ci
    fi

Graceful degradation: если manifest недоступен (первый раз setup, или S3 down) — делаем full build. Дольше, но не блокируем разработку.


Попробуй сам

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

  1. Создайте .github/workflows/upload-manifest.yml:
name: Upload Manifest

on:
  workflow_dispatch:    # manual trigger для теста

jobs:
  upload:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install dbt-duckdb==1.10.0
      - run: dbt deps
      - run: dbt parse --target dev

      - name: Upload manifest as artifact
        uses: actions/upload-artifact@v4
        with:
          name: prod-manifest
          path: target/manifest.json
          retention-days: 30
  1. Запустите вручную через GitHub UI (Actions -> Upload Manifest -> Run workflow).

  2. После прогона — скачайте artifact через UI, посмотрите содержимое manifest.json.

  3. Создайте второй workflow, который скачивает этот artifact:

name: Use Manifest

on:
  workflow_dispatch:

jobs:
  use:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dawidd6/action-download-artifact@v9
        with:
          name: prod-manifest
          workflow: upload-manifest.yml
          path: ./prod-state/
      - run: ls -la ./prod-state/
      - run: cat ./prod-state/manifest.json | python -c "import json, sys; m = json.load(sys.stdin); print('Generated:', m['metadata']['generated_at'])"

Запустите — увидите как manifest перенесён между workflows.

Бонус: добавьте freshness check (manifest older than 2 days -> fail). Это шаблон, который используется в production CI.


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

  1. manifest.json — снимок состояния проекта (модели, колонки, конфиги, hashes). Генерируется dbt parse/run/build. Нужен для Slim CI — сравнения с production.
  2. Три варианта storage: S3 (production-ready, платный), GitHub Pages (бесплатно, public по дефолту), GitHub Artifacts (бесплатно с лимитами, retention 90d).
  3. Workflow паттерн: prod build -> upload manifest -> PR build -> download manifest -> dbt build --state ./prod-state/.
  4. Versioning: latest + timestamped/SHA backup. Latest для Slim CI, history для troubleshooting.
  5. Stale manifest — критическая гочча. Если prod build упал, manifest устаревший, Slim CI работает неверно. Решение — мониторинг свежести (fail if older than 2 days) и graceful fallback на full build.
  6. GitHub Pages — public по умолчанию. Manifest может содержать sensitive metadata — нужно учесть.
  7. GitHub Artifacts — простой вариант для CI-only, ограничен retention и доступен только через GH API.
Проверка знанийKnowledge check
Команда настроила Slim CI с manifest в S3. Через 2 недели prod build начал падать (изменился schema source). Manifest не обновляется. Slim CI всё ещё запускается на PR-ах. Что начинает ломаться и как защититься?
ОтветAnswer
**Что начинает ломаться:**\n\n1. **Slim CI работает с устаревшим manifest** (от прошлого успешного prod build). Сравнивает PR с состоянием 2-недельной давности.\n\n2. **`state:modified+` неверный**. Модели, которые изменились в main с тех пор, но НЕ в текущем PR — Slim CI всё равно считает их 'modified' (потому что они не совпадают с устаревшим prod manifest). Запускает их в PR build, хотя они уже в проде.\n\n3. **`--defer` ломается**. defer указывает ссылаться на prod таблицы для неизменённых моделей. Если в prod таблицы тоже изменились (вместе со схемой), а manifest старый — defer ссылается на не-существующие таблицы.\n\n4. **CI зелёный, но prod артефакты сломаны**. Когда PR смерджится, prod build снова упадёт по той же причине. Никто не заметил.\n\n**Защита (по нарастающей):**\n\n**1. Manifest freshness check** в CI:\n\n```yaml\n- name: Check manifest freshness\n run: |\n python -c "\n import json, datetime\n with open('./prod-state/manifest.json') as f:\n m = json.load(f)\n gen = datetime.datetime.fromisoformat(m['metadata']['generated_at'].replace('Z', '+00:00'))\n age = datetime.datetime.now(datetime.timezone.utc) - gen\n if age > datetime.timedelta(days=2):\n raise Exception(f'Prod manifest is {age.days} days old — fix prod build first')\n "\n```\n\nCI фейлится с понятной ошибкой. Разработчики не могут мержить пока prod не починят.\n\n**2. Алерты на prod build failures**. GitHub Actions -> Slack уведомления при fail. Команда узнаёт сразу, не через 2 недели.\n\n**3. Daily smoke test prod build** через cron. Даже если нет изменений, раз в день прогон чтобы проверить что прод работает.\n\n**4. Fallback на full build**. Если manifest старше N дней — автоматически переключиться на full build (вместо Slim). Медленнее, но safer.\n\n```yaml\n- name: Build (smart fallback)\n run: |\n if [ $(manifest_age_days) -gt 2 ]; then\n dbt build --target ci # full\n else\n dbt build --target ci --select state:modified+ --defer --state ./prod-state/\n fi\n```\n\n**Главный принцип**: Slim CI — это оптимизация, не основная защита. Когда основной механизм (prod build) сломан, оптимизация работает неправильно. Нужны health checks и fallbacks.
Проверка знанийKnowledge check
Маленькая open-source команда хочет Slim CI. У них нет S3, не хотят настраивать AWS. Какой минимальный путь и что важно учесть?
ОтветAnswer
**Минимальный путь — GitHub Artifacts.** Встроено в GH Actions, бесплатно (с retention 90 дней что обычно более чем достаточно), нет external dependencies.\n\n**Setup:**\n\n1. **Prod workflow** загружает artifact:\n\n```yaml\nname: dbt Prod Build\non:\n push:\n branches: [main]\n schedule:\n - cron: '0 5 * * *'\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pip install dbt-duckdb==1.10.0\n - run: dbt deps\n - run: dbt build --target prod\n\n - uses: actions/upload-artifact@v4\n with:\n name: prod-manifest\n path: target/manifest.json\n retention-days: 30\n```\n\n2. **PR workflow** скачивает:\n\n```yaml\nname: dbt CI\non:\n pull_request:\n branches: [main]\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pip install dbt-duckdb==1.10.0\n\n - uses: dawidd6/action-download-artifact@v9\n with:\n name: prod-manifest\n workflow: dbt-prod.yml\n workflow_conclusion: success\n branch: main\n path: ./prod-state/\n\n - run: |\n dbt build --target ci \\\n --select state:modified+ \\\n --defer \\\n --state ./prod-state/\n```\n\n**Что важно учесть:**\n\n1. **Retention 90 дней.** Если prod build не запускался месяцами — manifest исчезнет. Защита: добавить cron schedule для регулярного prod build (даже без изменений).\n\n2. **dawidd6/action-download-artifact** — community action, не родной. Это нормально (популярная и поддерживаемая), но pin'ить к конкретной версии для безопасности.\n\n3. **First run** — нет prod-manifest. Fallback:\n\n```yaml\n- uses: dawidd6/action-download-artifact@v9\n continue-on-error: true\n id: download\n ...\n- run: |\n if [ -f ./prod-state/manifest.json ]; then\n dbt build --select state:modified+ --defer --state ./prod-state/\n else\n dbt build # full на первом запуске\n fi\n```\n\n4. **Cross-workflow sequencing**. PR workflow зависит от prod workflow артефакта. Если prod build падает — PR не получит manifest. Нужно либо graceful fallback (как выше), либо alerting.\n\n5. **Manifest публично НЕ доступен** — это плюс по сравнению с GitHub Pages. Хотя для open source проекта manifest часто и так не секретный.\n\n**Когда мигрировать на S3:**\n\n- Когда команда выросла и нужно cross-repo state sharing.\n- Когда retention 90 дней недостаточно (например, нужна история year-over-year).\n- Когда добавится dbt Cloud или Airflow — они умеют S3 нативно.\n\nНо для большинства open-source dbt-проектов GitHub Artifacts достаточно индефинитно.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Какие три ВАРИАНТА storage для prod manifest и для каких сценариев каждый?

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

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

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

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