跳转到主要内容
  • 过程式管线消耗的计算时间约为声明式管线的34倍,主要原因是结构无效的声明式管线中的错误会被廉价的“试运行”捕获,而试运行并不会完整执行这些管线。
  • 对于相同的管线,声明式管线的代码量少51%,因此需要阅读和审查的内容要少得多。
  • 综合来看,这些效率优势使 SDP 等声明式框架成为自主智能体构建生产管线的明确架构选择。

AI 智能体现在编写着越来越多的生产数据管线(超过70%的SDP管线 在 Databricks 上现在由智能体创建),这提高了两个方面的风险:(1) 智能体的每一次尝试都会消耗计算和时间,因此迭代需要廉价且高效地运行;(2) 代码审查成为关键关卡,因此代码需要简洁、可读且正确。Apache Spark® 提供两种编程模型来编写管线:过程式和声明式,它们在编写内容、Spark 在运行前能检查的内容以及错误的代价方面有所不同。我们用同一个智能体在相同的任务上对两者进行了基准测试,结果声明式更安全、更廉价、更精简。

过程式管线是告诉 Spark 如何逐步构建每个数据集的代码。要知道它是否有效,唯一的方法就是运行它。

声明式管线描述你想要的数据集,让 Spark 决定如何构建它们。这类似于你向 LLM 发出提示,由它来弄清楚如何完成(通过使用正确的技能和工具)。用代码来展示一个简单的拼写错误会如何失败,最能说明这一点:

过程式声明式
bronze = spark.read.parquet(RAW)
bronze.write.saveAsTable("bronze")
silver = 
bronze.withColumnRenamed("revenue", 
"revenues")  # typo
silver.write.saveAsTable("silver")
gold = 
spark.read.table("silver").groupBy("revenue")
.sum("revenue")
gold.write.saveAsTable("gold")  
# runtime error
@dp.materialized_view(name="gold")
def gold():
    return 
spark.read.table("silver").groupBy("revenue")
.sum("revenue")

1. 声明式管线可以在运行前进行验证

缺陷分为两类。结构性缺陷可以通过阅读管线来发现:未解析的列引用、尚不存在的表、不允许的配置更改。静默缺陷只有在运行结束、答案错误时才会在输出中显现。

声明式管线可以在运行前检查结构性缺陷,类似于 Rust 等类型化语言中的数据流可以在编译时被检查。由于声明式管线是描述而非运行,Spark 可以通过阅读它来回答三类问题:

  • 每个列引用都会根据其读取的数据集的模式进行解析,因此不存在的列会被拒绝,而不是在运行时失败。
  • 数据集之间的每个依赖关系都会与图中的生产者匹配,因此对没有任何声明所引用的表的引用会在管线启动前被捕获。
  • 图会检查完整性和顺序,因此由引擎决定何时运行什么,而不是信任模型碰巧写出的顺序。

2. 声明式管线将样板任务卸载给 Spark

在过程式 PySpark 中,AI 编码智能体编写数据操作,例如过滤无效行和连接表。它还会编写代码来按正确顺序运行管线的各个步骤并保存其结果。这种支持性代码在多个管线中重复了大量相同的样板内容。

在声明式管线中,AI 智能体定义每个数据集以及生成它的查询。Spark 处理执行顺序并保存结果。由于 Spark 处理这些样板内容,智能体编写的代码更少,团队需要审查和维护的内容也更少。

我们测试了什么

我们测试了22个数据工程任务。每个任务看起来都像一张真实的工单:它描述了一个问题,并指定了管线必须生成的表、列名和数据类型。智能体可以选择任何实现,只要输出符合这些要求即可。

对于每个任务,我们生成了12个不同的输入数据集,其中包含生产数据中常见的各种杂乱值。智能体为每个数据集编写了一个使用过程式 PySpark 的新管线,以及另一个使用 Spark 声明式管线的管线。这样每种方法产生264次构建,总计528次:22个任务 × 12个数据集 × 2种方法。所有构建均使用 Opus 4.8。

我们运行了每个管线,并对照所需的表检查其输出。自动评分器不知道结果是哪种方法产生的。声明式管线首先经过 Spark 的试运行。如果构建失败,智能体可以修改并重试,直到达到固定上限。

你可以从 公共仓库自行重新运行整个过程:包括设置、提示、数据集、评分器以及每个数字背后的结果。

结果

两种方式几乎完成了所有任务,过程式完成了264个中的255个,声明式完成了264个中的258个,因此任何一种都能让你得到可用的管线。不同之处在于围绕它的一切:错误何时显现、修复需要多少代码,以及输出的一致性如何。下面我们介绍声明式管线的三个优势,随后是两点注意事项。

1. 运行前验证使声明式管线的时间减少约34倍

管线的错误会在执行前、运行期间或根本不显现。过程式管线在执行前无法阻止任何错误,因为它们没有预执行阶段。要发现结构性错误,Spark 必须在集群上运行管线,直到到达出错的步骤。

在我们测量的构建中,过程式运行使用了596个执行器秒,而声明式运行仅使用了17个。对声明式代码的试运行验证及早捕获了结构性错误,将计算量减少了34倍。

