Skip to content

test: support fallback chain in CometPlanStabilitySuite, dedupe existing goldens#4129

Open
andygrove wants to merge 2 commits intoapache:mainfrom
andygrove:plan-stability-fallback-chain
Open

test: support fallback chain in CometPlanStabilitySuite, dedupe existing goldens#4129
andygrove wants to merge 2 commits intoapache:mainfrom
andygrove:plan-stability-fallback-chain

Conversation

@andygrove
Copy link
Copy Markdown
Member

@andygrove andygrove commented Apr 28, 2026

Which issue does this PR close?

Closes #.

Rationale for this change

Each Spark-version-specific TPC-DS plan-stability directory currently stores a full copy of every query's golden plan, even when that query's plan is byte-identical to the previous version's. After the recent Spark 4.1 enablement (#4106), the per-version approved-plans-*-spark4_1 directories duplicated 196 of 206 (v1_4) and 60 of 64 (v2_7) plans against the 4.0 directory, which itself was largely a duplicate of 3.5, which was largely a duplicate of the base. This pattern grows quadratically as new versions are added.

This PR teaches CometPlanStabilitySuite to fall back through the chain of older version directories at read time, so each version-specific directory only needs to contain the queries whose plans actually diverge.

What changes are included in this PR?

  • CometPlanStabilitySuite gains a protected def fallbackGoldenFilePaths: Seq[String] hook. getDirForTest is unchanged in regenerate mode (always writes to the primary directory). For read-time lookups, when the primary goldenFilePath does not contain a directory for the active query, the suite walks the fallback list and returns the first existing directory.

  • Both CometTPCDSV1_4_PlanStabilitySuite and CometTPCDSV2_7_PlanStabilitySuite build their fallback chain via CometPlanStabilitySuite.planNameChain(variant), which assembles the right sequence based on isSpark{35,40,41}Plus. The chain ends at the unsuffixed base directory.

  • When SPARK_GENERATE_GOLDEN_FILES=1, the suite's afterAll() walks the just-written primary directory and drops any query directory whose contents match what the fallback chain resolves to. This mirrors the read-time logic and keeps version-specific directories sparse without any extra bash plumbing.

  • dev/regenerate-golden-files.sh is now just a thin wrapper that invokes the suites for each Spark version and accepts --spark-version 4.1 (now supported on main).

  • Applies the prune once across the existing tree, removing 766 duplicate query directories:

    directory before after
    approved-plans-v1_4-spark3_5 206 10
    approved-plans-v1_4-spark4_0 206 12
    approved-plans-v1_4-spark4_1 206 10
    approved-plans-v2_7-spark3_5 64 4
    approved-plans-v2_7-spark4_0 64 4
    approved-plans-v2_7-spark4_1 64 4

How are these changes tested?

Locally, CometTPCDSV1_4_PlanStabilitySuite (194 tests) and CometTPCDSV2_7_PlanStabilitySuite (64 tests) pass with 0 failures against -Pspark-3.5, -Pspark-4.0, and -Pspark-4.1 after the prune. Spark 3.4 reads only the base directory and is unaffected. CI will exercise all four matrix profiles.

Verified the auto-prune end-to-end: a full regen of -Pspark-4.1 writes all 206 query directories to approved-plans-v1_4-spark4_1/, then afterAll() drops the 196 that match what the fallback chain (4.0 → 3.5 → base) would resolve to, leaving the 10 divergent directories. Repeat regens are idempotent.

@andygrove andygrove marked this pull request as draft April 28, 2026 15:35
Adds a fallback mechanism so that when a Spark-version-specific plan-stability
directory does not contain a golden directory for a given query, the suite
walks the chain of older-version directories and ultimately the base directory
(approved-plans-vX_Y). This lets each version's directory store only the
queries whose plans actually changed against the previous version.

CometPlanStabilitySuite gains a fallbackGoldenFilePaths: Seq[String] hook.
getDirForTest is unchanged in regenerate mode (always writes to the primary
directory) and in the case where the primary already has a directory for the
query; otherwise it walks the fallback list and returns the first hit. Both
V1_4 and V2_7 subclasses build the chain via planNameChain(variant), which
assembles the correct sequence based on isSpark3{5,4{0,1}}Plus.

When SPARK_GENERATE_GOLDEN_FILES=1, afterAll() walks the just-written primary
directory and drops any query directory whose contents match what the fallback
chain resolves to, mirroring the read-time logic. This keeps version-specific
directories sparse without any extra bash plumbing; dev/regenerate-golden-files.sh
just invokes the suites and now also accepts --spark-version 4.1 (now supported
on main).
…chain

Drops 766 query directories from approved-plans-{v1_4,v2_7}-spark{3_5,4_0,4_1}
whose contents are identical to what the fallback chain resolves to (the
older-version directory or, ultimately, the base approved-plans-{v1_4,v2_7}).
Each version-specific directory now retains only the queries whose plans
actually diverge from the previous tier:

  approved-plans-v1_4-spark3_5: 196 -> 10 dirs (10 diverge from base)
  approved-plans-v1_4-spark4_0: 194 -> 12 dirs
  approved-plans-v1_4-spark4_1: 196 -> 10 dirs
  approved-plans-v2_7-spark3_5:  60 ->  4 dirs
  approved-plans-v2_7-spark4_0:  60 ->  4 dirs
  approved-plans-v2_7-spark4_1:  60 ->  4 dirs

Verified locally that CometTPCDSV1_4_PlanStabilitySuite (194 tests) and
CometTPCDSV2_7_PlanStabilitySuite (64 tests) pass with 0 failures against
-Pspark-3.5, -Pspark-4.0, and -Pspark-4.1. Spark 3.4 reads only the base
directory and is unaffected.
@andygrove andygrove force-pushed the plan-stability-fallback-chain branch from 461869e to ab65163 Compare April 28, 2026 15:52
@andygrove andygrove marked this pull request as ready for review April 28, 2026 21:36
Copy link
Copy Markdown
Contributor

@comphead comphead left a comment

Choose a reason for hiding this comment

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

Thanks @andygrove this is a nice cleanup, just thinking aloud about dedup can it be a situation when the same query has diff plans between Spark versions?

@andygrove
Copy link
Copy Markdown
Member Author

Thanks @andygrove this is a nice cleanup, just thinking aloud about dedup can it be a situation when the same query has diff plans between Spark versions?

Yes, some queries have different plans between Spark versions. There are some optimizer changes in 4.1 that affected a few queries and produced different plans than 4.0, but most queries were not affected.

@comphead
Copy link
Copy Markdown
Contributor

Yes, some queries have different plans between Spark versions. There are some optimizer changes in 4.1 that affected a few queries and produced different plans than 4.0, but most queries were not affected.

Ic, so if such discrepancy happens we would need to attach the query plans for all specific spark versions like before?

Copy link
Copy Markdown
Contributor

@kazuyukitanimura kazuyukitanimura left a comment

Choose a reason for hiding this comment

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

looks like there is a conflict

fallbackGoldenFilePaths.iterator
.map(p => new File(p, goldenFileName))
.find(_.isDirectory)
.getOrElse(primary)
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.

is it correct to return primary as default?

@andygrove
Copy link
Copy Markdown
Member Author

Yes, some queries have different plans between Spark versions. There are some optimizer changes in 4.1 that affected a few queries and produced different plans than 4.0, but most queries were not affected.

Ic, so if such discrepancy happens we would need to attach the query plans for all specific spark versions like before?

Yes, we will store all unique plans per query.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants