Learning Platform
Глоссарий Troubleshooting
Урок 05.01 · 24 мин
Продвинутый
manifestartifactsdbt-internalsschema

manifest.json: top-level keys и анатомия артефакта

target/manifest.json — это central artifact dbt-core. Всё, что dbt знает о проекте после parsing phase, сериализовано сюда: nodes (models, tests, seeds, snapshots, analyses, operations), sources, macros, docs blocks, exposures, metrics, groups, selectors, semantic_models, плюс graph relationships (parent_map, child_map, group_map). dbt docs site, Slim CI, Elementary, dbt-osmosis, dbt-coverage, lineage extraction для BI tools — все эти инструменты потребляют manifest.json.

Senior должен знать структуру manifest наизусть. Это позволяет писать observability tooling, debugging parsing issues, building custom integrations без рефлексивного “посмотрю в docs”. В этом уроке — топология файла, semantic каждого top-level key, schema versioning и пример реального manifest.

Что лежит в target/: compiled, run, manifest, run_results (dbt I) Exposures как контракт с downstream-консьюмерами (dbt II)

Откуда берётся manifest.json

manifest пишется в конце parsing phase. Pipeline:

  1. RuntimeConfig загружает dbt_project.yml, profiles.yml, vars, target.
  2. ManifestLoader обходит все .sql, .py, .yml файлы через tree-sitter static parser.
  3. Каждый файл превращается в ParsedNode (для models/tests/snapshots/seeds), ParsedSourceDefinition, ParsedMacro, и т.д.
  4. Linker строит граф зависимостей (networkx DAG), вычисляет depends_on.nodes.
  5. Результат сериализуется в manifest.json (плюс target/partial_parse.msgpack для следующего запуска).

Любая команда, которая требует graph (run, test, build, compile, parse, ls, docs generate), сначала загружает или строит manifest. Если есть актуальный partial_parse.msgpack и ни один файл не изменился — manifest восстанавливается мгновенно (~100ms). Если что-то изменилось — partial reparse только изменённой части.

TIP

manifest.json пишется всегда после parsing, даже если команда не выполняет run. dbt parse — это самый быстрый способ обновить manifest без выполнения SQL. Часто используется в Slim CI и observability pipelines.


Top-level структура

manifest.json — это JSON dictionary с фиксированным набором top-level keys. Версия v12 (dbt-core 1.10/1.11):

{
  "metadata": { "...": "..." },
  "nodes": { "model.jaffle_shop.customers": { "...": "..." } },
  "sources": { "source.jaffle_shop.raw.orders": { "...": "..." } },
  "macros": { "macro.dbt.statement": { "...": "..." } },
  "docs": { "doc.jaffle_shop.customer_id": { "...": "..." } },
  "exposures": { "exposure.jaffle_shop.dashboard": { "...": "..." } },
  "metrics": { "metric.jaffle_shop.revenue": { "...": "..." } },
  "groups": { "group.jaffle_shop.finance": { "...": "..." } },
  "selectors": { "nightly": { "...": "..." } },
  "disabled": { "model.jaffle_shop.deprecated": [ {} ] },
  "parent_map": { "model.jaffle_shop.customers": [ "source.jaffle_shop.raw.customers" ] },
  "child_map": { "source.jaffle_shop.raw.customers": [ "model.jaffle_shop.stg_customers" ] },
  "group_map": { "finance": [ "model.jaffle_shop.fct_revenue" ] },
  "saved_queries": { "...": "..." },
  "semantic_models": { "semantic_model.jaffle_shop.orders": { "...": "..." } },
  "unit_tests": { "unit_test.jaffle_shop.test_revenue_logic": { "...": "..." } }
}

Конкретные top-level keys могут добавляться/удаляться между версиями. Сейчас разберём каждый.


metadata

metadata — header блок с info про сам manifest, не про nodes. Используется tools для совместимости и debugging.

