Repository navigation
Conversation
SegmentPartitionMetadataManager.onAssignmentChange() rebuilt the online-server list of every segment and then ran computeAllTablePartitionInfo(), two full passes over all segments, on every ideal state or external view change of the table, even when nothing relevant to the partition info changed. The method now detects changes and recomputes only what depends on them: - A pulled segment counts as a change only when it is new or its partition id or creation time differs. Segments without ZK metadata are pulled again on every change, so this avoids a spurious change for them. - hasSameOnlineServers() compares the stored online-server list with the external view instance state map without allocating. A new list is built only for segments whose online servers changed, and equal lists are shared through a per-call cache instead of one list per segment. - A change of the segment set or of a partition id recomputes both TablePartitionInfo and TablePartitionReplicatedServersInfo. A change of the online servers recomputes only TablePartitionReplicatedServersInfo, since TablePartitionInfo does not depend on them. The rebuild is also the clock for new segments: newness is re-evaluated only when TablePartitionReplicatedServersInfo is computed. To keep that behavior, computeTablePartitionReplicatedServersInfo() records the earliest expiration time among the new segments in a writer-private field, and an otherwise no-op change still recomputes once that time has passed. A new segment that was excluded therefore becomes visible on the first change after it expires, as before. Published objects are still replaced with a single volatile write and never mutated, so the query path is unaffected.
TimeBoundaryManager.onAssignmentChange() read the ZK metadata of each new ONLINE segment with its own ZkHelixPropertyStore.get() call, and scanned the whole end time map to find the max end time on every ideal state or external view change, even when no segment was added or removed. onAssignmentChange() now collects the paths of the new ONLINE segments and reads them with a single batched ZkHelixPropertyStore.get(List, ...), the same call init() and SegmentZkMetadataFetcher use. A segment whose record comes back null (missing node or failed read) is not recorded, so the next change reads it again, as the per-segment read did on a read failure. The max end time is kept in the writer-private field _maxEndTimeMs, in sync with _endTimeMsMap: - Added segments raise it with Math.max(). - Removed segments trigger a full scan only when one of them holds the current max end time. - refreshSegment() triggers a full scan only when the refreshed segment held the max end time and its end time moved back. The time boundary is still derived on every change through updateTimeBoundaryInfo(), so explicitly set time boundary changes in the ideal state keep being picked up. The published TimeBoundaryInfo is unchanged: it is still replaced with a single volatile write.
SegmentPartitionUtils.extractPartitionInfo() and extractPartitionInfoMap() created a new PartitionFunction through PartitionFunctionFactory for every segment, and kept the IntOpenHashSet deserialized from the segment ZK metadata as the partition set of every segment. All segments of a table usually carry the same partition function metadata and a single partition, so a large table held one function instance and one hash set per segment, and every routing build created them again. SegmentPartitionUtils now: - Shares one PartitionFunction per (function name, number of partitions, function config) through a static cache. The PartitionFunction contract requires implementations to be stateless and safe for concurrent use, and the query path already calls one instance from many threads, so sharing it across segments adds no new requirement. The cache stops growing at 1024 entries; past that, functions are created without being cached. - Replaces a one-element partition set with a shared immutable IntSets.singleton() for partition ids below 1024. Callers only read the partition sets. Shared function instances also let the prepared partition pruning in SinglePartitionColumnSegmentPruner match functions by identity first. SegmentPartitionInfo is used by SegmentPartitionMetadataManager, SinglePartitionColumnSegmentPruner and MultiPartitionColumnsSegmentPruner, so all three benefit.
4 tasks
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #19783 +/- ##
============================================
+ Coverage 68.47% 68.49% +0.01%
Complexity 1470 1470
============================================
Files 3522 3522
Lines 229423 229511 +88
Branches 36431 36456 +25
============================================
+ Hits 157101 157206 +105
+ Misses 60019 60007 -12
+ Partials 12303 12298 -5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
PR flow
Broker routing components now update incrementally, recomputing only when inputs change and sharing partition functions/sets.
AI-generated · Green: added · Yellow: modified · Red: removed · Gray: existing
Diff evidence
Summary
Part of #19782.
This PR makes three broker routing components do less work on each ideal state or external view (IS/EV) change.
SegmentPartitionMetadataManagerrecomputes its published partition info only when its inputs change.TimeBoundaryManagerreads the ZK metadata of new segments in one batch and keeps the max end time up to date incrementally.SegmentPartitionUtilsshares onePartitionFunctionand one immutable one-element partition set across segments with equal metadata.Each component compares the new inputs with its writer-private state, and builds a new published object only when the result can be different. Most of the cost in #19782 comes from the IS/EV parse and the instance selector. These three components are a small part of it.
Changes
1.
SegmentPartitionMetadataManager: recompute only when inputs changeBefore,
onAssignmentChange()did two full passes over all segments on each change. Now it finds what changed and recomputes only what depends on it:TablePartitionInfoTablePartitionReplicatedServersInfohasSameOnlineServers()compares without allocation. Equal lists of online servers are shared through a per-call cache.New-segment expiry.
TablePartitionReplicatedServersInfotreats a segment as "new" for a configured time after its creation. Before, every change recomputed the object and so evaluated the clock. Now the compute step stores the earliest expiration time of the new segments in_nextNewSegmentExpirationTimeMs(Long.MAX_VALUEif none). If the current time is past it, the next change recomputes the object. Thus a new segment still becomes visible on the first change after it expires.2.
TimeBoundaryManager: batched ZK reads and incremental max end timeZkHelixPropertyStore.get(List, ...)call reads the ZK metadata of all new ONLINE segments, asinit()andSegmentZkMetadataFetcheralready do. It returnsnullfor a missing node and for a failed read. Then the manager does not record the segment, and the next change reads it again._maxEndTimeMsholds the max of_endTimeMsMap. An added segment updates it withMath.max(). A removed segment causes a full scan only when it held the max.refreshSegment()causes a full scan only when the segment held the max and its end time decreased.updateTimeBoundaryInfo()still runs on each change. Thus a change of the explicit time boundary in the ideal state still takes effect.3.
SegmentPartitionUtils: share partition functions and partition setsPartitionFunctionis shared per (function name, number of partitions, function config) through a staticConcurrentHashMap. The cache holds at most 1024 entries. Past that limit, functions are created and not cached.IntSets.singleton()for partition ids less than 1024. All callers only read the partition sets.PartitionFunctioncontract already requires stateless implementations that are safe for concurrent use. The query path already calls one instance from many threads.SinglePartitionColumnSegmentPrunercompares functions withequals()and keys aHashMapby function. Shared instances only help there through the usualthis == ofast path.Concurrency
This PR keeps the concurrency rule of #19782.
TablePartitionInfo,TablePartitionReplicatedServersInfoandTimeBoundaryInfo, swapped with one volatile write as before. When nothing changes, the old object stays published._segmentInfoMap,_nextNewSegmentExpirationTimeMs,_endTimeMsMapand_maxEndTimeMs.synchronizedmethods serialize the writers, as before.Tests
SegmentPartitionMetadataManagerTest.testRecomputeOnlyOnChange: equal inputs keep both objects, an online server change replaces onlyTablePartitionReplicatedServersInfo, and a removed segment replaces both. ONLINE to CONSUMING is not a change. A segment without ZK metadata does not cause a recompute on each change.SegmentPartitionMetadataManagerTest.testNewSegmentExpiresOnNoOpChange: a new segment that misses a replica is excluded. After it expires, a no-op change includes it. After that, a no-op change keeps the published object.TimeBoundaryManagerTest.testBatchedReadsAndIncrementalMaxEndTime: two added segments cause one batched read. A change with no new segment does no ZK read. A removal or a refresh moves the time boundary back only when it affects the max end time. A new segment without ZK metadata is read again on the next change.SegmentPartitionUtilsTest(new): sharing for equal metadata, distinct functions for a different name, number of partitions or config, and multi-partition sets kept as they are.All tests under
org.apache.pinot.broker.routingpass (19 classes, 257 tests, 0 failures, 0 skipped).Measured effect
TimeBoundaryManagerruns only for the offline side of a hybrid table. We replayed the segments of a real 380k-segment table as the OFFLINE side of a hybrid table. Per change,TimeBoundaryManagerwent from 22 MB and about 440 ms to 14 MB and about 40 ms.Compatibility
There is no change to configs, wire formats, or public APIs. All changed code is broker-internal.