@@ -233,17 +233,62 @@ export class LoadBalancer {
233233
234234 /**
235235 * Round Robin strategy
236+ * Uses two-level round-robin: provider level + account level.
237+ * At account level, uses weighted random when weights are configured.
236238 */
237239 private selectRoundRobin ( candidates : AccountSelection [ ] ) : AccountSelection {
238- const providerIds = [ ...new Set ( candidates . map ( c => c . provider . id ) ) ]
239- const key = providerIds . join ( ',' )
240+ // Group candidates by provider (preserve insertion order)
241+ const byProvider = new Map < string , AccountSelection [ ] > ( )
242+ const providerIds : string [ ] = [ ]
243+ for ( const c of candidates ) {
244+ if ( ! byProvider . has ( c . provider . id ) ) {
245+ byProvider . set ( c . provider . id , [ ] )
246+ providerIds . push ( c . provider . id )
247+ }
248+ byProvider . get ( c . provider . id ) ! . push ( c )
249+ }
250+
251+ // Provider-level round-robin
252+ const providerKey = providerIds . join ( ',' )
253+ const providerIdx = this . roundRobinIndex . get ( providerKey ) || 0
254+ const chosenProviderId = providerIds [ providerIdx % providerIds . length ]
255+ this . roundRobinIndex . set ( providerKey , providerIdx + 1 )
256+
257+ // Account-level: weighted random
258+ const providerAccounts = byProvider . get ( chosenProviderId ) !
259+ return this . selectWeightedAccount ( providerAccounts , chosenProviderId )
260+ }
261+
262+ /**
263+ * Weighted random account selection.
264+ * Each account is selected with probability proportional to its configured weight.
265+ * Stateless — immune to dynamic pool changes (accounts entering/leaving).
266+ */
267+ private selectWeightedAccount (
268+ accounts : AccountSelection [ ] ,
269+ providerId : string
270+ ) : AccountSelection {
271+ if ( accounts . length === 1 ) {
272+ return accounts [ 0 ]
273+ }
240274
241- const currentIndex = this . roundRobinIndex . get ( key ) || 0
242- const selected = candidates [ currentIndex % candidates . length ]
275+ const config = storeManager . getConfig ( )
276+ const weights = config . accountWeights || { }
243277
244- this . roundRobinIndex . set ( key , ( currentIndex + 1 ) % candidates . length )
278+ let totalWeight = 0
279+ for ( const a of accounts ) {
280+ totalWeight += Math . max ( weights [ a . account . id ] ?? 100 , 5 )
281+ }
282+
283+ let random = Math . random ( ) * totalWeight
284+ for ( const a of accounts ) {
285+ random -= Math . max ( weights [ a . account . id ] ?? 100 , 5 )
286+ if ( random <= 0 ) {
287+ return a
288+ }
289+ }
245290
246- return selected
291+ return accounts [ accounts . length - 1 ]
247292 }
248293
249294 /**
0 commit comments