perf(core): authenticated request issues ~16 sequential queries (duplicate authz + 3× localization) — add request-scoped memoization#2412
Merged
Conversation
… resolution An authenticated REST request resolves its execution context (identity + RBAC/RLS + localization) many times in one handler — the data op, app-nav RBAC filtering, dashboard widget gating, the ADR-0069 auth gate. Each resolveExecCtx pass is the full resolveAuthzContext aggregation plus the localization read (~16 sequential queries), and nothing memoized it, so a request that resolved twice paid for duplicate authz + repeated localization. - rest: memoize resolveExecCtx per request (WeakMap keyed by req + input environmentId; caches the in-flight Promise; heavy path moved to computeExecCtx). Anonymous resolutions cached too. - core: read sys_user at most once per resolveAuthzContext pass (email fallback + ai_seat synthesis shared a duplicate query on the API-key path); batch the localization direct-read fallback (timezone/locale/currency) into one sys_setting query ($in on key) instead of three sequential reads. No authorization-behavior change. sys_member reads (per-user vs all-org) left distinct on purpose (different filters/limits). Tests: query-counting regressions (sys_user once, localization once) + rest-server memo contract (per-request, per-environment, anonymous cached). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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.
Problem
An authenticated REST request resolves its execution context (identity + RBAC/RLS + localization) multiple times within a single handler — the data operation itself, app-nav RBAC filtering, dashboard widget gating, and the ADR-0069 auth gate all call
RestServer.resolveExecCtx. Each pass runs the fullresolveAuthzContextaggregation plus the localization read — ~16 sequential queries — and nothing memoized it. So a request that resolves the context twice (e.g.GET /meta/:type, which gates app nav at one site and dashboard widgets at another) pays for duplicate authz + repeated localization.Concrete duplication confirmed by tracing the path:
rest-server.ts—resolveExecCtxis invoked 2× in the same handler (app RBAC filter + dashboard widget gating), each re-running everything.resolve-authz-context.ts—sys_userread twice per pass (email fallback at one site,ai_seatsynthesis at another) on the API-key path; the localization direct-read fallback issues 3 sequentialsys_settingqueries (timezone / locale / currency).Fix — request-scoped memoization
@objectstack/restresolveExecCtxis now a thin memoizing wrapper; the heavy resolution moved tocomputeExecCtx.WeakMapkeyed by the per-requestreqobject (collected with the request → naturally request-scoped, no TTL, no cross-request leak), then by the inputenvironmentId(one host can route multiple environments).undefined) resolutions are cached too, so repeat callers don't re-rungetSession.@objectstack/coreresolveAuthzContextreadssys_userat most once per pass (a memoizedgetUserRow()shared by the email fallback and theai_seatsynthesis).resolveLocalizationContext's direct-read fallback batchestimezone/locale/currencyinto onesys_settingquery ($inonkey) instead of three sequential reads.Safety
resolveExecCtxcall sites treat the result as read-only (no mutation), so sharing one cached instance per request is safe.sys_memberreads (per-user roles vs. all-org-members for RLS scoping) are intentionally left distinct — different filters and limits; merging them would risk a subtle behavior change for marginal gain.Tests
resolve-authz-context.test.ts— query-counting harness assertssys_useris read once (email +ai_seatboth satisfied) and localization readssys_settingonce for all three keys; plus UTC/en-US fallback.rest-exec-ctx-memo.test.ts(new) — pins the memo contract: once per request object, re-resolves for a different request, keyed byenvironmentId, and caches anonymous results.@objectstack/coresecurity (79) and@objectstack/rest(144). Both packages build clean (incl. strict DTS).🤖 Generated with Claude Code