Skip to content

Spark 4.1: Add per-task scanDuration metric - #17562

Open
venkata91 wants to merge 6 commits into
apache:mainfrom
venkata91:spark-task-scan-duration-metric
Open

venkata91 wants to merge 6 commits into
apache:mainfrom
venkata91:spark-task-scan-duration-metric

Conversation

@venkata91

@venkata91 venkata91 commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Changes

  • Add scanDuration: wall time a task spends in BaseReader.next(), covering split open and iteration. Accumulated in a finally, so a task that throws still reports what it read.
  • TaskScanDuration from BatchDataReader and RowDataReader, aggregated by ScanDuration, mirroring the TotalPlanningDuration pair.
  • Nanoseconds rather than the millis used by totalPlanningDuration, since a split can be read in well under a millisecond.
  • Aggregate renders as total (min, med, max), matching MetricUtils.stringValue, so a straggler task stays visible.

Testing done

  • TestSparkReadMetrics (5): V1 and V2 tables, delete path, multi-task aggregation, and the non-vectorized RowDataReader path.
  • TestScanDuration (5): rendering, empty and straggler cases, input not mutated, locale independence.
  • 0 failures. spotlessCheck and checkstyle pass.

Notes

  • Scoped to spark/v4.1. Happy to backport to 4.0 and 3.5 in a follow-up.

@github-actions github-actions Bot added the spark label Aug 9, 2026
@venkata91
venkata91 force-pushed the spark-task-scan-duration-metric branch 2 times, most recently from b88f53d to f1a6f78 Compare August 9, 2026 02:34

@anoopj anoopj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code change looks good to me. We might be missing code coverage for the non-vectorized path (RowDataReader) because the Spark read follows the vectorized path?

@venkata91

venkata91 commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor Author

The code change looks good to me. We might be missing code coverage for the non-vectorized path (RowDataReader) because the Spark read follows the vectorized path?

@anoopj Addressed by adding unit test. Also added couple of integration tests as well. Thanks!

@venkata91

Copy link
Copy Markdown
Contributor Author

Gentle bump!

@venkata91

Copy link
Copy Markdown
Contributor Author

@anoopj Gentle bump!!

@anoopj anoopj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks. We should get a committer to review/merge this as the next step.

@venkata91

Copy link
Copy Markdown
Contributor Author

cc @aokolnychyi or @anuragmantri for review please. Thanks!

@venkata91

Copy link
Copy Markdown
Contributor Author

Gentle ping @rdblue !!

@uros-b uros-b left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this @venkata91, and thank you @anoopj for reviewing the changes! The accumulation logic looks good (monotonic += in the finally, single-threaded per reader; ScanDuration.NAME / TaskScanDuration.name() pair up). Leaving here just a couple of inline comments that should be considered before merge (the Locale.ROOT thing is pretty important, PTAL).

Also, the PR description is a bit stale now, "V1, V2 and V3 tables. 3 tests" no longer matches (five tests now, no V3 format exercised), and "Timed per next() call, not per row" is inaccurate for RowDataReader, where each next() yields a single InternalRow. Please update both.

} else if (totalNanos < TimeUnit.SECONDS.toNanos(1)) {
return TimeUnit.NANOSECONDS.toMillis(totalNanos) + " ms";
} else {
return String.format("%.1f s", totalNanos / (double) TimeUnit.SECONDS.toNanos(1));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locale.ROOT - ScanDuration.aggregateTaskMetrics uses String.format("%.1f s", ...) without a locale, so it renders "2,5 s" on a de/fr/pt JVM. The rest of the main source tree passes Locale.ROOT for numeric formatting; please use String.format(Locale.ROOT, "%.1f s", ...).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed it.

}

@Override
public String aggregateTaskMetrics(long[] taskMetrics) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aggregateTaskMetrics sums per-task nanos into a single scalar, which discards straggler-task skew. Spark's own scanTime reports total (min, med, max) and you already have the full per-task array in hand - worth considering matching that shape.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed it.