{
  "metadata": {
    "dbt_schema_version": "https://schemas.getdbt.com/dbt/manifest/v12.json",
    "dbt_version": "1.11.0",
    "generated_at": "2026-05-19T10:23:14.812345Z",
    "invocation_id": "a3f5e8c2-...",
    "send_anonymous_usage_stats": true,
    "project_name": "jaffle_shop",
    "project_id": "9d2f...",
    "user_id": null,
    "env": {},
    "adapter_type": "duckdb"
  }
}

Ключевые поля:

  • dbt_schema_version — URL на JSON Schema, описывающую этот manifest. Версионирование позволяет tools валидировать.
  • dbt_version — версия dbt-core, которая сгенерировала manifest.
  • generated_at — UTC timestamp генерации.
  • invocation_id — UUID конкретного dbt invocation. Связывает manifest с run_results.json того же запуска.
  • project_name — из dbt_project.yml.
  • adapter_type — duckdb / snowflake / postgres / bigquery / и т.д.
NOTE

invocation_id критичен для observability tooling. Manifest + run_results + catalog с одинаковым invocation_id — это snapshot одного запуска. Несовпадение invocation_id означает, что артефакты из разных runs.


nodes

nodes — самый большой key. Dictionary, где ключ — unique_id (например, model.jaffle_shop.customers), значение — полная node definition. Включает:

  • Models (model.<project>.<name>)
  • Tests (test.<project>.<name> или data_test.<project>.<name> для generic tests; unit_test идёт в отдельный top-level)
  • Snapshots (snapshot.<project>.<name>)
  • Seeds (seed.<project>.<name>)
  • Analyses (analysis.<project>.<name>)
  • Operations (operation.<project>.<name>on-run-start/on-run-end hooks)
  • SQL Operations (sql_operation.<project>.<name>)

Реальный фрагмент (укороченный):

{
  "nodes": {
    "model.jaffle_shop.customers": {
      "database": "jaffle_shop",
      "schema": "main",
      "name": "customers",
      "resource_type": "model",
      "package_name": "jaffle_shop",
      "path": "marts/customers.sql",
      "original_file_path": "models/marts/customers.sql",
      "unique_id": "model.jaffle_shop.customers",
      "fqn": ["jaffle_shop", "marts", "customers"],
      "alias": "customers",
      "checksum": {
        "name": "sha256",
        "checksum": "8f2a1c..."
      },
      "config": {
        "enabled": true,
        "materialized": "table",
        "schema": null,
        "tags": ["nightly"],
        "meta": {},
        "grants": {}
      },
      "tags": ["nightly"],
      "description": "Customer dimension with lifetime value",
      "columns": {
        "customer_id": {
          "name": "customer_id",
          "description": "PK",
          "data_type": "VARCHAR",
          "constraints": [],
          "quote": null,
          "tags": [],
          "meta": {}
        }
      },
      "meta": {},
      "group": null,
      "refs": [
        {"name": "stg_customers", "package": null, "version": null},
        {"name": "stg_orders", "package": null, "version": null}
      ],
      "sources": [],
      "metrics": [],
      "depends_on": {
        "macros": ["macro.dbt.statement"],
        "nodes": [
          "model.jaffle_shop.stg_customers",
          "model.jaffle_shop.stg_orders"
        ]
      },
      "compiled_path": "target/compiled/jaffle_shop/models/marts/customers.sql",
      "compiled": false,
      "compiled_code": null,
      "raw_code": "SELECT ... FROM {{ ref('stg_customers') }} ..."
    }
  }
}

Детали — в следующем уроке (02-node-properties.mdx). Главное здесь: каждая node имеет unique_id, resource_type, путь к файлу, raw_code, refs, depends_on, config. После compilation phase появляются compiled_code и compiled_path. Между parsing и run эти поля пусты.

WARNING

В parsing phase compiled_code равно null. Это часто confusing — tools думают, что manifest сломан. На самом деле, manifest до compilation phase валиден, но содержит только raw_code. После dbt compile или dbt run поле заполняется.


