diff --git a/.env.example b/.env.example index bcff1ff98b..546cd4dd69 100644 --- a/.env.example +++ b/.env.example @@ -1527,6 +1527,11 @@ APP_LOG_TO_FILE=true # Timeout for fast-fail health checks (ms). Default: 2000 # PROXY_FAST_FAIL_TIMEOUT_MS=2000 +# Time window (hours) for calculating the average latency of candidate proxies +# in the latency-optimized pool strategy. Default: 3 +# Used by: src/lib/db/proxies.ts +# PROXY_LATENCY_WINDOW_HOURS=3 + # Health check result cache TTL (ms). Default: 30000 (30s) # PROXY_HEALTH_CACHE_TTL_MS=30000 diff --git a/changelog.d/features/6798-latency-optimized-proxy-rotation.md b/changelog.d/features/6798-latency-optimized-proxy-rotation.md new file mode 100644 index 0000000000..e15583b2ec --- /dev/null +++ b/changelog.d/features/6798-latency-optimized-proxy-rotation.md @@ -0,0 +1 @@ +- **feat(proxy):** add a latency-optimized proxy rotation strategy that ranks pool entries by measured round-trip latency, extending the existing round-robin/random/sticky proxy-pool selection (#6798 — thanks @iamraydoan). diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index 927cb93973..6bad9d7969 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -835,6 +835,7 @@ Anthropic-compatible provider instead. | Variable | Default | Source File | Description | | ----------------------------------------------- | ----------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. | +| `PROXY_LATENCY_WINDOW_HOURS` | `3` | `src/lib/db/proxies.ts` | Time window (hours) for calculating the average latency of candidate proxies in the latency-optimized pool strategy. | | `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. | | `PROXY_HEALTH_UNHEALTHY_CACHE_TTL_MS` | `2000` | `src/lib/proxyHealth.ts` | Cache TTL for failed proxy health probes. Keep this shorter than `PROXY_HEALTH_CACHE_TTL_MS` so transient proxy timeouts under high concurrency retry quickly without disabling fast-fail for truly dead proxies. | | `PROXY_HEALTH_ENABLED` | `true` | `src/lib/proxyHealth/scheduler.ts` | Set `false` to disable the background proxy health scheduler that periodically probes registered proxies. | diff --git a/scripts/check/check-db-rules.mjs b/scripts/check/check-db-rules.mjs index dae4b114f5..d91d7f52c0 100644 --- a/scripts/check/check-db-rules.mjs +++ b/scripts/check/check-db-rules.mjs @@ -64,6 +64,7 @@ export const INTENTIONALLY_INTERNAL = new Set([ "prompts", // DEAD? (production): zero callers de produção encontrados; domínio domain/prompts.ts é independente; testado por tests/integration/proxy-pipeline.test.ts "providerNodeSelect", // db-internal: importado só por db/providers.ts (selectProviderNodeForConnection — lógica pura de seleção de provider node split do providers.ts, #4421) "providerStats", // intentionally-internal: src/app/api/provider-stats/route.ts + "proxyLatency", // intentionally-internal: imported directly by src/lib/db/proxies.ts (anti-barrel, #6798) "recovery", // intentionally-internal: bin/cli/runtime.mjs (import() dinâmico) + tests "schemaColumns", // db-internal: importado só por db/core.ts (ensureProviderConnections/UsageHistory/CallLogsColumns + hasColumn/hasTable/getTableColumns — schema-column reconciliation split do core.ts, #4948) "secrets", // intentionally-internal: src/instrumentation-node.ts (import() dinâmico na inicialização) diff --git a/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx b/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx index 9e064eeeee..191be4cc9d 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx @@ -8,6 +8,7 @@ import { ProxyStatusBadge } from "./ProxyStatusBadge"; import { ProxyHealthCell } from "./ProxyHealthCell"; import { ProxyBatchActions } from "./ProxyBatchActions"; import { ProxyCheckboxCell } from "./ProxyCheckboxCell"; +import { POOL_STRATEGY_OPTIONS, isPoolStrategy, type PoolStrategy } from "./proxyStrategyOptions"; type ProxyItem = { id: string; @@ -180,9 +181,7 @@ export default function ProxyRegistryManager() { const [poolOpen, setPoolOpen] = useState(false); const [poolScope, setPoolScope] = useState("provider"); const [poolScopeId, setPoolScopeId] = useState(""); - const [poolStrategy, setPoolStrategy] = useState<"round-robin" | "random" | "sticky">( - "round-robin" - ); + const [poolStrategy, setPoolStrategy] = useState("round-robin"); const [poolMembers, setPoolMembers] = useState([]); const [poolAddProxyId, setPoolAddProxyId] = useState(""); const [poolLoading, setPoolLoading] = useState(false); @@ -569,11 +568,7 @@ export default function ProxyRegistryManager() { ? payload.members : []; setPoolMembers(members.map((m) => m.proxyId)); - setPoolStrategy( - ["round-robin", "random", "sticky"].includes(payload?.strategy) - ? payload.strategy - : "round-robin" - ); + setPoolStrategy(isPoolStrategy(payload?.strategy) ? payload.strategy : "round-robin"); setPoolLoaded(true); } catch (e: any) { setError(e?.message || t("poolLoadFailed")); @@ -638,7 +633,7 @@ export default function ProxyRegistryManager() { } }; - const handlePoolStrategyChange = async (strategy: "round-robin" | "random" | "sticky") => { + const handlePoolStrategyChange = async (strategy: PoolStrategy) => { const previous = poolStrategy; setPoolStrategy(strategy); setError(null); @@ -1151,7 +1146,9 @@ export default function ProxyRegistryManager() { {poolScope !== "global" && (
- +
- +

{t("poolStrategyHint")}

diff --git a/src/app/(dashboard)/dashboard/settings/components/proxyStrategyOptions.ts b/src/app/(dashboard)/dashboard/settings/components/proxyStrategyOptions.ts new file mode 100644 index 0000000000..3f7a74e576 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/components/proxyStrategyOptions.ts @@ -0,0 +1,17 @@ +// Pool rotation strategy options shared by ProxyRegistryManager's pool strategy +// selector. Extracted so the union type has a single source of truth and the +//