@venkata91

Copy link
Copy Markdown
Contributor Author

Thanks @uros-b for the review and your suggestions. Addressed all the review comments.

@venkata91

Copy link
Copy Markdown
Contributor Author

@uros-b Gentle ping! Do you think we can merge this PR?

@uros-b

uros-b commented Sep 22, 2026

Copy link
Copy Markdown
Member

@venkata91 Please ping an expert Iceberg committer for further review here ^^

@venkata91

Copy link
Copy Markdown
Contributor Author

@nastra @singhpk234 @manuzhang mind taking a look when you get a chance? This has been open a while and is ready for a committer review.

It adds a scanDuration read metric alongside the existing totalPlanningDuration, following the same pattern as #15104. Approved by @anoopj, and review feedback from @uros-b has been addressed (the aggregate now renders as total (min, med, max) to match MetricUtils.stringValue, so straggler tasks stay visible). CI is green.

#17820 builds on this with the write-side equivalent (writeDuration and writeCloseDuration).

@venkata91

Copy link
Copy Markdown
Contributor Author

Gentle ping, please review @nastra @singhpk234 @manuzhang !! Thanks!

@venkata91

Copy link
Copy Markdown
Contributor Author

Gentle ping, please review @nastra

public class TestScanDuration {

@Test
public void testAggregateTaskMetrics() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's avoid using test prefix in newly added tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 51ae742. I also dropped the prefix from the two tests this PR adds to TestSparkReadMetrics, and made TestScanDuration package-private. Thanks for the review! I'll open the Spark 4.2 port once this merges.

@nastra nastra left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with one comment that would be good to address. Also please make sure to port this to Spark 4.2

@@ -88,7 +89,9 @@ class BatchDataReader extends BaseBatchReader<FileScanTask>
@Override
public CustomTaskMetric[] currentMetricsValues() {
return new CustomTaskMetric[] {
new TaskNumSplits(numSplits), new TaskNumDeletes(counter().get())
new TaskNumSplits(numSplits),

@manuzhang manuzhang Oct 26, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the no-op initMEtricsValues is inherited, metrics from a later reader will override that of a previous one in the same task. Could you please fix it in a follow-up PR? Same for RowDataReader.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. With the no-op inherited, a later reader in the same task starts from zero instead of picking up the earlier reader's value, so only the last reader's duration survives. I'll fix BatchDataReader and RowDataReader together in a follow-up and link it here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #18726. It turned out Spark 4.2 is not affected, since it no longer calls initMetricsValues, so the fix is 4.1 only.

Venkata Krishnan Sowrirajan and others added 6 commits November 1, 2026 02:48
Iceberg reports scan bytes and file/manifest counts per task, but the only
timer in the metrics system is totalPlanningDuration, which measures
driver-side planning. There is no metric for how long a task spent actually
reading data, so a scan's throughput cannot be derived from Iceberg metrics
alone.

Add scanDuration: wall time a task spends in BaseReader.next(), accumulated
per call rather than per row so the timer costs nothing measurable relative
to the read. Emitted as TaskScanDuration from BatchDataReader and
RowDataReader, aggregated by ScanDuration, mirroring the existing
TotalPlanningDuration pair.

Values are nanoseconds: a single split can be read in well under a
millisecond, so a millisecond timer would round most per-task reads to zero.
ScanDuration formats the aggregate as ns/us/ms/s for the UI.

Generated-by: Claude Opus 5
@venkata91
venkata91 force-pushed the spark-task-scan-duration-metric branch from 51ae742 to 901bf2b Compare November 1, 2026 02:49
@venkata91

Copy link
Copy Markdown
Contributor Author

@nastra I renamed the tests as you asked and rebased onto main, so CI is green now. Could you merge this when you get a chance? I'll open the 4.2 port right after.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spark: No metric for time spent reading data in a scan task

6 participants