Repository navigation
docs: back up and restore SQLite and PostgreSQL with their own tools - #737
Open
LeeroyHannigan wants to merge 1 commit into
Open
LeeroyHannigan wants to merge 1 commit into
LeeroyHannigan wants to merge 1 commit into
Conversation
The admin guide and the security model showed two pg_dump commands with a data database name that init does not create, and no page said how to copy a SQLite deployment or whether two pg_dump runs agree. Add a Database Backup and Restore section to the admin guide. Each procedure that needs the server stopped runs as one block under set -e and ends before it touches a file or a database unless extenddb status reports the server stopped, because extenddb stop stops nothing for a server started with serve --foreground. For SQLite the section gives VACUUM INTO while the server runs, a copy of the database file and whichever of its -wal and -shm files exist with the server stopped, and a restore that checks the copy with PRAGMA integrity_check before it moves the old files aside. For PostgreSQL it states that two pg_dump runs disagree when a table is created or deleted between them, and gives a dump pair taken with the servers stopped, pg_basebackup with its prerequisites, and the restore of each. The dump pair restore renames the databases it replaces and removes them only after the restored server passes a check. It prints the catalog's data_database_name and data_database_connection_string rows and sets both to their full new values when they name another database or address, because the server opens the database the connection string names. Every restore ends with a read of every table. The section also says what a copy holds: the catalog stores the encryption key next to the secrets it encrypts. The upgrade manual, security model and deployment guide now point at the section. The upgrade manual sends its plain-text dumps to the Rollback Procedure, which restores them with psql -f.
LeeroyHannigan
requested review from
amrith,
c33howard,
jcshepherd,
pdf-amzn,
robinnsc and
yesyayen
as code owners
November 1, 2026 20:05
This branch has not been deployed
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.
What
Adds a Database Backup and Restore section to the admin guide (
docs/manuals/05-admin-guide.md) with commands measured on SQLite and PostgreSQL, and points the upgrade manual, the security model and the deployment guide at it. The admin guide's Data Recovery entry becomes a pointer to the section.extenddb stopexits 1 and stops nothing for a server started withserve --foreground, which writes no PID file, and when its--configpath does not name the server's config file. The section says to stop the server through whatever runs it. Each procedure that needs the server stopped is one block in a subshell underset -e. The block first runsextenddb status --config /path/to/extenddb.toml | grep -q 'is not running', so it ends before it touches a file or a database while the server is up.VACUUM INTOwhile the server runs, and a copy taken with the server stopped that copies the database file and whichever of its-waland-shmfiles exist. The restore copies the saved files into a new work directory and runsPRAGMA integrity_checkon them. It then moves the current files into the work directory and moves the saved files into place. The section says not to use the sqlite3.backupcommand while the server takes writes.pg_dumpruns disagree when a table is created or deleted between them. The section gives a dump pair taken with the servers stopped andpg_basebackupwith its prerequisites (replication entry inpg_hba.conf,wal_keep_sizefor-X fetch). The dump block also requires no session of the application role on either database. The restore renames databases that hold the target names to<name>_before_restoreand restores the pair. It prints the catalog'sdata_database_nameanddata_database_connection_stringrows. When they name another database or address, the section sets both to their full new values and reads them back. The renamed databases are removed only after the restored server passes its check, and a failed restore is undone by renaming.aws dynamodb list-tables, thenscan --select COUNTon each table.pg_restorerefuses. Its new sentence now sends them to the Rollback Procedure, which restores them withpsql -f. Deployment guide: archived WAL restores only on top of a base backup.Why
The admin guide and the security model showed
pg_dump extenddb_catalogandpg_dump extenddb_catalog_data.initcreatesextenddb_catalogandextenddb, so the second command names a database that does not exist. Both pages said ExtendDB stores all data in PostgreSQL, which leaves out SQLite. No page said how to copy a SQLite deployment, and none said whether two separatepg_dumpruns give a pair that agrees.CreateBackupkeeps its backups in the catalog on this release, so they are lost with the database. The off-host design (docs/design/15-backup-restore.md) keeps the database tools for whole-instance recovery.Related: #624 (off-host backup and restore), #707 (keeping the encryption key outside the catalog).
Testing done
Measured on 1 November 2026 at main
f49c5f823, with release builds ofextenddbfor SQLite and for PostgreSQL, sqlite3 3.46.0 (the SQLite version the server links) and PostgreSQL 16.15 (pgvector/pgvector:pg16). A client sent PutItem and UpdateItem one request at a time to two tables, one of them with a GSI whose key the UpdateItem changes, and logged each acknowledged write with its send and acknowledgement times. The copies taken while a server ran were taken under those writes. The copies taken with a server stopped were taken after its last acknowledged write. A table backup was taken before any copy. For each restored copy a server was started and checked:InvalidSignatureException;AVAILABLE.Each command block in the section was then run as printed, with only its placeholders replaced, against local servers; the PostgreSQL client tools ran inside the database container. Two blocks were not rerun: the
DROP DATABASEof the renamed databases, and thetarextract of a base backup, which is unchanged.SQLite:
VACUUM INTO, 1 MB database, under writesVACUUM INTO, 284 MB database, about 100 writes a second.backup, 284 MB database, about 100 writes a secondextenddb stopexits 1 (wrote no PID file); the block ends at theextenddb statuscheck; no file touched-walafterextenddb stop-waland-shmpresent, and with both absent (after the sqlite3 shell closed the database)-waland-shm-walPRAGMA integrity_check:database disk image is malformed; the block ends before it moves anything-waland-shmbeside itListTablesanswers,Scanis HTTP 500; another: the server cannot open the database and exits 34 s after the start, and the table-read check fails at its first requestextenddb serveset a restored 0644 file to 0600VACUUM INTOfails withnear "INTO": syntax error; the schema cannot be read (malformed database schema (idx_login_attempts_source_ip_time))PostgreSQL:
pg_dumpof the catalog, then of the data database, under writesACTIVE, its item requestsResourceNotFoundException,DeleteTableremoves it; created table missing, its 20 items left in a data table the catalog does not nameextenddb statuscheck; with a config whose port nothing listens on, ends at the session check (19 sessions); no dump writtenextenddb; with the rows unchanged the server served the original database's current items. After both rows were set: sessions on the restored databases, items written to the restored server absent from the originalis being accessed by other users)pg_restoreof a truncated catalog dumpcould not read from input file: end of file, exit 1; renaming the new pair aside and the old pair back restored service with every write presentpg_restorebefore the role exists stops atrole "extenddb" does not exist; with roles from--no-role-passwordsthe server failspassword authentication failed for user "extenddb"until the password is set; then checks passpg_basebackup -Ft -X fetchunder writesconsistent recovery state reached, and ExtendDB served it with the config file unchangedpg_basebackupfrom a non-loopback addressno pg_hba.conf entry for replication connection; works after ahost replicationline and a reloadpg_dumpall --globals-onlyas a role withCREATEDBandCREATEROLEpermission denied for table pg_authid; with--no-role-passwordsit succeedspg_restorerefuses them (input file appears to be a text format dump); the Rollback Procedure'sCREATE DATABASEandpsql -flines restore them with every write presentextenddb settings set data_database_nameSetting 'data_database_name' is read-only and cannot be changed via this command.Gates:
cargo test --locked -p extenddb-storage-postgres --test doc_version_literals(3 passed). A test merge with #666, #682, #691, #228 and #668 is clean. #491 and #523 conflict on lines this PR replaces: they change the capitalization ofextenddb stores all data in PostgreSQLin the admin guide and ofextenddb stores all state in PostgreSQLin the security model, and this PR removes both lines.Statements that depend on open PRs:
-wal,-shmand-journalfiles to 0600. The section saysextenddb servedoes this, which is true on main; no statement needs fix: create SQLite database files 0600 and set wider ones to 0600 when opened #691.migrateconnect as the application role, which must own every table of both PostgreSQL databases. The restore steps leave the tables owned by that role (measured). feat(migrations): one list of catalog migrations with a ledger in every store, for SQLite and PostgreSQL #666 also refuses a catalog whose two data database rows name different databases; the restore prints both rows, sets both to their full new values when either names another database or address, and reads them back.Follow-up: the section takes
05-admin-guide.mdto 1210 lines.docs/design/15-backup-restore.mdsection 10 plans a page of its own for it,docs/manuals/14-backup-and-restore.md. Moving it there is left to a separate change, so that this one does not widen its conflicts with #491 and #523 in the admin guide.Found while measuring, not changed here:
extenddb catalog-checkon a consistent restored PostgreSQL pair reports each data table as orphaned and each table as missing, and--fixwould drop the tables it calls orphaned (catalog-check expects pre-id relation names; --fix drops live data tables on PostgreSQL #652). The section does not mentioncatalog-check.extenddb servein daemon mode printedextenddb server startedand exited 0, and the daemon then exited because it could not open a damaged SQLite file.Checklist
cargo test --workspace)cargo fmt --check)cargo clippy -- -W clippy::pedantic)Storagetrait, auth model, on-diskformat, or public CLI surface, an RFC has been accepted or is linked
below. Otherwise, an ADR captures the decision (link below).
ADR / RFC: n/a
By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache License 2.0 and I agree to the Developer Certificate of
Origin (DCO). See CONTRIBUTING.md for details.