mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 09:02:11 +03:00
feat(resilience): add hierarchical concurrency admission
This commit is contained in:
committed by
Markus Hartung
parent
943b9aaa84
commit
2b9f25accb
@@ -13,6 +13,7 @@ type RequestQueueSettings = {
|
||||
requestsPerMinute: number;
|
||||
minTimeBetweenRequestsMs: number;
|
||||
concurrentRequests: number;
|
||||
globalConcurrentRequests: number;
|
||||
maxWaitMs: number;
|
||||
};
|
||||
|
||||
@@ -240,13 +241,21 @@ function RequestQueueCard({
|
||||
}
|
||||
/>
|
||||
<NumberField
|
||||
label={t("resilienceConcurrentRequests")}
|
||||
label={t("resilienceConnectionScopeConcurrentRequests")}
|
||||
value={draft.concurrentRequests}
|
||||
min={1}
|
||||
onChange={(concurrentRequests) =>
|
||||
setDraft((prev) => ({ ...prev, concurrentRequests }))
|
||||
}
|
||||
/>
|
||||
<NumberField
|
||||
label={t("resilienceGlobalConcurrentRequests")}
|
||||
value={draft.globalConcurrentRequests}
|
||||
min={0}
|
||||
onChange={(globalConcurrentRequests) =>
|
||||
setDraft((prev) => ({ ...prev, globalConcurrentRequests }))
|
||||
}
|
||||
/>
|
||||
<NumberField
|
||||
label={t("resilienceMaxQueueWait")}
|
||||
value={draft.maxWaitMs}
|
||||
@@ -278,11 +287,21 @@ function RequestQueueCard({
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-bg-subtle p-4">
|
||||
<div className="text-xs text-text-muted">{t("resilienceConcurrentRequests")}</div>
|
||||
<div className="text-xs text-text-muted">
|
||||
{t("resilienceConnectionScopeConcurrentRequests")}
|
||||
</div>
|
||||
<div className="mt-1 text-sm font-semibold text-text-main">
|
||||
{value.concurrentRequests}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-bg-subtle p-4">
|
||||
<div className="text-xs text-text-muted">
|
||||
{t("resilienceGlobalConcurrentRequests")}
|
||||
</div>
|
||||
<div className="mt-1 text-sm font-semibold text-text-main">
|
||||
{value.globalConcurrentRequests || t("statusDisabled")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-bg-subtle p-4">
|
||||
<div className="text-xs text-text-muted">{t("resilienceMaxQueueWait")}</div>
|
||||
<div className="mt-1 text-sm font-semibold text-text-main">
|
||||
|
||||
@@ -7538,6 +7538,8 @@
|
||||
"resilienceRequestsPerMinute": "Requests per minute",
|
||||
"resilienceMinTimeBetweenRequests": "Minimum time between requests",
|
||||
"resilienceConcurrentRequests": "Concurrent requests",
|
||||
"resilienceConnectionScopeConcurrentRequests": "Gleichzeitige Anfragen pro Verbindung/Quota-Bereich",
|
||||
"resilienceGlobalConcurrentRequests": "Globale gleichzeitige Upstream-Anfragen (0 = deaktiviert)",
|
||||
"resilienceMaxQueueWaitTime": "Maximale Wartezeit in der Warteschlange",
|
||||
"resilienceBaseCooldown": "Base cooldown",
|
||||
"resilienceUseUpstreamRetryHints": "Use upstream retry hints",
|
||||
|
||||
@@ -7550,6 +7550,8 @@
|
||||
"resilienceRequestsPerMinute": "Requests per minute",
|
||||
"resilienceMinTimeBetweenRequests": "Minimum time between requests",
|
||||
"resilienceConcurrentRequests": "Concurrent requests",
|
||||
"resilienceConnectionScopeConcurrentRequests": "Concurrent requests per connection/quota scope",
|
||||
"resilienceGlobalConcurrentRequests": "Global concurrent upstream requests (0 = disabled)",
|
||||
"resilienceMaxQueueWaitTime": "Maximum queue wait time",
|
||||
"resilienceBaseCooldown": "Base cooldown",
|
||||
"resilienceUseUpstreamRetryHints": "Use upstream retry hints",
|
||||
|
||||
@@ -58,6 +58,7 @@ export const DEFAULT_RESILIENCE_SETTINGS: ResilienceSettings = {
|
||||
requestsPerMinute: DEFAULT_API_LIMITS.requestsPerMinute,
|
||||
minTimeBetweenRequestsMs: DEFAULT_API_LIMITS.minTimeBetweenRequests,
|
||||
concurrentRequests: DEFAULT_API_LIMITS.concurrentRequests,
|
||||
globalConcurrentRequests: 0,
|
||||
maxWaitMs: DEFAULT_REQUEST_QUEUE_MAX_WAIT_MS,
|
||||
maxQueueDepth: DEFAULT_REQUEST_QUEUE_MAX_DEPTH,
|
||||
},
|
||||
@@ -208,6 +209,8 @@ function buildLegacyFallback(settings: JsonRecord): ResilienceSettings {
|
||||
DEFAULT_RESILIENCE_SETTINGS.requestQueue.concurrentRequests,
|
||||
{ min: 1, max: 10_000 }
|
||||
),
|
||||
globalConcurrentRequests:
|
||||
DEFAULT_RESILIENCE_SETTINGS.requestQueue.globalConcurrentRequests,
|
||||
maxWaitMs: DEFAULT_RESILIENCE_SETTINGS.requestQueue.maxWaitMs,
|
||||
maxQueueDepth: DEFAULT_RESILIENCE_SETTINGS.requestQueue.maxQueueDepth,
|
||||
},
|
||||
|
||||
@@ -124,6 +124,11 @@ export function normalizeRequestQueueSettings(
|
||||
min: 1,
|
||||
max: 10_000,
|
||||
});
|
||||
const globalConcurrentRequests = toInteger(
|
||||
record.globalConcurrentRequests,
|
||||
fallback.globalConcurrentRequests,
|
||||
{ min: 0, max: 100_000 }
|
||||
);
|
||||
const maxWaitMs = toInteger(record.maxWaitMs, fallback.maxWaitMs, {
|
||||
min: 1,
|
||||
max: 24 * 60 * 60 * 1000,
|
||||
@@ -141,6 +146,7 @@ export function normalizeRequestQueueSettings(
|
||||
requestsPerMinute,
|
||||
minTimeBetweenRequestsMs,
|
||||
concurrentRequests,
|
||||
globalConcurrentRequests,
|
||||
maxWaitMs,
|
||||
maxQueueDepth,
|
||||
};
|
||||
@@ -431,8 +437,16 @@ function normalizeProviderQuotaOverrideEntry(raw: unknown): ProviderQuotaOverrid
|
||||
const out: ProviderQuotaOverrideSettings = {};
|
||||
const rpm = typeof record.rpm === "number" ? record.rpm : Number(record.rpm);
|
||||
if (Number.isFinite(rpm) && rpm > 0) out.rpm = Math.trunc(rpm);
|
||||
const concurrency = typeof record.concurrency === "number" ? record.concurrency : Number(record.concurrency);
|
||||
const concurrency =
|
||||
typeof record.concurrency === "number" ? record.concurrency : Number(record.concurrency);
|
||||
if (Number.isFinite(concurrency) && concurrency > 0) out.concurrency = Math.trunc(concurrency);
|
||||
const providerConcurrency =
|
||||
typeof record.providerConcurrency === "number"
|
||||
? record.providerConcurrency
|
||||
: Number(record.providerConcurrency);
|
||||
if (Number.isFinite(providerConcurrency) && providerConcurrency >= 0) {
|
||||
out.providerConcurrency = Math.trunc(providerConcurrency);
|
||||
}
|
||||
return Object.keys(out).length > 0 ? out : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ export interface RequestQueueSettings {
|
||||
requestsPerMinute: number;
|
||||
minTimeBetweenRequestsMs: number;
|
||||
concurrentRequests: number;
|
||||
/** Whole-process upstream concurrency cap. Zero disables the global gate. */
|
||||
globalConcurrentRequests: number;
|
||||
/**
|
||||
* Legacy persisted key used as Bottleneck's post-dispatch execution
|
||||
* expiration. It does not bound time spent in Bottleneck's QUEUED state.
|
||||
@@ -169,6 +171,8 @@ export interface ProviderQuotaOverrideSettings {
|
||||
rpm?: number;
|
||||
/** Overrides the static per-connection concurrency cap. */
|
||||
concurrency?: number;
|
||||
/** Shared concurrency cap across every connection for this provider. */
|
||||
providerConcurrency?: number;
|
||||
}
|
||||
|
||||
export interface StreamRecoverySettings {
|
||||
|
||||
@@ -32,6 +32,7 @@ export const legacyResilienceDefaultsSchema = z
|
||||
requestsPerMinute: z.number().int().min(1).optional(),
|
||||
minTimeBetweenRequests: z.number().int().min(0).optional(),
|
||||
concurrentRequests: z.number().int().min(1).optional(),
|
||||
globalConcurrentRequests: z.number().int().min(0).max(100_000).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -170,6 +171,7 @@ export const updateResilienceSchema = z
|
||||
.object({
|
||||
rpm: z.number().int().min(1).optional(),
|
||||
concurrency: z.number().int().min(1).optional(),
|
||||
providerConcurrency: z.number().int().min(0).max(100_000).optional(),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user