Skip loadLinks for prerender-issued searches; remove dead per-instance cache#5128
Merged
Merged
Conversation
…e cache Prerender `_federated-search` no longer runs the post-SQL loadLinks / populateQueryFields relationship-assembly pass. The prerender host re-resolves every result from its raw card+source file and consumes only the matching result ids, so the per-result query-field umbrellas and the transitive included[] expansion were throwaway work — loadLinks was ~95% of the search handler's wall-clock. Gated on the existing prerender flag (omitIncluded); live host-SPA searches still receive fully-assembled compound documents. Also removes the now-dead job-scoped per-instance wire-format cache (job_scoped_instance_cache): its read/write lived inside loadLinks and only activated for the job-scoped searches that no longer run it (and it measured zero hits independently). Drops the class, its engine members + call sites, main.ts wiring, its participation in JobsFinishedListener (the listener and the whole-doc search cache are untouched), its tests, and the table (new migration + regenerated sqlite schema). Deletes dead loadLinksForResource() and the now-dead jobIdentity / search-path skipQueryBackedExpansion threading; the card-document path keeps skipQueryBackedExpansion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
The migration body is only `pgm.sql(...)` template strings (no JS identifiers that violate camelcase), so the generated `/* eslint-disable camelcase */` directive is unused and `eslint --report-unused-disable-directives` rejects it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request optimizes prerender-driven _federated-search performance by skipping the expensive post-SQL relationship assembly (loadLinks() / populateQueryFields()) when the caller signals prerender behavior (omitIncluded). It also removes the job-scoped per-instance wire-format cache and its storage/wiring because that cache only operated inside the removed relationship-assembly path.
Changes:
- Treat
omitIncludedprerender searches as “no relationship assembly”: return the raw search rows (plus page meta) and skiploadLinks()entirely. - Remove the job-scoped per-instance cache implementation, its realm-server wiring, tests, and the Postgres/SQLite schema surface.
- Update tests and internal diagnostics docs to reflect the prerender search contract and updated timing breakdown.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/search-utils.ts | Removes job-identity threading from SearchOpts and timing output for federated search. |
| packages/runtime-common/realm.ts | Stops threading job identity / query-expansion flags through the search path; keeps card-document prerender behavior. |
| packages/runtime-common/realm-index-query-engine.ts | Skips loadLinks entirely when omitIncluded is set; deletes per-instance cache logic and loadLinksForResource(). |
| packages/realm-server/handlers/handle-search.ts | Sets omitIncluded for prerender searches and removes now-dead skipQueryBackedExpansion/jobIdentity search threading. |
| packages/realm-server/lib/jobs-finished-listener.ts | Stops evicting the removed per-instance cache; only evicts the job-scoped search cache. |
| packages/realm-server/main.ts | Removes JobScopedInstanceCache wiring and lifecycle management. |
| packages/realm-server/job-scoped-instance-cache.ts | Deletes the per-instance cache implementation (no remaining callers). |
| packages/realm-server/tests/skip-query-backed-expansion-test.ts | Updates omitIncluded expectations to “skip loadLinks entirely”; keeps card-document skip behavior coverage. |
| packages/realm-server/tests/per-instance-cache-test.ts | Deletes tests that exercised the removed per-instance cache. |
| packages/realm-server/tests/job-scoped-instance-cache-test.ts | Deletes tests for the removed JobScopedInstanceCache eviction surface. |
| packages/realm-server/tests/index.ts | Removes deleted test files from the test manifest. |
| packages/postgres/migrations/1780627635271_drop-job-scoped-instance-cache.js | Drops the job_scoped_instance_cache table (with reversible down). |
| packages/host/config/schema/1780627635271_schema.sql | Regenerates SQLite schema snapshot to remove job_scoped_instance_cache. |
| .claude/skills/indexing-diagnostics/SKILL.md | Updates diagnostics documentation for prerender search timing now that loadLinks is skipped and the per-instance cache is gone. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The prerender search skips the loadLinks relationship-assembly pass but still returns each result's pristine row (id + attributes + static-link relationships) + page meta — it omits only the query-field umbrellas and included[]. Reword the omitIncluded option comment, the test name, the handle-search comment, and the indexing-diagnostics skill so they don't read as "ids only", and drop the temporal "no longer" phrasing in the skill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
State the prerender-search timeline as current behavior (sql carries most of the handler because the post-SQL assembly doesn't run on that path) rather than "now the common case, since the post-SQL assembly is gone". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
backspace
approved these changes
Jun 5, 2026
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
When a
_federated-searchis issued from a prerender / indexing render context, the realm server runs the fullloadLinks()→populateQueryFields()relationship-assembly pass: for each query-backedlinksTo/linksToManyfield on each result it runs an extra SQL sub-query to build therelationships.{field}.data"umbrella," then expands the linked resources transitively intoincluded[]. On the realms we measured, that pass is ~95% of the search handler's wall-clock (sqlis ~3%). It's the root cause behind indexing searches being slow.The catch: for a prerender search, all of that assembly is thrown away.
Why it's throwaway (how a prerender search is actually consumed)
A prerender search is one-shot (
isLive=false) and the render store resolves cards from raw source, not from search response bodies:card+source(the raw file). In a render store,Store.getCardInstancealways fetchesgetSource(<url>.json)(Accept: application/vnd.card+source), which the realm serves as the raw local file. A raw source file has no computed query-field data._federated-search. That search is the one this PR optimizes.data[].idsurvives.SearchResourcehands each result to the store, but when the card is actually rendered it is re-resolved throughgetCardInstance→getSource(the raw-source path above). The result'sattributesand per-result query-fieldrelationshipsare superseded by that re-fetch; the only thing consumed is which ids matched.included[]was already skipped in prerender (omitIncluded). The remaining waste was the relationship-umbrella assembly insideloadLinks.The change
Gate on the existing prerender flag (
omitIncluded— the same signal that already suppressedincluded[]). When set,searchCardsUncoalescedskips theloadLinks/populateQueryFieldspass entirely and returns the matching result identifiers + page meta (pristine attributes and static-link relationships from each index row; no query-field umbrellas, noincluded[]).Live / external host-SPA searches are untouched — they run genuinely live searches and still receive fully-assembled compound documents.
Correctness boundary
There is one host path that consumes
relationship.dataids without issuing a search —applySeed/captureQueryFieldSeedData. It reads the umbrella from a parent card document's own indexer-resolved relationships, i.e. from the card-document prerender path, which still runsloadLinkswithskipQueryBackedExpansionand is left intact by this PR. It never sourced those umbrellas from a one-shot search response. So skippingloadLinkson the search removes nothing that path relies on.Also removed (now-dead)
job_scoped_instance_cache). Its read and write lived entirely insideloadLinksand only activated for job-scoped (indexer/prerender) searches — exactly the searches that no longer runloadLinks. It also never earned its keep independently: it measured zero hits (every read missed, because withincluded[]omitted a result URL rarely recurs within a job). Removed: theJobScopedInstanceCacheclass, the engine's cache members + call sites + instrumentation, themain.tswiring, and its participation inJobsFinishedListener. The whole-docJobScopedSearchCacheand theJobsFinishedListeneritself are untouched — the listener still evicts the search-doc cache onjobs_finished. A new migration drops the table and the sqlite schema is regenerated.loadLinksForResource()— no callers.jobIdentityand search-pathskipQueryBackedExpansionthreading. The card-document GET/POST/PATCH path keepsskipQueryBackedExpansion(it still uses it).Tests
omitIncludedengine test now asserts the new contract: ids only, no query-field umbrella, noincluded[].included[]for non-prerender callers) is unchanged and guards that the live path still assembles everything.JobsFinishedListenertests pass unchanged (it never depended on the per-instance cache); the per-instance-cache tests are removed.Verifying the win
The mechanism removes provably-unused work rather than chasing a hotspot, so the direction holds regardless of magnitude. The before/after on
realm:search-timing(theloadLinksstage collapsing toward ~0 for prerender requests) will be confirmed with a from-scratch reindex on staging after deploy.