sources

sources — отдельный top-level key для source definitions. Структура похожа на nodes, но дополнительно содержит freshness config, loaded_at_field, external_location (для dbt-duckdb sources как parquet).

{
  "sources": {
    "source.jaffle_shop.raw.orders": {
      "database": "jaffle_shop",
      "schema": "raw",
      "name": "orders",
      "resource_type": "source",
      "package_name": "jaffle_shop",
      "path": "models/_sources/raw.yml",
      "original_file_path": "models/_sources/raw.yml",
      "unique_id": "source.jaffle_shop.raw.orders",
      "fqn": ["jaffle_shop", "raw", "orders"],
      "source_name": "raw",
      "source_description": "Raw events from upstream",
      "loader": "fivetran",
      "identifier": "orders",
      "quoting": {
        "database": null,
        "schema": null,
        "identifier": null,
        "column": null
      },
      "loaded_at_field": "_synced_at",
      "freshness": {
        "warn_after": {"count": 12, "period": "hour"},
        "error_after": {"count": 24, "period": "hour"},
        "filter": null
      },
      "external": null,
      "description": "Order events",
      "columns": {
        "order_id": {
          "name": "order_id",
          "description": "Order PK",
          "data_type": "BIGINT",
          "constraints": [],
          "quote": null
        }
      },
      "meta": {},
      "tags": []
    }
  }
}

Sources не имеют compiled_code — это references, не SQL. Но у них есть freshness config — это используется командой dbt source freshness, результат пишется в target/sources.json.


macros

macros хранит все macros, и проектные, и из packages (dbt, dbt_utils, audit_helper, elementary, custom). Это самая большая часть manifest по количеству entries.

{
  "macros": {
    "macro.dbt.statement": {
      "name": "statement",
      "resource_type": "macro",
      "package_name": "dbt",
      "path": "macros/etc/statement.sql",
      "original_file_path": "macros/etc/statement.sql",
      "unique_id": "macro.dbt.statement",
      "macro_sql": "{% macro statement(name=None, fetch_result=False, auto_begin=True, language='sql') %}...{% endmacro %}",
      "depends_on": {"macros": []},
      "description": "Run a SQL statement against the warehouse",
      "meta": {},
      "docs": {"show": true, "node_color": null},
      "arguments": [],
      "supported_languages": null
    }
  }
}

Когда модель вызывает {{ ref('foo') }}, parser резолвит ref к macro macro.dbt.ref (или к override в myproject.ref). Этот lookup осуществляется через macros dictionary. dispatch() использует тот же mechanism для adapter-specific macros (default__create_table_as, duckdb__create_table_as, и т.д.).

NOTE

Custom adapter dispatch macros имеют префикс по adapter type: default__, duckdb__, snowflake__, postgres__. dbt разрешает их через search_order (project -> packages -> adapter). Знание этого pattern критично для написания adapters (модуль 09).


docs

docs хранит doc blocks ({% docs name %}...{% enddocs %}). Они referenced через {{ doc('name') }} в model description.

{
  "docs": {
    "doc.jaffle_shop.customer_id": {
      "name": "customer_id",
      "resource_type": "doc",
      "package_name": "jaffle_shop",
      "path": "models/_docs/columns.md",
      "original_file_path": "models/_docs/columns.md",
      "unique_id": "doc.jaffle_shop.customer_id",
      "block_contents": "Primary key for customers table. Format: UUID v4."
    }
  }
}

Когда docs site рендерит описание модели, он подставляет block_contents. То же при persist_docs.columns: true — comment в warehouse получает этот текст.


exposures, metrics, groups, selectors

Меньшие top-level keys для governance и semantic layer.

exposures — downstream dependencies (dashboards, ML models, notebooks). dbt не запускает их, но трекает в DAG для visibility.

