Repository navigation
Push any size as one version: paged run indexes, compaction pieces, byte-sized units, streamed leaves, parallel schema and privacy changes, stalled-commit recovery, a bulk Worker - #39
Merged
Conversation
…ed run count before commit, units sized by bytes with base-leaf cost, plans written in chunks
…-record reads: zlib's streaming gunzip, a line at a time, no whole body in memory
…and units go again, a unit out of attempts or a commit that stops moving fails with a reason
…runs, compactions, the commit's phase and units done
… consumer, no routes, a smaller isolate cache
…s) and check it in CI; 8 MB upload batches, 4 MB run parts with one stored at a time, 512 MB compaction pieces
…e's progress, and underlay.org's new limits
… missing files drops its commit phase
…s revalidate in parallel jobs, and privacy moves and type removals commit in parallel, matching the serial build
… (scripts/plan-memory.ts)
…fetch and decompress it once
… re-serialising them
…he same session; storage cleanup holds for a commit while it makes progress, not only in its first six hours
…ep leaves a long one alone; a run writer fills the next part while the previous one is stored
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.
Push a collection of any size as one version, over any number of upload requests, with every step of the push bounded in memory and resumable, on Workers. This follows #38 (merged), which bounded the refs index, the repo sink and run prefetch. Loading a large bibliographic corpus on staging (2026-10-14/15), with records of several KB each, kept running out of memory: a 5M-record commit split into 34 units, and 14 of them hit the 128 MB isolate limit on every attempt. The session then sat in
committingforever.The design was already right: uploads are sorted runs in the internal bucket, merged during upload and committed in key-range units. These are the places that still held, in one isolate, memory proportional to the push or to a leaf:
phase: compacting), and the last compaction queues it againcommit.planjobs, 1,000 units eachcommittingwrangler.bulk.jsonc) with an 8 MB cache; uploads are 8 MB batchesschemaRelaxes) isn't revalidated; any other is revalidated in parallel jobs after assemblyOne version from many uploads
GET …/push/:sidnow reportsprogress:runs,run_bytesandruns_compactingwhile open; thenphase(compacting,planning,units,assembling) andunitsdone of total.Schema and privacy changes
Relaxations aren't revalidated.
schemaRelaxes(protocol) is true when the new schema differs only byrequiredlists losing names, annotations (title,description,$comment), or the rootprivateflag. A record valid before stays valid, so the serial build, the parallel planner and the async check all skip revalidation for these changes. Only positions where loosening can only accept more count: undernot,oneOf,ifanddefinitions/$defseverything must stay equal, and a schema with a$refgets the annotation allowances only.Other schema changes revalidate in parallel. After assembly, the session goes to
phase: validating, andcommit.validatejobs each stream about 256 MB of the new tree's records.commit.publishthen publishes, or fails the session with the sameSchema validation failed422 the serial commit gives (first 100 errors, total count). Revalidation counts as work in the planner, so a small push that changes a large type's schema goes parallel too. The sweep resumes this phase like the others.Privacy changes and removals go parallel where the serial build has a plain tree move and the base counts file references per type:
Units merge into the moved tree, and the publish moves the type's file references and counts as the serial build does. The rest stays serial, with the type's size counted, and past ~2 GB it's refused with a 422 saying to push that change on its own: a privacy change with records already in both sets, private records pushed as the type turns public, or a removal from a base without per-type counts.
test/schema-parallel.test.tscommits each case both ways, on two collections with the same base, and checks the same sets, counts, file sets and errors.Measured
scripts/memory.tsruns every job in-process on synthetic bibliographic records (about 10 KB each, non-ASCII text, long reference lists). It reports each job's peak live memory: JS heap plus array buffers, after a forced GC, above where the job started.The run: 60,000 records (614 MB) in one version, then 15,000 updates spread over all of them, so every base leaf is rewritten. Production sizes, except 64 MB compaction pieces (so pieces run) and a 4-run commit limit (so the commit compacts first):
commit.unit(8)refs.index(2)push.compactPiece(14)commit.assemble,commit.plan,push.commit,push.compactEvery job is under a third of an isolate. Array-buffer readings vary between runs (Node frees them lazily); before the batch, part and sink changes in the last commits, the same run read 52 MB for uploads and 64 MB for units.
test/memory.test.tsruns the script on ~40 MB in CI and fails if any job's JS heap passes 16 MB (32 MB for an upload).Retries, cleanup and reads
POST …/commitplans afresh from them instead of the client uploading everything again. A refusal the client has to fix (422 invalid records, 409 base moved) answers 409 with the cause. Retries are allowed until an hour before cleanup would delete the session's uploads (about a day after it would have expired).{"id","type","data"}, so it's copied into the page rather than parsed and serialised again.Compatibility
{seq, tier, blocks}) and slices of plans already in flight still read: the blocks become inline pages. The protocol, tree shapes and version hashes are unchanged.batch_bytesadvertised drops from 16 MiB to 8 MiB. Clients read it from the session: PubPub's delta push and the scrapers do.Deploy (in this order)
wrangler d1 migrations apply underlay-staging --env staging --remote(0024: run bytes and claims, commit phase and progress, unit attempts).wrangler deploy --env staging. The main Worker no longer consumes the bulk queue, so bulk jobs wait on it until step 4.wrangler secret put <NAME> -c wrangler.bulk.jsonc --env stagingforR2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY,SIGNING_KEY,LOCATION_KEY.wrangler deploy -c wrangler.bulk.jsonc --env staging(createsunderlay-staging-bulk, the bulk queue's consumer).To hold the Worker split back, deploy without 0d244f1: everything else works with one Worker.
Not done / not checked
scripts/plan-memory.ts: 32 runs, 50,000 parts): 8.4 MB of summaries, 12.6 MB in memory after splitting into 4,207 units, about 8 KB per unit's slice.Tests: server 246/246 (new:
stream-push,recover,memory,schema-parallel, byte-capped and passed-through diff pages), protocol 214/214 (streaming gunzip, leaf streams, shared reads,schemaRelaxes), typecheck, oxlint and oxfmt clean. Both Workers build (wrangler deploy --dry-run).🤖 Generated with Claude Code