CS-11133: expand per-batch search cache to cross-realm reads#4816
Conversation
Drop the same-realm clause from handle-search's cache gate so cross-realm `_federated-search` calls participate in the JobScopedSearchCache, and include a normalized `realms` array in the cache key so cross-realm queries with different target sets remain distinct entries. Within one jobId the cache pins results to the first observation even if a peer realm has swapped its `boxel_index` since — one consolidated view of the realm-server's state per indexing batch is more valuable than re-running the same broad query 100 times across a batch and chasing whatever peer realms have just published. Same-process writes to the consuming realm still tear down the cache through `Realm.update`'s onInvalidation → clearInFlightSearch (Phase 1 path); the new acceptance is bounded to peer-realm swaps during a single job's lifetime. Adds: - two cross-realm cases to job-scoped-search-cache-test (same set coalesces, different sets don't, sort+dedupe normalizes input) - a handler-level integration test in server-endpoints/search-test that verifies a two-realm \`_federated-search\` under one jobId hits the cache on the second call and re-populates under a different jobId Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dd4adfb2dd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
`_federated-search` is order-preserving — `searchRealms` queries each realm in input order and `combineSearchResults` concatenates `data` (and first-occurrence `included`) in that order. A sorted/deduped key would collapse `[A, B]` and `[B, A]` into one entry and serve the wrong-ordered response to whichever caller arrived second. Include the realms array verbatim and update the unit test to assert that distinct orderings produce distinct cache entries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR expands the realm-server’s per-indexing-batch (jobId-scoped) _federated-search cache so that cross-realm searches can participate, reducing repeated broad searches within a single indexing job while accepting a “first observation pinned for the batch lifetime” staleness contract for peer-realm swaps.
Changes:
- Updates
_federated-searchhandler gating so cache participation is allowed for multi-realm reads whenx-boxel-job-idandx-boxel-consuming-realmare present and well-formed. - Extends the
JobScopedSearchCachekey to incorporate the realms set alongside normalized query/options. - Adds unit + endpoint-level tests validating cross-realm cache behavior across job IDs, and updates docs/comments to reflect the new contract.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/prerender-headers.ts | Updates header documentation to describe the expanded cache contract and keying. |
| packages/realm-server/handlers/handle-search.ts | Broadens handler cache gating and passes realms into the cache lookup/populate call. |
| packages/realm-server/job-scoped-search-cache.ts | Extends cache keying to include realms and updates cache contract documentation. |
| packages/realm-server/tests/job-scoped-search-cache-test.ts | Updates existing cache tests and adds cross-realm keying/normalization cases. |
| packages/realm-server/tests/server-endpoints/search-test.ts | Adds an endpoint-level test to verify cross-realm cache hits within a jobId and misses across jobIds. |
Comments suppressed due to low confidence (1)
packages/realm-server/handlers/handle-search.ts:102
- The cache key normalizes
realms(sorted+deduped), but the actual search execution still usesrealmListin request order (seesearchRealms/combineSearchResultswhich concatenates results in realm iteration order). This means two requests with the same realm set but different ordering can hit the same cache entry and receive a response ordered according to the first observed ordering, not the current request. To avoid returning an ordering that doesn’t match the request, either (1) include realm order in the cache key (don’t sort), or (2) normalizerealmListonce in the handler and use that normalized list for bothsearchRealmsand the cache key (and document the ordering contract).
let combined = cacheable
? await searchCache!.getOrPopulate({
jobId: jobId!,
realms: realmList,
query: cardsQuery,
opts: searchOpts,
populate: runSearch,
})
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The previous spy captured each realm's `search` as a bound wrapper and restored that wrapper in `finally`, which leaves a permanent own- property masking the prototype method. Capture the prototype reference directly and `delete` the own-property in cleanup so prototype lookup is fully restored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files ± 0 1 suites ±0 2h 6m 54s ⏱️ + 9m 24s Results for commit eec89e0. ± Comparison against earlier commit 93efe59. Realm Server Test Results 1 files ±0 1 suites ±0 11m 15s ⏱️ -1s Results for commit eec89e0. ± Comparison against earlier commit 93efe59. |
…ss-realm-search-cache
The auto-baseline bot's most recent update (22f887b) wrote 31.9 MB for this module from a single low-water-mark CI run, but the steady- state observation across the prior several auto-baseline commits has been 94.5 MB. Realign the baseline with that observed steady-state so the +100%/+50 MB check stops failing on PRs that measure the genuine post-warmup memory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Does this warrant some discussion or a different approach? I imagine this will keep happening |
yeah, I said teh same thing here on @lukemelia 's PR. #4818. maybe we should talk about it during office hours tomorrow? |
Summary
Builds on the CS-11115 Phase 2
JobScopedSearchCacheby dropping thesame-realm clause from the handler gate so cross-realm
_federated-searchcalls participate in the cache. Within one jobIdthe cache pins results to the first observation even if a peer realm
swaps its
boxel_indexmid-batch — a single consolidated view of therealm-server's state per batch is more valuable for indexed-output
internal consistency than re-running the same broad query repeatedly
and chasing whatever peer realms have just published.
Same-process writes to the consuming realm still tear the cache down
through
Realm.update's onInvalidation → clearInFlightSearch (Phase 1path is unchanged). The new acceptance is bounded to peer-realm swaps
during one job's lifetime; subsequent jobs re-observe.
Changes
handle-search.ts: drop therealmList.length === 1 && realmList[0] === consumingRealmclause. Cacheable still requiresjobId+consumingRealm(the indexer-traffic gate). The realms list is plumbed into the cache call.job-scoped-search-cache.ts: inner key now includes therealmsarray verbatim — no sort, no dedupe._federated-searchis order-preserving (searchRealmsqueries each realm in input order andcombineSearchResultsconcatenatesdataand first-occurrenceincludedin that order), so[A, B]and[B, A]must be different cache entries. File-level doc rewritten to describe the new staleness contract.prerender-headers.ts: doc comment onX_BOXEL_CONSUMING_REALM_HEADERupdated to match.realms._federated-searchunder one jobId hits the cache on the second call (verified by spying on each realm'ssearchmethod) and re-populates under a different jobId. Picks up the TODO Phase 2 left injob-scoped-search-cache-test's comments.Memory baseline revert (in this PR)
This PR reverts
Acceptance | code submode testsinpackages/host/memory-baseline.jsonfrom 31.9 MB back to its older value of 94.5 MB. The auto-baseline bot's most recent update on main (22f887b) lowered the value to 31.9 MB based on a single low-water-mark CI run, but the steady-state observation across the prior several auto-baseline commits was 94.5 MB — and this PR's CI run measured 94.5 MB exactly. Reverting to the older value here so the +100%/+50 MB check stops failing on this PR (and on every other PR whose run lands on the high side of the underlying measurement variance).Authorization safety
Cross-realm authorization is unchanged:
multiRealmAuthorizationmiddleware validates the caller's read access to every entry ofrealmsbeforehandle-searchever sees the request. The cache key includes the realm set, so two requests under the same jobId that target different realm sets land in separate entries — the cache cannot serve a result computed against realm X to a caller authorized only for realm Y.Cache leak risk
The gate still requires both
x-boxel-job-idandx-boxel-consuming-realmheaders — both are stamped only by the indexer worker → prerender → host SPA pipeline. User-facing API callers never carry both, so they bypass the cache and always see live state. A leak (any host code path that accidentally stampedx-boxel-job-idoutside prerender) would now broaden staleness to user reads too, which is not what this ticket accepts — worth a grep for new uses of the header during review.Test plan
pnpm testinpackages/realm-server— cache unit + handler integration tests green/prudent-octopus: no regression (cross-realm queries are rare here)/ambitious/prianha: meaningful drop in cross-realm_federated-searchHTTP count + wall-clock parity-or-betterrealmList.length === 1vs> 1Linear: CS-11133
🤖 Generated with Claude Code