Skip to content

perf(routing): look up models by selector and memoize resolved-model strings - #829

Open
SantiagoDePolonia wants to merge 1 commit into
mainfrom
perf/small-leftovers
Open

perf(routing): look up models by selector and memoize resolved-model strings#829
SantiagoDePolonia wants to merge 1 commit into
mainfrom
perf/small-leftovers

Conversation

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor

Third round of hot-path allocation work (follows #792/#793). Profiling showed ModelSelector.QualifiedModel() as the single largest remaining allocation site: ~12 heap allocations per request spent building the qualified provider/model string, which the registry then immediately split apart again.

Changes

  • Registry: the six duplicated string-lookup bodies (GetProvider, GetModel, LookupModel, Supports, GetProviderType, GetProviderName) collapse into one shared modelInfoLocked helper, and new *ForSelector variants answer the same queries for an already-parsed selector without materializing the qualified string (it is only built on the rare second-chance paths, e.g. slash-shaped model IDs). A parity test pins both paths to identical answers across selector shapes.
  • Router: asserts the selector fast path once at construction (same pattern as the existing qualifiedSelectorResolver) and uses it everywhere it previously called r.lookup.X(selector.QualifiedModel()). Lookups that only speak qualified strings (test fakes) keep working through the fallback.
  • RequestModelResolution: memoizes its two derived strings — the resolved qualified model and the audit route label (formerly resolvedModelForAuditLog, now ResolvedRouteModel() on the resolution) — computed once when the gateway builds the resolution instead of on every audit-enrich pass, rate-limit read and cache read. Getters fall back to computing when the cache wasn't filled, so literal-constructed resolutions behave as before.
  • Small ones: QualifyModelWithProvider no longer allocates for its prefix check; the snapshot middleware canonicalizes the brand-cased X-GoModel-User-Path key once at construction instead of on every Header.Get (wire format and error messages unchanged).

Impact

No user-visible behavior change; request/response shapes, audit log contents and the public string-based lookup APIs are unchanged.

benchmark main this PR
production shape 157 allocs/op, 19.4 KB 145 allocs/op, 19.2 KB
routed 99 allocs/op 90 allocs/op
bare 85 allocs/op 84 allocs/op

CI alloc ceilings lowered to match.

…strings

The hot path built the qualified "provider/model" string ~12 times per
request only to have the registry split it apart again. The registry now
answers Supports/GetProvider/GetProviderType/GetProviderName for an
already-parsed selector directly (an optional fast path the router asserts
once at construction), and the six duplicated string-lookup bodies collapse
into one shared helper. RequestModelResolution memoizes its two derived
selector strings, computed once at resolution time instead of on every
audit-enrich, rate-limit and cache read. Also: QualifyModelWithProvider no
longer allocates for its prefix check, and the snapshot middleware
canonicalizes the brand-cased user-path header key once instead of on every
Header.Get.

Production-shape hot path: 157 -> 145 allocs/op, routed 99 -> 90; ceilings
lowered to match.
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 758c2397-516f-4ff1-a32a-05e68ee0807b

📥 Commits

Reviewing files that changed from the base of the PR and between becf43f and e46f50d.

📒 Files selected for processing (10)
  • internal/auditlog/enrich.go
  • internal/core/request_model_resolution.go
  • internal/core/request_model_resolution_test.go
  • internal/gateway/model_helpers.go
  • internal/gateway/request_model_resolution.go
  • internal/providers/registry.go
  • internal/providers/registry_selector_test.go
  • internal/providers/router.go
  • internal/server/request_snapshot.go
  • tests/perf/hotpath_test.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 93.00000% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/providers/router.go 81.48% 5 Missing ⚠️
internal/gateway/model_helpers.go 66.66% 1 Missing ⚠️
internal/providers/registry.go 97.72% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

Safe to merge based on the focused routing and registry behavior checks.

There are no final findings. The focused validation passed for selector lookup parity and the router's selector-capable and string-only fallback behaviors.

Files Needing Attention: None. The exercised changes in internal/providers/registry.go and internal/providers/router.go preserved expected resolution behavior.

Reviews (1): Last reviewed commit: "perf(routing): look up models by selecto..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants