Repository navigation
feat(core-ts): purge referrers from the snapshots' bodies (RFC-0045 step 3b) - #212
Merged
Merged
Conversation
…ddleware
Builds the starter under prefix-except-default and prefix-all with a Turkish referrer and
a fallback card: the venue links /tr/places/… exactly once, the fallback the default
locale's member. Reverting EntryFields.astro to href={c.url} fails it with /tr/tr/….
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
…tep 3b) - references/purge.ts: the purge planner's referrer closure. Seeds are the groups of changed entries and of entries the asset rule reached; two hops of referrers (with every member of each referrer's group); "used by" from resolved values, before and after. Reads only the from/to bodies: one hash-keyed cache, one bundle (to's, else from's) streamed and cut to what the closure needs, then the missing hashes. Byte and node budgets are checked before each parse; past them, or past maxReads/maxTags, or with any field-operation marker on either site node, it purges everything. Sites without reference definitions skip it (no bundle read). - readSnapshotBundle takes maxBytes / maxNodes / keep (BundleBudgetError); the store and SnapshotReader pass them. No new tags. - references/release-purge.ts: release runs re-plan their purge at run time, from the pointer they moved; RELEASE_EXECUTOR_COST 16 → 18. - planCachePurge stays under its baseline (diffNodes, countedReader, ancestry helpers). - tests: every purge case of the RFC's Renderer test plan, and the release re-plan in the shared release suite (SQLite, D1). Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
- A: no seeds (no changed entry, no reached asset), no reads: term-, asset- and site-only publishes on a reference site never reach the budgets - B: the closure reads missing hashes itself (readSnapshotBundle `only`), six at a time, counted against maxReads and the same byte total before each parse, kept as Slim and never cached whole; a reader that cannot stream purges everything - C: release runs re-plan over the tick's counting driver; a re-plan that throws sends purge-everything, never the stored plan; RELEASE_EXECUTOR_COST 26 (refreshSearch, the re-plan and the bundle's reads, bookkeeping) - D: a release run writes its scheduled snapshot's bundle first (best effort, ≤ 1,000 nodes); one left without is passed as `unbundled`, so no shard reads are spent on it - E: the pin-time plan skips the closure (`references: false`); it is the expected outcome only for reference sites - F: a field still converting from references counts as one (the marker rule applies during reference → text) - G: tests for each, including the closure's own reads hitting maxReads; RFC "Amended in 3b" rewritten Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
- release runs on sites without references behave exactly as before 3b: the stored pin-time plan, no extra read (decided on the site row publishRelease returns) - reference sites: re-plan within a per-tick blob allowance (400 Free, 4,000 Paid; ≤ 250 per re-plan), purge-everything past it; to's shards never read - the bundle write comes after the purge, only when from has a bundle, built from from's bodies plus ≤ 100 changed nodes; skipped when the re-plan found from bundle-less - RELEASE_EXECUTOR_COST 26 with recordPurge counted (the bundle's own snapshot read is gone), subrequest numbers documented beside it - a discarded partial bundle gives its bytes back; the no-seeds return precedes the site-node reads - tests: blob gets counted through a wrapped BlobStore (no-reference site equals the executor-less run; back-to-back releases with from bundled and bundle-less), the shard spend pinned at own+shards, the byte give-back; changeset and RFC amended Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
Removes machinery: - the release gate tests the published site node (publishRelease returns its ref), not the working row: a draft that drops the last reference field still re-plans, one that adds a first one still sends the stored plan - the run-time bundle write is gone (writeBundle, its cost, its caps); back-to-back releases with no visitor re-plan node by node or purge everything - blob calls are charged as they happen to one allowance per invocation (350 Free, 3,000 Paid; arithmetic beside the constant), shared by the cron's two ticks (cronTick); each re-plan gets min(2,000, left); RELEASE_REPLAN_MAX_READS is gone - tests: back-to-back within the allowance, both gate directions, 13 Paid releases in one tick, a 260-term reference site on Paid, two ticks sharing the allowance; RFC "Amended in 3b" and the changeset rewritten Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
…nd 4 (PR #212) - the gate read is counted but never refused; if it fails, the working row decides (no references: the stored plan): sites without references never depend on the shared allowance - a re-plan whose snapshots hold more distinct terms than reads left purges everything before reading - the tick charges the release graph's blob calls (refreshSearch, labelOf, a first publish's head/put) to the allowance, never refusing them (chargedBlobs) - "Publish now" sizes its allowance from the deployment's tick budget (ReleaseServiceOptions/SchedulesDeps.tickBudget, publishNowPurge); ReleaseServiceOptions moves to release-service-options.ts (re-exported) to keep releases.ts at its baseline - docs: labelOf keeps its JSDoc; "except on a first publish"; Paid fits ~27 releases by D1; accepted over-purges listed (asset rule on Free, AI Search mirror uncounted) - tests: stored plan with the allowance spent and with a failing gate read; the terms pre-check costs only the gate read Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
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.
RFC-0045 rollout step 3b: publishes purge the pages that show references (§2 "Purges", §9 step 3, watch-list item 2). It follows 3a (#211). The types stay refused on every production write path; the tests enable them with
SiteGraphStoreOptions.fieldTypes.First commit: a 3a verification leftover
packages/cli/test/reference-links.test.tsbuilds the real starter withcloudhal build, underprefix-except-defaultand underprefix-all. The pages go through the realEntryFields.astroand the real locale middleware. In both routings:/tr/places/salonexactly once;Putting
href={c.url}back intoEntryFields.astromakes both builds fail with/tr/tr/places/salon.The closure (
core-ts/src/references/purge.ts)planAssetPurgenow returns them.resolveFieldsruns with the snapshot's same-site original and without it, so a translation that only inherits counts.from/tobodies, never the working copy orentry_refs.to's, elsefrom's. It is streamed through the newreadSnapshotBundleoptions:keepcuts each body to what the closure needs as its shard is parsed, andmaxBytes/maxNodesare checked before each parse (BundleBudgetError).maxReads.maxReads,maxTags) the publish purges everything. So does any field-operation marker on either site node.SnapshotReader.readSnapshotBundleis the one reader addition. The store's method takes the options and stays at its baselined line count.planCachePurgegoes from 207 lines to 184 (diffNodes,countedReader,ancestry).Releases (
core-ts/src/references/release-purge.ts)publishRelease, the executor plansfrom → snapshotIdand sends that, rather than the plan stored at pin time.fromis the pointer the batch moved.recordPurge, as deferred purges already were.RELEASE_EXECUTOR_COSTgoes from 16 to 18. The comment says why: twogetSnapshotreads, while bundle shards and node bodies are blob reads, bounded bymaxReads.Tests:
core-ts/test/references-purge.test.ts, every case of the Test plan's Renderer bulletEach case asserts the existing
cloudhal-node-<id>tags only.createSnapshotandpublish. A referrer edited to stop naming the target, or deleted, between snapshot and purge, is still purged fromto's bodies.tothat only inherits, deleted since.scheduledsnapshot, or an approval's manual one): two bundle attempts, node reads only for the diff,maxReads20, no purge-everything.fromortopurges everything: on the marker's own publish, on both snapshots, and onfromonly.Mutation checks:
No new SQL: the closure reads blobs, plus the existing
getSnapshot.Deviations (also in the RFC's "Amended in 3b")
maxNodescounts the entry bodies held across both snapshots, plus the bundle's node count.Checks
pnpm check: it failed on one Studio UI test (chrome-settings, 11 s) under load, and turbo stopped after it.--continue(17/17); Studio passes alone (83 files, 1,339 tests).references-purge(12),cache-purge(12),bundles(4), the release suite (30), Cloudflarepurge,purge-siteandscheduler, andreference-links(both builds).Review fixes (
1cb2520a)A. No seeds, no reads. A publish that changes no entry and no shown asset returns before any read, so a term-only publish reads no bundle. Tested.
B. The closure's own reads. Missing hashes go through
readSnapshotBundle({ only }):maxReads;spent) before each parse;A reader that can't stream bodies purges everything. Tested: no bundles and a small
maxBodyBytes.C. A failed re-plan never drops the purge.
ReleaseExecutorOptions.counted, which the tick passes;releases.tsstays at its baseline).{ mode: "everything" }through the purger.RELEASE_EXECUTOR_COSTis 26: the batch up to 8,refreshSearch7, the bundle and re-plan reads 3, and bookkeeping 4. The site-moved retry stays outside it, as before; it fits Workers Free (40).everything.D. Back-to-back releases. Each run writes its
scheduledsnapshot's bundle first, best effort, for snapshots of at most 1,000 nodes. A snapshot left without one is passed to the planner asunbundled, so no shard reads are spent on it. Tested on SQLite and D1 (two consecutive releases, each snapshot bundled, both planstags), plus a planner test forunbundled.E. The pin-time plan skips the closure (
references: false). It is documented as the expected outcome only.F. A field still converting from references counts as one (
migration.fromType: "reference", on the field or viasub). Tested by writing the reference → text state directly.G. The closure itself hits
maxReads. The test measures the planner's own reads first, with the closure off, and asserts purge-everything at that count plus 5.Mutation checks: undoing each of A, B, C, D, F and G fails exactly its own test.
The RFC's "Amended in 3b" is rewritten, including the corrected marker and conversion rule.
Local checks for this push:
pnpm checkfailed on load and environment, not on this change.mcp-server'srender_pagetest timed out under load.--continuerun then hit core-ts and Studio timeouts.EADDRNOTAVAIL: the machine ran out of ephemeral ports during Miniflare's D1 tests (TIME_WAIT peaked at about 16,200 of 16,384). Another project'sworkerdprocesses were running at the same time.origin/main'sstore.test.tsfails the same way on this machine (120 failures, the same peak).store.test.tsandlist-values-query.test.ts: 64 files, 740 tests;references-purge(17),cache-purgeandbundles;purge,purge-siteandscheduler;mcp-serveralone (183).Verification round 2 (
b4b19b0b)publishReleasereturns, which isto's site node. Sincefrom's site node isn't read, the "two site-node reads" aren't needed. A site whose last reference field was removed sincefromgets the stored plan; that edge is in the amendment.fromhas a bundle.from's bodies plus at most 100 changed nodes, for snapshots of at most 1,000 nodes.frombundle-less, the write is skipped, sofrom's shards are tried only once.to's shards.releaseBlobAllowance, passed byrunScheduledTick). A re-plan reads at most 250 blobs and a bundle write at most 108. Past the allowance the run purges everything. The numbers sit next toRELEASE_EXECUTOR_COST.BlobStore, on a reference site:frombundled:to's shards are never read,from's are read once by the re-plan and once by the bundle write, and the referrer tag is present;frombundle-less:from's shards are tried exactly once,to's never; the plan has the referrer tag or purges everything; and no bundle is written.RELEASE_EXECUTOR_COSTstays at 26, and that is correct. It now countsrecordPurge. The bundle write's owngetSnapshotis gone (it reuses the re-plan's two reads), so the sum is 1 + 3 + 8 + 7 + 2 + 4 + 1 = 26, written out in the comment.toshard that fits the budget on its own but not together withfrom's.fromand a manualto: tags at own + shards (with no extra node reads), purge-everything at own + shards − 1. Deletingspend(bundleShardCount)fails it.Local checks for this push:
pnpm check: 15 packages passed. Cloudflare'splayground-workerd"20 sessions at once" failed under load and passes alone (4/4). Turbo stopped before core-ts, api and cli finished.maindoes on this machine. Split up, it passes: 741 tests without the two Miniflare-heavy files; both files' SQLite variants; the release suite on SQLite and D1 (38); the live reads on all three backends.references-purge(18),cache-purge,bundles, and Cloudflarepurge,purge-siteandscheduler.Verification round 3 (
740e2e9c): removing machineryA. The gate is the published site node.
publishReleasereturns its ref (publishedSite), with its 212-line baseline kept.publishedSnapshotIdandrevalways differ.B. The run-time bundle write is deleted (
writeBundle, its cost, its caps). Back-to-back releases with no visitor re-plan node by node within the allowance, or purge everything. The test asserts two plans (tags or everything, with the referrer when tags) and the allowance never below 0.C. One allowance per invocation, charged by actual calls.
BlobStorewraps the planner's blobs; nothing is reserved up front. Each re-plan getsmaxReads = min(2,000, left), andRELEASE_REPLAN_MAX_READSis gone.cronTick, keyed by the invocation's controller;createAdminWorkerstays at its baseline). The alarm and the backstop run one tick each, with the default.references/release-purge.ts; the RFC, the changeset andRELEASE_EXECUTOR_COST's comment refer to it.refreshSearchalone, re-plan or not; that is pre-existing, and noted.D. Tests, on SQLite and D1:
Plus a Cloudflare unit test for
cronTick.RELEASE_EXECUTOR_COSTstays at 26 D1 statements: 1 + 3 + 8 + 7 + 2 + 4 + 1,recordPurgeincluded.labelOf's read is a blob call, not a statement. If you want 27 as a margin, it's a one-line change.pnpm check: green in one full run. Typecheck 20/20, 19/19 test tasks (core-ts 1,393, cli 568, Studio 1,339), exit 0.Verification round 4 (
01a2ce71, narrow)left = 0, and with a gate read that throws, it still sends the stored plan. Re-adding the refusal fails the test.chargedBlobs(count only), sorefreshSearch,labelOfand a first publish's head and put subtract fromleft. A store that is already charged is not wrapped twice.ReleaseServiceOptions.tickBudgetandSchedulesDeps.tickBudget, set by the admin Worker throughpublishNowPurge.ReleaseServiceOptionsand the request types moved torelease-service-options.ts, re-exported unchanged, soreleases.tsstays at its baseline.labelOfkeeps its JSDoc.pnpm check: green in one full run (typecheck 20/20, test tasks 19/19; core-ts 1,395, cli 568).🤖 Generated with Claude Code