HIVE-29702: Reduce-side merge join can produce NULL values or fail with IllegalStateException under Tez container reuse - #6646
Draft
ryukobayashi wants to merge 2 commits into
Draft
Conversation
…n failures on Tez container reuse
…g the same merge join
|
ryukobayashi
marked this pull request as draft
July 28, 2026 06:09
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What changes were proposed in this pull request?
This PR fixes two related defects in Hive's reduce-side merge join (SMB / DummyStore +
CommonMergeJoinOperatorpipeline) on Tez:ReduceRecordProcessorhad two bugs that surface specifically when Tez reuses a container across multiple tasks of the same reducer vertex (Hive's per-queryObjectCache, keyed by vertex name, hands the new task the same already-initialized operator instances the previous task used):getShuffleInputs()only calledstart()on the mainReduceWork's tagged inputs. ThemergeWorkListsiblings (e.g. theDummyStoreOperatorside of the merge join) have their own tagged inputs thatinit()also reads from viatagToReducerMap, but those were never started, sogetReader()throws"Must start input before invoking this method".getJoinParentOp()located theDummyStoreOperatorby treating "childOperators is empty" as the leaf marker. On a reused operator tree, a prior attempt'sCommonMergeJoinOperator#initializeLocalWorkmay have already appended itself to thatDummyStoreOperator's children, so the leaf check no longer holds and the walk recurses into the wrong subtree, throwingIllegalStateException: Was expecting dummy store operator but found: ....close()closed the mainreducerbefore themergeWorkList(DummyStore chain). TheDummyStoreOperatorhas no rows of its own to close out, so closing its side first is what letsOperator#allInitializedParentsAreClosed()see it as done, and the main reducer'sclose()— now closing last — is what finally letsCommonMergeJoinOperatoremit its last join group.GenTezUtils#createReduceWorksets eachReduceWork'snumReduceTaskspurely from that branch's ownReduceSinkOperator, with no cross-check against siblingReduceSinkOperators that feed the same downstreamCommonMergeJoinOperatorthrough a separate, independently auto-parallelized Tez vertex (theDummyStoreOperator/mergeWorkListcase above).SetReducerParallelismassigns eachReduceSinkOperator's reducer count/traits independently, so two such siblings can end up with differentnumReducers, and Tez's dynamic auto-parallelism (ShuffleVertexManager) can shrink one side's task count at runtime independently of the other's.A new
normalizeMergeJoinReducershelper walks from aReduceSinkOperatordownstream to find aCommonMergeJoinOperator, and only acts when: (a) one of the merge join's parents is aTezDummyStoreOperator— this is what marks the sibling as living in its own, separately auto-parallelized vertex; a merge join whose parents are just multiple tags of the sameReduceWorkalready shares that single vertex's parallelism, so there's nothing to reconcile — and (b) at least one sibling carries theAUTOPARALLEL/UNIFORMtrait — i.e. itsnumReducersis a mutable suggestion Tez can shrink at runtime. Genuine bucketed SMB joins (e.g. a 2-bucket table joined with a 3-bucket table) legitimately have different,FIXEDnumReducersper side, each anchored to that side's own on-disk bucket layout; without guard (b) this normalization would overwrite one side's bucket count with the other's and break the bucket-to-reducer correspondence those joins rely on. When both guards hold, all siblings are normalized to the same (maximum)numReducerswithFIXEDtraits, so Tez's auto-parallelism cannot independently shrink one side's task count at runtime.Why are the changes needed?
IllegalStateException) when Tez reuses a container across multiple tasks of the same reducer vertex.Does this PR introduce any user-facing change?
No
How was this patch tested?
tez_dummystore_reduce_side_reuse.q(TestMiniTezCliDriver) for (1): forces Tez to reuse a container across multiple tasks of the same reducer vertex. Reproduces the exactIllegalStateExceptionagainst the pre-fix code; passes cleanly after the fix.TestGenTezUtilsMergeJoinNumReducers(3 unit tests) for (2):ReduceSinkOperators feeding the same merge join through aGroupByOperatorand aTezDummyStoreOperatorrespectively, with differentnumReducersandAUTOPARALLELon the DummyStore side: fails against the pre-fix code (mismatchednumReduceTasks), passes after the fix.TezDummyStoreOperatorsibling (both sides plainReduceSinkOperators, i.e. tags of the same vertex): asserts normalization is a no-op, since there is no independent-vertex mismatch to guard against.TezDummyStoreOperatorsibling but both sidesFIXED-trait with differentnumReducers(mirroring a 2-bucket/3-bucket SMB join): asserts normalization is a no-op, since forcing them to match would break the bucket-to-reducer correspondence the join relies on.sample8,bucketmapjoin1,pcr,join9,smb_join_with_different_bucket_size,auto_sortmerge_join_9) to confirm the guards in (2) leave unrelated merge-join plans untouched.