Learning Platform
Глоссарий Troubleshooting
Урок 03.04 · 22 мин
Продвинутый
parserdbt-parseci

dbt parse: отдельная команда и use cases

В предыдущих уроках мы видели parse как стадию pipeline dbt run. Но в dbt-core есть отдельная командаdbt parse — которая выполняет только стадии 1-4 pipeline (CLI, RuntimeConfig, ManifestLoader, Linker) и записывает Manifest на диск.

В этом уроке мы разберём use cases команды, как использовать её программно через dbtRunner, как дебажить parsing issues через dbt parse --no-partial-parse, и paterns для CI/CD.


Что делает dbt parse

dbt parse

Выполняет:

  1. Load RuntimeConfig (dbt_project.yml + profiles.yml).
  2. Load Manifest через ManifestLoader (с partial parse если возможно).
  3. Build Graph (через Linker).
  4. Write target/manifest.json + target/partial_parse.msgpack.
  5. Exit (без execution).

Не выполняет:

  • SelectionSpec (не нужен — селекция на execute phase)
  • GraphQueue
  • Runners
  • adapter.execute

dbt parse — это только парсинг.

# core/dbt/task/parse.py (упрощённо)
class ParseTask(ConfiguredTask):
    def run(self):
        # 1. Load Manifest
        self.manifest = ManifestLoader.get_full_manifest(self.config, write_perf_info=True)
        
        # 2. Compile manifest (build graph)
        Compiler(self.config).compile(self.manifest)
        
        # 3. Write manifest.json
        write_manifest(self.manifest, self.config.target_path)
        
        return self.manifest

write_perf_info=True — флаг для записи target/perf_info.json с временами parse phase, полезно для дебага.


Use case 1: prebuild Manifest для CI

В CI/CD pipeline часто полезно:

  1. Сначала parse + validate проекта (быстро, без execution).
  2. Если parse failed — fail fast (no SQL execution).
  3. Если success — proceed to execution с pre-loaded Manifest.
# .github/workflows/ci.yml (упрощённо)
jobs:
  parse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install dbt-core dbt-duckdb
      - run: dbt deps
      - run: dbt parse  # 30-60 sec, validates Manifest
      - uses: actions/upload-artifact@v4
        with:
          name: dbt-manifest
          path: target/manifest.json
  
  run:
    needs: parse
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install dbt-core dbt-duckdb
      - uses: actions/download-artifact@v4
        with:
          name: dbt-manifest
          path: target/
      - run: dbt run --select state:modified+ --defer --state target/

dbt run --defer --state target/ — это canonical Slim CI pattern. --state target/ указывает dbt-core использовать prebuilt Manifest как baseline.

Benefit: parse считается один раз, не на каждом subsequent step. Если у вас 5 steps в CI (parse, test, run, lint, docs) — parse делается в один общий step, остальные читают artifact.


Use case 2: programmatic post-parse hooks

После dbt parse у вас есть target/manifest.json — гигантский JSON со всем проектом. Senior может писать инструменты, которые анализируют Manifest до execution.

Примеры:

Doc coverage check

import json

with open('target/manifest.json') as f:
    manifest = json.load(f)

models = [n for n in manifest['nodes'].values() if n['resource_type'] == 'model']
no_desc = [m['name'] for m in models if not m.get('description')]

if no_desc:
    print(f"FAIL: {len(no_desc)} models without description:")
    for name in no_desc:
        print(f"  - {name}")
    exit(1)

В CI:

- run: dbt parse
- run: python scripts/check_doc_coverage.py

Contract enforcement check

critical_marts = [
    'fct_orders', 'dim_customers', 'agg_revenue'
]

for name in critical_marts:
    node = next((n for n in manifest['nodes'].values() if n.get('name') == name), None)
    if not node:
        print(f"FAIL: model {name} not found")
        exit(1)
    if not node.get('contract', {}).get('enforced'):
        print(f"FAIL: model {name} should have contract enforced")
        exit(1)

Schema namespace check

prod_schemas = ['marts', 'staging', 'intermediate']

for node in manifest['nodes'].values():
    if node['resource_type'] != 'model':
        continue
    if 'dev' in node['schema'] or 'ci' in node['schema']:
        print(f"FAIL: {node['name']} has invalid schema {node['schema']}")
        exit(1)

Эти проверки до execution. Если что-то не так — fail fast, no warehouse queries fired.


Use case 3: state comparison для Slim CI

