spark.read.parquet与format.load读取parquet速度巨大差异原因咨询
Spark Parquet两种写法读取性能巨大差异的核心原因
首先明确基础事实:原生Spark源码中spark.read.parquet(path)本身就是spark.read.format("parquet").load(path)的语法糖,二者在API层面没有任何逻辑差异,不存在原生性能差。观测到的14分钟与2秒的悬殊耗时,完全是运行环境(路径特征指向Databricks环境)的路径解析逻辑、执行阶段判定、隐式配置触发三类差异导致的,按影响权重排序如下:
- 路径协议解析错误触发非分布式读取(核心诱因)
路径前缀/dbfs/是Databricks提供的DBFS文件系统FUSE本地挂载标识:- 调用
spark.read.format("parquet").load("/dbfs/xxx")时,高版本Databricks Runtime会自动识别路径归属DBFS,将路径重写为分布式协议路径dbfs:/FileStore/path/to/file.parquet,走分布式分片读取逻辑,整个read阶段仅做Parquet footer元数据解析、逻辑执行计划生成,属于轻量元数据操作,耗时仅2秒——这个阶段根本没有读取实际存储的业务数据。 - 调用
spark.read.parquet("/dbfs/xxx")时,老版本Databricks Runtime、或配置了自定义Parquet读取规则的环境不会触发上述路径重写,会将/dbfs/开头的路径识别为节点本地文件系统(file://协议)路径,强制触发非分布式读取逻辑:首先要把路径下所有Parquet文件全量拉取到Driver节点本地临时目录,再执行本地扫描。3000万行38列的Parquet全量拉取本身就会消耗大量时间,若Driver节点磁盘、内存规格不足,还会触发频繁磁盘溢写,直接将耗时拉到10分钟以上级别。
- 调用
- 执行阶段混淆导致计时基准不一致
Spark DataFrameReader的所有读取方法默认都是懒执行的:无论哪种写法,调用read方法本身不会触发实际数据读取,只有执行count()、show()、write()这类Action算子时才会真正拉取数据。观测到的14分钟耗时,本质上是第一种写法的代码块隐式触发了Action(比如同代码块执行了结果预览、全量count、环境自动开启了读取后schema校验/统计信息收集),而第二种写法仅单独统计了read阶段的元数据操作耗时,二者对比的根本不是同一个执行阶段的耗时。 - 隐式配置触发元数据全量扫描
部分定制版Spark/Databricks Runtime中,直接调用spark.read.parquet()方法会默认开启mergeSchema、parquet.enable.summary-metadata等重配置:递归扫描路径下所有Parquet文件的footer、跨文件合并schema、读取全量文件的统计元数据。如果路径下Parquet分片数量较多,仅这个元数据扫描过程就会消耗数分钟到十几分钟;而调用spark.read.format("parquet").load()时,未显式传入上述参数的情况下会走默认优化配置,仅读取必要的分片footer元数据,不会做全路径递归扫描,元数据操作速度极快。
验证与修复方案
- 分布式读取Parquet时不要使用
/dbfs/前缀的FUSE本地路径,直接将路径替换为dbfs:/FileStore/path/to/file.parquet,两种写法的性能表现会完全一致。 - 性能测试时拆分执行阶段计时:单独统计read方法的元数据操作耗时,再单独统计触发Action后的实际数据读取耗时,不要混同两个阶段的耗时做对比。
- 无schema合并需求时,读取Parquet显式添加
.option("mergeSchema", "false")配置,关闭无意义的全量元数据扫描。
内容的提问来源于stack exchange,提问作者Vishal Balaji
相关产品推荐
相关产品推荐

