Skip to content

Commit d7ea0af

Browse files
author
Theodore Li
committed
Switch to round robin for api key distribution
1 parent a90777a commit d7ea0af

File tree

2 files changed

+14
-33
lines changed

2 files changed

+14
-33
lines changed

apps/sim/lib/core/rate-limiter/hosted-key/hosted-key-rate-limiter.ts

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ interface AvailableKey {
4444
/**
4545
* HostedKeyRateLimiter provides:
4646
* 1. Per-billing-actor rate limiting (enforced - blocks actors who exceed their limit)
47-
* 2. Least-loaded key selection (distributes requests evenly across keys)
47+
* 2. Round-robin key selection (distributes requests evenly across keys)
4848
* 3. Post-execution dimension usage tracking for custom rate limits
4949
*
5050
* The billing actor is typically a workspace ID, meaning rate limits are shared
5151
* across all users within the same workspace.
5252
*/
5353
export class HostedKeyRateLimiter {
5454
private storage: RateLimitStorageAdapter
55-
/** In-memory request counters per key: "provider:keyIndex" -> count */
56-
private keyRequestCounts = new Map<string, number>()
55+
/** Round-robin counter per provider for even key distribution */
56+
private roundRobinCounters = new Map<string, number>()
5757

5858
constructor(storage?: RateLimitStorageAdapter) {
5959
this.storage = storage ?? createStorageAdapter()
@@ -176,11 +176,11 @@ export class HostedKeyRateLimiter {
176176
}
177177

178178
/**
179-
* Acquire the best available key.
179+
* Acquire an available key via round-robin selection.
180180
*
181181
* For both modes:
182182
* 1. Per-billing-actor request rate limiting (enforced): blocks actors who exceed their request limit
183-
* 2. Least-loaded key selection: picks the key with fewest in-flight requests
183+
* 2. Round-robin key selection: cycles through available keys for even distribution
184184
*
185185
* For `custom` mode additionally:
186186
* 3. Pre-checks dimension budgets: blocks if any dimension is already depleted
@@ -229,31 +229,21 @@ export class HostedKeyRateLimiter {
229229
}
230230
}
231231

232-
let leastLoaded = availableKeys[0]
233-
let minCount = this.getKeyCount(provider, leastLoaded.keyIndex)
234-
235-
for (let i = 1; i < availableKeys.length; i++) {
236-
const count = this.getKeyCount(provider, availableKeys[i].keyIndex)
237-
if (count < minCount) {
238-
minCount = count
239-
leastLoaded = availableKeys[i]
240-
}
241-
}
242-
243-
this.incrementKeyCount(provider, leastLoaded.keyIndex)
232+
const counter = this.roundRobinCounters.get(provider) ?? 0
233+
const selected = availableKeys[counter % availableKeys.length]
234+
this.roundRobinCounters.set(provider, counter + 1)
244235

245236
logger.debug(`Selected hosted key for ${provider}`, {
246237
provider,
247-
keyIndex: leastLoaded.keyIndex,
248-
envVarName: leastLoaded.envVarName,
249-
requestCount: minCount + 1,
238+
keyIndex: selected.keyIndex,
239+
envVarName: selected.envVarName,
250240
})
251241

252242
return {
253243
success: true,
254-
key: leastLoaded.key,
255-
keyIndex: leastLoaded.keyIndex,
256-
envVarName: leastLoaded.envVarName,
244+
key: selected.key,
245+
keyIndex: selected.keyIndex,
246+
envVarName: selected.envVarName,
257247
}
258248
}
259249

@@ -337,15 +327,6 @@ export class HostedKeyRateLimiter {
337327

338328
return { dimensions: results }
339329
}
340-
341-
private getKeyCount(provider: string, keyIndex: number): number {
342-
return this.keyRequestCounts.get(`${provider}:${keyIndex}`) ?? 0
343-
}
344-
345-
private incrementKeyCount(provider: string, keyIndex: number): void {
346-
const key = `${provider}:${keyIndex}`
347-
this.keyRequestCounts.set(key, (this.keyRequestCounts.get(key) ?? 0) + 1)
348-
}
349330
}
350331

351332
let cachedInstance: HostedKeyRateLimiter | null = null

apps/sim/tools/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ interface HostedKeyInjectionResult {
4444

4545
/**
4646
* Inject hosted API key if tool supports it and user didn't provide one.
47-
* Checks BYOK workspace keys first, then uses the HostedKeyRateLimiter for least-loaded key selection.
47+
* Checks BYOK workspace keys first, then uses the HostedKeyRateLimiter for round-robin key selection.
4848
* Returns whether a hosted (billable) key was injected and which env var it came from.
4949
*/
5050
async function injectHostedKeyIfNeeded(

0 commit comments

Comments
 (0)