{
  "exposures": {
    "exposure.jaffle_shop.weekly_jaffle_metrics": {
      "name": "weekly_jaffle_metrics",
      "type": "dashboard",
      "owner": {"email": "[email protected]", "name": "Analytics Team"},
      "url": "https://looker.example.com/dashboards/42",
      "depends_on": {"nodes": ["model.jaffle_shop.fct_orders"]},
      "maturity": "high",
      "description": "Top-level KPIs for executive review"
    }
  }
}

metrics — semantic layer metrics v1 (deprecated с 1.12 в пользу MetricFlow YAML, но всё ещё в manifest).

groups — model ownership groups для access control (public/private models).

{
  "groups": {
    "group.jaffle_shop.finance": {
      "name": "finance",
      "owner": {"email": "[email protected]"},
      "package_name": "jaffle_shop"
    }
  }
}

selectors — saved selectors из selectors.yml (например, --selector nightly).

{
  "selectors": {
    "nightly": {
      "name": "nightly",
      "definition": {"tag": "nightly"},
      "description": "Nightly refresh models"
    }
  }
}

disabled

disabled — отдельный bucket для nodes с config: {enabled: false}. Они парсятся, но не выполняются и не появляются в графе. Полезно для feature flags и postpone deprecation.

{
  "disabled": {
    "model.jaffle_shop.legacy_revenue": [
      {
        "name": "legacy_revenue",
        "resource_type": "model",
        "unique_id": "model.jaffle_shop.legacy_revenue",
        "config": {"enabled": false}
      }
    ]
  }
}

Заметьте — значение это list (не object), потому что одно имя может конфликтовать (например, в разных пакетах). dbt хранит все версии в массиве.


parent_map, child_map, group_map

Это flattened graph для O(1) lookup зависимостей без обхода depends_on каждой node.

{
  "parent_map": {
    "model.jaffle_shop.customers": [
      "model.jaffle_shop.stg_customers",
      "model.jaffle_shop.stg_orders"
    ],
    "model.jaffle_shop.stg_customers": [
      "source.jaffle_shop.raw.customers"
    ]
  },
  "child_map": {
    "source.jaffle_shop.raw.customers": [
      "model.jaffle_shop.stg_customers"
    ],
    "model.jaffle_shop.stg_customers": [
      "model.jaffle_shop.customers"
    ]
  },
  "group_map": {
    "finance": [
      "model.jaffle_shop.fct_revenue",
      "model.jaffle_shop.fct_arr"
    ]
  }
}
  • parent_map[X] — список upstream nodes для X (что X нужно для compile/run).
  • child_map[X] — список downstream nodes (что зависит от X).
  • group_map[group_name] — модели в группе.

Используется dbt для --select model+ (children), +model (parents), model+1 (1-hop downstream), +1 model (1-hop upstream). Также tools используют для lineage visualization.

TIP

parent_map/child_map — это reverse views одного DAG. Их избыточность намеренная: O(1) lookup в обе стороны без перестроения. Не пытайтесь использовать только один — некоторые tools полагаются на оба.


semantic_models и saved_queries

С dbt-core 1.6+ MetricFlow интеграция добавила два новых top-level keys.

{
  "semantic_models": {
    "semantic_model.jaffle_shop.orders": {
      "name": "orders",
      "node_relation": {
        "alias": "fct_orders",
        "schema_name": "marts",
        "database": "jaffle_shop"
      },
      "entities": [
        {"name": "order", "type": "primary", "expr": "order_id"},
        {"name": "customer", "type": "foreign", "expr": "customer_id"}
      ],
      "dimensions": [
        {"name": "order_date", "type": "time"},
        {"name": "status", "type": "categorical"}
      ],
      "measures": [
        {"name": "revenue", "expr": "order_total", "agg": "sum"}
      ]
    }
  },
  "saved_queries": {
    "saved_query.jaffle_shop.weekly_revenue": {
      "name": "weekly_revenue",
      "query_params": {
        "metrics": ["revenue"],
        "group_by": ["TimeDimension('order__order_date', 'WEEK')"]
      }
    }
  }
}