Procedural Pipelines

2. 声明式管线代码的规模是过程式管线的一半

在已完成的构建中,声明式管线的规模约为一半,中位数为64行,而过程式管线为131行。

过程式实现的变化也更大。对于相同的任务,一些使用 Spark 转换,而另一些则将数据加载到内存中并逐行处理。两者在测试数据上都通过了,但内存版本在数据增长时可能会失败。在整个基准测试中,有19个过程式构建使用了这种捷径。声明式构建则没有一个这样做。

更短、更一致的代码让团队需要审查的内容更少,并且只需维护一种模式。

Declarative Pipeline Code

3. 在逻辑错误方面,两者打成平手

以上就是两项优势。在运行测试时,我们还发现了两点注意事项。

第一,试运行只能捕获结构性错误。它无法捕获静默错误:即管线运行正常却返回错误答案,而这是更危险的一类错误。在这一点上,两者打成平手:它们的静默错误率彼此相差约15%以内,差距太小,不足以构成真正的差异。声明式让结构性错误的捕获成本很低;它对逻辑错误毫无帮助,逻辑错误仍然需要测试和审查。

4. 声明式前期投入更多,后期节省更多

声明式在某一处投入更多。智能体会在试运行上反复迭代,不断重试,直到管线健全可靠,因此它把更多思考以token的形式前置完成,而不是等到之后在集群上才发现问题。这些token是一次性的固定开销,在编写管线时支付;其数量不随数据规模或管线运行频率而变化。浪费的集群计算则恰恰相反:它会在每一次失败的运行中反复出现,并随数据增长而增加,因此你处理的数据越多,一次糟糕构建的代价就越高。前期支付固定成本以避免一项不断增长的成本,随着管线和数据规模变大,只会显得越来越划算。

我们测量的一切,并排对比

测量到的全部差异如下所示。

维度过程式 PySpark声明式(SDP)
智能体产出什么可运行的代码一份会被检查的描述
结构性错误在运行中途、部分写入之后才暴露,或永远不暴露;没有任何一项在输出前被阻止在试运行时被捕获;264项中有55项在任何输出前被阻止
成本

集群时间: 约高34倍(一次失败运行最高约521执行器秒)

LLM token: 基线

集群时间: 基线(一次失败运行约0.5执行器秒)

LLM token:约2.3倍(构建中位数约0.50美元对0.26美元,按Opus 4.8标价)

代码规模基线

代码行数: 约少51%(中位数)

AST规模: 约小44%

静默缺陷(运行正常、答案错误)264项中73项(27.7%)264项中85项(32.2%)

结论

在生产规模下,声明式方法更胜一筹。它在LLM token上的成本约高出2.3倍,作为交换,它绝不会把集群时间花在一条根本不可能成功的管线上。在生产规模下,声明式方法通过以略高的LLM token成本换取对失败管线浪费集群时间的可靠预防,证明了自己的优势。随着数据量扩大,这会变得越来越划算,因为计算浪费会按比例增长,而token成本保持不变。

如果你正在评估用于管线工作的智能体,请像重视选择哪个智能体一样,重视智能体如何编写。更好的智能体犯更少的错误;声明式管线让错误的代价很低,并在它们造成任何损失之前将其捕获。

该 仓库 记录了设计、数据和完整的来源信息。要开始让你的智能体使用 Spark 声明式管线,请参阅 官方编程指南 ,并将 SDP skill 与 PySpark 一起使用。

常见问题

我必须信任这个AI智能体,它才能工作吗?

不需要,而不必信任正是关键所在。声明式智能体只会编写声明式管线代码——一份它想要的表的描述——而Spark本身会在任何东西运行之前检查这份描述。检查器是平台的一部分:你信任的是Spark对管线的评估,而不是模型。智能体从不运行代码,也不自行读取你的数据。

这仅涉及 Spark 声明式管线,还是适用范围更广?

它更广泛地适用于声明式方法:在任何你能将“这是我想要的”与“现在去构建它”分开的地方,这样第一部分就可以被单独检查。你在SQL本身、基础设施即代码工具以及模式优先的API中都能看到同样的形态。Spark 声明式管线是我们进行基准测试的那一个,但其推理适用于任何以这种方式构建的工具。

声明式智能体使用的token是2.3倍。这难道不是致命问题吗?

在现实的管线规模下并非如此。按Opus 4.8标价,2.3倍意味着每次声明式构建中位数约0.50美元的token成本,而过程式为0.26美元。一次失败的过程式运行烧掉了约521执行器秒,而一次被拒绝的声明式运行仅花费约半秒——因此避免几次糟糕的运行就能覆盖大量额外token,而且随着数据量增长,差距只会扩大。token价格也会随你使用的模型服务而波动,而计算成本更稳定,也更容易规划。

相关标签:

数据工程 和 开源

获取最新文章,直达你的收件箱

订阅我们的博客,最新文章将直接发送到你的收件箱。

注册

查看所有博客 slice-start id="_gatsby-scripts-1" slice-end id="_gatsby-scripts-1"