Repository navigation
Conversation
…ssignments On every ideal state or external view change, BaseInstanceSelector.updateSegmentMaps rebuilt the candidates of every segment from scratch. Per segment it copied the ideal state instance map into a TreeMap (the Helix maps are LinkedHashMap, so the SortedMap shortcut never applied), built a TreeSet of online instances, and allocated a new candidate list. refreshSegmentStates then copied each list again to filter disabled instances. In a realtime table all segments of a stream partition sit on the same servers, so a table with hundreds of thousands of segments has only about as many distinct assignments as partitions. Topology-independent changes: - convertToSortedMap becomes sortByKeyIfNeeded: it returns the map itself when its iteration order is already sorted (linear check) and copies only otherwise. - The base selector builds old and new segment candidates in a single pass over the sorted ideal state map (computeOldSegmentCandidates and computeNewSegmentCandidates) instead of building a TreeSet of online instances. - getEnabledCandidatesAndAddToServingInstances returns the input list itself when every candidate is enabled, instead of copying it. Sharing: within one rebuild, InstanceStateMapsCache canonicalizes the pair of (ideal state, external view) instance state maps of each segment, compared by content. The canonical InstanceStateMaps caches what derives from the pair: the old segment candidate list, and for the strict replica-group path in ReplicaGroupInstanceSelector.updateSegmentMapsForUpsertTable also the online instances, and whether the pair already contributed its unavailable instances. The cache holds at most MAX_SHARED_CANDIDATE_LISTS (4096) pairs; past that, segments get their own lists as before. Candidate order, replica ids, new versus old segment rules, unavailable segments, serving instances and replica health are unchanged. Sharing lists is safe because they are never modified once built: every consumer (the balanced, replica-group, strict replica-group and multi-stage replica-group selectors, and PriorityPoolInstanceSelector) only reads them. The broker test suite passes with the lists wrapped as unmodifiable. InstanceSelectorTest adds an equivalence test that compares a sharing selector with one that builds a list per segment, for the balanced, replica-group and strict replica-group selectors. It uses instance state maps that are not in sorted order, a partially offline replica, a disabled instance and new segments.
The two INFO logs that print the whole new-segment map on every rebuild
(read from ZK metadata, and derived from existing states) are noisy on
tables with many segments. Pass the public marker
BaseInstanceSelector.NEW_SEGMENTS_MARKER ("ROUTING_NEW_SEGMENTS") to
LOGGER.info so operators can drop them with a log4j2 MarkerFilter,
without code changes. Level, message and arguments are unchanged, so
existing log greps keep working. Shipped log4j2 configs are untouched.
InstanceSelectorTest.testSharedCandidateListsMatchPerSegmentLists now also runs a selector with _maxSharedCandidateLists = 1, so one rebuild mixes shared lists with lists that InstanceStateMapsCache does not cache once it is full. It compares the candidates, unavailable segments, serving instances and selections with the selector that builds one list per segment. This covers the strict replica-group path, where uncached InstanceStateMaps compute their own online instances and unavailable instances. BaseInstanceSelector._maxSharedCandidateLists gets @VisibleForTesting, and the Javadoc of getEnabledCandidatesAndAddToServingInstances is reworded.
4 tasks
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #19785 +/- ##
=========================================
Coverage 68.47% 68.48%
Complexity 1470 1470
=========================================
Files 3522 3522
Lines 229423 229478 +55
Branches 36431 36446 +15
=========================================
+ Hits 157101 157159 +58
+ Misses 60019 60016 -3
Partials 12303 12303
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
Sharing candidate lists for old segments via canonical instance state maps
AI-generated · Green: added · Yellow: modified · Red: removed · Gray: existing
Diff evidence
Summary
Part of #19782.
Within one instance selector rebuild, segments with equal ideal state and external view instance state maps now share one list of routing candidates. In a realtime table, all segments of a stream partition usually have the same servers and states, so a large table has about as many distinct assignments as partitions. The rebuild also stops building a
TreeMapand aTreeSetper segment, and stops copying candidate lists when all candidates are enabled.The PR also adds the SLF4J marker
ROUTING_NEW_SEGMENTSto the two "Got N new segments" INFO logs, so operators can drop them with a log4j2MarkerFilter.Changes
convertToSortedMapbecomessortByKeyIfNeeded. It returns the map itself when its iteration order is already sorted (a linear check), and copies it only otherwise. The Helix maps areLinkedHashMap, so the oldSortedMapshortcut never applied. ZooKeeper maps are usually written in key order, so the copy is rare.computeOldSegmentCandidatesandcomputeNewSegmentCandidatesbuild the candidates in one pass over the sorted ideal state map, without aTreeSetof online instances.getEnabledCandidatesAndAddToServingInstancesreturns the input list itself when every candidate is enabled.InstanceStateMapsCachemaps each distinct pair of (ideal state map, external view map), compared by content, to one canonicalInstanceStateMaps. This entry keeps the candidate list of the first segment. ForReplicaGroupInstanceSelector.updateSegmentMapsForUpsertTable, it also keeps the online instances and a flag that tells if the pair already added its unavailable instances.These do not change: the candidate order, the replica ids, the rules for new and old segments, the unavailable segments, the serving instances, and the replica health metrics.
Why shared lists are safe: the lists are read-only
The broker publishes the candidate lists to queries through
SegmentStates, so a shared list must never change after the rebuild. These are all the consumers:BalancedInstanceSelector.selectget(index)andsize()ReplicaGroupInstanceSelector.select(alsoStrictReplicaGroupInstanceSelector)get(index),size(), and iterationMultiStageReplicaGroupSelectorPriorityPoolInstanceSelector.selectandrankBaseInstanceSelector.refreshSegmentStatesNo consumer calls
add,remove,set,sortorclear. The rebuild adds elements only to lists that it creates, before it stores them.SegmentInstanceCandidateis immutable (all fields are final).As evidence, we ran the full
pinot-brokertest suite with a temporary change that wraps every candidate list inCollections.unmodifiableList. All 555 tests passed. The wrapper is not part of this PR, because it adds one object per list.Concurrency
This PR keeps the concurrency rule of #19782.
InstanceStateMapsCache,InstanceStateMaps, and their online instances and flags are mutable but private to the rebuild thread. The only shared objects that queries see are the candidate lists, which are read-only (see above). Thus one list can be shared between many segments, and between the old and the newSegmentStates.Compatibility
There are no config, API, metric or wire format changes. The routing results are the same as before.
The two INFO logs keep the same level, message and arguments. To drop them, add this filter to the log4j2 config of the broker (for example, on the logger for
BaseInstanceSelector). The log4j2 configs that Pinot ships do not change.Tests
InstanceSelectorTest.testSharedCandidateListsMatchPerSegmentLists, for the balanced, replica-group and strict replica-group selectors. It compares a sharing selector with a per-segment selector: candidates, unavailable segments, serving instances, replica health, and selection for 6 request ids. A third selector with a cache limit of 1 mixes shared and per-segment lists. The test then enables a disabled instance and compares again. The data has unsorted maps, an OFFLINE replica in the external view, a disabled instance, a segment missing from the external view, and new segments.InstanceSelectorTest.testSortByKeyIfNeeded: a sortedLinkedHashMapand aTreeMapcome back unchanged. An unsorted map comes back as a sorted copy.Local results:
org.apache.pinot.broker.routing(18 classes) runs 256 tests with 0 failures, including the 91 tests inInstanceSelectorTest. Spotless, license and checkstyle checks pass forpinot-commonandpinot-broker.Measured effect
Synthetic realtime table with 380k segments, one instance selector rebuild (times are noisy):
We also replayed the assignment changes of a real 380k-segment realtime table. With this PR, the full rebuild of all routing components allocates 80 MB per change instead of 280 MB.