Jackie-Jiang commented on code in PR #19915:
URL: https://github.com/apache/pinot/pull/19915#discussion_r4383150890


##########
pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java:
##########
@@ -73,6 +75,45 @@ public class ReplicaGroupInstanceSelector extends 
BaseInstanceSelector {
 
   private static final Logger LOGGER = 
LoggerFactory.getLogger(ReplicaGroupInstanceSelector.class);
 
+  // Immutable key for instance groups that reduces hash collisions.
+  static final class InstanceSetKey implements Comparable<InstanceSetKey> {
+    private final String[] _instances;
+    private final int _hashCode;
+
+    InstanceSetKey(Set<String> instances) {
+      _instances = instances.toArray(new String[0]);
+      Arrays.sort(_instances);

Review Comment:
   Can we avoid the extra copy per entry/lookup here? Each `InstanceSetKey` 
construction allocates a wrapper, copies the instance names into a new array, 
and sorts it. We do this twice per processed old `InstanceStateMaps` 
(collection and lookup), and once per new segment. When candidate sharing is 
exhausted or assignments are distinct, that becomes two constructions per old 
segment. Even with small replica counts, this adds allocation and GC work 
across large routing rebuilds.
   
   The normal assignment writers create natural-order `TreeMap`s, and the Helix 
serialization/deserialization path preserves their iteration order even when 
the result is a `LinkedHashMap`. We should be able to use the existing 
`idealStateInstanceStateMap.keySet()` views directly with an 
`Object2ObjectOpenCustomHashMap<Set<String>, Set<String>>` and a custom 
`Hash.Strategy`:
   
   - Compute the hash in iteration order, retaining the same mixed fold used 
here:
     ```java
     int hash = 1;
     for (String instance : instances) {
       hash = 31 * hash + HashCommon.mix(instance.hashCode());
     }
     ```
   - Compare sizes and then compare the two iterators element by element. Only 
instance names participate; state values must not affect grouping.
   - Handle identity/nulls in the strategy, since fastutil also calls 
`equals(key, null)` internally.
   
   This removes the wrapper, array copy, and sort while retaining the same hash 
for naturally ordered inputs. The map is local to the rebuild, so the backing 
assignment maps only need to remain unchanged for that lifetime. Please 
document the natural-order iteration precondition and cover the actual 
assignment/serialization round trip in a test; account for the existing 
shuffled-map tests when defining that contract.
   
   Could you also share some before/after performance numbers? The 
hash-distribution test demonstrates fewer collisions, but does not measure the 
rebuild cost or the added allocation. Reusing the workload from #19852 (380k 
segments, 200 servers, 3 replicas) would make the results comparable:
   
   - End-to-end `onAssignmentChange` time and allocated bytes per rebuild, with 
GC time/count if available.
   - A few distinct-instance-set counts, such as 64, 4,096, 50,000, and 
380,000, to cover both heavily shared and mostly distinct assignments.
   - Healthy assignments and a case with unavailable instances, using the same 
JVM/configuration and reporting the candidate-sharing limit.
   - Baseline versus this PR; include the custom-strategy version if you try 
it. Please use warmup and repeated measurements so we can see the variation.
   
   This would show both the collision-related improvement and whether the extra 
copying adds meaningful overhead in the common case.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to