perf(swr): cache CRDs + /managed in localStorage per MCP, dedup + shrink payload - #657
Draft
maximilianbraun wants to merge 4 commits into
Draft
perf(swr): cache CRDs + /managed in localStorage per MCP, dedup + shrink payload#657maximilianbraun wants to merge 4 commits into
maximilianbraun wants to merge 4 commits into
Conversation
Three layered changes that compound on MCP page load: 1. Wire: add jq filters to CRDRequest and ManagedResourcesRequest (X-jq header). CRD responses drop from 1-10 MB to ~50-500 KB; /managed loses the per-item metadata.managedFields bloat. 2. In-session SWR: globally set dedupingInterval=30s and turn off revalidateOnFocus/revalidateOnReconnect (the 10s poll already covers staleness). useCRDItemsMapping and useProvidersConfigResource additionally set refreshInterval=0 + revalidateIfStale=true — CRDs are effectively static within a session. 3. Cross-session: new src/lib/swr/persistentProvider.ts. SWR cache provider keyed per MCP (mcp-ui:swr:v1:<project>:<workspace>:<mcpName>). Mounted inside RequireDownstreamLogin so each MCP gets its own bucket. Hydrates on mount so cached entries paint instantly on reload, then SWR revalidates in the background. 4 MB cap per bucket with oldest-eviction; QuotaExceededError-safe. Only entries with `data` and no `error` are persisted (failed 403s stay in memory). 30-minute TTL per entry. clearPersistedSwrCache() wired into redirectToLogin for full wipe on logout. Signed-off-by: Maximilian Braun (SAP) <maximilian.braun@sap.com>
- Wrap persisted JSON in { schemaVersion, entries } envelope so future
format changes (entry layout, jq filters, SWR State shape) can bump
the version and old buckets are silently dropped on hydrate. Old
flat-array buckets fail the envelope check and get ignored — same
effective behaviour as before, with explicit forward-compat.
- New persistentProvider.spec.ts (11 cases): hydrate-empty,
hydrate-valid, TTL drop, schema mismatch reject, malformed-JSON
reject, debounced persist, skip-on-error, skip-when-no-data,
quota-exceeded swallowed, single-bucket clear, all-buckets clear.
The project's jsdom config doesn't expose a working localStorage
to specs (same blocker as ViewModeContext.spec.tsx). Installed a
minimal in-memory shim per test so this suite is self-contained.
Signed-off-by: Maximilian Braun (SAP) <maximilian.braun@sap.com>
Bounds the cross-bucket total: when setItem throws QuotaExceededError while writing the active MCP's bucket, find the *other* MCP bucket whose most-recent entry is the oldest and remove it, then retry the write. Up to 4 evictions per write, then a final last-resort wipe of all other buckets before giving up. The active MCP's bucket is never sacrificed to write itself out — the user-facing cache for the current cluster always wins. Added a 12th spec case covering the LRU path. Signed-off-by: Maximilian Braun (SAP) <maximilian.braun@sap.com>
Two cases verifying the per-MCP design holds under common navigation patterns: - Two providers with different mcpIds produce distinct Maps and write to distinct localStorage buckets — no shared state. - A late callback resolving into the old MCP's Map (after the user has navigated to a new MCP) writes only to the old MCP's bucket and never reaches the new bucket. This is correct SWR behaviour (each SWRConfig holds its own cache reference) but worth pinning with a test. Signed-off-by: Maximilian Braun (SAP) <maximilian.braun@sap.com>
Member
Author
|
My naive understanding is, that we not really need the CRDs content at this point in time, especially not the schemas? |
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
Three independent but layered optimisations that compound on MCP page load: server-side
jqfilters to shrink CRD and/managedresponses by ~95%; SWR global dedup tuning to suppress redundant focus-revalidations; and a new per-MCPlocalStoragecache provider so a cold reload paints from disk and revalidates in the background. Each layer survives the others being rolled back independently — in particular, the in-memory part (jq filter + SWR config tweaks) is still a meaningful win even if the persistence layer is reverted.What changed
Wire (payload shrink)
src/lib/api/types/crossplane/CRDList.ts— add anX-jqfilter toCRDRequestthat strips OpenAPI schemas down to the handful of fields the client actually reads. Unfiltered responses on a real Crossplane MCP are 1-10 MB; filtered are ~50-500 KB.src/lib/api/types/crossplane/listManagedResources.ts— addX-jqto dropmetadata.managedFieldsserver-side. Saves multiple MB per/managedresponse.In-session SWR config
src/components/SWRConfigWithTokenRefresh.tsx— globally setdedupingInterval: 30sand disablerevalidateOnFocus/revalidateOnReconnect. The existing 10s poll already covers staleness; focus-revalidation just double-spends. (Survives rollback of the persistence layer.)src/lib/api/useApiResource.ts—useCRDItemsMappinganduseProvidersConfigResourceadditionally setrefreshInterval: 0+revalidateIfStale: true. CRDs are effectively static within a session.Cross-session persistence
src/lib/swr/persistentProvider.ts(new, 229 lines) — SWR cache provider that persists entries tolocalStorageundermcp-ui:swr:v1:<project>:<workspace>:<mcpName>(one bucket per MCP, never shared). Persisted JSON is wrapped in{ schemaVersion, entries }— a schema sentinel so future format changes bump the version and old buckets are silently dropped on hydrate. Each entry is timestamped with a 30-minute TTL; older entries are dropped on hydrate. Only entries withdataand noerrorare persisted (a transient 403 doesn't sticky into the next page load). Per-bucket cap is 4 MB with oldest-entry-eviction; onQuotaExceededErrorwhile writing the active bucket, an LRU cross-bucket eviction finds the other MCP's bucket whose most-recent entry is the oldest and removes it before retrying — the active MCP's bucket is never sacrificed to write itself out.clearPersistedSwrCache()wired intoredirectToLoginfor a full wipe on logout.src/lib/shared/McpContext.tsx— mount the provider insideRequireDownstreamLoginso each MCP gets its own bucket scoped by(project, workspace, mcpName).src/common/auth/redirectToLogin.ts— callclearPersistedSwrCache()on logout.Tests
src/lib/swr/persistentProvider.spec.ts(new, 14 cases) — hydrate-empty / hydrate-valid / TTL drop / schema mismatch reject / malformed-JSON reject / debounced persist / skip-on-error / skip-when-no-data /QuotaExceededErrorswallowed / single-bucket clear / all-buckets clear / LRU cross-bucket eviction / two-MCP isolation / late-callback into stale Map writes only to the stale bucket. Spec installs a minimal in-memorylocalStorageshim per test because the project's jsdom config doesn't expose a workinglocalStorage(same blocker asViewModeContext.spec.tsx).Why
On a typical Crossplane MCP, the MCP page kicks off:
/CRDListrequest: 1-10 MB of OpenAPI schemas the client never reads./managedrequest: hundreds of KB ofmetadata.managedFieldsno UI surface touches.Wire-layer wins are pure: less data over the wire, less JSON to parse, less RAM. SWR dedup tuning kills wasted requests. The persistence layer turns reload-after-reload from "spinner for 2-5s" into "stale data immediately, revalidate in background" — and importantly, doesn't share state across MCPs, so switching between clusters can't poison one cache with another's data.
Rollback safety: if the persistence layer turns out to be problematic in production, removing
persistentProvider.ts+ revertingMcpContext.tsxleaves the jq filters and SWR config tweaks intact — those are still a meaningful win on their own.Test plan
npm run type-checknpm run lintnpm run test:vi -- src/lib/swr/persistentProvider.spec.ts— all 14 cases pass.npm run test:vi— full suite./CRDListpayload is ≤ ~500 KB (was multi-MB) and/managedno longer containsmanagedFields.mcp-ui:swr:v1:<proj>:<ws>:<mcp>bucket appears with{ schemaVersion, entries }shape.mcp-ui:swr:*buckets are removed.