run_results.json: schema v6, status, timing, adapter_response
target/run_results.json — это execution log dbt run. После каждой dbt команды (run, test, build, seed, snapshot, compile, source freshness), dbt пишет результаты выполнения сюда. Manifest описывает что dbt должен делать, run_results — что произошло.
Senior должен знать структуру run_results наизусть: schema v6 поля, status enum, timing structure, adapter_response — это foundation для observability tools, cost tracking, performance dashboards.
logs/dbt.log: чтение, фильтрация и типовые паттерны (dbt I)Когда создается
run_results пишется в конце каждого dbt invocation:
dbt run -> target/run_results.json
dbt test -> target/run_results.json (overwrites)
dbt build -> target/run_results.json
dbt source freshness -> target/sources.json (отдельно)
dbt parse -> НЕ создает run_results (только parsing)
Note: каждый run перезаписывает run_results. Для observability tooling — копируйте immediately после run.
Top-level структура
{
"metadata": {
"dbt_schema_version": "https://schemas.getdbt.com/dbt/run-results/v6.json",
"dbt_version": "1.11.0",
"generated_at": "2026-05-19T11:23:14.812345Z",
"invocation_id": "a3f5e8c2-...",
"env": {}
},
"results": [
{
"status": "success",
"timing": [...],
"thread_id": "Thread-1 (worker)",
"execution_time": 12.456,
"adapter_response": {...},
"message": null,
"failures": null,
"unique_id": "model.jaffle_shop.fct_orders",
"compiled": true,
"compiled_code": "MERGE INTO ...",
"relation_name": "\"jaffle_shop\".\"main\".\"fct_orders\""
}
],
"elapsed_time": 234.567,
"args": {
"which": "run",
"rpc_method": null,
"log_format": "default",
"select": ["state:modified+"],
"exclude": null,
"vars": {},
...
}
}
Top-level keys:
- metadata — invocation info (matches manifest.metadata.invocation_id)
- results — array of per-node results
- elapsed_time — total wall-clock time
- args — command arguments + flags + vars
metadata
{
"dbt_schema_version": "https://schemas.getdbt.com/dbt/run-results/v6.json",
"dbt_version": "1.11.0",
"generated_at": "2026-05-19T11:23:14.812345Z",
"invocation_id": "a3f5e8c2-...",
"env": {
"DBT_USER": "alice",
"DBT_TARGET": "prod"
}
}
dbt_schema_version— formal schema (v6 для dbt 1.6+)invocation_id— matches manifest.metadata.invocation_id. Связывает run_results с produced manifest.env— env vars dbt видел при run startgenerated_at— UTC timestamp
`invocation_id` critical для observability tooling. Match run_results к correct manifest через invocation_id. Mismatch = drift между artifacts.
results array
Каждый result — execution outcome для one node:
{
"status": "success",
"timing": [
{
"name": "compile",
"started_at": "2026-05-19T11:23:01.123456Z",
"completed_at": "2026-05-19T11:23:01.234567Z"
},
{
"name": "execute",
"started_at": "2026-05-19T11:23:01.234567Z",
"completed_at": "2026-05-19T11:23:13.580123Z"
}
],
"thread_id": "Thread-2 (worker)",
"execution_time": 12.456,
"adapter_response": {
"_message": "MERGE 1500 0",
"code": "MERGE",
"rows_affected": 1500,
"bytes_processed": 124567890,
"query_id": "01abc..."
},
"message": null,
"failures": null,
"unique_id": "model.jaffle_shop.fct_orders",
"compiled": true,
"compiled_code": "MERGE INTO \"jaffle_shop\".\"main\".\"fct_orders\" ...",
"relation_name": "\"jaffle_shop\".\"main\".\"fct_orders\""
}
Полное описание каждого поля ниже.
status — execution outcome
"status": "success"
Possible values:
success— node executed successfullyerror— execution failed (SQL error, adapter exception)skipped— selected but not run (e.g., upstream failed, no relevant changes для defer)pass— only для tests, means assertion passedfail— only для tests, assertion failed (rows returned)warn— only для tests, warning severity threshold exceededruntime error— adapter could not execute (connection issue, etc.)partial success— some batches succeeded, others failed (microbatch)
Different resource types have different status semantics:
# Models, snapshots, seeds:
# 'success' or 'error' or 'skipped'
# Tests:
# 'pass' or 'fail' or 'warn' or 'error' or 'skipped'
# Operations:
# 'success' or 'error'
timing — execution phases
"timing": [
{
"name": "compile",
"started_at": "2026-05-19T11:23:01.123456Z",
"completed_at": "2026-05-19T11:23:01.234567Z"
},
{
"name": "execute",
"started_at": "2026-05-19T11:23:01.234567Z",
"completed_at": "2026-05-19T11:23:13.580123Z"
}
]
Array — каждый element — phase of execution.
Common phases:
compile— rendering Jinja templates -> SQLexecute— running SQL against warehouse
Snapshots, seeds, complex materializations могут add more phases.
Compute phase duration:
from datetime import datetime
for phase in result['timing']:
start = datetime.fromisoformat(phase['started_at'].rstrip('Z'))
end = datetime.fromisoformat(phase['completed_at'].rstrip('Z'))
duration = (end - start).total_seconds()
print(f" {phase['name']}: {duration:.3f}s")
Sum of phase durations может slightly differ от `execution_time` (overhead, IO). Используйте `execution_time` для overall, timing для phase breakdown.
thread_id
"thread_id": "Thread-2 (worker)"
dbt uses parallel execution через worker threads (threads в profiles.yml). thread_id identifies which thread executed this node.
Useful for:
- Concurrency analysis (which threads handled which models)
- Identifying serialization bottlenecks
- Multi-thread observability
from collections import defaultdict
by_thread = defaultdict(list)
for result in run_results['results']:
by_thread[result['thread_id']].append(result['execution_time'])
for thread, times in by_thread.items():
total = sum(times)
print(f'{thread}: {len(times)} tasks, total {total:.1f}s')
execution_time
"execution_time": 12.456
Wall-clock seconds для full execution (compile + execute + any overhead).
Sum across all results approximates wall-clock total (if threads=1), но parallelism makes actual elapsed_time shorter.
adapter_response — warehouse details
{
"_message": "MERGE 1500 0",
"code": "MERGE",
"rows_affected": 1500,
"bytes_processed": 124567890,
"query_id": "01abc-2345-..."
}
Adapter-specific structure. dbt-core defines minimum fields, adapters can extend.
Common fields
_message— human-readable status (legacy field)code— SQL command type (CREATE, INSERT, UPDATE, MERGE, COPY, etc.)rows_affected— rows changed by DMLquery_id— warehouse-specific query identifier
Adapter-specific extensions
Snowflake:
{
"code": "MERGE",
"rows_affected": 1500,
"bytes_processed": 124567890,
"query_id": "01abc-...",
"credits_used": 0.0042,
"execution_time_ms": 12456,
"warehouse": "PROD_WH"
}
BigQuery:
{
"code": "DML",
"rows_affected": 1500,
"bytes_processed": 124567890,
"query_id": "job_abc123",
"slot_ms": 23456789,
"total_slot_ms": 23456789,
"total_bytes_billed": 100000000
}
Redshift:
{
"code": "INSERT",
"rows_affected": 1500,
"query_id": "abc123"
}
DuckDB:
{
"_message": "OK",
"code": "CREATE TABLE AS",
"rows_affected": 1500,
"query_id": null
}
DuckDB is local — no query_id для tracking.
message and failures
"message": null,
"failures": null
message— для errors, contains error messagefailures— для tests, integer count of failed rows
Error example:
{
"status": "error",
"unique_id": "model.jaffle_shop.fct_orders",
"message": "Database Error in model fct_orders (models/marts/fct_orders.sql)\n Column \"order_idd\" does not exist...",
"failures": null
}
Test failure:
{
"status": "fail",
"unique_id": "test.jaffle_shop.not_null_fct_orders_id",
"message": "Got 5 rows where expected 0",
"failures": 5
}
compiled_code и compiled
"compiled": true,
"compiled_code": "MERGE INTO ..."
Final SQL что dbt actually sent к warehouse. Useful для debugging — exact query что executed.
Note: для some node types (operations, snapshots), compiled_code может быть multi-statement.
relation_name
"relation_name": "\"jaffle_shop\".\"main\".\"fct_orders\""
Fully qualified warehouse identifier. Quoted properly для adapter syntax.
Useful для:
- Joining к warehouse metadata (INFORMATION_SCHEMA)
- Generating cleanup scripts
- Identifying actual tables/views в warehouse
elapsed_time — total wall-clock
"elapsed_time": 234.567
Total seconds от dbt start к finish. Includes:
- Parsing time
- Compilation
- Execution
- Final teardown
For parallel runs с threads > 1, elapsed_time < sum(execution_time).
args — invocation context
"args": {
"which": "build",
"select": ["state:modified+"],
"exclude": null,
"selector": null,
"vars": {"my_var": "foo"},
"target": "prod",
"full_refresh": false,
"fail_fast": false,
"threads": 8,
"log_format": "json",
"log_level": "info",
"warn_error": false
}
Full command-line args + config. Useful для reproducing run.
Реальный fragment — successful incremental run
{
"status": "success",
"timing": [
{
"name": "compile",
"started_at": "2026-05-19T11:23:01.123Z",
"completed_at": "2026-05-19T11:23:01.234Z"
},
{
"name": "execute",
"started_at": "2026-05-19T11:23:01.234Z",
"completed_at": "2026-05-19T11:23:13.580Z"
}
],
"thread_id": "Thread-2 (worker)",
"execution_time": 12.456,
"adapter_response": {
"_message": "MERGE 1500 0",
"code": "MERGE",
"rows_affected": 1500,
"bytes_processed": 124567890,
"query_id": "01abc..."
},
"message": null,
"failures": null,
"unique_id": "model.jaffle_shop.fct_orders",
"compiled": true,
"compiled_code": "MERGE INTO \"jaffle_shop\".\"main\".\"fct_orders\" AS DBT_INTERNAL_DEST USING (...) ...",
"relation_name": "\"jaffle_shop\".\"main\".\"fct_orders\""
}
Incremental MERGE updated 1500 rows. 12.5 seconds. 125MB bytes processed.
Failed test example
{
"status": "fail",
"timing": [
{"name": "compile", "started_at": "...", "completed_at": "..."},
{"name": "execute", "started_at": "...", "completed_at": "..."}
],
"thread_id": "Thread-1 (worker)",
"execution_time": 2.34,
"adapter_response": {
"_message": "SELECT 5",
"code": "SELECT",
"rows_affected": -1
},
"message": "Got 5 rows where expected 0",
"failures": 5,
"unique_id": "test.jaffle_shop.not_null_fct_orders_id.abc12",
"compiled": true,
"compiled_code": "SELECT * FROM \"jaffle_shop\".\"main\".\"fct_orders\" WHERE id IS NULL",
"relation_name": null
}
5 rows violated not_null constraint.
Skipped node example
{
"status": "skipped",
"timing": [],
"thread_id": "Thread-3 (worker)",
"execution_time": 0.0,
"adapter_response": {},
"message": "Skipped due to upstream failure",
"failures": null,
"unique_id": "model.jaffle_shop.dim_customers",
"compiled": false,
"compiled_code": null,
"relation_name": null
}
dbt didn’t execute (upstream model failed).
Парсинг run_results в Python
import json
from collections import Counter
from datetime import datetime
run_results = json.load(open('target/run_results.json'))
# Status breakdown
statuses = Counter(r['status'] for r in run_results['results'])
print(f'Status: {statuses}')
# Total duration
total = sum(r['execution_time'] for r in run_results['results'])
print(f'Total execution time: {total:.1f}s')
print(f'Wall-clock elapsed: {run_results["elapsed_time"]:.1f}s')
print(f'Parallelism speedup: {total / run_results["elapsed_time"]:.1f}x')
# Failed
failed = [r for r in run_results['results'] if r['status'] in ('error', 'fail')]
for f in failed:
print(f"\nFAILED: {f['unique_id']}")
print(f" Status: {f['status']}")
print(f" Message: {f['message'][:200] if f['message'] else 'N/A'}")
if f.get('failures'):
print(f" Failures: {f['failures']}")
# Slowest nodes
slowest = sorted(run_results['results'], key=lambda r: -r['execution_time'])[:10]
print('\nTop 10 slowest:')
for r in slowest:
print(f" {r['unique_id']}: {r['execution_time']:.1f}s")
Output:
Status: Counter({'success': 47, 'pass': 28, 'fail': 1, 'skipped': 2})
Total execution time: 145.6s
Wall-clock elapsed: 24.3s
Parallelism speedup: 6.0x
FAILED: test.jaffle_shop.not_null_fct_orders_id.abc12
Status: fail
Message: Got 5 rows where expected 0
Failures: 5
Top 10 slowest:
model.jaffle_shop.fct_orders: 45.2s
model.jaffle_shop.fct_revenue: 32.1s
...
Use cases run_results
1. Performance dashboard
import pandas as pd
df = pd.DataFrame([
{
'unique_id': r['unique_id'],
'status': r['status'],
'execution_time': r['execution_time'],
'thread_id': r['thread_id'],
'rows_affected': r.get('adapter_response', {}).get('rows_affected'),
'bytes_processed': r.get('adapter_response', {}).get('bytes_processed')
}
for r in run_results['results']
])
# Aggregate
print(df.groupby('status')['execution_time'].sum())
print(df.nlargest(10, 'execution_time'))
2. Cost tracking
# Snowflake credits = bytes_processed / X (warehouse-specific)
total_bytes = sum(
r.get('adapter_response', {}).get('bytes_processed', 0)
for r in run_results['results']
)
print(f'Total bytes: {total_bytes / 1e9:.1f} GB')
3. Anomaly detection
# Compare runs over time
# If fct_orders typically takes 12s but ran 60s today -> alert
historical = load_historical_runs(unique_id='model.proj.fct_orders')
current = next(r for r in run_results['results'] if r['unique_id'] == 'model.proj.fct_orders')
avg = sum(h['execution_time'] for h in historical) / len(historical)
if current['execution_time'] > avg * 3:
print(f'ANOMALY: 3x slower than average')
4. Slack notifications
failed_tests = [
r for r in run_results['results']
if r['status'] == 'fail'
]
for failed in failed_tests:
send_slack({
'channel': '#data-quality',
'message': f'Test failed: {failed["unique_id"]}\n'
f'Failures: {failed["failures"]}\n'
f'Compiled SQL: {failed["compiled_code"][:500]}'
})
Combining manifest + run_results
Critical pattern — enrich run_results с manifest metadata:
manifest = json.load(open('target/manifest.json'))
run_results = json.load(open('target/run_results.json'))
# Verify invocation_id matches
assert manifest['metadata']['invocation_id'] == run_results['metadata']['invocation_id'], \
'Manifest and run_results from different runs!'
for r in run_results['results']:
uid = r['unique_id']
node = manifest['nodes'].get(uid) or manifest.get('sources', {}).get(uid)
if not node:
continue
# Enriched record
enriched = {
# From run_results
'status': r['status'],
'duration': r['execution_time'],
'rows_affected': r.get('adapter_response', {}).get('rows_affected'),
# From manifest
'resource_type': node['resource_type'],
'name': node['name'],
'materialized': node.get('config', {}).get('materialized'),
'tags': node.get('tags', []),
'owner': node.get('meta', {}).get('owner'),
'access': node.get('config', {}).get('access')
}
store_к_warehouse(enriched)
This — основа всех observability tools.
Schema v6 detail
Поля специфичны для v6 (dbt 1.6+). Earlier versions имеют subset:
- v3 (dbt 0.20) — basic
- v4 (dbt 1.0) — added some fields
- v5 (dbt 1.2)
- v6 (dbt 1.6+) — current
v7 expected для dbt 1.12+ (semantic layer updates).
Antipatterns
1. Not validating invocation_id match
# BAD — using run_results from different run
run_results = json.load(open('target/run_results.json'))
manifest = json.load(open('artifacts/old_manifest.json'))
# Different invocations — fields don't correlate
Always check invocation_id consistency.
2. Assuming all results have adapter_response
# BAD
rows = r['adapter_response']['rows_affected']
# Skipped nodes: adapter_response = {}, KeyError
Use .get():
rows = r.get('adapter_response', {}).get('rows_affected')
3. Treating failures as error
# BAD
if r['status'] == 'error':
handle_failure(r)
# Misses 'fail' status (tests)
Multi-status check:
if r['status'] in ('error', 'fail', 'runtime error'):
handle_failure(r)
4. Trusting timing for cost
# BAD — assumes execution_time × hourly rate
cost = r['execution_time'] / 3600 * hourly_rate
# Snowflake bills based on warehouse size and time, not just duration
# Use bytes_processed or credits_used from adapter_response
Use adapter_response fields для accurate cost.
Ключевые выводы
- run_results.json — execution log dbt run. Overwritten each invocation.
- invocation_id matches manifest — критично для observability.
- status enum: success/error/skipped (models), pass/fail/warn (tests), partial success (microbatch).
- timing array — phases (compile, execute) с timestamps.
- execution_time — wall-clock seconds для full execution.
- adapter_response — warehouse details: rows_affected, bytes_processed, query_id; adapter-specific extensions.
- message + failures — error details (model) или test failure count.
- compiled_code — exact SQL sent to warehouse.
- elapsed_time — total wall-clock; parallelism makes < sum(execution_time).
- args — full invocation context.
- Combine manifest + run_results через invocation_id для full observability.
- Schema v6 для dbt 1.6+; v7 expected с MetricFlow updates.