Repository navigation
feat: restore a database page by page, on demand - #37
Merged
Merged
Conversation
mcollina
force-pushed
the
feat/paged-restore
branch
from
November 6, 2026 10:20
271b925 to
0311a2d
Compare
mcollina
changed the base branch from
feat/incremental-sync-after-restore
to
main
November 6, 2026 10:21
Replica.restorePaged() restores the replica's own database with the SQLite VFS of @platformatic/sqlite-js-stream-paged-vfs, a new optional peer dependency. It reads the page indexes of the restore plan, creates the database sparse with every page missing, fills page 1 and the schema, writes the first WAL and resolves. Queries from other threads read the pages they need on demand, with read-ahead, while the others are filled in the background with large ranged reads; `filled` resolves once every page is local. It restores fully below `minSize` bytes, or when the VFS cannot load. Replication goes on during the fill: sync() and snapshot() read missing pages first, and retention keeps the files the restore reads. A marker in <db>-litestream/ marks the database incomplete until the fill is done, so a restore stopped by close() or a crash is never uploaded with holes: localPos() is 0, sync() and snapshot() throw ErrIncompleteDatabase, and ensureExists() restores it again. S3ReplicaClient.openLtxFile() reads a range with one GetObject instead of a HeadObject followed by a GetObject. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sfj4nDS4Mc5fAjmzSLYG7k
mcollina
force-pushed
the
feat/paged-restore
branch
from
November 6, 2026 10:22
0311a2d to
6970123
Compare
This was referenced Nov 6, 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.
Step 3 of PRD-2215. Builds on #36 (merged).
Replica.restorePaged()Restores the replica's own database page by page, with the VFS of
@platformatic/sqlite-js-stream-paged-vfs, now an optional peer dependency (imported only here, so the core stays pure TypeScript). It resolves once the database can be opened, long before it is downloaded:<db>-litestream/pagedis written, the database is created sparse through the VFS with every page missing, and page 1 and the schema (thesqlite_masterb-tree with its overflow pages) are filled.Then:
databaseUri()of the VFS package. A read of a missing page waits while it is read with the next pages of the same file (readAhead, 32 by default) in one ranged read. A page the background fill is reading is read again in this smaller read, so a query never waits for a large one.result.filledresolves once every page is local.sync()andsnapshot()read missing pages first, the replica's own reader opens the database through the VFS (a miss there fails instead of reading zeros), and retention keeps the files the restore reads.close(), another restore, or a crash leaves the database incomplete. Without a fill in progress,localPos()is 0 andsync()/snapshot()throwErrIncompleteDatabase, so holes are never uploaded as zeros.ensureExists()and Lavoratore's activation restore it again; a full restore removes the marker.minSize, or when the VFS cannot load (result.unavailable).New exports:
PagedRestoreOptions,PagedRestoreResult,ErrIncompleteDatabase,ErrPagedRestoreClosed,ErrPagedVfsUnavailable.decodePageData()takes an optional output buffer (the fill reuses one per read) and rejects pages that do not fit.S3
openLtxFile()reads a range with a singleGetObjectinstead of aHeadObjectfollowed by aGetObject: open ranges read to the end, ranges ending past it are cut, and a range starting past it (416) reads nothing. That halves the requests of a miss. The in-memory S3 of the tests now answers 416 like S3.Tests
test/integration/paged-restore.test.ts, with a client that holds the background fill:close(), a crash (the restore runs in a child process killed with SIGKILL), and a full restore over an incomplete database;minSize.They need the native library: they run in the
paged-vfsCI job (pnpm test:paged-vfsnow builds the package and runs them) and are skipped elsewhere.Also checked by hand against rustfs (the S3 store of CI): the range cases above, and a paged restore of a 9.4 MB database whose first query was answered 88 ms after the restore started.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Sfj4nDS4Mc5fAjmzSLYG7k