Repository navigation
Index multi-account routing and probe one catalog per profile - #68
Merged
Merged
Conversation
|
Sorry @maiphucgiang, your pull request is larger than the review limit of 150,000 diff characters |
maiphucgiang
force-pushed
the
perf/routing-scheduler
branch
from
October 18, 2026 06:27
b203d81 to
1b047dd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Request-path scheduling
pick()/headers_for()no longer rescan the credential directory:CredentialPoolkeepsby_id/by_cm/by_account_keyindexes plus a cached profile per entry, and a background_reconcile_loopapplies file changes after a directory fingerprint move. Explicit imports, logins and management paths still reload immediately;get_headers()performs exactly one file-version check on the selected account, and reconcile-time reloads skip unchanged files with a singlestat(a generation gap still forces the re-read).model_policy.view), socredential_enabled/route_allowed/rule_forreuse the same revision instead of re-copying the policy per candidate. Publishedaccount_catalogsare indexed once per catalog revision (app/catalog_index.py);/v1/modelstables are cached by pool/policy/catalog/ledger revision under the existing TTL, and the table key is taken after the catalog index is warm, so the first build after an invalidation does not rebuild twice.app/routing_scheduler.pyserves static eligibility per(model, region, revision-set, alias scope)shard with an LRU cache; cooldowns, sticky, capacity leases andtriedstay per request.routing_scheduler = legacy | shadow | indexed; the default isshadowfor a conservative rollout — it returns the legacy answer while recording bounded, credential-free mismatches underGET /admin/performance(admin-auth) — and the startup path (argparse defaults, environment fallback and the settings schema) agrees, so a deployment without explicit flags getsshadow, notindexed.reload()bumps the pool revision only on a real identity/profile/order move, the ledger exposes arouting_revisionthat moves only when a balance crosses zero or an intl account gains its first balance row, catalog republishing is keyed by content (and still publishes whenaccount_catalogshas never been set), and the stale table is dropped before the fresh index is installed. Deleted credential files surface through the 5 s reconcile window; a removed credential selected in that window fails fast inget_headersand records a cooldown.Catalog probing and the manual full scan
catalog_probe_mode = account(default, wired through the same startup paths) refreshes every synced account each cycle;representativeprobes one healthy credential per product profile per TTL cycle — accounts without a catalog first, then the current source, then the stalest — with exponential backoff after failures, and back-fills missing or dirty account catalogs under a bounded per-cycle quota (floor 16, converging within about twelve cycles, re-woken only on progress), so new accounts become routable without a manual scan. A cycle without candidates for a profile no longer resets that profile's retry state.model_policy.viewfor the whole loop), skips accounts still in breaker cooldown, and guards each header fetch individually: one broken account is recorded as failed without aborting the pass, so the model cache still publishes and the remaining accounts back-fill. The serial (housekeep_concurrency=1) branch materializes its results before checking for progress.AggregateCatalog.model_ids), so mixed catalog versions stop re-queueing every cycle; a background token refresh neither clears a pending refresh marker (only a successful catalog store does) nor sets one on its own, while imports and logins force a refresh through the credential writer./v1/modelsstays the union of current per-account catalogs, request-time networking stays zero, and account-unique models keep routing only through their own account.POST /admin/models/scanruns a user-triggered full scan in the background (single-flight, bounded progress payload, no credential material in the view);GET /admin/models/scanreports progress, per-account success/failure, catalog differences, the overall error and a per-profile breakdown of distinct catalog versions. A failed account keeps its previous catalog. The WebUI models page gains the scan button, restores an in-progress scan on page load, polls while running, reloads the catalog list when the scan finishes, and keeps passive polling errors silent.Background maintenance concurrency
housekeep_concurrency(1–16, default 4) restores serial behaviour at 1. Failed accounts retry with60·2^nbackoff capped at 900 s plus jitter, and cycles skip accounts synced within five minutes.Benchmarks
benchmarks/bench_routing.pymeasures pick/header/cooldown/capacity/tried/models cases for 10–1000 accounts; per-oppickmedians drop from 8.5 ms / 413 ms / 10.0 s (10/100/500) to 0.10 / 0.6 / 2.8 / 5.8 ms (10/100/500/1000) with zerosummary()calls, candidate checks growing with shard size only. First pick after a revision move rebuilds the shard (1000 accounts ≈ 250 ms). A dedicatedpick + revision churncase replays a token-refresh-like reconcile plus a maintenance ledger write per pick: zerosummary()calls, no shard rebuild, median 0.6 / 2.9 / 15.3 ms (10/100/500) — the cost of stat-only rechecks.Commits
fdc4b23Add offline routing benchmark and process countersb9272afMove credential file scanning out of the request path2e5240aReuse one policy revision and indexed catalogs per routing decision8184dfcAdd revision-keyed routing shards with legacy and shadow modesa395d5dProbe one representative catalog per profile and add a manual full scan068b3fbBound background maintenance concurrency per endpointd526b0dServe published model tables from the catalog index1b047ddReuse one credential read per header fetchf00c18dBack-fill laggard catalogs and gate the indexed rollout on shadow934a3c3Keep passive scan polling silent in the models pagebf96143Apply rollout defaults on real startup and harden the laggard passaf082cbKeep pending catalog refresh markers across background reloadsadaf4b5Trim lock-held policy copies and keep the published catalog index warmValidation
tests/test_*.pyfile exits 0 (75 files), includingtest_routing_scheduler.py(LRU eviction, semantic revision stability, conservative startup defaults via the realmain()parse path, token-rotation shard stability, pending-marker preservation),test_routing_snapshot.py,test_catalog_probe.py(new-account routability, bounded-cycle convergence, broken-account survival, cooldown skipping, retry-backoff preservation, no re-marking on unchanged catalogs, cross-account catalog-version reporting),test_environment_config.py(rollout switches resolve toshadow/accountwith sourcedefaultand explicit CLI still wins),test_housekeep.py,test_perf_metrics.pyandtest_identity_sync.py(relogin still forces the catalog refresh).vp test run153 passed;vp checkformatting, lint and type checks clean; the full Playwright suite passes 18/18 (npm run test:browser), including the models page with restored scan polling.benchmarks/bench_routing.py --accounts 10,100,500,1000 --repeat 3): steady-state medians 0.097/0.575/2.91/5.93 ms,summary()calls 0,headers_forstat calls 1.0 per operation; legacy/shadow/indexed choose identical credentials across normal, sticky, cooldown, capacity-full and failover cases; the revision-churn case shows zero summary calls and a hot shard, andcurrent_modelsmedians stay at the sub-millisecond table-cache level after the warm-index change.adaf4b5): test, web, image and all three CodeQL analyses passed; CodeQL open alerts on the PR head ref: 0.