2026年1月9日,Spark 4.1.1正式发布。同一天,Uber的数据团队里有人重跑了一段5年前的旧代码——结果CPU占用掉了40%。没改业务逻辑,没换集群配置,只升级了版本。这背后藏着一个被误解了10年的设计:你的代码,其实什么都没做。
四行代码,零字节执行
看这段典型代码:
df = spark.read.parquet('s3://uber/trips/') filtered = df.filter(col('country') == 'US') joined = filtered.join(drivers_df, on='driver_id') result = joined.groupBy('city').agg(sum('fare').alias('revenue'))
四行写完,如果你此刻去监控集群,会看到诡异画面:网络流量为零,磁盘IO为零,Executor内存纹丝不动。你的代码像一封没寄出的信,静静躺在Driver的内存里。
直到你写下result.show(20)或result.write.parquet(...)——这些叫Action的操作——整个集群才突然苏醒。Spark把这个设计叫Lazy Evaluation(惰性求值)。翻译成人话:先记账,后买单。
2012年Matei Zaharia在伯克利写Spark论文时,这个选择曾引发争议。同期的MapReduce是即时执行,写一行跑一行,像老式收银机咔哒咔哒响。Spark的账本模式更像信用卡:攒够一笔,统一结算。代价是延迟反馈,收益是全局优化。
DAG:Spark的记账本长什么样
那四行代码在内存里变成一棵树。根节点是读取Parquet,叶子节点是按城市聚合。中间分叉:过滤、关联、分组。这棵树叫DAG,有向无环图(Directed Acyclic Graph)。
DAG的妙处在于,Spark能看到完整剧本。它发现你在关联前先过滤了国家,于是把过滤器推到数据源——S3只返回美国数据,而不是全量拉取再过滤。它发现你的三个窄变换(filter、select、withColumn)可以串成一条流水线,一次内存遍历完成,不需要落盘。
2016年Spark 2.0统一了DataFrame和SQL引擎,DAG的优化能力第一次完整释放。当时Databricks内部测试显示,同样的TPC-DS查询,手工调优的RDD代码被自动优化后的SQL反超30%。很多老工程师至今记得那个屈辱的周一晨会。
但DAG是静态的。它基于你写的代码生成,运行前锁定。如果数据分布和假设不符——比如某个城市的司机ID异常集中——计划不会变。这就是Spark 3.0要解决的问题。
Catalyst:会进化的账本
Catalyst优化器2015年随Spark 1.3登场,是DAG之上的第二层大脑。它的工作分四步:分析(解析列名、类型)、优化(应用规则)、物理计划(选择算法)、代码生成(把逻辑翻译成JVM字节码)。
最狠的是代码生成。传统数据库用解释器执行,每行数据都要走一遍虚函数调用。Catalyst把整个查询编译成单条JVM方法,循环展开、SIMD向量化、分支预测友好。2016年Databricks的测试,代码生成让过滤操作快了10倍。
但Catalyst仍是编译期优化。它不知道运行时数据长什么样。2019年Spark 3.0引入AQE(Adaptive Query Execution,自适应查询执行),账本终于能改。
AQE在Shuffle边界插入检查点。第一阶段跑完,Spark看一眼实际数据量:预估100万行的表实际只有5万?把Sort-Merge Join换成Broadcast Join,小表直接广播到各节点,省去Shuffle。某个分区异常膨胀?动态拆分,避免单点拖垮全局。
Netflix 2021年的生产案例:一个ETL作业因数据倾斜卡在99%三小时,开启AQE后降到12分钟。不是工程师调优了,是Spark在运行时自己改了计划。
Spark 4.x:账本系统的第三次革命
2024年Spark 4.0预览版流出时,内部代号叫"Catalyst 2.0"。正式文档里没这说法,但架构确实重构了。核心变化:优化器从批处理变成增量式,计划可以边执行边调整,不再依赖Shuffle边界。
更隐蔽的改动在内存管理。Spark 3.x的内存模型分Execution和Storage两块,动态借用机制复杂且容易OOM。4.0引入统一内存池,用类似JVM G1的Region设计,GC停顿时间从秒级降到毫秒级。Databricks基准测试显示,交互式查询的P99延迟下降了60%。
2026年1月的4.1.1是稳定版。官方发布说明里有一句容易被忽略:「改进Python UDF的向量化执行,Pandas UDF开销降低至原生Scala的1.2倍」。这意味着PySpark——被诟病多年的性能短板——终于追上第一梯队。
一个细节:4.x的Spark UI重写了SQL标签页。以前看计划图要眯眼找瓶颈,现在时间轴直接标红慢算子,像Profiler的热力图。产品经理出身的工程师会懂这个改动:降低观测成本,比优化执行本身更能提升效率。
为什么现在必须理解这些
2025年Gartner报告显示,73%的企业数据平台基于Spark或其商业发行版。但调研同时暴露一个断层:只有12%的工程师能解释Lazy Evaluation和AQE的交互原理。大多数人停留在「Spark很快」的模糊认知,遇到性能问题时盲目调参,或干脆加机器。
这种认知税在云时代更贵。AWS EMR和Databricks按秒计费,一个未触发剪枝的全表扫描,可能让本可10美元解决的作业烧掉800美元。2024年某金融科技公司的账单审计发现,31%的Spark开销源于「过早物化」——工程师在调试时反复调用.count()或.show(),每次都在集群上完整跑一遍计划。
理解账本系统的运作,本质是理解「延迟」的价值。不是拖延,是等待信息完备。Spark 4.x把这条原则推到极致:不仅延迟执行,还延迟决策,让优化器在最后一刻拥有最多信息。
回到Uber那个案例。升级4.1.1后降了40% CPU,不是因为新功能,而是因为AQE的增强版更激进地选择了Broadcast Join——而他们的数据分布恰好符合触发条件。同样的版本,数据特征不同的公司可能毫无感知。
这引出一个尴尬事实:Spark的性能是概率性的。同样的代码,不同的数据,不同的表现。4.x没有消除这种不确定性,只是让系统更善于应对它。
你的集群上周有没有出现「计划选择正确但执行缓慢」的查询?Spark UI里那个标红的Exchange算子,是数据倾斜还是预估偏差——你分得清吗?
热门跟贴