Repository navigation
Audit the PRs in 1.1.0 for regressions since 1.0.0 #6399
Description
What / Why
I'd like to go through every PR that went into 1.1.0 and look for regressions against 1.0.0, specifically the kind a reviewer could have caught from the diff. That covers wrong or silently different results, new failures, a query that used to fall back to Spark now running natively and getting it wrong, lost native coverage, slower default paths, and config or API behavior changes. Bugs that were already in 1.0.0 are out of scope. For each regression found, I also want to write down why review missed it, so the answer can go back into the review checklists and the review-comet-* skills.
The range is af534e0fa..branch-1.1, where af534e0fa is the merge base of branch-1.1 and the 1.0.0 tag. That is 399 PRs at 1.1.0-rc1, not counting #5192, which shipped in 1.0.0 as #5261. 292 of them touch production code. The other 107 only change docs, CI, tests or the Spark diffs.
This is too much for one sitting, so I've split it into phases that can run on separate days. Claude agents do the per-PR reading: Sonnet for the cheap passes and Opus for the deep reviews. I'll post the results of each phase as a comment here.
Where results go
Confirmed regressions are tracked in #6402, a WIP epic. Whenever a phase confirms a regression, rules one out, or a fix or backport for one lands, update the description of #6402 in place rather than commenting there. Add or move the entry and keep its progress line current. Each phase's full results still go in a comment here.
Phase 0: scope and script-only checks (done)
- The TPC-DS golden plans show no lost native coverage. I compared all 574 effective plans between
1.0.0andbranch-1.1, per Spark version and following the suite's fallback chain. 26 gained native operators and none lost any. - The dependency upgrades are DataFusion 54.1 → 55.1, arrow and parquet 58.4 → 59.3, opendal 0.57 → 0.58.2 and iceberg-rust 0.10.0 → 0.10.1. Upstream behavior changes won't show up in our own diffs, so they get their own review in Phase 3.
- Blaming the lines that each
fix:PR changed gives 86 links where the fixed code was written by another PR from the same window: 78 fixes in 1.1.0 and 8 onmainafter the cut. They point at 54 PRs, and 17 of those needed two or more fixes. Blame is noisy, because a fix can touch code that a refactor only moved, so Phase 1 confirms them. - Five fixes that merged to
mainafter the cut touch code from this window and are not onbranch-1.1: fix: return a native plan's memory before its Spark task ends #6261 (touching fix: let Comet memory pools overcommit on grow instead of panicking #6128's code), fix: report task input metrics after the native iterator closes and add to Spark's counters #5880 (fix: report native operator spill metrics in Spark task metrics for non-shuffle stages #5497), fix: match Spark ordering in native array extrema #5403, fix: validate map constructor row lengths and null short-circuiting #5846 and fix: native ansi-errors raised as spark error #5169. The last three look like weak links. fix: report task input metrics after the native iterator closes and add to Spark's counters #5880 was on my backport list for rc1 and didn't make it. I think its bug predates 1.0.0 and only affects metrics, but Phase 1 should confirm that.
Phase 1: confirm the fix → origin links (Sonnet)
- Start with the five fixes that aren't on
branch-1.1. If any of them fixes a regression from this window, 1.1.0 ships it. None of them does: all five fix bugs that were already in 1.0.0. - For each of the 86 links, decide whether the origin PR introduced the bug that the fix addresses, and whether the bug was absent in 1.0.0. Done: 7 regressions, all fixed before rc1 (see [EPIC] Regressions in 1.1.0 since 1.0.0 #6402).
The output is the list of regressions that got past review and were caught after merge, plus any backport candidates for another RC or 1.1.1. The confirmed origins are also the known answers for checking the Phase 2 triage.
Phase 2: triage the 292 production PRs (scripts, then Sonnet)
- Build one packet per PR: the production diff, the PR description, who reviewed it, which CI tiers ran, and any later fixes to its lines. Later fixes were left out, so that the calibration stayed blind.
- Triage in batches of about 12, asking whether the PR changes behavior on a path that already ran in 1.0.0. That means new defaults, removed guards or fallbacks, an expression or operator that is now native by default, changed semantics, config parsing, error handling, or a perf rewrite of a default-path kernel. Anything unclear gets escalated. Done: 47 HIGH, 119 MEDIUM, 126 LOW.
- Check the triage against the origins confirmed in Phase 1. If it misses more than one or two, rerun it with Opus. 5 of 7 were rated HIGH, and 3 of 5 among the blind checks. That is within tolerance, so there's no rerun.
Phase 3: deep review of the escalated PRs (Opus)
First, run the #6261 single-worker hang reproducer on 1.0.0 and 1.1.0-rc1, because #6261 isn't on branch-1.1. Then do one area per day, using the Phase 2 HIGH list in the comment below:
- fix: return a native plan's memory before its Spark task ends #6261 hang: new in 1.1.0, or already in 1.0.0? Already in 1.0.0. With one Tokio worker and 32m off-heap, 1.0.0 hung 2/2 and rc1 1/2 with the same stack.
mainwith fix: return a native plan's memory before its Spark task ends #6261 never hung, so it's a 1.1.1 candidate rather than an RC blocker. - Expressions and casts (12 PRs): 5 regressions confirmed (Native regr_* aggregates return wrong results for a constant variable merged from several partitions #6423, Typed Dataset.filter returns wrong rows over a native aggregate #6424, Decimal-returning DSv2 functions lose their scale through the codegen dispatcher #6425, Native Iceberg years/months/days/hours differ from Iceberg for some pre-1970 timestamps #6426, Native IF fails with "column types must match schema types" when its branches differ in nested nullability #6334)
- Planner, operators and shuffle (13 PRs): 3 regressions confirmed (Native explode returns wrong booleans for arrays of structs after the first batch of output #6464, Match Spark ordering and rank semantics for floating values nested in arrays and structs #5507, Adaptive partial aggregation can shuffle 10x more rows than 1.0.0, with no supported way to turn it off #6466)
- Scans: Parquet, Iceberg and Delta (15 PRs): 3 regressions confirmed (Native Iceberg scan fails on files written before a nested field was added, and since 1.1.0 null checks and explode hit it too #6504, Native Parquet scan returns wrong _metadata.file_block_start when Spark splits a file #6505, Native Parquet scan fails on an empty file whose nested field type differs from the read schema #6506)
- Memory, FFI, config and shims (7 PRs): no regressions; fix: Native S3 scan on EKS/IRSA turns a transient STS throttle into a hard 403 storm #6025's IRSA no-fallback needs an upgrade-guide line
- The DataFusion 55.1 and arrow/parquet 59.3 changes to APIs we use: 2 regressions confirmed (array_distinct and array_union diverge from Spark on -0.0 for Spark versions without SPARK-54918 #5701, A native final aggregate that has spilled can fail the task during its replay #6254), both from DataFusion 55
Each review uses review-comet-pr plus the matching area skill, and compares behavior at 1.0.0 and rc1. Every finding needs a reproducer and a sentence on why review missed it. The checklist includes the misses we've seen before: a stale rebase undoing a refactor on main, a new sibling class missing a guard its predecessor had, a loud failure turned into a silent wrong answer, timestamp label drift, sliced arrays across FFI, the two shuffle paths disagreeing, and tests that pass without testing anything (cache-vs-cache comparisons, assertions against the initial AQE plan).
Phase 4: verification (Sonnet, after each Phase 3 batch)
- Build
1.0.0and 1.1.0-rc1 once, then run each reproducer on both. Done for every area, with each workaround also checked on rc1.
A finding counts as a regression only if 1.0.0 gets it right and 1.1.0 doesn't, or if a loud failure became a silent wrong answer. Perf findings need evidence in the code, and only the high-impact ones get benchmarked.
Phase 5: report
- File each confirmed regression that ships in 1.1.0 as its own issue with the
regressionlabel, add it to [EPIC] Regressions in 1.1.0 since 1.0.0 #6402, and recommend one of: RC blocker, 1.1.1, or release note. Done: 13 shipped in rc1, all filed or labeled and in [EPIC] Regressions in 1.1.0 since 1.0.0 #6402, with a recommendation for each in the Phase 5 comment. - Summarize why review missed them, for example no human review, no Spark SQL or Iceberg suite run, or tests that only covered the new path, and turn that into updates to the
review-comet-*skills. Done in the Phase 5 comment; the skill updates are in docs: add release-behavior checks and the 1.1.0 audit's lessons to the review skills #6516.
Rough sizes in agent turns: Phase 1 about 180, Phase 2 about 650, Phase 3 about 2,000 across its batches, and Phase 4 about 200 plus build and test time.
Activity
andygrove commentedon Sep 29, 2026
Phase 1 is done. Sonnet agents checked all 83 fix → origin links from the Phase 0 blame against the
1.0.0tag, and I spot-checked the verdicts that mattered. That is 86 links minus 3 that were amaincommit and itsbranch-1.1backport.Seven are regressions, and all seven were fixed before 1.1.0-rc1. They're listed in #6402 with the introducing PR, the fix, and why review missed each one. None of the seven introducing PRs was backported to
branch-1.0, and all seven fixes are in rc1. The five fixes that merged tomainafter the cut without reachingbranch-1.1(#6261, #5880, #5403, #5846 and #5169) all fix bugs that were already in 1.0.0, so none of them needs a backport as a regression fix.Of the other 76, 45 fixed bugs that were already in 1.0.0. There the blame link was noise, because the fix touched lines that a PR in the window had only moved or edited. 28 were bugs in functionality that is new in 1.1.0 and didn't break anything that worked in 1.0.0. Most of those are in the native Iceberg writer, the native Celeborn shuffle and the native in-memory cache, which are all off by default. The last 3 weren't fixes for a defect. I overrode two regression verdicts:
The reasons review missed the seven fall into three groups:
TestForwardCompatibilitycaught deps: bump DataFusion 55.0 and Arrow/Parquet 59.2 #5262's break after merge, and the nightly Spark 3.4 job caught fix: let decimal SUM recover from an intermediate overflow like Spark #6041's. No CI profile could have caught feat: implement regr_slope, regr_intercept, regr_r2, regr_sxx, regr_syy, regr_sxy aggregates #4775's, because CI builds only the newest patch of each Spark line and the behavior it got wrong changed in earlier patches.WindowGroupLimitExec#4870 documented its signed-zero rank mismatch in the compatibility guide but still enabledWindowGroupLimitExecby default, instead of gating it behindallowIncompatible. feat: implement regr_slope, regr_intercept, regr_r2, regr_sxx, regr_syy, regr_sxy aggregates #4775 has the same shape.s3.The seven introducing PRs (#5602, #5262, #5314, #5368, #6041, #4870 and #4775) are the known answers for checking the Phase 2 triage.
andygrove commentedon Sep 29, 2026
Phase 2 is done. Sonnet agents triaged all 292 PRs that touch production code, in 25 batches. Each PR got HIGH, MEDIUM or LOW for how plausibly its diff regressed something that worked in 1.0.0. The result is 47 HIGH, 119 MEDIUM and 126 LOW. A HIGH is a reason to look closer, not a finding, and I expect most of them to turn out fine in Phase 3.
The method changed in two ways from the plan above. First, the packets left out later fixes to each PR's lines, because including them would have given away the seven known answers from Phase 1. Second, one agent looked up my local notes, which describe some of those answers, so the later batches were told to judge each PR only from its own diff and the code.
For calibration, five of the seven PRs behind the Phase 1 regressions were rated HIGH. Two of those five aren't clean hits: #5368 shared a batch with its own fix, and the agent that rated #5602 had read my notes. Of the five blind checks, #4775, #4870 and #5262 were rated HIGH, and #6041 and #5314 were rated MEDIUM. Both misses only showed up under a non-default configuration:
spark.sql.codegen.factoryMode=NO_CODEGENon Spark 3.4fs.comet.libhdfs.schemeslist namings3but nots3aBoth were at least flagged as touching a default path. That's within the plan's tolerance of one or two misses, so I'm not rerunning the triage.
The 47 HIGH PRs go to Phase 3, grouped by area:
CreateArrayliteral #5452 fix: normalize noncanonical NaN literals in comparisons #5472 feat: add native spark_sequence kernel for integral element types #5614 feat: Support Iceberg system functions (bucket, truncate, years/months/days/hours) natively #5638 fix: align string to timestamp parsing with Spark's segment rules #5682 feat: route unrecognized StaticInvoke and Invoke through the codegen dispatcher #5692 fix: dispatch Iceberg system functions wrapped as ApplyFunctionExpression #5773WindowGroupLimitExec#4870 fix: enable FIRST/LAST partial merge #5041 fix: make CometExplodeExec respect batch size #5362 fix: revert unsafe partial aggregates after final fallback #5421 perf: bulk copy fixed-width columns in ArrowWriter #5442 feat: add raw Celeborn native shuffle reader (7/n) #5531 perf: cache parsed plan data across a stage tasks #5615 perf: slice the child instead of gathering it when unnesting #5667 fix: recover native Celeborn shuffle from oversized rows #5668 feat: Enable adaptive partial aggregation for eligible native shuffle plans #5723 feat: hook native Parquet writes into Spark's WriteFilesExec seam on Spark 4.0+ #5763 perf: give collect_list and collect_set a native GroupsAccumulator #5803 perf: spill every shuffle partition of a task into one file #5916The dependency-upgrade review in Phase 3 starts from #5262, #5324, #5865 and #6094.
The common reasons for a HIGH were:
One open question goes first in Phase 3 because it could affect the release. #6261 fixed a hang that needs a single Tokio worker, and it isn't on
branch-1.1. A standalone executor withoutspark.executor.coresgets exactly one worker. Phase 1 traced the memory-release bug in #6261 to before 1.0.0, but nobody has run the hang reproducer on 1.0.0. So it isn't known yet whether the hang is new in 1.1.0.andygrove commentedon Sep 29, 2026
Phase 3, expressions and casts: all 12 PRs have been reviewed against 1.0.0 and against Spark. Every reproducer was then run on 1.0.0 and 1.1.0-rc1 builds, which covers Phase 4 for this area. The #6261 check is done too: that hang was already in 1.0.0.
Five regressions that ship in 1.1.0 are confirmed, and #6402 has the details:
regr_slope,regr_interceptandregr_r2return large wrong values instead of NULL for a constant variable merged across partitions.Dataset.filterover a native aggregate returns wrong rows. fix: zero sliced boolean offsets at every level before exporting to the JVM #6339 fixes it onmainand needs a backport.years,months,daysandhoursdiffer from Iceberg for rare pre-1970 timestamps.CreateArrayliteral #5452: a nativeIFwith a map or struct literal branch throws when every row of a batch takes the same branch.Each one has a draft fix PR open or in progress, linked from its issue.
No regression was found in #5472, #5166, #5415, #5614, #5280 or #5682. Several of them fix wrong answers that 1.0.0 gave. #4816 changes the order of values in
listaggonly where Spark leaves that order unspecified, so it's not a regression.Why review missed these:
CreateArrayliteral #5452.andygrove commentedon Sep 30, 2026
Phase 3, planner, operators and shuffle: all 13 PRs have been reviewed against 1.0.0 and against Spark. Every reproducer was then run on 1.0.0 and 1.1.0-rc1 builds, which covers Phase 4 for this area.
Three more regressions that ship in 1.1.0 are confirmed, and #6402 has the details:
explode,posexplodeandexplode_outerof an array of structs with a boolean field return wrong booleans and NULLs after the firstspark.comet.batchSizeoutput rows of each input batch, with default configs. 3,000 rows of 12 elements gave wrong booleans in 22,824 of the 36,000 output rows. The new chunking slices the output, and Arrow Java ignores the bit offset of the sliced boolean children, which is Sliced booleans nested in structs, and sliced inputs to the JVM UDF bridge, reach the JVM misaligned and return wrong results #6288. fix: [branch-1.1] zero sliced boolean offsets at every level before exporting to the JVM (#6339) #6449, thebranch-1.1backport of fix: zero sliced boolean offsets at every level before exporting to the JVM #6339, fixes every case on rc1, so the same backport covers this and Typed Dataset.filter returns wrong rows over a native aggregate #6424.WindowGroupLimitExec#4870: the nativeWindowGroupLimittreats-0.0and0.0inside an array or struct sort key as different, so arank <= kfilter drops tied rows. 1.0.0 ranWindowGroupLimitin Spark.No regression was found in #5041, #5442, #5531, #5615, #5668, #5763 or #5803. #5421 and #5916 are deliberate trade-offs. #5421 moves partial
AVG,stddev,first,lastand a few others back to Spark when the final aggregate runs in Spark, mostly for anyone running without the Comet shuffle manager, because 1.0.0 could return NULL averages there. That's worth a line in the release notes. #5916 puts all of a map task's spill in one local directory, which #6010 tracks.One memory concern isn't verified. Since #5803, a native partial
collect_listdirectly overUNION ALLorcoalescekeeps every input batch it has read, including the columns it doesn't collect, and those aren't counted against the memory pool until the aggregate emits. I couldn't make that observable in a test on both builds.Why review missed these:
LIMITand aggregates as triggers, not explode.WindowGroupLimitbug and moved the nested case to Match Spark ordering and rank semantics for floating values nested in arrays and structs #5507 as a documented incompatibility, instead of falling back for it by default.andygrove commentedon Oct 1, 2026
Phase 3, scans: all 15 Parquet, Iceberg and Delta PRs have been reviewed against 1.0.0 and against Spark. The reproducers were then run on 1.0.0 and 1.1.0-rc1 builds, which covers Phase 4 for this area.
Three more regressions that ship in 1.1.0 are confirmed, and #6402 has the details:
IS NULLandIS NOT NULLon the column, non-outerexplodeof the array, and joins on the struct now fail. 1.0.0 fell back to Spark for these. The root cause is older (Type casting error when reading files persisted with old schema for complex type iceberg-rust#2617): a plain read of such a column fails natively in 1.0.0 too._metadata.file_block_startandfile_block_lengthcome out wrong when Spark splits a Parquet file into several partitions, because DataFusion and parquet-mr assign row groups to splits differently. 1.0.0 fell back to Spark for metadata columns.All three have a workaround in the draft release notes (#6469), checked against the reproducers.
No regression was found in #5262 (its scan side), #5377, #5503, #5602, #5654, #5715, #5853, #6116, #6154 or #6219. #6154 fixes wrong rows that 1.0.0 returned for some Iceberg transform residuals.
Two findings aren't counted:
LIMITcan fail on a value Spark never reads. That needs timestamps past year 294,000, which Spark can't write.Why review missed these:
main, which already had feat: support unicode case sensitive field names for reading parquet #5602's behavior, rather than against 1.0.0.andygrove commentedon Oct 1, 2026
Phase 3, memory, FFI, config and shims: all 7 PRs have been reviewed against 1.0.0. None of them has a regression to count, so there was nothing to run on the 1.0.0 and rc1 builds for this area.
Four findings aren't counted, but are worth knowing:
spark.comet.scan.icebergNative.enabled=false.spark.comet.debug.memoryvalue such asyesinstead of ignoring it, which fails every native plan. It's a testing setting, but one sentence in the upgrade guide would cover it.UNION ALLuncoalesced when its branches carry different top-level field metadata. The results are correct, and the PR accepted that cost on purpose.No regression was found in #5368, #5493, #5539, #6166 or #6191's intended changes, which the upgrade guide documents. #5368's code only runs with
spark.comet.exec.pyarrowUDF.enabled, and #5493's riskiest code was test-only and removed by #6066 before rc1.andygrove commentedon Oct 1, 2026
Phase 3, dependency upgrades: I reviewed the upstream changes between the versions in 1.0.0 and 1.1.0-rc1, starting from the code Comet calls on default paths. That covers DataFusion and datafusion-spark 54.1 to 55.1, arrow and parquet 58.4 to 59.3, opendal 0.57 to 0.58.2, and the 154 iceberg-rust commits from
3d84c81tobb1e4a4. The reproducers were run on 1.0.0 and rc1 builds. This completes Phase 3.Two regressions that ship in 1.1.0 are confirmed, both from DataFusion 55, and #6402 has the details:
array_distinctandarray_unionnow treat-0.0and0.0as one value and return0.0. Spark versions without SPARK-54918 keep them apart: 3.4, 3.5, 4.0 before 4.0.5 and 4.1 before 4.1.4. 1.0.0 matched Spark.Failed to acquire N bytes. At 96m of off-heap memory, rc1 failed every run and 1.0.0 passed every run.Both were already filed, so I added the
regressionlabel and the evidence to each. Their workarounds are in the draft release notes (#6469).No regression was found in arrow, parquet, opendal or iceberg-rust. A few upstream changes improve on 1.0.0. Comparisons now treat
-0.0as equal to0.0, as Spark does.map_from_entriesraises Spark'sDUPLICATED_MAP_KEYerror. A Date32 toTIMESTAMP_NTZoverflow now errors as it does in Spark. The DataFusion review wasn't exhaustive: it didn't cover the sort-merge join and sort spill changes, row-based group values, or several string kernels.One gap is worth checking before rc2. Native Iceberg reads from S3, GCS or Azure now depend on opendal 0.58 installing its HTTP transport when the native library loads. That works in local runs, but no CI job reads from a real object store, so a quick S3 smoke test on the rc2 build would cover it.
Why review missed these:
array_distinctandarray_unionsigned-zero tests toignoreto get the upgrade through, so a behavior change from 1.0.0 went in as a skipped test rather than a fallback.andygrove commentedon Oct 1, 2026
Phase 5: what to do with each regression, and why review missed them.
What to do with each regression
rc2 comes from
branch-1.1, where #6449 already fixes #6424 and #6464. For the other 11:regr_*IFWindowGroupLimit_metadatablock columnsarray_distinct/array_union-0.0The fixes I'd take in rc2 are small, and each either falls back to Spark or restores 1.0.0's behavior. All of them except #6425 are approved or merged. The draft release notes in #6469 have a checked workaround for every entry, so anything that misses rc2 is covered there. #6515 changes when scan errors are raised, so I'd rather it had some time on
mainthan went in at the last minute.Why review missed them
Across all 20 regressions, the 7 fixed before rc1 and the 13 that shipped in it, the same few things come up:
-0.0and NaN (fix: normalize scalar float sort and window rank keys #5469, Match Spark ordering and rank semantics for floating values nested in arrays and structs #5507, array_distinct and array_union diverge from Spark on -0.0 for Spark versions without SPARK-54918 #5701), a constant like 0.1 merged across partitions (Native regr_* aggregates return wrong results for a constant variable merged from several partitions #6423), pre-1970 unit boundaries (Native Iceberg years/months/days/hours differ from Iceberg for some pre-1970 timestamps #6426), a batch where every row takes one branch (Native IF fails with "column types must match schema types" when its branches differ in nested nullability #6334), output sliced past the first batch (Native explode returns wrong booleans for arrays of structs after the first batch of output #6464), files Spark splits or that are empty (Native Parquet scan returns wrong _metadata.file_block_start when Spark splits a file #6505, Native Parquet scan fails on an empty file whose nested field type differs from the read schema #6506), data that defeats a heuristic (Adaptive partial aggregation can shuffle 10x more rows than 1.0.0, with no supported way to turn it off #6466), and an asymmetric config (fix: decide libhdfs routing from the scheme as written #5825).WindowGroupLimitExec#4870 documented its signed-zero gap and still enabled the operator (fix: normalize scalar float sort and window rank keys #5469), and deps: bump DataFusion 55.0 and Arrow/Parquet 59.2 #5262 switched thearray_distinctandarray_uniontests toignoreinstead of falling back (array_distinct and array_union diverge from Spark on -0.0 for Spark versions without SPARK-54918 #5701).regr_*(Native regr_* aggregates return wrong results for a constant variable merged from several partitions #6423), and iceberg-rust instead of Iceberg Java for the temporal functions (Native Iceberg years/months/days/hours differ from Iceberg for some pre-1970 timestamps #6426).Ten of the 18 PRs that introduced these had a single approver.
#6516 turns these lessons into checks in the
review-comet-pr,review-comet-expression-prandreview-comet-memory-prskills.docs: check behavior against the latest release and add the 1.1.0 aud…
docs: check behavior against the latest release and add the 1.1.0 aud…