dbt build --select state:modified+ --defer --state path/to/prev/manifest. Эта команда:

  1. Load target/manifest.json текущий (build после dbt parse).
  2. Load path/to/prev/manifest/manifest.json (от prod build).
  3. Compare — найти state:modified nodes.
  4. Select их и descendants (state:modified+).
  5. --defer — для зависимостей вне modified set, использовать prod schemas.

dbt parse упрощает CI:

- run: dbt parse  # genera new manifest.json
- run: dbt build --select state:modified+ --defer --state s3://my-bucket/prod-manifest/

--state принимает либо local path, либо URI (s3:// в новых версиях).

State comparison требует Manifest как input. dbt parse — это way to generate его без execution.

Slim CI: state:modified+ deep dive (dbt II) Manifest storage: где хранить production manifest для Slim CI

Use case 4: debug parsing issues

Когда что-то с парсингом не так:

# Forced full parse, ignore msgpack cache
dbt parse --no-partial-parse 2>&1 | tee parse.log

--no-partial-parse отключает кэш. Полезно когда:

  • Подозреваете, что cache stale (rare, но bывает после crashes)
  • Хотите чистый timing для baseline
  • Хотите воспроизвести prod CI behavior локально (CI часто не имеет cache)

Debug log показывает:

DBT_LOG_LEVEL=debug dbt parse --no-partial-parse 2>&1 | grep -i error

Найдёт все warnings и errors:

  • “static parser failure on model X”
  • “model X refs unknown model Y”
  • “duplicate macro foo defined in package bar”
  • “YAML schema validation error in _models.yml”

Use case 5: warm-up partial parse cache

Чтобы избежать cold parse на первом dbt run в CI:

- run: dbt parse  # writes partial_parse.msgpack
- run: dbt run --select my_model  # uses partial parse cache

Хотя dbt run сам бы делал parse, но если у вас несколько subsequent commands:

- run: dbt parse           # 30 sec — warm-up
- run: dbt deps            # 5 sec
- run: dbt seed            # 10 sec, uses cache
- run: dbt run             # 5 sec parse + execution
- run: dbt test            # 2 sec parse + execution
- run: dbt docs generate   # 5 sec parse + catalog gen

Total parse cost — 30 + 4×~5 = 50 sec vs 5×30 = 150 sec без cache. 3x speedup просто за счёт явного warm-up step.


Programmatic invocation

dbtRunner API позволяет вызывать parse программно:

from dbt.cli.main import dbtRunner

runner = dbtRunner()

# Parse only
result = runner.invoke(["parse"])

if result.success:
    manifest = result.result  # Manifest object
    print(f"Parsed {len(manifest.nodes)} nodes")
else:
    print("Parse failed:")
    for exc in result.exception or [result.exception]:
        print(f"  {exc}")

Это эквивалент dbt parse но без subprocess. Manifest доступен как Python object с type hints — удобнее, чем JSON.

# Используйте Manifest object
for unique_id, node in manifest.nodes.items():
    if node.resource_type.value == 'model':
        print(f"{node.name}: materialized={node.config.materialized}")

Модуль 06 курса детально разбирает dbtRunner API.


perf_info.json — где parse тратит время

При dbt parse пишется target/perf_info.json:

{
  "path_count": 1245,
  "is_partial_parse_enabled": true,
  "is_static_analysis_enabled": true,
  "node_counts": {
    "model": 856,
    "source": 234,
    "test": 1402,
    "snapshot": 12,
    "seed": 45
  },
  "version": "1.11.5",
  "elapsed": 4.32,
  "projects": [
    {
      "project_name": "my_project",
      "load_macros": 0.45,
      "parse_project": 2.89,
      "parse_yaml": 0.78,
      "process_refs": 0.12,
      "process_sources": 0.05
    },
    {
      "project_name": "dbt_utils",
      "load_macros": 0.18,
      ...
    }
  ]
}

Используйте для:

  • Identify slow steps — если parse_yaml ест 3 сек, у вас гигантские YAML files
  • Identify slow packages — если dbt_utils ест больше своего main project, что-то странное
  • Track regression — store perf_info.json в git, sравнить между versions

Anti-pattern: dbt parse и потом dbt run без —defer

dbt parse                       # generates target/manifest.json (cold parse)
dbt run --select my_model       # ALSO does parse internally

dbt run всё равно делает parse при старте (через partial parse cache). Так что вторая команда тратит ~3 сек на parse повторно (warm). Это не «двойной parse» — благодаря cache.

Но если вы хотите полностью пропустить parse в dbt run, нужна --defer --state target/ сoupling:

dbt parse
dbt run --defer --state target/ --select my_model

--state target/ — это путь к prebuilt manifest. Используется только для state comparison, не для skip parsing своего проекта. dbt run всё равно parsит свой проект.

True skip parse невозможен без programmatic API. Через dbtRunner можно передать pre-loaded Manifest:

runner = dbtRunner()

# Step 1: parse once
parse_result = runner.invoke(["parse"])
manifest = parse_result.result

# Step 2: pass manifest to subsequent invocations
runner = dbtRunner(manifest=manifest)
runner.invoke(["run", "--select", "model_a"])  # no re-parse!
runner.invoke(["run", "--select", "model_b"])  # no re-parse!
runner.invoke(["test", "--select", "model_a"])  # no re-parse!

Это самая мощная оптимизация для long-running processes (Dagster, Airflow), модуль 06.


Anti-pattern 2: dbt parse в CI без deps

- run: dbt parse  # fails: "Package dbt_utils not installed"

Нужно dbt deps first:

- run: dbt deps   # clones packages
- run: dbt parse

dbt parse требует, чтобы все packages были на disk (dbt_packages/). dbt deps загружает их.

В CI с cache: cache dbt_packages/ отдельно от target/. dbt deps fast (sec-level), но в большом проекте может быть 30+ sec на cold.


Попробуй сам

  1. dbt parse на любом проекте:
    time dbt parse
    ls target/
    cat target/perf_info.json | jq .
  2. Прочитайте perf_info.json, найдите slow projects.
  3. Force full parse:
    dbt parse --no-partial-parse
  4. Compare manifest.json sizes:
    wc -c target/manifest.json
    wc -c target/partial_parse.msgpack
    Manifest.json — JSON, более читаемый, больше. Msgpack — binary, меньше, faster load.
  5. Programmatic parse via dbtRunner:
    from dbt.cli.main import dbtRunner
    r = dbtRunner()
    result = r.invoke(["parse"])
    manifest = result.result
    print(f"Nodes: {len(manifest.nodes)}")
    print(f"Sources: {len(manifest.sources)}")
    print(f"Macros: {len(manifest.macros)}")
  6. Use parsed Manifest для analysis:
    # Models with most dependencies
    nodes = [(uid, len(n.depends_on.nodes)) for uid, n in manifest.nodes.items()]
    nodes.sort(key=lambda x: -x[1])
    for uid, dep_count in nodes[:10]:
        print(f"{uid}: {dep_count} deps")

Проверка знанийKnowledge check
Вы хотите ускорить CI pipeline, который сейчас выглядит так: dbt deps (30s) + dbt seed (15s) + dbt run (45s) + dbt test (30s) + dbt docs generate (20s) = 140s total. На каждой команде parse занимает 25s. Как сократить?
ОтветAnswer
Несколько оптимизаций. (1) Warm-up partial parse cache явно — добавить dbt parse в начале: dbt deps + dbt parse (25s cold) + dbt seed (cache hit, parse 2s + execution 13s = 15s) + dbt run (parse 2s + 43s = 45s) + dbt test (parse 2s + 28s = 30s) + dbt docs generate (parse 2s + 18s = 20s). Total parse: 25 + 4×2 = 33s vs 5×25 = 125s. Saving 90+ sec. (2) Cache target/ между CI runs — actions/cache key based on git SHA. Cold parse только when project files change. С cache hit: dbt parse 2s + остальные ~2s parse каждая. (3) Cache dbt_packages/ — dbt deps уже cached, не нужно clone packages each run. (4) Cache profiles.yml or use env_var consistency — minimize env var changes на parse phase. (5) Программный путь — если CI запускает Python скрипт (не shell), использовать dbtRunner(manifest=manifest) chain, делать parse один раз и переиспользовать Manifest object across invocations. Это самая мощная оптимизация — 25s -> 0s на subsequent commands. (6) Если проект 1000+ моделей — рассмотрите dbt Fusion (модуль 12), Rust parsing даёт 30x speedup, 25s -> 1s cold. Combined CI: 30 + 1 + 15 + 45 + 30 + 20 = 141s (no parse savings но other things) vs full optimized: 30 + 2 + 13 + 43 + 28 + 18 = 134s, sub-second parse with Fusion. Strategy: easy wins first (явный warm-up parse), затем cache target/, затем оценить Fusion для большого проекта.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. В CI pipeline senior хочет: validate проект fail-fast перед execution. Какой command выполняет only parse + validation без warehouse queries?

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

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

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

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