Эти ключи потребляются MetricFlow при компиляции semantic_manifest.json (отдельный артефакт, рассмотрим в 05-run-results-artifacts/02-other-artifacts.mdx).


unit_tests

С dbt-core 1.8+ unit tests получили top-level key.

{
  "unit_tests": {
    "unit_test.jaffle_shop.test_revenue_logic": {
      "name": "test_revenue_logic",
      "model": "fct_revenue",
      "given": [
        {"input": "ref('stg_orders')", "rows": [
          {"order_id": 1, "amount": 100, "status": "completed"}
        ]}
      ],
      "expect": {"rows": [{"revenue": 100}]},
      "config": {"meta": {}, "tags": []},
      "depends_on": {"nodes": ["model.jaffle_shop.fct_revenue"]}
    }
  }
}

До 1.8 unit tests шли в nodes с resource_type: "unit_test". Сейчас вынесены — упрощает фильтрацию.


Размер manifest в реальных проектах

  • Маленький проект (10-50 моделей): ~500KB-2MB
  • Средний (200-500 моделей, dbt_utils + audit_helper): ~10-30MB
  • Большой (1000+ моделей, многих packages): 50-200MB
  • Очень большой (3000+ моделей, mesh с 5+ projects): 500MB-1GB+
Состав manifest по размеру
WARNING

В больших проектах загрузка manifest.json как Python dict через json.load() может занять секунды и съесть гигабайты RAM. Для observability tooling рассмотрите streaming JSON parsers (ijson) или работу с уменьшенной копией. Подробнее — в уроке 04-parsing-manifest-python.mdx.


Schema versioning

metadata.dbt_schema_version указывает на формальную JSON Schema:

  • v1-v3 — dbt 0.x (legacy)
  • v4 — dbt 1.0
  • v5 — dbt 1.1
  • v6 — dbt 1.2
  • v7 — dbt 1.3 (groups added)
  • v8 — dbt 1.4
  • v9 — dbt 1.5 (versioned models)
  • v10 — dbt 1.6 (semantic_models, saved_queries)
  • v11 — dbt 1.7 (unit_tests, model contracts expansion)
  • v12 — dbt 1.8-1.11 (current major)
  • v13 — dbt 1.12 (planned, semantic layer v2)

Подробнее — в уроке 03-schema-versions.mdx. Главная идея: schema versions меняются, и tools должны проверять dbt_schema_version перед парсингом. Простой json.load() всегда работает, но семантика полей различается.


Реальный use case — minimal Python load

import json
from pathlib import Path

manifest_path = Path("target/manifest.json")
manifest = json.loads(manifest_path.read_text())

print(f"dbt version: {manifest['metadata']['dbt_version']}")
print(f"Project: {manifest['metadata']['project_name']}")
print(f"Total nodes: {len(manifest['nodes'])}")
print(f"Total sources: {len(manifest['sources'])}")
print(f"Total macros: {len(manifest['macros'])}")

# Только models
models = {
    uid: node
    for uid, node in manifest['nodes'].items()
    if node['resource_type'] == 'model'
}
print(f"Models: {len(models)}")

# Только incremental
incremental = {
    uid: node
    for uid, node in models.items()
    if node['config']['materialized'] == 'incremental'
}
print(f"Incremental models: {len(incremental)}")

Эта простая функциональность — фундамент любого observability tool. dbt-osmosis, Elementary, dbt-coverage — все начинают с такого парсинга. Полные паттерны — в уроке 04-parsing-manifest-python.mdx.


Антипаттерны при работе с manifest

1. Хардкод путей внутри manifest

# ПЛОХО: ломается при migration на mesh
node['root_path']
# В mesh projects root_path может относиться к другому проекту

Используйте original_file_path и относительный path.

2. Assumption fixed schema

# ПЛОХО: упадёт на v13
metric = manifest['metrics']['metric.foo.bar']

