Skip to content

HIVE-29820: Refactor and improve ObjectCache in some RecordProcessors - #6709

Open
abstractdog wants to merge 1 commit into
apache:masterfrom
abstractdog:HIVE-29820-objectcache
Open

HIVE-29820: Refactor and improve ObjectCache in some RecordProcessors#6709
abstractdog wants to merge 1 commit into
apache:masterfrom
abstractdog:HIVE-29820-objectcache

Conversation

@abstractdog

@abstractdog abstractdog commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

  • Introduces a TrackedCache nested class in RecordProcessor that pairs an ObjectCache with the list of keys retrieved through it, replacing the parallel cache + cacheKeys and dynamicValueCache + dynamicValueCacheKeys field pattern. MapRecordProcessor, ReduceRecordProcessor, and MergeFileRecordProcessor retrieve plans through the wrapper, and releaseCache() at close time releases every tracked key.
  • Small cleanup in ObjectCacheFactory.getLlapObjectCache: removes a duplicate import, and swaps the get/putIfAbsent/log dance for computeIfAbsent with Objects.requireNonNull.
  • Adds a regression test that pins the plan cache to per-processor mr.ObjectCache on LLAP — the safe default. See the note below on why plan sharing across fragments is deferred.

Why are the changes needed?

The wrapper class removes the two parallel-list bookkeeping pattern that had drifted between subclasses and made "which cache owns this key" hard to see at the call sites. It's the seam a future daemon-wide plan-sharing change would need.

Plan sharing across concurrent fragments on LLAP

An earlier iteration of this PR routed the plan cache through the daemon-wide LlapObjectCache (ObjectCacheFactory.getCache(..., isPlanCache=true, llapCacheAlwaysEnabled=true)) so N concurrent fragments would share one deserialised MapWork/ReduceWork instead of each running Kryo. Verifying against TestMiniLlapLocalCliDriver -Dqfile=acid_globallimit.q — which failed deterministically with FileAlreadyExistsException: .../acidtest1/delta_0000001_0000001_0000/bucket_00000_0 and NPEs in Operator.initialize — showed this is unsafe today: operator trees in every RecordProcessor subclass carry per-fragment mutable state that initializeOp() resets (FileSinkOperator.fsp, VectorGroupByOperator.aggregator, Operator.childOperatorsArray, VectorTopNKeyOperator's per-instance filter state, ...), and sharing the operator tree across concurrent fragments races on those fields — the HIVE-14433 class of failure.

Selective sharing (Map-only, per Reduce/MergeFile's own state) also failed the same qtest 2/2 with a VectorHashKeyWrapperBase.isNull NPE inside TopNKeyFilter.compareWithBoundary — Map-side operator trees carry per-fragment state too.

Kept off until an operator-tree refactor separates the plan descriptor from per-fragment state. The trap is documented on RecordProcessor#planCache so it doesn't have to be rediscovered.

Does this PR introduce any user-facing change?

No. Internal refactor. Runtime behaviour is unchanged from pre-refactor: each processor deserialises its own plan.

How was this patch tested?

New unit test TestRecordProcessor:

  • retrieveReturnsUnderlyingValueTrackedCache.retrieve is a pure pass-through to the underlying ObjectCache.
  • planAndDynamicValueCachesAreIndependent — releasing one wrapper never touches the other.
  • plansAreNotSharedAcrossProcessorsOnLlap — drives the real production path with LlapProxy.setDaemon(true), constructs two RecordProcessor instances through the production constructor, and asserts each loads its own plan (loader fires twice, results are distinct). Guards against the plan cache being re-flipped to llapCacheAlwaysEnabled=true without a matching operator-tree fix.

Also verified TestMiniLlapLocalCliDriver -Dqfile=acid_globallimit.q — deterministic failure on the earlier iteration, green on this PR.

return Utilities.getMapWork(jconf);
}
});
MapWork mapWork = (MapWork) planCache.retrieve(MAP_PLAN_KEY, (Callable<Object>) () -> Utilities.getMapWork(jconf));

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.

nit: Extra casting ? in other hunks, the Callable / MapWork is not present in retrieve().

MapWork mapWork = planCache.retrieve(MAP_PLAN_KEY, () -> Utilities.getMapWork(jconf));

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.

I just did java level review, The LLAP is not my forte.

@abstractdog

Copy link
Copy Markdown
Contributor Author

UPDATE: mass test failures prove pretty well that HIVE-14433 is still valid, we cannot cache MapWork/ReduceWork until we don't take care of separating what's specific to each instance and mutable: work objects contain the whole operator pipeline, containing paths, etc.

@sonarqubecloud

Copy link
Copy Markdown

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants