Skip to content

Search keeps every locale's index in one Worker isolate, so docs content growth pushes /api/search over the Worker's memory limit (503, deploy rolled back) #393

Closed

Description

@objectstack-fleet

What happened

#390 (ja PR 2 of 7 for #388, +11 ja pages) merged as 3bbc710. The post-deploy smoke check then failed twice, on main CI run 40862958838 and on its re-run. The failure card is #391.

On the previous run, 40861333382 for #389, all eight locales answered 200 in 3.6–5.3 s each. #392 reverts #390 so that main can deploy again.

Cause, measured

apps/docs/app/api/search/route.ts builds one Orama index per locale on that locale's first search (#301). It keeps each index in a module-level servers map that is never emptied, so one isolate holds as many indexes as locales it has served.

The measurement asked the built route under Node (next build at 3bbc710, then routeModule.userland.GET, --expose-gc), one ?query=permissions per locale in order. Heap is heapUsed after a forced GC.

after heap, as on main heap, map emptied before each new build
route loaded 17.9 MB 17.9 MB
en 58.6 MB 58.5 MB
zh-Hans 98.2 MB 72.5 MB
zh-Hant 137.3 MB 83.3 MB
ja 174.2 MB 87.4 MB
de 203.6 MB 86.1 MB
es 232.4 MB 86.3 MB
fr 259.9 MB 85.2 MB
ko 285.9 MB 86.8 MB
  • Index size. Each locale's index holds 30–40 MB. A Worker isolate has 128 MB, so two or three indexes and the rest of the app already fill it. That is why the 503s hit the second or third locale an isolate served.
  • The prototype. One line, servers.clear() before a new build, keeps a single index. Heap levels off at about 86 MB.
  • What is left. The remaining growth from 58 to 86 MB is the compiled page modules that page.data.load() imports for each locale. They stay in the module registry.
  • Full ja coverage. With every missing ja page faked as a copy of the English, the one-index prototype levels off at about 94 MB.

Ask

Make /api/search fit the Worker with room to spare, now and after #388 brings ja to full coverage (79 more pages) and phase 3 adds further locales.

  1. Bound the per-isolate index cache. A single entry is the measured minimum. Say what a locale switch then costs (measured 0.5–1.5 s per rebuild), and whether a second entry fits.
  2. Look at what stays resident besides the index. The question is whether search can index without keeping every page module of every locale loaded. One option is structuredData written at build time to a per-locale JSON that the route reads, while keeping the /api/search server contract that Docs polish from the 2026-10-06 audit: muted-text contrast, the License table on phones, FAQ formatting, search ranking, glossary order, licence/license spelling #301 explains. Measure before and after.
  3. Make the limit testable before deploy. Add a heap check to check-search-locales.mjs (or a sibling script) after the build. It asks every locale in turn, then fails above a budget that leaves headroom under 128 MB, such as 100 MB heapUsed after GC. A future content PR should then go red in CI, not after deploy.

Acceptance

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: search memory fix, blocking #388 · 2026-10-29T11:28Z
    Session: session_01QsR2jTaZ2GeyfjANMwstiw (seat thread; the dev is card thread session_01QsR2jRsjbApbf83iyTDDXm, which also holds #388, identity by branch)
    Account: zhuangjianguo
    Branch: claude/project-thread-u7eoxi (platform-assigned; restarted from main)
    Worktree: the card thread's own docs.objectos.ai checkout
    Domain: domain:docs (single-lane repository)
    Seat: repo:docs.objectos.ai#1
    File surface: apps/docs/app/api/search/route.ts; .github/scripts/check-search-locales.mjs or a new sibling heap-check script, plus its SELF_TESTED entry in tools/ci-scripts/run-self-tests.mjs and its CI step if it is a new script; any build-time step that item 2 needs under apps/docs/ (for example source.config.ts or scripts/). No content files. If the work goes beyond this surface, stop and explain why in the report.
    Container & model: S, mode:thread, model: default tier (TIER_DEFAULT — sister repository, hand-derived: no packages/spec, no protocol-semantic surface), effort: project default
    Clause-②: no
    Metadata-protocol: none
    Responsibility: production regression on the deploy path. #390 tipped search over the Worker memory limit, and #392 reverts it.
    Thread-read: none
    Premise: confirmed by the seat's heap measurement in the card body (built route at 3bbc710, Node --expose-gc) and by both smoke logs of run 40862958838.
    Tier: none.
    Serial constraints cleared: the lane is serial. #388 pauses after its PR 1 (#389) until this card lands, and it resumes by re-landing #390's pages. #392 (the revert) is the only other PR in flight, and it touches content only.

  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing · 2026-10-29T12:28Z · session_01QsR2jTaZ2GeyfjANMwstiw · seat repo:docs.objectos.ai#1

    #394 merged as 9e51646, and it closed this card. Main CI run 40872624098 is green: build success, including the new Search fits the Worker's memory step. Publish success, the new version is serving, and the smoke check passed, with search answering in all eight locales and no rollback. Next, #388 resumes by re-landing #390's pages. The Worker size limit the dev measured is tracked on #395.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions