Spark SQL----Performance Tuning性能调优
对于某些工作负载,可以通过在内存中缓存数据或打开一些实验性选项来提高性能。
一、在内存中缓存数据
Spark SQL可以通过调用spark.catalog.cacheTable(“tableName”)或dataFrame.cache()来使用内存中的列格式来缓存表。然后Spark SQL将只扫描所需的列,并自动调整压缩以最小化内存使用和GC压力。你可以调用spark.catalog.uncacheTable(“tableName”)或dataFrame.unpersist()从内存中删除表。内存缓存的配置可以使用SparkSession上的setConf方法或使用SQL运行SET key=value命令来完成。
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.inMemoryColumnarStorage.compressed | true | 当设置为true时,Spark SQL将根据统计数据自动为每列选择一个压缩编解码器。 | 1.0.1 |
spark.sql.inMemoryColumnarStorage.batchSize | 10000 | 控制列式缓存的批大小。较大的批处理大小可以提高内存利用率和压缩,但在缓存数据时可能会有OOM的风险。 | 1.1.1 |
二、其他配置选项
还可以使用以下选项来调优查询执行的性能。在未来的版本中,这些选项可能会被弃用,因为会自动执行更多的优化。
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.files.maxPartitionBytes | 134217728 (128 MB) | 读取文件时装入单个分区的最大字节数。此配置仅在使用基于文件的源(如Parquet、JSON和ORC)时有效。 | 2.0.0 |
spark.sql.files.openCostInBytes | 4194304 (4 MB) | 打开文件的估计成本,用同一时间内可以扫描的字节数来衡量。这在将多个文件放入分区时使用。最好是高估(over-estimate),这样具有小文件的分区将比具有大文件的分区快(大文件首先被调度)。此配置仅在使用基于文件的源(如Parquet、JSON和ORC)时有效。 | 2.0.0 |
spark.sql.files.minPartitionNum | Default Parallelism | 建议的(不保证的)最小分割文件分区数。如果没有设置,默认值为spark.sql.leafNodeDefaultParallelism。此配置仅在使用基于文件的源(如Parquet、JSON和ORC)时有效。 | 3.1.0 |
spark.sql.files.maxPartitionNum | None | 建议的(不保证的)最大分割文件分区数。如果设置了这个值,那么当初始分区数超过这个值时,Spark将重新调整每个分区,使分区数接近这个值。此配置仅在使用基于文件的源(如Parquet、JSON和ORC)时有效。 | 3.5.0 |
spark.sql.broadcastTimeout | 300 | broadcast joins中的广播等待时间超时值(以秒为单位) | 1.3.0 |
spark.sql.autoBroadcastJoinThreshold | 10485760 (10 MB) | 配置表的最大大小(以字节为单位),该表将在执行join时广播到所有工作节点。通过将此值设置为-1,可以禁用广播。请注意,目前统计数据只支持Hive Metastore表,其中命令ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan已经运行。 | 1.1.0 |
spark.sql.shuffle.partitions | 200 | 配置为join或聚合shuffle数据时使用的分区数量。 | 1.1.0 |
spark.sql.sources.parallelPartitionDiscovery.threshold | 32 | 配置阈值以启用job输入路径的并行列表。如果输入路径数大于此阈值,Spark将使用Spark分布式job列出文件。否则,它将退回到sequential listing。此配置仅在使用基于文件的数据源(如Parquet、ORC和JSON)时有效。 | 1.5.0 |
spark.sql.sources.parallelPartitionDiscovery.parallelism | 10000 | 为job输入路径配置最大listing并行度。如果输入路径的数量大于此值,则将减少使用此值。此配置仅在使用基于文件的数据源(如Parquet、ORC和JSON)时有效。 | 2.1.1 |
三、SQL查询的连接策略提示(join strategy hints)
join策略提示,即BROADCAST、MERGE、SHUFFLE_HASH和SHUFFLE_REPLICATE_NL,指示Spark在将每个指定关系与另一个关系join时,对它们使用提示的策略。例如,当对表“t1”使用BROADCAST提示时,即使统计数据建议的表“t1”的大小高于配置 spark.sql.autoBroadcastJoinThreshold,Spark也会优先考虑以“t1”为构建端的broadcast join(广播hash join或广播nested loop join,具体取决于是否存在任何equi-join键)。
当在join的两侧指定不同的join策略提示时,Spark会将使用如下优先级:BROADCAST提示优先于MERGE提示,MERGE提示优先于SHUFFLE_HASH提示,SHUFFLE_HASH提示优先于SHUFFLE_REPLICATE_NL提示。当使用BROADCAST提示或SHUFFLE_HASH提示指定两侧时,Spark将根据join类型和relations的大小选择构建侧。
请注意,不能保证Spark会选择提示中指定的join策略,因为特定的策略可能不支持所有join类型。
spark.table("src").join(spark.table("records").hint("broadcast"), "key").show()
要了解更多细节,请参考Join Hints的文档。
四、SQL查询的合并提示(Coalesce hints)
Coalesce提示允许Spark SQL用户控制输出文件的数量,就像在Dataset API中的coalesce、repartition 和repartitionByRange 一样,它们可以用于性能调优和减少输出文件的数量。“COALESCE”提示只有一个分区号作为参数。“REPARTITION”提示有分区号、列或两者都有/都没有作为参数。“REPARTITION_BY_RANGE”提示必须有列名,分区号是可选的。“REBALANCE”提示有一个初始分区号、列,或者两者都有/都没有作为参数。
SELECT /*+ COALESCE(3) */ * FROM t;
SELECT /*+ REPARTITION(3) */ * FROM t;
SELECT /*+ REPARTITION(c) */ * FROM t;
SELECT /*+ REPARTITION(3, c) */ * FROM t;
SELECT /*+ REPARTITION */ * FROM t;
SELECT /*+ REPARTITION_BY_RANGE(c) */ * FROM t;
SELECT /*+ REPARTITION_BY_RANGE(3, c) */ * FROM t;
SELECT /*+ REBALANCE */ * FROM t;
SELECT /*+ REBALANCE(3) */ * FROM t;
SELECT /*+ REBALANCE(c) */ * FROM t;
SELECT /*+ REBALANCE(3, c) */ * FROM t;
要了解更多细节,请参考Partitioning Hints文档。
五、自适应查询执行Adaptive Query Execution(AQE)
自适应查询执行(AQE)是Spark SQL中的一种优化技术,它利用运行时统计信息来选择最有效的查询执行计划,自Apache Spark 3.2.0以来默认启用。Spark SQL可以通过spark.sql.adaptive.enabled作为一个伞形配置来打开和关闭AQE。从Spark 3.0开始,AQE中有三个主要特性:合并shuffle后分区,将sort-merge join转换为broadcast join,以及skew join优化。
5.1 合并Shuffle后分区
当spark.sql.adaptive.enabled和spark.sql.adaptive.coalescePartitions.enabled配置都为true时,该特性根据映射输出统计数据合并shuffle后分区。这个特性简化了运行查询时shuffle分区数的调优。你不需要为你的数据集设置合适的shuffle分区数。一旦你通过 spark.sql.adaptive.coalescePartitions.initialPartitionNum配置设置了足够大的初始shuffle分区数,Spark就可以在运行时选择合适的shuffle分区数。
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.adaptive.coalescePartitions.enabled | true | 当为true且spark.sql.adaptive.enabled为true时,Spark将根据目标大小(由spark.sql.adaptive.advisoryPartitionSizeInBytes指定)合并连续的shuffle分区,以避免过多的小任务。 | 3.0.0 |
spark.sql.adaptive.coalescePartitions.parallelismFirst | true | 当为true时,Spark在合并连续shuffle分区时忽略spark.sql.adaptive.advisoryPartitionSizeInBytes(默认64MB)指定的目标大小,而只考虑spark.sql.adaptive.coalescePartitions.minPartitionSize(默认1MB)指定的最小分区大小,以最大化并行性。这是为了在启用自适应查询执行时避免性能退化。建议将此配置设置为false,并遵循spark.sql.adaptive.advisoryPartitionSizeInBytes指定的目标大小。 | 3.2.0 |
spark.sql.adaptive.coalescePartitions.minPartitionSize | 1MB | 合并后shuffle分区的最小大小。它的值最多可以是spark.sql.adaptive.advisoryPartitionSizeInBytes的20%。当在分区合并期间忽略目标大小时,这是很有用的,这是默认情况。 | 3.2.0 |
spark.sql.adaptive.coalescePartitions.initialPartitionNum | (none) | 合并前shuffle分区的初始数目。如果没有设置,它等于spark.sql.shuffle.partitions。此配置仅在spark.sql.adaptive.enabled和spark.sql.adaptive.coalescePartitions.enabled同时启用时有效。 | 3.0.0 |
spark.sql.adaptive.advisoryPartitionSizeInBytes | 64 MB | 自适应优化期间shuffle分区的建议大小(当spark.sql.adaptive.enabled为true时)。当Spark合并小shuffle分区或拆分倾斜shuffle分区时生效。 | 3.0.0 |
5.2 分开倾斜(skew)的shuffle分区
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.adaptive.optimizeSkewsInRebalancePartitions.enabled | true | 当为true且spark.sql.adaptive.enabled为true时,Spark将优化RebalancePartitions中的倾斜shuffle分区,并根据目标大小(由spark.sql.adaptive.advisoryPartitionSizeInBytes指定)将其拆分为更小的分区,以避免数据倾斜。 | 3.2.0 |
spark.sql.adaptive.rebalancePartitionsSmallPartitionFactor | 0.2 | 如果分区的大小小于这个因子乘以spark.sql.adaptive.advisoryPartitionSizeInBytes,则分区将在拆分期间合并。 | 3.3.0 |
5.3 将sort-merge join转换为broadcast join
当任何join端的运行时统计数据小于自适应broadcast hash join阈值时,AQE将sort-merge join转换为broadcast hash join。这并不像一开始就计划broadcast hash join那么有效,但它比继续进行sort-merge join要好,因为我们可以保存join两端的排序,并在本地读取shuffle文件以节省网络流量(如果spark.sql.adaptive.localShuffleReader.enabled为true)。
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.adaptive.autoBroadcastJoinThreshold | (none) | 配置表的最大大小(以字节为单位),该表将在执行join时广播到所有工作节点。通过将此值设置为-1,可以禁用广播。默认值与spark.sql.autoBroadcastJoinThreshold相同。注意,此配置仅在自适应框架中使用。 | 3.2.0 |
spark.sql.adaptive.localShuffleReader.enabled | true | 当为true且spark.sql.adaptive.enabled为true时,Spark会在不需要shuffle分区时(例如,将sort-merge join转换为broadcast-hash join之后)尝试使用本地shuffle reader读取shuffle数据。 | 3.0.0 |
5.4 将sort-merge join转换为shuffled hash join
当所有shuffle后分区小于阈值时,AQE将sort-merge join转换为shuffled hash join,最大阈值可以查看配置spark.sql.adaptive.maxShuffledHashJoinLocalMapThreshold。
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.adaptive.maxShuffledHashJoinLocalMapThreshold | 0 | 配置可允许构建本地hash map的每个分区的最大字节大小。如果此值不小于spark.sql.adaptive.advisoryPartitionSizeInBytes且所有分区大小不大于此配置,则无论spark.sql.join.preferSortMergeJoin的值如何,join选择都倾向于使用shuffled hash join而不是sort merge join。 | 3.2.0 |
5.5 Skew Join优化
数据倾斜会严重降低join查询的性能。该特性通过将倾斜的任务拆分(如果需要还可以复制)成大小大致相等的任务,动态地处理sort-merge join中的倾斜。当同时启用spark.sql.adaptive.enabled和 spark.sql.adaptive.skewJoin.enabled配置时生效。
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.adaptive.skewJoin.enabled | true | 当为true并且spark.sql.adaptive.enabled为true时,Spark通过拆分(如果需要的话还可以复制)倾斜分区来动态处理sort-merge join中的倾斜。 | 3.0.0 |
spark.sql.adaptive.skewJoin.skewedPartitionFactor | 5.0 | 如果分区的大小大于该因子乘以中位数分区大小,并且大于spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes,则认为分区是倾斜的。 | 3.0.0 |
spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes | 256MB | 如果分区的字节大小大于这个阈值,并且大于spark.sql.adaptive.skewJoin.skewedPartitionFactor乘以中位数分区大小,则认为分区是倾斜的。理想情况下,这个配置应该设置得比spark.sql.adaptive.advisoryPartitionSizeInBytes大。 | 3.0.0 |
spark.sql.adaptive.forceOptimizeSkewedJoin | false | 当为true时,强制启用OptimizeSkewedJoin,这是一个自适应规则,用于优化倾斜join以避免straggler任务,即使它引入了额外的shuffle。 | 3.3.0 |
5.6 杂项
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.adaptive.optimizer.excludedRules | (none) | 配置要在自适应优化器中禁用的规则列表,其中规则由其规则名称指定,并用逗号分隔。优化器将记录那些确实被排除在外的规则。 | 3.1.0 |
spark.sql.adaptive.customCostEvaluatorClass | (none) | 用于自适应执行的自定义cost evaluator类。如果没有设置,Spark将默认使用自己的SimpleCostEvaluator。 | 3.2.0 |