Repository navigation
Conversation
robinnsc
force-pushed
the
feat/backup-docs
branch
2 times, most recently
from
November 6, 2026 03:22
04c2915 to
f78e9bc
Compare
The integration workflow's PostgreSQL and SQLite jobs ran the wire suite with `--filter "not import_export"`. The filter came with the PostgreSQL job's first version (687e46f) and was copied into the SQLite job (556c501); neither commit gives a reason. A foreground server wrote a PID file in every tree up to e0d6a0a, which made foreground mode write none by default and added `--write-pid-file` to the job in the same commit; 556c501 came after e0d6a0a. So the restart `devtools/run-tests` makes for the import and export tests could stop the job's server in every tree, and the filter was not working around it. The MongoDB and Cassandra jobs already run `tests/test_import_export.py` unfiltered. The expression selects out that file's ten tests and nothing else. Run the job's way on release builds of this tree they pass 10 of 10 on SQLite and on PostgreSQL, so both filters go and no narrower one stays. `docs/design/15-backup-restore-bench-pr11.md` (row 15d of the design index) records the cells the testing summary left open, in the PR 9 record's method: release builds, a filesystem store, the server restarted before each phase, `VmHWM` sampled every 200 ms, on the 16-core host class of the earlier records. 1M items of 200 bytes restore in 33.8 s on MongoDB (its own backup) and 49.0 s on Cassandra (a SQLite backup adopted with `backup sync`), 52 and 33 MiB over idle. A 1M-item table with a 128-dimension vector index backs up in 243 s on PostgreSQL and 210 s on SQLite and restores in 246 s and 214 s, 111 MiB and 552 MiB over idle; SQLite's figure is higher than at 100k under glibc's default arenas and is 204 MiB, flat from 100k, under `MALLOC_ARENA_MAX=1`, where the backup took 222 s and the restore 283 s. The Cassandra cells ran on a node of their own with the Cassandra workflow's service settings. No cell is left open. Later runs on the same host, also in the record, reproduce the cells and show that SQLite's restore memory is its write batch and decode chunks, not the vector index: it grows with the item's size up to the 32 MiB batch bound whatever the attribute types (253 MiB, 130 MiB under one arena, for 100k items of 2.5 KB strings), and lists of numbers decode larger (397 to 424 MiB for the same 100k items of 128 numbers with no index, 301 to 316 MiB under one arena for 20,000 items of 1,536 numbers). Two restores at once under one arena took 57.5 s against 24.9 s. Five pairs of PostgreSQL runs with and without the vector index, each pair from one harness, put its share of the 100k restore at 27 to 53 MiB and 33 to 47% of the duration. A MongoDB restore becomes ACTIVE at the backfill worker's next 5 s cycle after its load, so it takes the load's time plus up to 5 s. `docs/manuals/14-backup-and-restore.md` is the operator's manual, derived from the design: the strategy (its first sentence: a filesystem store keeps the host's failure domain, S3 is the production recommendation), RPO and RTO per store kind with the three records' figures and host class, the configuration (pointing at the admin guide's rows and the store crate's README), securing the store, what a running backup costs each database, a restore drill, catalog-loss recovery with `backup sync`, `--adopt-store` and the in-memory startup sync, legacy backups and what to do with them while `migrate-legacy` (#821) is not shipped, what a restore preserves, and moving data to and from Amazon DynamoDB with ImportTable and export (a store-prefix import is #748). It states each deployment statement the design's PR 11 acceptance lists. Figures measured for it, with their method in the record's "Figures measured for the manual": a 25 s PostgreSQL backup beside 12,156 overwrites of 200-byte items grew the written table from 0.4 to 4.4 MiB, its dead row versions not removable by a `VACUUM` during the backup (0.5 MiB with no backup); on SQLite the WAL grows by the pages each write changes, from 4.0 to 61.1 MiB over 15,472 writes to a table with no index during a 22.8 s backup and from 4.1 to 1,294 MiB over 13,996 writes to a table with two GSIs during a 25.6 s backup (about 20 to 24 pages per write over four runs). The manual's process-level statements were checked on release builds of this tree, SQLite and PostgreSQL: the root placement refusals and their texts, the drill, a re-created catalog's store health with an explicit store and with SQLite's default root (unconfigured, with CreateBackup writing in-catalog backups until the adoption), the accounts re-created with their old ids and old key pairs imported, the adoption, the in-memory startup sync and, on the in-memory dev build, the adopted backups refused until their account is re-created, the in-catalog create's warning and the restore of its backup through a store-configured server, an S3 store at moto with the default spool, `backup sync` exiting 6 beside a SQLite server, ImportTable refusing a gzip file and both kinds of symbolic link and importing a decompressed backup data file, an off-host copy with loose modes refused by `backup sync`, and on PostgreSQL the claim session found by its lock key, whose termination stops the job at its next heartbeat and leaves the row to the reconciler, the registration session's termination failing the server's running backups, the wait of `--prune-missing` on a recently failed backup, a catalog restored alone beside a newer data database, and the catalog and data dumps restored into new databases. On PostgreSQL 16 containers the restore the admin guide now gives ran end to end, on the old server and on a new, empty one, and an access key was imported through the management API from a request body read from a file. The admin guide's Data Recovery entry named the PostgreSQL data database `extenddb_catalog_data`; `init` names it after the catalog without its `_catalog` suffix (`extenddb` by default). The entry now lists each backend's databases, says a catalog name without the suffix needs `--data-db`, and treats the two PostgreSQL databases as one copy: both dumps with the server stopped, under `umask 077` since the catalog dump holds the encryption key and the application role's password, or a base backup; the restore goes into new, empty databases created with `createdb -O` by the admin role, since one over the existing databases restores nothing and one into databases the admin role owns leaves the server unable to create tables. The old databases are renamed aside until the restored server has read every table; the dumps use the server's major version of `pg_dump` and are checked non-empty, since an older one exits 1 leaving an empty file that `psql` restores without an error; and on a new server the application role is created first with the dump's password and, at another address, the catalog's recorded data-database address is set, or the server keeps using the old one. The SQLite row gives the restore with the current `-wal` and `-shm` removed. The security model and the deployment guide's checklist say the same, and the upgrade manual's dumps take `umask 077` and its rollback restores both databases. The architecture, design, quickstart and security-model guides, AGENTS.md, the troubleshooting guide, the technical-debt register and the storage-extension guide carry the right name, the quickstart's `init` table gives the PostgreSQL admin and application roles' flags with their defaults, and `init --help` states both database defaults. The admin guide's `[backup]` section and the upgrade manual's backup paragraph point at the manual; the upgrade manual's shared-root and re-created-catalog sentences move there. The admin guide's `max_concurrent_jobs` row says that SQLite restores of items larger than a few hundred bytes hold more and points at the manual, its backup commands' paragraph gives the exit codes 3, 4 and 5 on a catalog `serve` refuses, and the deployment guide's checklist has a `[backup.store]` line. ADR-0019's Status line says it is superseded in part by the design, whose section 10 states what that covers, a form `docs/adr/README.md` now names; the Cassandra RFC's "Backup and restore" section carries a note, superseded in part, since CreateBackup on Cassandra still writes their in-catalog format, and its lines on synchronous operations and on restored indexes carry notes too. The differences document gives the 1M MongoDB and Cassandra restore figures. `docs/build-docs.py` lists the manual as document 19 (the build renders all 19), and the README's and AGENTS.md's documentation tables have a row for it (AGENTS.md's Extending Storage row now names its file). The quickstart and the `extenddb` skill say that `init` refuses an existing config file without `--overwrite`. The design's testing summary states the measured cells in place of "still to be measured", section 7 says what a SQLite restore of items larger than a few hundred bytes holds, the PR 11 section's line says what shipped, and revision item 35 records the filter history, the measurements, the manual and the database-name correction. Co-authored-by: Lee <leeroyhannigan@yahoo.ie> Signed-off-by: Scott Robinson <robinnsc@amazon.com>
robinnsc
force-pushed
the
feat/backup-docs
branch
from
November 6, 2026 05:19
f78e9bc to
88d445d
Compare
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.
Backup/restore series (design: #396): #384 restore fidelity (merged) · #393 store · #394 format · #395 SnapshotEngine · #666 catalog migration framework (merged) · PR 6 the catalog index · PR 7 configuration, the store identity file and
backup purge· #700 the CreateBackup job · #740 the restore job, the cross-backend matrix andbackup remove-target· #825backup sync,--prune-missing,--adopt-store· this PR: the CI filters, the open measurement cells and the backup and restore manual · next: #820 the store-maintenance options, #821migrate-legacy, #748 export and import through the store.Closes #747 (part of #624).
Stacked on #825 (
feat/backup-sync, head358ff8bd), opened as a draft against it. The diff is one workflow file, documentation, and one source change: the help text ofinit --data-dband--catalog-db(crates/app/src/cmd_init.rs), which named the old defaults. The binaries every figure and check below used are release builds of this tree (358ff8bdplus this diff); the help text is the only source line the diff changes, so builds before and after it serve the same requests.What
CI filters (
.github/workflows/integration.yml). The PostgreSQL job's and the SQLite job's "Run integration tests" steps drop--filter "not import_export". History: the filter came with the PostgreSQL job's first version (687e46f, "ci: add integration test workflow with PostgreSQL") and was copied into the SQLite job (556c501); neither commit gives a reason. A foreground server wrote a PID file in every tree up to e0d6a0a (crates/bin/src/cmd_serve.rsat 687e46f,crates/server/src/serve.rsat e0d6a0a^), sostopcould signal the job's server. e0d6a0a made foreground mode write no PID file by default and added--write-pid-fileto the job's start in the same commit. 556c501 came after e0d6a0a (git merge-base --is-ancestor e0d6a0af 556c501e), and its SQLite job passes the flag. So the restartdevtools/run-testsmakes for the import and export tests could stop the job's server in every tree, and the filter was not working around it. The MongoDB and Cassandra jobs already runtests/test_import_export.pyunfiltered (--pytest --comprehensive --parallel). What the expression selects out (collect-only, Python 3.10, the job's own ignore list): the 10 tests oftests/test_import_export.pyand nothing else (0 of 502 comprehensive, 0 of the 52 CLI lifecycle tests, 0 of the 18 backup-store step tests). Run the job's way (release build,init,serve --foreground --write-pid-file, the job's environment,run-tests --extenddb --pytest --parallel --filter import_export): the main pytest step passed 10 of 10 on SQLite and on PostgreSQL. That command exits 1, because its later steps select nothing under the filter (on SQLite the backup-store step's 18 tests are deselected, on PostgreSQL the CLI lifecycle list's 52) and pytest exits 5 there. No test fails, so both filters go and no narrower filter is kept.The open measurement cells (
docs/design/15-backup-restore-bench-pr11.md, new; row 15d ofdocs/design/README.mdbeside 15b and 15c). In the PR 9 record's method: release builds, a filesystem store,[backup] max_concurrent_jobs = 2, the table loaded through the wire from four processes of eight threads, the script'scontrol_plane_delay_seconds = 0.05andindex_propagation_delay_ms = 0, the server restarted before each phase,VmHWMread one second after the start and one second after the phase and sampled every 200 ms. The plain items' 200-byte pad is one repeated character, which the record states. Host: 16 cores (Xeon Platinum 8124M), 30 GiB, Linux 5.10, glibc 2.26; PostgreSQL 16.15 with pgvector 0.8.7, MongoDB 7.0.43, Cassandra 4.1.12, each in a container on loopback; one run each, load average beside each figure.backup sync)MALLOC_ARENA_MAX=1The 100k cells for each shape are in the record. From 100k to 1M the restore's peak moved +16.7 MiB on MongoDB, +13.2 on Cassandra, +10.5 on PostgreSQL with a vector index, and +198 MiB on SQLite with a vector index under the default arenas (the 1M run levelled off after about 150 s; no other size was measured) against +8.6 MiB under one arena. One arena cost that restore 32% in duration. A whole-table
SearchVectorsover the 1M restored vectors answered in 0.24 s on PostgreSQL and 14.7 s on SQLite (an exact scan; 5.5 to 15.8 s over the seven runs the record gives). MongoDB's restore becomesACTIVEat the backfill worker's next cycle after the load (GSI_BACKFILL_INTERVAL, 5 s,crates/storage-mongodb/src/ttl_worker.rs), so its duration is the load's plus up to 5 s: 3.9 to 8.9 s at 100k over five runs, and 33.8 s at 1M in one run, which another may exceed by up to one cycle. No cell is left open: the 1M Cassandra restore finished. It ran on a Cassandra 4.1 node of its own with the Cassandra integration workflow's service settings (PasswordAuthenticator,MAX_HEAP_SIZE=1024M), under the Cassandra lock, removed afterwards: the shared node on this host runsAllowAllAuthenticator, whereextenddb initcannot create its role, and it was not reconfigured. The record says the node was used by these runs alone.The record's "Later runs on the same host" section holds a second set of runs on release builds of 30553b3 (this tree's sources before the help-text line) and, for two confirming runs, of this tree, on the same host, with a separate harness and the server's default settings. They reproduce every cell in kind (within 12% on the peak, 20% on the durations; the SQLite 1M vector restore 522.8 MiB, 218.7 s) and add:
The record's new "Runs on this commit's Rust sources" section holds a fourth harness's runs on release builds of this commit's sources, which reproduce the cells in kind, and a control: 100k SQLite items whose 128-number list is replaced by a random 2,290-character string (2,569 bytes of line text per item against 2,503) held 253.4 and 252.5 MiB over idle in two runs, 129.6 and 129.8 MiB under one arena, against 38 MiB for 100k items of 200 bytes (the PR 9 record). So SQLite's restore memory is its write batch (up to
RESTORE_BATCH_ITEMS, 10,000 items, orRESTORE_BATCH_BYTES, 32 MiB of item text) and the decode chunks in front of it, which grow with the item's size up to the batch bound whatever the attribute types; lists of numbers decode larger at the same text size (344.6 to 424.5 MiB and 184.0 to 195.3 MiB under one arena at 100k items of 128 numbers, with or without the index). The one-arena 1M restore measured 203.9 to 208.0 MiB over three runs, flat from 100k within about 25 MiB over the measured runs. The record, the manual, section 7, the testing summary, item 35 and the admin guide's row say so, and theMALLOC_ARENA_MAX=1advice and the sizing are keyed to items of more than a few hundred bytes.The manual (
docs/manuals/14-backup-and-restore.md, new), for operators and derived from the design, which remains the record. Its first sentence: a filesystem store keeps the backups in the host's failure domain, so S3 is the production recommendation. Sections: strategy (what a table backup holds and does not: the catalog, scheduling with a crontab line that escapes%and namesAWS_CA_BUNDLE, retention, PITR; Cassandra's in-catalog create); RPO and RTO per store kind (RPO equals the backup interval, no PITR; the restore and backup durations and the memory figures from the three records and the later runs, with the host class and what was not measured;MALLOC_ARENA_MAX=1recommended on a SQLite host that restores tables of items of more than a few hundred bytes, with the measured points to size from and its duration cost for one job and for two); configuration (filesystem and S3 examples, the root that must exist, the spool's mode rule, pointing at the admin guide's[backup]and[backup.store]rows andcrates/backup-store/README.md); securing the store (including that the check does not probekms:Decrypt); what a running backup costs each database; a restore drill (everyawscommand with--endpoint-url, and the copy modes an off-host drill needs); catalog-loss recovery withbackup sync(a catalog restored from a dump, a re-created catalog with--adopt-storein both SQLite shapes, the management API as the way to import a key pair without its secret in the process list, sync's exit codes, the in-memory startup sync and the accounts it needs re-created); legacy backups (they stay restorable;migrate-legacyis #821, so the manual gives today's path: restore, back up again with a store, check, delete, and restore into the original name only once that table is gone, since deleting a live table loses its writes since the legacy backup); what a restore preserves (the table the differences document carries, with its one gap); and moving data to and from Amazon DynamoDB (the format is the S3 export layout; the service's ImportTable parameters and that it creates no LSI; ExtendDB's ImportTable reads one uncompressed local file named by its canonical path; the export'sFilePath; a store-prefix import is #748, not in this release).Each statement of the design's PR 11 acceptance is in the manual: the first-sentence failure domain and S3 recommendation; encryption at rest is the store's job; a copy tool runs as the server's user or as root, and a root under a storage administrator's directory, an NFS mount owned by
nobodyor a group-writable parent without the sticky bit is refused; a bind mount or a filesystem without Unix permission semantics is outside what the store observes, and a root mounted with a mask that reports0700enforces nothing;AbortIncompleteMultipartUpload, and for a versioned bucketNoncurrentVersionExpirationandExpiredObjectDeleteMarker; the least-privilege IAM policy (pointed at); a PostgreSQL snapshot retains every dead row version and the SQLite WAL grows during a backup, with figures measured for the manual (below); the claim connection's keepalive bound (30 s idle, three probes 10 s apart: about a minute) andpg_terminate_backendon the claim's session, with the query that finds it by its lock key in the catalog database; a transaction-mode pooler is unsupported; the SDK'stracelogs carry the session token; write access to an account's prefix is backup-administrator authority for that account, with per-account IAM prefix policies; MongoDB'sminSnapshotHistoryWindowInSeconds.Figures measured for the manual (the record's new "Figures measured for the manual" section gives their method, build and load, and their reproductions on this tree), same host class, release builds, 100k-item vector tables as the backup's source: on PostgreSQL a 25.2 s backup beside 12,156
PutItemoverwrites of 1,000 200-byte items in another table grew that table from 0.4 to 4.4 MiB, and aVACUUM (VERBOSE)10 s into the backup reported 4,948 row versions "dead but not yet removable"; the same writer for the same time with no backup left the table at 0.5 MiB, and the firstVACUUMafter the backup removed every dead version. On SQLite the WAL grows by the pages each write changes, which depends on the table's indexes. A 22.8 s backup beside 15,472 such writes to a table with no secondary index grew the-walfile from 4.0 to 61.1 MiB, about 3.9 KB per write. The same writer on a table with two GSIs grew it from 4.1 to 1,294.1 MiB over 13,996 writes in a 25.6 s backup (96.6 KB per write), 58.7 to 1,277.6 MiB over 13,953 writes in a second run on a WAL already grown (91.6 KB), and 4.1 to 1,261.8 MiB over 13,891 writes in 26.0 s in the run on this tree (94.9 KB, load 3.7); a fourth run on this tree grew it from 4.2 to 1,117.7 MiB over 14,212 writes in 25.9 s (82.2 KB, load 3.9). The reproductions of the other two on this tree: a 28.1 s PostgreSQL backup beside 12,623 overwrites grew the table from 0.49 to 4.91 MiB, "3952 are dead but not yet removable" 10 s in; a 22.5 s SQLite backup beside 15,267 writes grew the-walfile from 4.1 to 63.0 MiB. With no backup the writers left it at 4.0 and 4.1 MiB, and the file kept its size after the backup and after the server stopped. The manual's sizing rule names the indexes: about one 4 KiB page per write for a table with no index and about 24 for a table with two GSIs (20 to 24 over the four runs), since an asynchronous GSI write goes throughgsi_pendingand is applied in later transactions.Binary checks of the manual's process-level statements (release builds of this tree, SQLite and PostgreSQL unless named):
0775parent: startup error, "has mode 0775 and is writable by group or others without the sticky bit"; a0755root: "chmod it to 0700 (on a filesystem without Unix permissions, mount it with a mask that yields 0700)"; a0700root under a1777parent starts healthy; an absent configured root stops startup, "backup root is not usable: No such file or directory (os error 2)".CREATINGfrom both requests,AVAILABLE,ACTIVE, the count equal;backup listbeside a running server. An off-host copy with0755/0644modes is refused bybackup sync(exit 1, "has mode 0755 and is accessible to other users; chmod it to 0700"), a restore of such a backup fails, and only a copy that is the newest backup makes the next check mark the store unhealthy; with the modes kept, or afterchmod -R u=rwX,go=and sync again, the copy adopts and restores.[backup.store]configured explicitly: the database removed andinitrun again;/healthreports"reason":"owned by another catalog",degraded;DescribeBackupisBackupNotFoundException; plainbackup syncexits 1;--adopt-storeadopts (its output quoted in the manual), the store is healthy and the backup restores. Plainsyncon an owned root reports the prefix as having a row.[backup.store]section): the server logs "the default root ... failed its check (extenddb-store.json names catalog ; this catalog is ); the catalog has no backup rows for it, so the store is unconfigured and CreateBackup writes in-catalog backups",/healthis{"components":{},"status":"healthy"}, and aCreateBackupanswersAVAILABLEwith the in-catalog warning. The account re-created withmanage create-account --account-id 123456789012, its user and policy, and the old key pair imported withmanage import-access-key ... --yes: the old key pair signs requests again.--adopt-storebeside the running server exits 6 ("another extenddb process is already using ..."); with it stopped, plain sync exits 1 and--adopt-storeexits 0; restarted,backup_storeis healthy, the old backup and the in-catalog one areAVAILABLE, and the old one restores with 300 of 300 items.DescribeBackupAVAILABLEin the new catalog once its account exists, the restore,backup listrefused with exit 1. On thesqlite-memory,dev-modebuild: a backup taken in the first start's default account (995720651603); the second start's default account was 823675310910,ListBackupslisted nothing andDescribeBackupansweredAccessDeniedExceptionafter the line "startup sync of the in-memory catalog: 1 backup(s) adopted"; aftermanage create-account --account-id 995720651603, a user, a policy and a key,DescribeBackupansweredAVAILABLEand the restore reachedACTIVEwith 300 of 300 items.kind = "disabled": the in-catalog create answersAVAILABLEand logs the deprecation warning; with the store back,ListBackupslists both kinds, the legacy backup restores, the restored table backs up to the store,backup listlists only store rows;backup migrate-legacyis an unrecognized subcommand.<prefix><account>/<table>/<id>/besideextenddb-store.json, a restore throughbackup-spool/beside the database file.backup syncbeside a running SQLite server exits 6 ("another extenddb process is already using ...").FileSource.Paththrough a symbolic link (/hometo/local/homeon this host) is refused, "Path must resolve under one of the configured allowed paths", whether the[import] pathsentry is written through the link or not; the canonical path imports with either entry. Export writes one uncompressed{"Item": ...}file under the account's directory, atFilePath, or atS3PrefixwhenFilePathis absent;S3Bucketalone is refused, "Either FilePath or S3Prefix must be provided".manage create-account --helpnames--account-id;manage import-access-key --helpnames--account-id,--user-name,--access-key-id,--secret-access-keyand--yes;init --helpgives the two new defaults.init --catalog-db <name>_catalogcreates the data database<name>and recordsdata_database_name. The claim session is found byhashtextextended('backup:<backup id>', 4539474)inpg_locks(and a restore's by<account id>/<table>), distinct from theextenddb serveregistration session; with two catalogs on one cluster holding the same key, the query without a database predicate returned both sessions, and withl.databaselimited to the current database (as the manual gives it) only its own. Terminating the claim stopped the job 13 s later at its heartbeat ("stopped with no further store or catalog access"), and withstale_job_after_secs = 45the reconciler failed the row and deleted its partial objects 47 s after the termination. Terminating the registration session as well (a first run that ended every advisory holder) makes the server answerServiceUnavailablewhile it registers again, which the manual warns against.extenddb serveregistration session during a backup failed that backup at the registration's next renewal ("FAILED (shutdown); its objects were deleted"), and the server registered again.backup sync --prune-missingthen waited 3 min 16 s on that backup ("the FAILED backup ... has a live owner (heartbeat within stale_job_after_secs)"), aspurgewaits.ResourceNotFoundExceptionuntil deleted again; a table created after it was absent, and creating it again gave an empty table; sync then listed the backups taken since.umask 077(files 0600; the catalog dump carriesencryption_keyanddata_database_connection_string);psql -fover the existing databases printed 110 and 33ERRORlines, exited 0 and restored nothing (-v ON_ERROR_STOP=1: exit 3); into databases created with plaincreatedb(ownerpostgres) the restore succeeded andCreateTablefailed, "permission denied for schema public"; into databases created withcreatedb -O extenddbit succeeded with 0 errors, the table and its GSI held the dump's 50 items, a table created after the dump was gone, andCreateTableandPutItemof a new table worked.pg_dump15.18 refused, "aborting because of server version mismatch", exit 1, leaving a 0-byte file; the container'spg_dumpunderumask 077wrote both files (exit 0, 41,258 and 18,413 bytes, 0600); after a table created since, the old databases renamed aside withALTER DATABASE ... RENAME TO ..._before_restore,createdb -O extenddb,psql -v ON_ERROR_STOP=1 -f(exit 0, noERRORline), the restored server listed the dump's table only, scanned its 50 items and its GSI's, andCreateTableandPutItemworked; thenDROP DATABASEof the two old ones. On a second, empty container:createdb -O extenddbfailed,role "extenddb" does not exist, untilCREATE USER extenddb WITH PASSWORDwith the dump's password; an empty file restored with exit 0 and the server then refused, "the catalog ... is not initialized"; after the restore the server, whose catalog'sdata_database_connection_stringstill named the first container, put a new table's items into the first container's data database while it ran and failed to start once it was removed ("connect to the data database extenddb: pool timed out"); with the row set to the new address byUPDATE settingsit served the restored table andCreateTableandPutItemlanded on the new server, andverifypassed. Both containers removed with-v.curl --netrc-file ... --data @key.jsontoPOST /management/accounts/<id>/users/<user>/access-keys/import: 201 "Access key imported", and the pair signs requests.initbeside an existingextenddb.tomlexits 255, "Config file "extenddb.toml" already exists. Use --overwrite to delete and regenerate it."; with--overwriteit prints "Loading defaults from extenddb.toml".versionprints a fifth line,test-hooks disabled.backup syncand the other backup commands exit withserve's code on a catalog it refuses: 3 behind, 4 ahead with a breaking migration (both observed on this tree's sources), 5 needing attention (CatalogStatus::exit_code).FileSource.Pathnaming a file that is itself a symbolic link:ValidationException"Symbolic links are not allowed in import/export paths".DescribeBackupof an on-demand table reports each GSI's throughput as 0 and 0, which botocore's parameter validation refuses inTableCreationParameters./health"reason":"manifest probe failed",CreateBackupand the restore of an older backup refused, plainsyncand--prune-missingexit 1 ("sync changes no row while the store is unhealthy"), still unhealthy after a restart. The manual says this release has no command that clears it without a copy of the prefix, and to take the catalog dump after deletions.Other documentation. The admin guide's Data Recovery entry named the PostgreSQL data database
extenddb_catalog_data;initderives it from the catalog's name without_catalog(extenddbby default) and records it insettingsasdata_database_name. The entry now has a table of each backend's databases, says that a catalog name without the suffix needs--data-db, and treats the two PostgreSQL databases as one copy: bothpg_dumps with the server stopped (each takes its own snapshot of one database, so two dumps beside a running server disagree when a table is created or deleted between them), or a base backup of the PostgreSQL server holding both, withumask 077before the dumps, since the catalog dump holds the encryption key and the application role's password. The restore goes into new, empty databases created withcreatedb -O extenddbby the admin role andpsql -v ON_ERROR_STOP=1 -f: a restore over the existing databases restores nothing, and one into databases the admin role owns leaves the server unable to create tables. The old databases are renamed aside (ALTER DATABASE <name> RENAME TO <name>_before_restore) and dropped only once the restored server has read every table; the dumps use thepg_dumpof the server's major version and go on only when both exited 0 and are not empty; on a new PostgreSQL server the application role is created first with the password the catalog dump'sdata_database_connection_stringcarries, and at another address that row is set to the new address before the server starts. The SQLite row says how to restore: stop the server, remove the current-waland-shm, and put the copied files in place. The security model's block and the deployment guide's PostgreSQL checklist say the same (the security model now says the entry restores the dumps, and a base backup, which covers both databases only when they are on one server, is restored with PostgreSQL's own procedure), the upgrade manual's threepg_dumplines takeumask 077and its rollback restores both databases and points at the manual for a catalog restored alone, and the manual's catalog-loss section restores the pair and says what a catalog database restored alone gives. The same wrong name is corrected in the architecture, design and security-model guides; the quickstart's--data-dbrow gave<catalog>_dataand now gives the derived name, and its--pg-userand--pg-passwordrows (extenddb,extenddb-local-dev) are--pg-user(the admin role,$USERthenpostgres),--pg-pass,--extenddb-userand--extenddb-passwith their defaults (bootstrapper.rs), with its sample lines at migration 7;AGENTS.md, the troubleshooting guide's and theextenddbskill'spg_stat_activityquery, the technical-debt register and the storage-extension guide named the PostgreSQL data databaseextenddb_data, MongoDB's name, and now sayextenddb(the last two also named the catalogextenddb, nowextenddb_catalog); the design guide's settings key wasdata_database_urland isdata_database_connection_string(anddata_database_name);init --helpsaid "Data database name (default: extenddb)" and "Catalog database name (default: _catalog)", and now says "the catalog's name without its_catalogsuffix" and "extenddb_catalog, or the config file's" (bootstrapper.rs: the catalog never derives from--data-db).git grep '<catalog>_data\|<data-db>_catalog\|data_database_url'finds nothing. The admin guide's[backup]section and the upgrade manual's backup paragraph point at the manual; the upgrade manual's shared-root and re-created-catalog sentences move to the manual, which states both. The admin guide'smax_concurrent_jobsrow says that on SQLite a restore of items of more than a few hundred bytes, of any attribute types, holds more than its page-cache term (its write batch and decode chunks) and points at the manual's figures, and its backup commands' paragraph gives the exit codes 3, 4 and 5 on a catalogservewould refuse. The deployment guide's PostgreSQL checklist has "Configure[backup.store](S3 for production) per the Backup and Restore manual", its overview says where table backups go on each backend, and its backup mentions and service-unit comment point at the manual (the comment keyed to SQLite restores of items larger than a few hundred bytes). The differences document's restore row gives the 1M Cassandra (49.0 s) and MongoDB (33.8 s, which another run may exceed by up to the backfill worker's 5 s cycle) restores with a pointer to the PR 11 record. ADR-0019 and the Cassandra RFC's "Backup and restore" section are superseded in part, since CreateBackup on Cassandra still writes their in-catalog format, before it returns.docs/adr/README.mdallows no edit of an accepted ADR but its Status, so ADR-0019 changes only its Status line (and its index row), "Superseded in part by the backup and restore design (section 10)", and design section 10's closing paragraph states what that covers: what still holds, what changed, and that the reconciler now removes an in-catalog backup whose CreateBackup did not finish, so the ADR's Negative consequence about a future reconciliation worker no longer holds for the create.docs/adr/README.md's process now names that form, "Superseded in part by [document]", for a decision a later design document replaces in part.docs/rfcs/has no such rule, so the RFC's section carries a note at its top pointing at the manual and the design, and its section 4 line on synchronous data movement and its Known Gaps lines on synchronous backup and restore and on restored indexes carry "Superseded in part" notes (the last for a backup that recorded its index definitions, since one an earlier release took may not have).docs/build-docs.pylists the manual as document 19; the full build ran on this host (Python 3.10, WeasyPrint 70.0): 19 HTML, 19 PDF, 0 failed. The README's and AGENTS.md's documentation tables have a row for it, and AGENTS.md's Extending Storage row namesdocs/manuals/12-extending-extenddb-storage.mdin place of a file that does not exist. The quickstart'sversionsample has the binary's fifth line, and the quickstart and theextenddbskill'sinitreference say thatinitrefuses an existingextenddb.tomlwithout--overwrite. The deployment guide's overview sentence is reworded to parse.Design (
docs/design/15-backup-restore.md). The testing summary's "still to be measured" sentence now states the measured cells, what the vector restore's memory belongs to and the vector index's share of the restore's duration (earlier revision items keep their wording); section 7's memory paragraph says that secondary indexes add only the page cache, what a SQLite restore of items larger than a few hundred bytes holds (the write batch and decode chunks, growing with the item's size up to the batch bound whatever the attribute types), and, in a sentence of its own, what a PostgreSQL vector index adds (15-backup-restore-bench-pr11.md); the PR 11 section's line says what shipped; section 10's row says "superseded in part" and its closing paragraph says what the ADR's Status and the RFC's notes cover and what the two manuals now carry; revision item 35 records the filter history, the measurements, the manual's place and checks, and the database-name correction, and names its builds as versions of this change's sources (04c2915 has this commit's Rust sources; 30553b3's differ only in the twoinithelp lines), since neither hash stays in the history. The design's revision items 32 to 34 already exist, so this PR's item is 35. The bench record's Durations section gives the one-arena SQLite backup (222 s) and restore (283 s) separately.Cross-references with #737
#737 (open, another author's) touches four files this PR also edits:
05-admin-guide.md,07-upgrade-manual.md,10-security-model.mdand11-deployment-guide.md. It is not on this branch and nothing it adds is edited here. The overlaps:pg_dumplines with the server stopped or a base backup). The manual links to that entry.[backup.store]line directly below it.CreateBackupstores each table backup in the deployment's own database, in the catalog'sbackupsandbackup_itemstables", which is false of this series once a backup store is configured.Whichever of the two lands second reconciles these: the entry's anchor and body, the security model's block, the checklist's lines, its CreateBackup sentence, and the manual's links.
Verification
cargo fmt --all -- --check: clean.cargo build -p extenddb-appandcargo clippy -p extenddb-app --all-targets --no-deps -- -D warnings: clean. No test or snapshot holds theinit --helptext.cargo test -p extenddb-storage-postgres --test doc_version_literals: 3 passed (the manual carries no binary version literal; the record's service versions are not flagged).integration.yml: only the pre-existing custom runner-label findings.docs/build-docs.pyon an exported copy of the tree (Python 3.10, WeasyPrint 70.0): 19 HTML, 19 PDF, 0 failed; the manual's HTML has one heading per Markdown heading and none made from a wrapped line (no line of the changed manuals or design documents begins with#and a digit).git grep '<catalog>_data\|<data-db>_catalog\|data_database_url': no hit;extenddb_catalog_dataonly in revision item 35, as history;extenddb_datain the documentation only for MongoDB; and nolists of numbersas the restore memory's cause,16%,five runsfor the spread,14.7 sas the scan's bound,0.2 to 0.7,340 bytes,within 9 MiB,27 to 41,35 to 38ordropdbin the documentation.Not here
migrate-legacy(Backup/restore: backup migrate-legacy #821), the store-maintenance options (Backup/restore: backup sync --move-store, --forget-store, --forget-backup #820), export and import through the store (Backup/restore: export and import through the store #748).RestoreTableFromBackupof a backup whose prefix has loose modes answersInternalServerError(HTTP 500), with the log line naming the directory's mode; the base's existing answer, not this diff's; the manual says such a restore is refused withInternalServerErrorand that the server log names the directory.init --catalog-dbwith a name that lacks the_catalogsuffix derives the catalog's own name for the data database, creates the database once, fails on the second create with "Run 'destroy' first", and leaves an empty database behind with no config file fordestroyto read. An existinginitdefect; the admin guide now says such a name needs--data-db.ACTIVEonly at the worker's next 5 s cycle; waking it at the load's end is a possible later change.AVAILABLEbackup was deleted after the dump keeps its store unhealthy (every store operation refused, across restarts), and this release has no remedy without a copy of that backup's prefix:--prune-missingrefuses while the store is unhealthy. The manual states it and says to take the dump after deletions.