Проверяйте dbt_schema_version и handle migration:

schema_version = manifest['metadata']['dbt_schema_version']
if 'v10' in schema_version or 'v11' in schema_version:
    metrics = manifest.get('metrics', {})
elif 'v12' in schema_version:
    metrics = manifest.get('metrics', {})
    semantic_models = manifest.get('semantic_models', {})

3. Loading огромного manifest синхронно

# ПЛОХО: 1GB manifest = 4GB RAM
manifest = json.load(open('manifest.json'))

Streaming для огромных:

import ijson
with open('manifest.json', 'rb') as f:
    for prefix, event, value in ijson.parse(f):
        if prefix == 'nodes' and event == 'map_key':
            print(f"Found node: {value}")

4. Игнорирование disabled

# ПЛОХО: пропускает disabled models при analytics
all_models = [n for n in manifest['nodes'].values() if n['resource_type'] == 'model']

Если cost analysis или audit — нужно учесть и disabled (они занимают место в repo, потенциально активируются):

disabled_models = [
    nodes[0]
    for uid, nodes in manifest['disabled'].items()
    if uid.startswith('model.')
]

Tooling экосистема

Кто потребляет manifest.json:

  • dbt docs site — рендерит lineage graph, model descriptions, tests
  • Elementary — observability на основе manifest + run_results + catalog
  • dbt-osmosis — propagates column descriptions через DAG
  • dbt-coverage — reports column-level documentation coverage
  • dbt-meshify — splits monorepo на mesh projects
  • Datafold — data diff между PR и main
  • Recce — review changes через manifest comparison
  • dbt-checkpoint — pre-commit hooks
  • dbt-loom — federated dbt projects
  • Слим CI / state:modified — внутренний механизм dbt-core

Каждый из них читает manifest по-разному, но фундамент один.


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

  1. manifest.json — central artifact dbt-core. Производится в конце parsing phase, содержит всю project graph state.
  2. Top-level keys: metadata, nodes, sources, macros, docs, exposures, metrics, groups, selectors, disabled, parent_map, child_map, group_map, saved_queries, semantic_models, unit_tests.
  3. nodes — самый большой key. Models/tests/snapshots/seeds/analyses/operations с unique_id, refs, depends_on, config, columns.
  4. sources отдельный key (freshness + external_location).
  5. macros хранит весь Jinja source (dbt + packages + custom). 60-80% размера manifest.
  6. parent_map / child_map — flattened graph для O(1) lookup в обе стороны.
  7. disabled — отдельный bucket для config: {enabled: false} nodes.
  8. semantic_models / saved_queries добавлены с 1.6 для MetricFlow.
  9. unit_tests вынесены в top-level с 1.8.
  10. Schema versioning через metadata.dbt_schema_version URL — tools должны проверять перед парсингом.
  11. Размер растёт нелинейно с проектом: macros доминируют, потом nodes; mesh projects могут давать 1GB+ manifest.
  12. Tooling экосистема (Elementary, dbt-osmosis, dbt-coverage, dbt-meshify) — все строятся на manifest.
