Narrow plugin storage prefix reads in SQL and bound stale catalog rebuild concurrency - #2124
Open
ian-pascoe wants to merge 2 commits into
Open
ian-pascoe wants to merge 2 commits into
ian-pascoe wants to merge 2 commits into
Conversation
…builds two at a time
This branch has not been deployed
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
On the Cloudflare host, every tool search could run the MCP session Durable Object out of memory once a large OpenAPI spec (Cloudflare's own API, 3600 operations) was connected. The OpenAPI store listed a connection's operations by loading and decoding every OpenAPI integration's rows and then filtering in JS, and each stale rebuild did that again.
pluginStorage.listand collectionquerynow pushkeyPrefixinto SQL (starts with) and keep the exact JSstartsWith. SQL only narrows the read, so results are unchanged for every caller. Prefixes containing\stay JS-only (PostgresLIKEescape; see fumadb drizzle: 'starts with' is an unescaped LIKE, and D1 batch upserts ignore the 32 MiB RPC cap #2123).op.<hash>.and the legacy<integration>.), decodes each row once, and keeps anintegration ===guard. GraphQL's store already passeskeyPrefixand gets the same narrowing.ExecutorConfig.toolsSyncConcurrency(default 10, unchanged), forwarded throughHostConfig. The Cloudflare host sets 2: each in-flight rebuild holds its resolved catalog until its write commits, and writes are serialized anyway.Related: #2122 (no backoff for failing syncs), #2123 (fumadb
starts with).Verification
Heap, measured locally on the D1 adapter options (
interactiveTransactions: false, maxBoundParameters: 100) with real co-tenant specs (Cloudflare, GitHub, Stripe ×2, Sentry, Vercel; 5,123 operation rows):The operation read went from all 5,123 rows to the connection's own 2,726. Concurrency 2 and concurrency 1 had the same wall time (21.7 s vs 23.3 s, within noise).
Production (self-hosted Cloudflare deployment): before,
wrangler tailshowedMcpSessionDOexceededMemoryon searches, and ~50% of searches across all integrations failed with-32010 session_reset. With this deployed there were noexceededMemoryoutcomes. The Cloudflare catalog still did not rebuild fully because of the delete-then-insert rebuild on D1, which the companion PR fixes.oxfmt --check .oxlint -c .oxlintrc.jsonc . --deny-warningstsgo --noEmit) incore/sdk,core/api,host-cloudflare,plugins/openapi,plugins/mcpcore/sdk944,plugins/openapi338,plugins/graphql118,plugins/mcpcatalog-sync9Checklist