-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Spark: Add custom metric for number of file splits read by a SparkScan #4395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
c547869
b0ebc47
dd5f50a
43d9e1e
0cf77ca
30767e2
2faea77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
|
|
||
| package org.apache.iceberg.spark.source; | ||
|
|
||
| import java.text.NumberFormat; | ||
| import org.apache.spark.sql.connector.metric.CustomMetric; | ||
|
|
||
| public class NumFiles implements CustomMetric { | ||
|
|
||
| @Override | ||
| public String name() { | ||
| return "numFiles"; | ||
| } | ||
|
|
||
| @Override | ||
| public String description() { | ||
| return "number of files read"; | ||
| } | ||
|
|
||
| @Override | ||
| public String aggregateTaskMetrics(long[] taskMetrics) { | ||
| long sum = initialValue; | ||
| for (int i = 0; i < taskMetrics.length; i++) { | ||
| sum += taskMetrics[i]; | ||
| } | ||
| return NumberFormat.getIntegerInstance().format(sum); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,6 +93,11 @@ class SparkBatchQueryScan extends SparkScan implements SupportsRuntimeFiltering | |
| } | ||
| } | ||
|
|
||
| @Override | ||
|
wypoon marked this conversation as resolved.
Outdated
|
||
| public String description() { | ||
| return "IcebergScan " + super.description(); | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately the label for the node in the SQL DAG in the Spark UI just says "BatchScan" (that is the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sounds good to me even outside the scope of this PR, although maybe we should write Iceberg Batch Scan, since we will probably be generating different descriptions for SparkFileScan and any other Scans we build
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see we have the other descriptions below, well at least let's match that pattern :) "IcebergBatchScan"?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was matching the pattern in the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am +1 on I think that updating the |
||
|
|
||
| Long snapshotId() { | ||
| return snapshotId; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -44,6 +44,8 @@ | |||||
| import org.apache.spark.broadcast.Broadcast; | ||||||
| import org.apache.spark.sql.SparkSession; | ||||||
| import org.apache.spark.sql.catalyst.InternalRow; | ||||||
| import org.apache.spark.sql.connector.metric.CustomMetric; | ||||||
| import org.apache.spark.sql.connector.metric.CustomTaskMetric; | ||||||
| import org.apache.spark.sql.connector.read.Batch; | ||||||
| import org.apache.spark.sql.connector.read.InputPartition; | ||||||
| import org.apache.spark.sql.connector.read.PartitionReader; | ||||||
|
|
@@ -162,6 +164,11 @@ public String description() { | |||||
| return String.format("%s [filters=%s]", table, filters); | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public CustomMetric[] supportedCustomMetrics() { | ||||||
| return new CustomMetric[] { new NumFiles() }; | ||||||
| } | ||||||
|
|
||||||
| static class ReaderFactory implements PartitionReaderFactory { | ||||||
| private final int batchSize; | ||||||
|
|
||||||
|
|
@@ -193,15 +200,41 @@ public boolean supportColumnarReads(InputPartition partition) { | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| static long numFilesToScan(CombinedScanTask scanTask) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. numSplitsToScan?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is now a one-liner, I have removed the function altogether. |
||||||
| long fileCount = 0L; | ||||||
| for (FileScanTask file : scanTask.files()) { | ||||||
| fileCount += 1L; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [quetion] should we incorporate delete's as well assosiated with the FileScanTask
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could have a separate metric for number of delete files to read. Or rather, a sum of the number of delete files to apply, over the data files to be read; we won't actually track what the delete files are, if there is any overlap among them (can there be overlap?).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fair to count all the delete files, but definitely in another metric. There will be duplicates in two cases
|
||||||
| } | ||||||
| return fileCount; | ||||||
|
wypoon marked this conversation as resolved.
Outdated
|
||||||
| } | ||||||
|
|
||||||
| private static class RowReader extends RowDataReader implements PartitionReader<InternalRow> { | ||||||
| private long numFilesToRead; | ||||||
|
|
||||||
| RowReader(ReadTask task) { | ||||||
| super(task.task, task.table(), task.expectedSchema(), task.isCaseSensitive()); | ||||||
| numFilesToRead = numFilesToScan(task.task); | ||||||
| LOG.debug("reading {} files for table {}", numFilesToRead, task.table().name()); | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public CustomTaskMetric[] currentMetricsValues() { | ||||||
| return new CustomTaskMetric[] { new TaskNumFiles(numFilesToRead) }; | ||||||
| } | ||||||
|
Comment on lines
+219
to
217
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: Spark calls this every 100 rows for each
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can it be a property of this RowReader then as numFilesToScan is already computed in the constructor, and we could just return that when ever asked.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is what I meant, is it worth caching the |
||||||
| } | ||||||
|
|
||||||
| private static class BatchReader extends BatchDataReader implements PartitionReader<ColumnarBatch> { | ||||||
| private long numFilesToRead; | ||||||
|
|
||||||
| BatchReader(ReadTask task, int batchSize) { | ||||||
| super(task.task, task.table(), task.expectedSchema(), task.isCaseSensitive(), batchSize); | ||||||
| numFilesToRead = numFilesToScan(task.task); | ||||||
| LOG.debug("reading {} files for table {}", numFilesToRead, task.table().name()); | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public CustomTaskMetric[] currentMetricsValues() { | ||||||
| return new CustomTaskMetric[] { new TaskNumFiles(numFilesToRead) }; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
|
|
||
| package org.apache.iceberg.spark.source; | ||
|
|
||
| import org.apache.spark.sql.connector.metric.CustomTaskMetric; | ||
|
|
||
| public class TaskNumFiles implements CustomTaskMetric { | ||
| private long value; | ||
|
|
||
| TaskNumFiles(long value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| @Override | ||
| public String name() { | ||
| return "numFiles"; | ||
| } | ||
|
|
||
| @Override | ||
| public long value() { | ||
| return value; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[question] should we make this return "total(min, med, max)" we already have all the info required, this should give us even deeper insights on files distribution at task level. WDYT ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@singhpk234 thank you for your interesting suggesions!
I am open to presenting the min, median and max as well, but I wonder how useful those statistics really are. My impression is that Iceberg scan planning works well enough that the number of files to scan are usually very evenly distributed (except for cases like where there are two
InputPartitions). Usually skew occurs with times (which can of course show great variation), or with number of records after a shuffle (rather than aBatchScan).@rdblue, @aokolnychyi, @RussellSpitzer what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I though the way these are shown in the UI are per task as well? Or am I remembering that wrong. IE we should have all these values (but not the calculated sum?) If not I think that would be good to report, I would suggest probably
min, 25percentile, median, 75percentile, max?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RussellSpitzer I think you may be thinking of task metrics that Spark itself collects (by default, Duration, GC Time, Input Size / Records, and Shuffle Size /Records are shown), and are shown with the min, 25th percentile, median, 75th percentile, and max.
For custom
SQLMetrics, one implements theaggregateTaskMetricsand we'd need to do the calculation ourselves if we want such statistics. Spark doesn't do it for us. Spark sends all the values it collected from all thePartitionReaders for a given custom metric in along[]to thisaggregateTaskMetrics; that's all. It also doesn't separately calculate these statistics either.