@@ -317,7 +317,7 @@ impl Picker for LeastConnPicker {
317317/// - Weighted selection based on node's recent response time (RTT)
318318/// - Smaller RTT means higher weight
319319/// - Also considers current load (in_flight)
320- /// - Performance optimization: scans nodes once to find the highest score
320+ /// - Performance optimization: single-pass scan to find the highest score (O(n))
321321#[ derive( Clone , Debug ) ]
322322pub struct ResponseTimeWeighted ;
323323
@@ -337,17 +337,17 @@ impl Picker for RTWeightedPicker {
337337 if len == 0 {
338338 return Err ( LoadBalanceError :: NoAvailableNodes ) ;
339339 }
340- if len == 1 {
341- return Ok ( self . nodes [ 0 ] . clone ( ) ) ;
342- }
343340
344- let mut best_node = self . nodes [ 0 ] . clone ( ) ;
345- let mut best_score = score ( & self . nodes [ 0 ] ) ;
341+ // Single pass O(n) selection; avoids allocation + sort on every pick
342+ let mut iter = self . nodes . iter ( ) ;
343+ let first = iter. next ( ) . unwrap ( ) ;
344+ let mut best_node = first. clone ( ) ;
345+ let mut best_score = score ( first) ;
346346
347- for node in self . nodes . iter ( ) . skip ( 1 ) {
348- let current_score = score ( node) ;
349- if current_score > best_score {
350- best_score = current_score ;
347+ for node in iter {
348+ let s = score ( node) ;
349+ if s > best_score {
350+ best_score = s ;
351351 best_node = node. clone ( ) ;
352352 }
353353 }
@@ -402,14 +402,33 @@ impl ConsistentHashPicker {
402402 fn new ( nodes : Arc < Vec < Arc < Node > > > , virtual_factor : usize ) -> Self {
403403 let mut ring = Vec :: new ( ) ;
404404
405+ // Normalize weights to avoid exploding virtual nodes when weights are large.
406+ let weights: Vec < usize > = nodes. iter ( ) . map ( |n| n. weight . max ( 1 ) as usize ) . collect ( ) ;
407+ let gcd_w = weights
408+ . iter ( )
409+ . copied ( )
410+ . fold (
411+ 0usize ,
412+ |acc, w| if acc == 0 { w } else { gcd_usize ( acc, w) } ,
413+ )
414+ . max ( 1 ) ;
415+
416+ // Hard cap to keep ring size reasonable while preserving relative weights.
417+ const MAX_VNODE_PER_NODE : usize = 1024 ;
418+
405419 // Create virtual nodes for each node
406420 for ( i, node) in nodes. iter ( ) . enumerate ( ) {
407- let weight = node. weight . max ( 1 ) as usize ; // Ensure weight is at least 1
408- let vnode_count = weight * virtual_factor;
421+ let normalized = ( weights[ i] / gcd_w) . max ( 1 ) ;
422+ let vnode_count = normalized
423+ . saturating_mul ( virtual_factor)
424+ . min ( MAX_VNODE_PER_NODE )
425+ . max ( 1 ) ;
426+
427+ let base_key = stable_node_key ( node, i) ;
409428
410429 for j in 0 ..vnode_count {
411430 // Generate hash value using node address and virtual node index
412- let key = format ! ( "{}: {j}" , node . endpoint . id ) ;
431+ let key = format ! ( "{base_key}# {j}" ) ;
413432 let hash = hash_str ( & key) ;
414433 ring. push ( ( hash, i) ) ;
415434 }
@@ -463,6 +482,28 @@ fn hash_str(s: &str) -> u64 {
463482 h. finish ( )
464483}
465484
485+ fn gcd_usize ( a : usize , b : usize ) -> usize {
486+ if b == 0 {
487+ a
488+ } else {
489+ gcd_usize ( b, a % b)
490+ }
491+ }
492+
493+ fn stable_node_key ( node : & Arc < Node > , idx : usize ) -> String {
494+ let addr = format_address ( & node. endpoint . address ) ;
495+ format ! ( "id:{}|addr:{}|idx:{idx}" , node. endpoint. id, addr)
496+ }
497+
498+ #[ cfg( feature = "volo-adapter" ) ]
499+ fn format_address ( addr : & volo:: net:: Address ) -> String {
500+ format ! ( "{addr:?}" )
501+ }
502+
503+ #[ cfg( not( feature = "volo-adapter" ) ) ]
504+ fn format_address ( addr : & String ) -> String {
505+ addr. clone ( )
506+ }
466507#[ cfg( test) ]
467508mod tests {
468509 use super :: * ;
0 commit comments