Skip to content

Commit 62fe817

Browse files
authored
Spark 4.1: Add session configs for adaptive split sizing and parallelism (#16088)
1 parent 8b45abc commit 62fe817

4 files changed

Lines changed: 127 additions & 2 deletions

File tree

spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/SparkReadConf.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ public boolean aggregatePushDownEnabled() {
279279
public boolean adaptiveSplitSizeEnabled() {
280280
return confParser
281281
.booleanConf()
282+
.sessionConf(SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_ENABLED)
282283
.tableProperty(TableProperties.ADAPTIVE_SPLIT_SIZE_ENABLED)
283284
.defaultValue(TableProperties.ADAPTIVE_SPLIT_SIZE_ENABLED_DEFAULT)
284285
.parse();
@@ -290,6 +291,17 @@ public int parallelism() {
290291
return Math.max(defaultParallelism, numShufflePartitions);
291292
}
292293

294+
public int splitParallelism() {
295+
int parallelism =
296+
confParser
297+
.intConf()
298+
.sessionConf(SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_PARALLELISM)
299+
.defaultValue(parallelism())
300+
.parse();
301+
Preconditions.checkArgument(parallelism > 0, "Split parallelism must be > 0: %s", parallelism);
302+
return parallelism;
303+
}
304+
293305
public boolean distributedPlanningEnabled() {
294306
return table instanceof SupportsDistributedScanPlanning distributed
295307
&& distributed.allowDistributedPlanning()

spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/SparkSQLProperties.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ private SparkSQLProperties() {}
110110
// Prefix for custom snapshot properties
111111
public static final String SNAPSHOT_PROPERTY_PREFIX = "spark.sql.iceberg.snapshot-property.";
112112

113+
// Controls whether adaptive split sizing is enabled
114+
public static final String READ_ADAPTIVE_SPLIT_SIZE_ENABLED =
115+
"spark.sql.iceberg.read.adaptive-split-size.enabled";
116+
117+
// Overrides the parallelism used for adaptive split sizing. When unset, the parallelism
118+
// defaults to max(spark.default.parallelism, spark.sql.shuffle.partitions).
119+
public static final String READ_ADAPTIVE_SPLIT_SIZE_PARALLELISM =
120+
"spark.sql.iceberg.read.adaptive-split-size.parallelism";
121+
113122
// Controls whether to enable async micro batch planning for session
114123
public static final String ASYNC_MICRO_BATCH_PLANNING_ENABLED =
115124
"spark.sql.iceberg.async-micro-batch-planning-enabled";

spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/SparkScan.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,17 @@ public CustomMetric[] supportedCustomMetrics() {
369369
protected long adjustSplitSize(List<? extends ScanTask> tasks, long splitSize) {
370370
if (readConf.splitSizeOption() == null && readConf.adaptiveSplitSizeEnabled()) {
371371
long scanSize = tasks.stream().mapToLong(ScanTask::sizeBytes).sum();
372-
int parallelism = readConf.parallelism();
373-
return TableScanUtil.adjustSplitSize(scanSize, parallelism, splitSize);
372+
int parallelism = readConf.splitParallelism();
373+
long adjustedSplitSize = TableScanUtil.adjustSplitSize(scanSize, parallelism, splitSize);
374+
if (adjustedSplitSize != splitSize) {
375+
LOG.debug(
376+
"Adjusted split size from {} to {} for table {} with parallelism {}",
377+
splitSize,
378+
adjustedSplitSize,
379+
table().name(),
380+
parallelism);
381+
}
382+
return adjustedSplitSize;
374383
} else {
375384
return splitSize;
376385
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.iceberg.spark;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
23+
24+
import org.apache.iceberg.ParameterizedTestExtension;
25+
import org.apache.iceberg.Table;
26+
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
27+
import org.apache.spark.sql.internal.SQLConf;
28+
import org.apache.spark.sql.util.CaseInsensitiveStringMap;
29+
import org.junit.jupiter.api.AfterEach;
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.TestTemplate;
32+
import org.junit.jupiter.api.extension.ExtendWith;
33+
34+
@ExtendWith(ParameterizedTestExtension.class)
35+
public class TestSparkReadConf extends TestBaseWithCatalog {
36+
37+
@BeforeEach
38+
public void before() {
39+
super.before();
40+
sql("CREATE TABLE %s (id BIGINT, data STRING) USING iceberg", tableName);
41+
}
42+
43+
@AfterEach
44+
public void after() {
45+
sql("DROP TABLE IF EXISTS %s", tableName);
46+
}
47+
48+
@TestTemplate
49+
public void testSplitParallelismDefault() {
50+
Table table = validationCatalog.loadTable(tableIdent);
51+
SparkReadConf conf = new SparkReadConf(spark, table, CaseInsensitiveStringMap.empty());
52+
assertThat(conf.splitParallelism()).isEqualTo(conf.parallelism());
53+
}
54+
55+
@TestTemplate
56+
public void testSplitParallelismSessionConf() {
57+
Table table = validationCatalog.loadTable(tableIdent);
58+
withSQLConf(
59+
ImmutableMap.of(
60+
SQLConf.SHUFFLE_PARTITIONS().key(),
61+
"999",
62+
SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_PARALLELISM,
63+
"42"),
64+
() -> {
65+
SparkReadConf conf = new SparkReadConf(spark, table, CaseInsensitiveStringMap.empty());
66+
assertThat(conf.splitParallelism()).isEqualTo(42);
67+
});
68+
}
69+
70+
@TestTemplate
71+
public void testSplitParallelismRejectsZero() {
72+
Table table = validationCatalog.loadTable(tableIdent);
73+
withSQLConf(
74+
ImmutableMap.of(SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_PARALLELISM, "0"),
75+
() -> {
76+
SparkReadConf conf = new SparkReadConf(spark, table, CaseInsensitiveStringMap.empty());
77+
assertThatIllegalArgumentException()
78+
.isThrownBy(conf::splitParallelism)
79+
.withMessageContaining("Split parallelism must be > 0");
80+
});
81+
}
82+
83+
@TestTemplate
84+
public void testSplitParallelismRejectsNegative() {
85+
Table table = validationCatalog.loadTable(tableIdent);
86+
withSQLConf(
87+
ImmutableMap.of(SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_PARALLELISM, "-5"),
88+
() -> {
89+
SparkReadConf conf = new SparkReadConf(spark, table, CaseInsensitiveStringMap.empty());
90+
assertThatIllegalArgumentException()
91+
.isThrownBy(conf::splitParallelism)
92+
.withMessageContaining("Split parallelism must be > 0");
93+
});
94+
}
95+
}

0 commit comments

Comments
 (0)