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:
RuntimeConfigзагружаетdbt_project.yml,profiles.yml, vars, target.ManifestLoaderобходит все.sql,.py,.ymlфайлы черезtree-sitterstatic parser.- Каждый файл превращается в
ParsedNode(для models/tests/snapshots/seeds),ParsedSourceDefinition,ParsedMacro, и т.д. Linkerстроит граф зависимостей (networkxDAG), вычисляетdepends_on.nodes.- Результат сериализуется в
manifest.json(плюсtarget/partial_parse.msgpackдля следующего запуска).
Любая команда, которая требует graph (run, test, build, compile, parse, ls, docs generate), сначала загружает или строит manifest. Если есть актуальный partial_parse.msgpack и ни один файл не изменился — manifest восстанавливается мгновенно (~100ms). Если что-то изменилось — partial reparse только изменённой части.
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 конкретногоdbtinvocation. Связывает manifest сrun_results.jsonтого же запуска.project_name— изdbt_project.yml.adapter_type— duckdb / snowflake / postgres / bigquery / и т.д.
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-endhooks) - 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 эти поля пусты.
В 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, и т.д.).
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.
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.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 по-разному, но фундамент один.
Ключевые выводы
- manifest.json — central artifact dbt-core. Производится в конце parsing phase, содержит всю project graph state.
- 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.
- nodes — самый большой key. Models/tests/snapshots/seeds/analyses/operations с unique_id, refs, depends_on, config, columns.
- sources отдельный key (freshness + external_location).
- macros хранит весь Jinja source (dbt + packages + custom). 60-80% размера manifest.
- parent_map / child_map — flattened graph для O(1) lookup в обе стороны.
- disabled — отдельный bucket для
config: {enabled: false}nodes. - semantic_models / saved_queries добавлены с 1.6 для MetricFlow.
- unit_tests вынесены в top-level с 1.8.
- Schema versioning через
metadata.dbt_schema_versionURL — tools должны проверять перед парсингом. - Размер растёт нелинейно с проектом: macros доминируют, потом nodes; mesh projects могут давать 1GB+ manifest.
- Tooling экосистема (Elementary, dbt-osmosis, dbt-coverage, dbt-meshify) — все строятся на manifest.