-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-56743][SPARK-56773][SQL][CORE][TESTS] Exercise writer-stage retries in DSv2 DML metric tests and fix injection-state cleanup under AQE #56597
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 all commits
afad6cd
c522bcb
a33978e
5cf13c8
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 |
|---|---|---|
|
|
@@ -420,6 +420,64 @@ class MetricsFailureInjectionSuite | |
| } | ||
| } | ||
|
|
||
| test("Three stage metrics block failure injection with AQE") { | ||
| // Same as the previous test but with AQE enabled. Under AQE each Exchange is materialized | ||
|
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. This test drops four assertions its non-AQE sibling has —
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. Told my agent to stop being sloppy and readd the assertion :-). |
||
| // as its own map-stage job, which exercises a different DAGScheduler path than the | ||
| // AQE-disabled variant: the injection's deferred corruption must survive across those | ||
| // per-shuffle jobs for the downstream FetchFailed (and thus the producer re-run) to fire. | ||
| val stage1Metric = SQLMetrics.createMetric(spark.sparkContext, "stage 1 counter") | ||
| val stage2Metric = SQLMetrics.createMetric(spark.sparkContext, "stage 2 counter") | ||
| val stage3Metric = SQLMetrics.createMetric(spark.sparkContext, "stage 3 counter") | ||
| val stage1SLAMetric = | ||
| SQLLastAttemptMetrics.createMetric(spark.sparkContext, "stage 1 SLAM") | ||
| val stage2SLAMetric = | ||
| SQLLastAttemptMetrics.createMetric(spark.sparkContext, "stage 2 SLAM") | ||
| val stage3SLAMetric = | ||
| SQLLastAttemptMetrics.createMetric(spark.sparkContext, "stage 3 SLAM") | ||
|
|
||
| withTable("primary_table", "secondary_table") { | ||
| setUpTestTable("primary_table") | ||
| setUpTestTable("secondary_table") | ||
| withSQLConf(SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true") { | ||
| withSparkContextConf( | ||
| config.Tests.INJECT_SHUFFLE_FETCH_FAILURES.key -> "true") { | ||
| val stage1MetricsExpr = incrementMetrics(Seq(stage1Metric, stage1SLAMetric)) | ||
| val stage1 = spark.read.table("primary_table") | ||
| .filter(Column(stage1MetricsExpr)) | ||
| val stage2MetricsExpr = incrementMetrics(Seq(stage2Metric, stage2SLAMetric)) | ||
| val stage2 = stage1.join( | ||
| spark.read.table("secondary_table"), | ||
| usingColumn = "id", | ||
| joinType = "fullOuter") | ||
| .filter(Column(stage2MetricsExpr)) | ||
| val stage3MetricsExpr = incrementMetrics(Seq(stage3Metric, stage3SLAMetric)) | ||
| val stage3 = stage2 | ||
| .groupBy("primary_table.low_cardinality_col") | ||
| .count() | ||
| .filter(Column(stage3MetricsExpr)) | ||
| val finalDf = stage3.as[(Int, Long)] | ||
| val result = finalDf.collect() | ||
| assert(result.toMap === (0 until 5).map(v => (v, 300 / 5)).toMap) | ||
|
|
||
| // Both the leaf stage 1 and the non-leaf stage 2 get their first successful attempt | ||
| // corrupted and re-run, so their raw counters overcount. SLAM reports only the last | ||
| // successful attempt per RDD. | ||
| assert(stage1Metric.value > 300, s"stage1Metric=${stage1Metric.value}") | ||
| assert(stage2Metric.value > 300, s"stage2Metric=${stage2Metric.value}") | ||
| assert(stage3Metric.value === 5) | ||
|
|
||
| assert(stage1SLAMetric.lastAttemptValueForHighestRDDId() === Some(300)) | ||
| assert(stage2SLAMetric.lastAttemptValueForHighestRDDId() === Some(300)) | ||
| assert(stage3SLAMetric.lastAttemptValueForHighestRDDId() === Some(5)) | ||
|
|
||
| assert(stage1SLAMetric.lastAttemptValueForDataset(finalDf) === Some(300)) | ||
| assert(stage2SLAMetric.lastAttemptValueForDataset(finalDf) === Some(300)) | ||
| assert(stage3SLAMetric.lastAttemptValueForDataset(finalDf) === Some(5)) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| test("Three stage metrics force-checksum-mismatch on recompute") { | ||
| // INJECT_SHUFFLE_FORCE_CHECKSUM_MISMATCH_ON_RECOMPUTE additionally flags the recompute of the | ||
| // partition-0 task as a checksum mismatch. The DAGScheduler then runs | ||
|
|
||
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.
Nothing here asserts a retry actually fired. With the writer single-counting by design, the test passes even if the injection silently stops retrying — the vacuous-pass gap this PR closes for metadata MERGE. Consider asserting an upstream raw-metric overcount, or note that the new infra-level AQE test is what guards retry-fires. (Same for the MERGE
if (!noMetadata)path.)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.
yes, unfortunately it's still impossible to assert for overcounts in result stage metrics, because Spark does not support any retries in this stage. But the infra PR at least added more "interesting" scenarios of restarts, and it added coverage for Merge with metadata.
The new infra-level AQE tests checks that retry fires - it asserts overcount in stage1 and stage2, but not stage3 (again, because there can't be restarts in result stage).