[SPARK-58069][SQL][FOLLOWUP] Handle empty approx_top_k combine buffers#57375
[SPARK-58069][SQL][FOLLOWUP] Handle empty approx_top_k combine buffers#57375cloud-fan wants to merge 1 commit into
Conversation
cd3a99d to
b4295d7
Compare
viirya
left a comment
There was a problem hiding this comment.
The fix is well-targeted. genSketchSerDe has no null/fallthrough case, so an untouched placeholder buffer (itemDataType == null) hit a MatchError on serialize between stages — branching on null before that call is exactly the right spot. I traced the round-trip and the downstream merge, and it holds up:
- Defaulting to
ArrayOfStringsSerDefor the null-type case is safe because of the invariantitemDataType == null<=> the sketch is empty (everyupdatesets the type from a non-null DDL; merging an empty input only carries an empty sketch). An empty sketch serializes no item bytes, so the serde choice is immaterial to the content. - On the later merge, the placeholder adopts the real type via the existing
updateItemDataTypenull-handling (and the reverse direction — real buffer, empty input — is a no-op), and the sketch merge runs on in-memory objects, independent of the serde used at (de)serialize time. - The zero-length type section round-trips symmetrically, and the
sketchByteslength math stays correct attypeLength == 0.
One non-blocking thought on testing: the new test exercises the serde in isolation (constructs CombineInternal and round-trips serialize()/deserialize()), which is a fine regression guard and fails before the fix. It doesn't reproduce the actual trigger — an empty partition contributing a placeholder buffer through a real multi-stage aggregation — but making that deterministic is awkward, so the unit-level test seems a reasonable call. If it's cheap, asserting the restored nullCount (0) alongside the type/size/sketch would round out the round-trip coverage.
LGTM.
| "CAST('13:00:00.123' AS TIME(3))")) | ||
| ) | ||
|
|
||
| test("SPARK-58069: serialize an empty approx_top_k_combine buffer") { |
There was a problem hiding this comment.
The new regression test is a pure in-JVM round-trip of serialize()/deserialize(); it never drives the reported failure through a real SQL query with a shuffle-forced empty partition (e.g. a REPARTITION or UNION of empty + non-empty approx_top_k_accumulate). It also exercises only the concrete maxItemsTracked = 100 variant, not the VOID_MAX_ITEMS_TRACKED = -1 placeholder that the size-unspecified production path (createAggregationBuffer) actually creates. An end-to-end SQL test (and a VOID variant) would faithfully guard the failure this PR claims to fix.
| buffer | ||
| } | ||
|
|
||
| override def eval(buffer: CombineInternal[Any]): Any = { |
There was a problem hiding this comment.
Please investigate a bit more and coordinate with #57289 accordingly: The fix closes the intermediate serialize/deserialize crash and correctly handles the mixed case (some empty some non-empty partitions resolve the null type during merge/update, so eval sees a concrete type). But a combine over an entirely empty input leaves the final buffer with itemDataType == null, and eval then calls ApproxTopK.genSketchSerDe(null) (line 900) and dataTypeToDDL(null) (line 902); neither has a null/wildcard case (verified against genSketchSerDe, lines 273-291), so the query still fails with a scala.MatchError. This is pre-existing (the query failed earlier, at serialize, before this PR) and arguably outside the declared "between stages" scope, and is separately handled by draft #57289. The author should decide whether to also cover it here (a fallback to uncheckedItemDataType in eval would close it) or explicitly document the remaining gap and defer to #57289; not leave a still-crashing realistic path silently behind a narrower fix.
What changes were proposed in this pull request?
Followup to #57177.
This change makes
CombineInternalserialize and deserialize the placeholder buffer created foran empty partition. It encodes a missing item type as a zero-length type section and uses a default
string serde for the necessarily empty sketch.
Why are the changes needed?
An untouched
approx_top_k_combinebuffer has no item type. It can nevertheless be serializedbetween aggregation stages, where selecting a type-specific serde or serializing the type currently
throws. Preserving the empty placeholder allows the later merge to initialize it from real input.
Does this PR introduce any user-facing change?
Yes.
approx_top_k_combineno longer fails when an empty partition contributes an untouchedplaceholder buffer between aggregation stages.
How was this patch tested?
Added a regression test to
ApproxTopKSuitethat round-trips an empty combine buffer and verifiesthat its missing item type, sketch, and configured size are preserved. The staged workflow will run
the required fail-before and pass-after suite cycle before opening the pull request.
Was this patch authored or co-authored using generative AI tooling?
Generated-by: Codex (GPT-5)