gerashegalov commented on code in PR #18709:
URL: https://github.com/apache/iceberg/pull/18709#discussion_r4540493612


##########
spark/v4.2/spark/src/test/java/org/apache/iceberg/spark/source/TestSparkReadMetrics.java:
##########
@@ -44,6 +61,180 @@ public class TestSparkReadMetrics extends 
TestBaseWithCatalog {
   @AfterEach
   public void removeTables() {
     sql("DROP TABLE IF EXISTS %s", tableName);
+    sql("DROP TABLE IF EXISTS dim");
+  }
+
+  @TestTemplate
+  void effectiveSplitTargetSize() throws NoSuchTableException, IOException {

Review Comment:
   The new tests follow one of the two consistent forms: package-private with 
no `test` prefix. That is also what the Iceberg AGENTS.md asks for ("Test 
classes and methods should be package private unless required by inheritance" 
and "Avoid using `test` prefixes for newly added tests"). The file was already 
mixed before this PR: #18751 added `scanDurationAggregatedAcrossTasks` and 
`scanDurationForNonVectorizedRead` as public methods without the prefix. Making 
the whole file consistent would mean renaming unrelated existing tests here, so 
I'd prefer to do that in a separate cleanup PR if you'd like. @liurenjie1024 
asked earlier for the opposite direction (keeping the `test` prefix), so I'd be 
glad to follow whichever convention you both agree on for that cleanup.



##########
spark/v4.2/spark/src/test/java/org/apache/iceberg/spark/source/TestSparkReadMetrics.java:
##########
@@ -44,6 +61,180 @@ public class TestSparkReadMetrics extends 
TestBaseWithCatalog {
   @AfterEach
   public void removeTables() {
     sql("DROP TABLE IF EXISTS %s", tableName);
+    sql("DROP TABLE IF EXISTS dim");
+  }
+
+  @TestTemplate
+  void effectiveSplitTargetSize() throws NoSuchTableException, IOException {
+    long tableSplitSize = 128L * 1024 * 1024;
+    long optionSplitSize = 32L * 1024 * 1024;
+    int parallelism = 1000;
+    sql(
+        "CREATE TABLE %s (id BIGINT) USING iceberg TBLPROPERTIES 
('read.split.target-size'='%s')",
+        tableName, tableSplitSize);
+    spark.range(100).coalesce(1).writeTo(tableName).append();
+
+    long scanSize = 0;
+    try (CloseableIterable<FileScanTask> tasks =
+        validationCatalog.loadTable(tableIdent).newScan().planFiles()) {
+      for (FileScanTask task : tasks) {
+        scanSize += task.sizeBytes();
+      }
+    }
+    long adaptiveSplitSize = TableScanUtil.adjustSplitSize(scanSize, 
parallelism, tableSplitSize);

Review Comment:
   Done in 1f96a63de.



##########
spark/v4.2/spark/src/test/java/org/apache/iceberg/spark/source/TestSparkReadMetrics.java:
##########
@@ -44,6 +61,180 @@ public class TestSparkReadMetrics extends 
TestBaseWithCatalog {
   @AfterEach
   public void removeTables() {
     sql("DROP TABLE IF EXISTS %s", tableName);
+    sql("DROP TABLE IF EXISTS dim");
+  }
+
+  @TestTemplate
+  void effectiveSplitTargetSize() throws NoSuchTableException, IOException {
+    long tableSplitSize = 128L * 1024 * 1024;
+    long optionSplitSize = 32L * 1024 * 1024;
+    int parallelism = 1000;
+    sql(
+        "CREATE TABLE %s (id BIGINT) USING iceberg TBLPROPERTIES 
('read.split.target-size'='%s')",
+        tableName, tableSplitSize);
+    spark.range(100).coalesce(1).writeTo(tableName).append();
+
+    long scanSize = 0;
+    try (CloseableIterable<FileScanTask> tasks =
+        validationCatalog.loadTable(tableIdent).newScan().planFiles()) {
+      for (FileScanTask task : tasks) {
+        scanSize += task.sizeBytes();
+      }
+    }
+    long adaptiveSplitSize = TableScanUtil.adjustSplitSize(scanSize, 
parallelism, tableSplitSize);
+    assertThat(adaptiveSplitSize).isNotEqualTo(tableSplitSize);
+
+    // adaptive sizing lowers the table target for a small scan
+    withSQLConf(
+        ImmutableMap.of(
+            SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_ENABLED,
+            "true",
+            SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_PARALLELISM,
+            String.valueOf(parallelism)),
+        () -> {
+          Dataset<Row> df = spark.sql(String.format("SELECT * FROM %s", 
tableName));
+          df.collect();
+          SparkPlan scan = 
df.queryExecution().executedPlan().collectLeaves().head();
+          
assertThat(scan.metrics().get("effectiveSplitTargetSize").get().value())
+              .isEqualTo(adaptiveSplitSize);
+        });
+
+    // without adaptive sizing, the table property is the target
+    withSQLConf(
+        ImmutableMap.of(SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_ENABLED, 
"false"),
+        () -> {
+          Dataset<Row> df = spark.sql(String.format("SELECT * FROM %s", 
tableName));
+          df.collect();
+          SparkPlan scan = 
df.queryExecution().executedPlan().collectLeaves().head();
+          
assertThat(scan.metrics().get("effectiveSplitTargetSize").get().value())
+              .isEqualTo(tableSplitSize);
+        });
+
+    // an explicit read option is used as is, even with adaptive sizing enabled
+    withSQLConf(
+        ImmutableMap.of(SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_ENABLED, 
"true"),
+        () -> {
+          Dataset<Row> df =
+              spark
+                  .read()
+                  .format("iceberg")
+                  .option(SparkReadOptions.SPLIT_SIZE, 
String.valueOf(optionSplitSize))
+                  .load(tableName);
+          df.collect();
+          SparkPlan scan = 
df.queryExecution().executedPlan().collectLeaves().head();
+          
assertThat(scan.metrics().get("effectiveSplitTargetSize").get().value())
+              .isEqualTo(optionSplitSize);
+        });
+  }
+
+  @TestTemplate
+  void effectiveSplitTargetSizeWithoutSnapshot() {
+    sql("CREATE TABLE %s (id BIGINT) USING iceberg", tableName);
+
+    // no task groups are planned for a table without a snapshot
+    Dataset<Row> df = spark.sql(String.format("SELECT * FROM %s", tableName));
+    df.collect();
+    SparkPlan scan = df.queryExecution().executedPlan().collectLeaves().head();
+    
assertThat(scan.metrics().get("effectiveSplitTargetSize").get().value()).isZero();
+  }
+
+  @TestTemplate
+  void effectiveSplitTargetSizeWithRuntimeFilter() throws 
NoSuchTableException, IOException {
+    long tableSplitSize = 128L * 1024 * 1024;
+    int parallelism = 1000;
+    sql(
+        "CREATE TABLE %s (id BIGINT, date DATE) USING iceberg PARTITIONED BY 
(date) "
+            + "TBLPROPERTIES ('read.split.target-size'='%s')",
+        tableName, tableSplitSize);
+    spark
+        .range(1, 100)
+        .withColumn("date", date_add(expr("DATE '1970-01-01'"), expr("CAST(id 
% 4 AS INT)")))
+        .coalesce(1)
+        .writeTo(tableName)
+        .option(SparkWriteOptions.FANOUT_ENABLED, "true")
+        .append();
+
+    sql("CREATE TABLE dim (id BIGINT, date DATE) USING parquet");
+    spark
+        .range(1, 10)
+        .withColumn("date", expr("DATE '1970-01-02'"))
+        .coalesce(1)
+        .write()
+        .mode("append")
+        .insertInto("dim");
+
+    long filteredScanSize = 0;
+    try (CloseableIterable<FileScanTask> tasks =
+        validationCatalog
+            .loadTable(tableIdent)
+            .newScan()
+            .filter(Expressions.equal("date", "1970-01-02"))
+            .planFiles()) {
+      for (FileScanTask task : tasks) {
+        filteredScanSize += task.sizeBytes();
+      }
+    }
+    long expectedSplitSize =

Review Comment:
   Done in 1f96a63de.



##########
spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/source/SparkPartitioningAwareScan.java:
##########


Review Comment:
   Done in 1f96a63de: `resetTasks` now resets the metric to 0 along with the 
task groups, and re-planning records it again.



##########
spark/v4.2/spark/src/test/java/org/apache/iceberg/spark/source/TestSparkReadMetrics.java:
##########
@@ -44,6 +61,180 @@ public class TestSparkReadMetrics extends 
TestBaseWithCatalog {
   @AfterEach
   public void removeTables() {
     sql("DROP TABLE IF EXISTS %s", tableName);
+    sql("DROP TABLE IF EXISTS dim");
+  }
+
+  @TestTemplate
+  void effectiveSplitTargetSize() throws NoSuchTableException, IOException {
+    long tableSplitSize = 128L * 1024 * 1024;
+    long optionSplitSize = 32L * 1024 * 1024;
+    int parallelism = 1000;
+    sql(
+        "CREATE TABLE %s (id BIGINT) USING iceberg TBLPROPERTIES 
('read.split.target-size'='%s')",
+        tableName, tableSplitSize);
+    spark.range(100).coalesce(1).writeTo(tableName).append();
+
+    long scanSize = 0;
+    try (CloseableIterable<FileScanTask> tasks =
+        validationCatalog.loadTable(tableIdent).newScan().planFiles()) {
+      for (FileScanTask task : tasks) {
+        scanSize += task.sizeBytes();
+      }
+    }
+    long adaptiveSplitSize = TableScanUtil.adjustSplitSize(scanSize, 
parallelism, tableSplitSize);
+    assertThat(adaptiveSplitSize).isNotEqualTo(tableSplitSize);
+
+    // adaptive sizing lowers the table target for a small scan
+    withSQLConf(
+        ImmutableMap.of(
+            SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_ENABLED,
+            "true",
+            SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_PARALLELISM,
+            String.valueOf(parallelism)),
+        () -> {
+          Dataset<Row> df = spark.sql(String.format("SELECT * FROM %s", 
tableName));
+          df.collect();
+          SparkPlan scan = 
df.queryExecution().executedPlan().collectLeaves().head();
+          
assertThat(scan.metrics().get("effectiveSplitTargetSize").get().value())
+              .isEqualTo(adaptiveSplitSize);
+        });
+
+    // without adaptive sizing, the table property is the target
+    withSQLConf(
+        ImmutableMap.of(SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_ENABLED, 
"false"),
+        () -> {
+          Dataset<Row> df = spark.sql(String.format("SELECT * FROM %s", 
tableName));
+          df.collect();
+          SparkPlan scan = 
df.queryExecution().executedPlan().collectLeaves().head();
+          
assertThat(scan.metrics().get("effectiveSplitTargetSize").get().value())
+              .isEqualTo(tableSplitSize);
+        });
+
+    // an explicit read option is used as is, even with adaptive sizing enabled
+    withSQLConf(
+        ImmutableMap.of(SparkSQLProperties.READ_ADAPTIVE_SPLIT_SIZE_ENABLED, 
"true"),
+        () -> {
+          Dataset<Row> df =
+              spark
+                  .read()
+                  .format("iceberg")
+                  .option(SparkReadOptions.SPLIT_SIZE, 
String.valueOf(optionSplitSize))
+                  .load(tableName);
+          df.collect();
+          SparkPlan scan = 
df.queryExecution().executedPlan().collectLeaves().head();
+          
assertThat(scan.metrics().get("effectiveSplitTargetSize").get().value())
+              .isEqualTo(optionSplitSize);
+        });
+  }
+
+  @TestTemplate
+  void effectiveSplitTargetSizeWithoutSnapshot() {
+    sql("CREATE TABLE %s (id BIGINT) USING iceberg", tableName);
+
+    // no task groups are planned for a table without a snapshot
+    Dataset<Row> df = spark.sql(String.format("SELECT * FROM %s", tableName));
+    df.collect();
+    SparkPlan scan = df.queryExecution().executedPlan().collectLeaves().head();
+    
assertThat(scan.metrics().get("effectiveSplitTargetSize").get().value()).isZero();
+  }
+
+  @TestTemplate
+  void effectiveSplitTargetSizeWithRuntimeFilter() throws 
NoSuchTableException, IOException {

Review Comment:
   Good point, done in 1f96a63de. The test now adds four 64 MB data files, 
which are never read, to a partition the runtime filter prunes, and uses an 
adaptive parallelism of 4. The target is about 64 MB before the filter and 16 
MB after. Both expected values are computed with 
`TableScanUtil.adjustSplitSize`, and the test asserts that they differ. If the 
metric is not recorded again after re-planning, the test fails and reports the 
stale pre-filter value (67109630).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to