Проверка знанийKnowledge check
Аналитик пишет observability tool. Хочет получить список всех incremental моделей с их unique_key. Опиши схему: какие top-level keys читать, какие поля каждой node нужны, как handle missing unique_key.
ОтветAnswer
**Цель**: список incremental моделей и их unique_key для observability dashboard.\n\n**Шаги**:\n\n**1. Загрузить manifest**:\n```python\nimport json\nmanifest = json.loads(open('target/manifest.json').read())\n```\n\n**2. Проверить schema version**:\n```python\nschema = manifest['metadata']['dbt_schema_version']\nassert 'v10' in schema or 'v11' in schema or 'v12' in schema, \n f'Unsupported schema: {schema}'\n```\n\n**3. Итерировать nodes** (НЕ disabled — disabled в отдельном bucket):\n```python\nincremental_models = []\nfor unique_id, node in manifest['nodes'].items():\n if node['resource_type'] != 'model':\n continue\n if node['config']['materialized'] != 'incremental':\n continue\n incremental_models.append({\n 'unique_id': unique_id,\n 'name': node['name'],\n 'unique_key': node['config'].get('unique_key'),\n 'strategy': node['config'].get('incremental_strategy', 'append'),\n 'on_schema_change': node['config'].get('on_schema_change', 'ignore'),\n 'database': node['database'],\n 'schema': node['schema'],\n 'path': node['original_file_path']\n })\n```\n\n**4. Handle missing unique_key**:\n\nИнкрементальная модель без unique_key — валидна для strategy='append'. Но для 'merge' или 'delete+insert' — обязательно.\n\n```python\nwarnings = []\nfor model in incremental_models:\n if model['strategy'] in ('merge', 'delete+insert', 'microbatch'):\n if not model['unique_key']:\n warnings.append(\n f"{model['unique_id']}: strategy={model['strategy']} "\n f"but no unique_key (will fail at run time)"\n )\n if model['strategy'] == 'microbatch':\n if model['unique_key']:\n warnings.append(\n f"{model['unique_id']}: microbatch + unique_key "\n f"is config error (microbatch doesn't upsert)"\n )\n```\n\n**5. Include disabled?** Опционально:\n\nЕсли audit — да:\n```python\nfor uid, nodes in manifest.get('disabled', {}).items():\n if not uid.startswith('model.'):\n continue\n node = nodes[0] # disabled is list\n if node['config']['materialized'] == 'incremental':\n incremental_models.append({\n 'unique_id': uid,\n 'name': node['name'],\n 'unique_key': node['config'].get('unique_key'),\n 'enabled': False, # explicit flag\n ...\n })\n```\n\n**6. Sort и output**:\n\n```python\nincremental_models.sort(key=lambda m: m['unique_id'])\nprint(json.dumps({\n 'total': len(incremental_models),\n 'by_strategy': Counter(m['strategy'] for m in incremental_models),\n 'missing_unique_key': [\n m['unique_id'] for m in incremental_models\n if m['strategy'] != 'append' and not m['unique_key']\n ],\n 'models': incremental_models\n}, indent=2))\n```\n\n**Top-level keys использованы**:\n- `metadata` — schema version check\n- `nodes` — основной источник\n- `disabled` — опционально для audit\n\n**Поля node нужны**:\n- `resource_type` (filter == 'model')\n- `config.materialized` (filter == 'incremental')\n- `config.unique_key`\n- `config.incremental_strategy`\n- `config.on_schema_change`\n- `name`, `unique_id`, `database`, `schema`, `original_file_path` для identification\n\n**Edge cases**:\n- unique_key может быть string ('id') или list (['id', 'tenant_id'])\n- incremental_strategy может отсутствовать -> default 'append' для most adapters, 'merge' для DuckDB 1.4+\n- microbatch + unique_key — config error\n- На-prod может быть materialized: incremental, на-dev: table (через target conditional) — manifest хранит уже resolved для current target\n\n**Production**: запускать после каждого CI, alert если новый incremental без unique_key.
Проверка знанийKnowledge check
dbt run упал с 'Could not parse manifest.json: schema v11 expected, got v12'. Tool команды разработан на schema v11. Что happens, какие top-level keys могут сломать tool, как fix.
ОтветAnswer
**Что happens**: tool построен на assumption schema v11 (dbt-core 1.7). После upgrade на dbt-core 1.8+ manifest получил schema v12. Tool читает поля, которые могут быть переименованы/удалены/добавлены.\n\n**Конкретные изменения v11 -> v12**:\n\n**Removed/renamed**:\n- `root_path` removed на nodes (используйте `original_file_path`)\n- Старые metric definitions могут перемещаться в semantic_models\n\n**Added**:\n- `unit_tests` top-level (раньше в `nodes` с resource_type='unit_test')\n- Дополнительные поля в semantic_models\n- New `saved_queries` если используется MetricFlow\n- Constraints поля могут expand\n\n**Modified**:\n- `refs` стали dictionaries с `{name, package, version}` вместо просто strings (versioned models)\n- `sources` могут содержать `tables` поле для freshness checks\n\n**Топ ключи риск**:\n\n**1. nodes** — критичный. Изменения semantics:\n\n```python\n# v11:\nfor ref_str in node['refs']: # list of strings\n print(ref_str)\n\n# v12:\nfor ref_obj in node['refs']: # list of dicts\n print(ref_obj['name'], ref_obj.get('version'))\n```\n\nTool который assumed strings — breaks.\n\n**2. unit_tests** — раньше внутри nodes. Tool iterating nodes для 'tests' может пропустить unit tests в v12 (они переехали).\n\n```python\n# v11:\nunit_tests = [n for n in manifest['nodes'].values() if n['resource_type'] == 'unit_test']\n\n# v12:\nunit_tests = list(manifest.get('unit_tests', {}).values())\n```\n\n**3. semantic_models / metrics** — semantic_models expanded в v12. Старые metrics могут не существовать.\n\n**4. disabled** — структура осталась стабильной (list of dicts), но содержимое внутри dicts может measure v12 schema.\n\n**Strategy для multi-version tool**:\n\n```python\ndef parse_manifest(path):\n manifest = json.load(open(path))\n schema = manifest['metadata']['dbt_schema_version']\n \n # Extract major version\n if 'v11' in schema:\n return parse_v11(manifest)\n elif 'v12' in schema:\n return parse_v12(manifest)\n else:\n raise UnsupportedManifestError(\n f'Schema {schema} not supported. Tool supports v11/v12.'\n )\n\ndef parse_v11(manifest):\n # Old logic\n unit_tests = [\n n for n in manifest['nodes'].values()\n if n['resource_type'] == 'unit_test'\n ]\n refs = lambda node: node['refs'] # strings\n return ...\n\ndef parse_v12(manifest):\n # New logic\n unit_tests = list(manifest.get('unit_tests', {}).values())\n refs = lambda node: [r['name'] for r in node['refs']] # extract name from dicts\n return ...\n```\n\n**Alternative — strict validation**:\n\n```python\nfrom jsonschema import validate\nimport requests\n\nschema_url = manifest['metadata']['dbt_schema_version']\nschema_def = requests.get(schema_url).json()\nvalidate(instance=manifest, schema=schema_def)\n```\n\nЕсли валидация falls — manifest invalid, не tool issue.\n\n**Best practice**:\n\n1. **Pin dbt version в tool dependencies** — если tool depends-on schema v11, requirements должны specify `dbt-coreне меньше1.7,менее 1.8` (хотя так не делается — tools обычно поддерживают range).\n\n2. **Version detection + branched logic** — самый pragmatic подход.\n\n3. **Test on multiple dbt versions** — CI matrix дrun against v11 и v12 manifest fixtures.\n\n4. **Use dbt-core Python API** вместо direct JSON parsing — manifest schema может меняться, но `Manifest.from_dict()` knows how to deserialize.\n\n```python\nfrom dbt.contracts.graph.manifest import Manifest\n\nmanifest = Manifest.from_dict(json.load(open('target/manifest.json')))\nfor node in manifest.nodes.values():\n print(node.name)\n```\n\nЭто abstracts schema migration. Но тоже не безопасно — dbt Python API между мажорными versions меняется.\n\n**Production-grade tool**:\n- Reads schema_version first\n- Has parsers for each supported major version\n- Fails loudly on unsupported version с migration message\n- Has integration tests against all supported dbt versions\n- Documentation о supported versions\n\n**Lessons**: `metadata.dbt_schema_version` — это не cosmetic field. Production tools must validate перед reading.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Tool читает manifest.json. Команда хочет получить все public models с активным contract. Какие top-level keys и поля нужны?

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

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

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

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