feat(quota): serializa concorrência por conexão no caminho quota-share (FASE 2.1) (#4970)

O gating de quota-share em selectQuotaShareTarget é fail-open: uma conexão
at-cap só é despriorizada, nunca bloqueada. Com 1 conexão por conta de
assinatura (caso comum), chamadas concorrentes ainda floodam a conta (→ 429 +
cooldown) — provado live na .15: 3 chamadas concorrentes com max_concurrent=1
despacharam todas em 94ms.

Adiciona um semáforo POR CONEXÃO em torno do dispatch quota-share: chamadas
excedentes esperam na fila em vez de floodar (key qsconn:<connectionId>, cap =
max_concurrent da conexão). Fail-open em fila saturada/timeout para nunca
piorar disponibilidade. Gated por strategy===quota-share + kill-switch
resilienceSettings.quotaShareConcurrencyLimit (default on; UI no ResilienceTab).

Lógica extraível isolada no leaf puro combo/quotaShareConcurrency.ts
(unit-testado: estabilidade da key, no-op sem cap, serialização real,
fail-open). Settings + schema + UI espelham comboCooldownWait.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-24 23:54:43 -03:00
committed by GitHub
parent 028ed4ea7b
commit 1f685ebcd7
10 changed files with 458 additions and 6 deletions

View File

@@ -106,6 +106,15 @@ export const comboCooldownWaitSettingsSchema = z
})
.strict();
// FASE 2.1: kill-switch for the per-connection quota-share concurrency limit.
// The cap itself comes from each connection's max_concurrent, so only `enabled`
// is configurable here.
export const quotaShareConcurrencyLimitSettingsSchema = z
.object({
enabled: z.boolean().optional(),
})
.strict();
export const providerCooldownSettingsSchema = z
.object({
enabled: z.boolean().optional(),
@@ -146,6 +155,7 @@ export const updateResilienceSchema = z
.optional(),
waitForCooldown: waitForCooldownSettingsSchema.optional(),
comboCooldownWait: comboCooldownWaitSettingsSchema.optional(),
quotaShareConcurrencyLimit: quotaShareConcurrencyLimitSettingsSchema.optional(),
providerCooldown: providerCooldownSettingsSchema.optional(),
profiles: z
.object({
@@ -164,6 +174,7 @@ export const updateResilienceSchema = z
!value.providerBreaker &&
!value.waitForCooldown &&
!value.comboCooldownWait &&
!value.quotaShareConcurrencyLimit &&
!value.providerCooldown &&
!value.profiles &&
!value.defaults