Conversation
createAdminSetup fired the `meta` thunk unconditionally during bootstrap, despite the comment claiming it was lazy. On a large site the composed schema is tens of MB of heap per isolate (montecarlo: ~40 MB of a 128 MB Workers budget) and nothing on the render path reads it — only GET /live/_meta does. setMetaProvider stores the thunk; handleMeta resolves it on first request and memoises it per isolate. A failed load answers 503 and clears the memo so the next request retries. Note this only drops the parsed schema — the meta.gen module's source text is still retained by V8 because it ships in the server bundle. Removing that needs the schema to leave the bundle entirely (static asset or KV). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The admin JSON Schema costs ~40 MB of a 128 MB Workers isolate on a large
site (montecarlo: 1690 blocks) and only GET /live/_meta reads it. V8 keeps
the module's source text AND materialises the JSON.parse argument as a
second string, both two-byte because the content is accented — so a 10.6 MB
chunk is paid roughly four times over.
Mirrors what fastDeploy already does for the decofile:
- `meta:<id>` + `meta:etag:<id>` alongside `decofile:<id>`, seeded by
deco-sync-blocks-to-kv (automatic when it finds the file; --no-meta opts
out).
- `decoVitePlugin({ metaFromKV: true })` stubs meta.gen out of the server
bundle. Independent of fastDeploy — different artefact, different seed.
- handleMeta streams `kv.get(..., { type: "stream" })` straight into the
Response. Nothing parses the payload; materialising it even as a string
would reintroduce most of the cost.
- The ETag is precomputed at build under its own small key, so If-None-Match
(what admin actually polls with) costs one tiny read, and because hashing
JSON.stringify(schema) is impossible once the schema is not in the isolate.
The same value is merged into the payload's `etag` field so the wire format
survives a byte-for-byte passthrough.
Registered from createDecoWorkerEntry rather than a setup call sites must
remember — montecarlo never calls setupTanstackFastDeploy, and a getter
nobody calls is the same bug as no getter.
Degrades safely: no keys means handleMeta falls back to the bundled schema;
bundle stubbed with no keys means 503 on /live/_meta only — the site itself
keeps serving.
Measured on montecarlo, production build in workerd, warm over 8 real routes:
70.5 MB -> 29.5 MB.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The read path was keyed off "are the keys present", which meant any site whose build ran deco-sync-blocks-to-kv would silently start serving /live/_meta from KV — opted in or not, since the seed is unconditional. That is the shape the decofile path already rejects on purpose (isFastDeployEnabled: binding a namespace must not by itself flip a site onto the KV path). `metaFromKV` is now the single switch for both sides, via a `__DECO_META_FROM_KV__` define mirroring `__DECO_BLOCKS_STUBBED__`. With the flag off, KV is never read and nothing changes for existing sites; with it on, there is no state where the bundle has no schema but the reader declines to fetch one. Verified end to end against montecarlo (production build in workerd, all KV keys seeded): flag off leaves /live/_meta on its previous behaviour and never touches KV; flag on serves 200 from KV with a stable ETag and 304 on If-None-Match, with only a 65-byte stub in place of the 10.6 MB chunk. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The admin JSON Schema costs ~40 MB of a 128 MB Workers isolate on a large site (montecarlo: 1690 blocks), and only
GET /live/_metareads it — nothing on the render path does. V8 keeps the module's source text and materialises theJSON.parseargument as a second independent string, both two-byte because the content is accented, so a 10.6 MB chunk is paid roughly four times over.This mirrors what
fastDeployalready does for the decofile:meta:<id>+meta:etag:<id>seeded bydeco-sync-blocks-to-kv,decoVitePlugin({ metaFromKV: true })stubbingmeta.genout of the server bundle, andhandleMetastreamingkv.get(..., { type: "stream" })straight into theResponse— nothing parses the payload, since materialising it even as a string would reintroduce most of the cost. The ETag is precomputed at build under its own small key, both soIf-None-Match(what admin actually polls with) costs one tiny read and because hashingJSON.stringify(schema)is impossible once the schema has left the isolate; the same value is merged into the payload'setagfield so the existing{...schema, etag}wire format survives a byte-for-byte passthrough.Measured on montecarlo, production build in workerd, warm over 8 real routes: 70.5 MB → 29.5 MB. (The first commit, making the eager
options.meta()load lazy, measured at 0 MB on its own — the source text and the string literal are both materialised at module load, before evaluation. It is kept because it makeshandleMetaasync, which is what lets it await KV, and because it removes a floating promise that made/live/_metanondeterministic.)Degrades safely, unlike the decofile stub: with no keys
handleMetafalls back to the bundled schema; with the bundle stubbed and no keys it answers 503 and only the admin loses the schema — the site keeps serving. The read side is registered fromcreateDecoWorkerEntryrather than a setup call sites must remember, since montecarlo never callssetupTanstackFastDeployand a getter nobody calls is the same bug as no getter.nextis unaffected: it never registers a KV getter, so it keeps the in-bundle path.🤖 Generated with Claude Code