Dagster数据血缘追踪与资产治理 - 第3节

Dagster 是什么?一个数据工程师的“操作系统”

说实话,我第一次接触 Dagster 的时候,心里想的是:“又一个调度框架?” 那时候我已经被 Airflow 折磨了大半年,Prefect 也试过一阵子。但真正用起来才发现,Dagster 的野心不止是做调度——它想成为数据工程的“操作系统”。

怎么理解呢?你想想看,传统的数据 pipeline,我们关注的是“什么时候跑”、“跑完没”。但 Dagster 更关心的是:“你生产了什么数据?”、“这个数据质量行不行?”、“上下游依赖清不清楚?”。说白了,它把数据本身当成了核心资产,而不是把任务执行当成核心。

Dagster 官方定义: 一个用于数据编排的开源框架,专为数据应用(数据管道、ETL、ML 训练等)的生产级开发、测试和运维而设计。

我个人习惯把 Dagster 比作“乐高积木 + 说明书”。Ops 是积木块,Assets 是搭好的模型,Jobs 是拼装步骤,Graph 是设计图纸。这套思维模型,让我在项目中少踩了很多坑。

Dagster vs Airflow vs Prefect:我选谁?

这个问题几乎每场分享都会被问到。我直接说结论:没有银弹,但有更适合的场景。下面这张表是我在实际项目中的体感对比。

维度 Airflow Prefect Dagster
核心抽象 DAG(有向无环图) Flow / Task Ops / Assets / Jobs / Graph
数据意识 弱(不感知数据产出) 中(有 Result 概念) 强(Asset 是一等公民)
本地开发 差(需要模拟调度器) 好(本地运行友好) 极好(Dagster Dev 一键启动)
测试能力 弱(依赖 mock) 中(有测试工具) 强(内置测试框架)
血缘追踪 需插件(如 Lineage) 需额外配置 原生支持(Asset 自动追踪)
学习曲线 中(概念少但坑多) 低(API 简洁) 中高(概念多但逻辑自洽)
社区生态 最成熟 快速增长 快速发展中

我的建议: 如果你团队已经深度绑定 Airflow 生态,别强行迁移。但如果是新项目,尤其重视数据质量和血缘追踪,Dagster 值得认真考虑。Prefect 更适合快速原型和中小规模场景。

Dagster 四大核心概念

嗯,这里要仔细讲。这四个概念搞懂了,Dagster 就算入门了。我当年花了整整一周才理清楚它们之间的关系,其实没那么复杂。

1. Ops:最小的计算单元

Ops 就是一段可复用的计算逻辑。你可以把它理解成一个函数,但它比函数多了输入输出声明和上下文管理。

from dagster import op

@op
def extract_data(context):
    """从 API 拉取原始数据"""
    context.log.info("开始提取数据...")
    # 模拟数据提取
    return [{"id": 1, "value": 100}, {"id": 2, "value": 200}]

@op
def transform_data(context, raw_data):
    """数据清洗和转换"""
    context.log.info(f"处理 {len(raw_data)} 条记录")
    return [{"id": d["id"], "value": d["value"] * 2} for d in raw_data]

注意看,每个 Op 都可以接收 context 参数,这里面有日志、资源配置、分区信息等。我曾经在项目中犯过一个错误:把所有逻辑塞进一个 Op 里,结果调试起来痛苦得要命。记住,一个 Op 只做一件事。

2. Assets:数据资产,Dagster 的灵魂

这是 Dagster 和其他框架最大的区别。Asset 代表一个数据产物——可以是一张表、一个文件、一个 ML 模型。你不再说“运行这个任务”,而是说“产出这个数据资产”。

from dagster import asset

@asset
def cleaned_sales_data(raw_sales_data):
    """清洗后的销售数据表"""
    # raw_sales_data 是上游 Asset 自动注入的
    cleaned = [r for r in raw_sales_data if r["value"] > 0]
    return cleaned

@asset
def daily_report(cleaned_sales_data):
    """每日销售报表"""
    total = sum(r["value"] for r in cleaned_sales_data)
    return {"total_sales": total, "record_count": len(cleaned_sales_data)}

关键理解: Asset 自动追踪上下游依赖。你不需要手动写 depends_on,Dagster 通过函数参数推断血缘关系。这就是为什么我说它“原生支持数据血缘”。

3. Jobs:把 Ops 和 Assets 串起来

Job 就是一组 Ops 或 Assets 的执行计划。你可以把它理解成“一个完整的 pipeline 实例”。

from dagster import define_asset_job

# 基于 Asset 定义 Job
sales_job = define_asset_job(
    name="sales_pipeline",
    selection=["raw_sales_data", "cleaned_sales_data", "daily_report"]
)

# 或者基于 Ops 定义 Job(传统方式)
from dagster import job

@job
def traditional_job():
    result = extract_data()
    transform_data(result)

我个人更推荐用 Asset 方式定义 Job,因为血缘关系一目了然。传统 Op 方式适合那些没有明确数据产出的“副作用”任务,比如发送告警邮件。

4. Graph:Job 的蓝图

Graph 是 Ops 之间的连接拓扑图。你可以把它理解成“可复用的子图”。当你的 pipeline 变得复杂时,Graph 能帮你组织代码。

from dagster import graph

@graph
def etl_pipeline():
    """ETL 子图,可被多个 Job 复用"""
    raw = extract_data()
    cleaned = transform_data(raw)
    return cleaned

# 在 Job 中引用 Graph
@job
def daily_etl_job():
    etl_pipeline()

@job
def weekly_etl_job():
    # 可以添加额外的处理步骤
    result = etl_pipeline()
    load_to_warehouse(result)

避坑指南: 我曾经在一个项目中过度使用 Graph 嵌套,导致调试时根本看不清执行路径。建议:Graph 嵌套不超过两层,超过就考虑拆分成独立 Job。

核心概念关系图

下面这张图是我自己总结的,每次给团队培训都用它。你看完应该能明白这四个概念是怎么配合的。

Dagster 核心概念关系图 Ops Assets Jobs Graph 产出 编排 定义拓扑 组合成 Ops 是积木块 → Graph 是设计图 → Assets 是产出物 → Jobs 是执行计划

小结:为什么我推荐你从 Assets 开始?

如果你刚接触 Dagster,我的建议是:先忘掉 Ops 和 Graph,直接从 @asset 开始写。为什么?因为 Assets 的思维方式最贴近数据工程师的日常——你每天关心的不就是“这张表什么时候更新”、“那个报表数据对不对”吗?

等你用 Assets 把 pipeline 搭起来,再回头看 Ops 和 Graph,你会发现它们只是更底层的工具。就像学开车,先学会踩油门打方向盘,再研究发动机原理,这样更顺。

快速上手路径:

  1. 用 @asset 定义你的数据产出
  2. 用 define_asset_job 创建执行计划
  3. 运行 dagster dev 启动本地 UI
  4. 在 UI 里查看自动生成的血缘图

整个过程不超过 10 分钟,你就能感受到 Dagster 的“数据优先”哲学。