Skip to content

docs: state the 1.0 scope, the backend tiers and the versioning policy - #897

Open
LeeroyHannigan wants to merge 3 commits into
mainfrom
docs/662-scope-tiers-versioning
Open

LeeroyHannigan wants to merge 3 commits into
mainfrom
docs/662-scope-tiers-versioning

Conversation

@LeeroyHannigan

@LeeroyHannigan LeeroyHannigan commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

What

  • README.md gets a Scope section after Use Cases and a Storage backends section before Quick Start. The Storage backends section defines three tiers and puts each backend in one. Its table gives the topology, upgrade path, API coverage, CI job and known limits of each backend.
  • The new docs/versioning.md says what each kind of release can change and what stays compatible within a major version. It also gives the catalog upgrade rules, what the policy does not cover, the support periods and the deprecation steps. The README documentation table links to it.
  • The comment in crates/bin/src/main.rs now describes sqlite as a production backend on a single node. The dev-mode comments in main.rs, crates/bin/Cargo.toml and crates/app/Cargo.toml now say that dev-mode compiles only with sqlite, and that a production sqlite build leaves dev-mode off. The text of the dev-mode compile error changes to match, and its condition stays the same.
  • docs/manuals/07-upgrade-manual.md now says that a later major release refuses CreateBackup without a backup store. Under the new policy, only a major release removes a deprecated item.

Each decision in the new text has its own line here, so a reviewer can object to each one separately. The list omits text that only describes current behavior, such as the catalog statuses and their exit codes.

Scope and tiers:

  • ExtendDB 1.0 is for local development, CI and self-hosted deployments, with one ExtendDB server for each catalog.
  • Stable (PostgreSQL, and SQLite on a single node): for production. The versioning policy applies in full, and a pull request merges only after the required CI jobs pass.
  • SQLite is stable with no production image, so an operator builds it from source.
  • Beta (MongoDB): for production within the known limits in the table. To remove a known limit, a minor release can change the listed behavior, and its release notes name the change.
  • Experimental (Cassandra): for evaluation. The versioning policy does not apply, and a release can change the backend's behavior or configuration with no deprecation period.

Releases:

  • The 2.0 release upgrades the catalog of any 1.x release.
  • A minor release can carry a breaking catalog migration, and its release notes then say so.
  • A minor release can raise the minimum Rust version for a build from source.
  • A patch release adds no configuration key, no breaking catalog migration and no new backup format version.
  • A change that makes ExtendDB answer a request as DynamoDB does ships in a minor release. It ships in a patch release only when it closes a security issue.

Compatibility within a major version:

  • An operation, request member or response member that a release implements stays in the later releases.
  • The keys of extenddb.toml, their EXTENDDB__ variables and the runtime settings keep their names and meanings.
  • A configuration key that a minor release adds has a default that keeps the earlier behavior.
  • The subcommands, flags and exit codes of the CLI stay, and the text of messages and log lines can change in any release.
  • The management API that extenddb manage calls, and the options and return values of start() in @extenddb/dev, stay compatible.
  • The HTTP status code of a /health response and its status field stay compatible.
  • A minor release ends support for a database version only after the version's upstream end of life. The minor release before it announces the change.
  • A release reads each backup format version that an earlier release wrote, and refuses a later one.
  • The policy does not cover the Rust crate APIs (the Storage trait among them) or preview settings such as enable_multipart_keys.
  • The policy does not cover the fields of a /metrics response or the experimental backends.

Support:

  • The newest minor release gets bug fixes and security fixes.
  • A fix for an earlier minor release ships in a patch release of its own, and the latest image tag stays on the newest release.
  • The minor release before the newest one gets security fixes for 3 months after the newest one ships.
  • After 2.0 ships, the last 1.x minor release gets security fixes for 12 months.
  • The 0.1.x releases get no fixes after 1.0 ships.
  • Support covers the stable and beta backends. An experimental backend gets fixes in the newest minor release only.

Deprecation:

  • The release notes of the minor release that deprecates an item announce it and name what replaces it.
  • For an item deprecated from 1.0 on, ExtendDB logs or prints a warning each time a request, a command or the configuration uses it.
  • A major release removes a deprecated item, no earlier than 6 months after the release that announced it.
  • The deprecation steps also apply when a release removes a stable or beta backend, or moves it to a lower tier.
  • A security issue can need a faster change, and its release notes then give the reason.

This change does not edit the following text on main, which disagrees with it:

  • docs/manuals/07-upgrade-manual.md says that MongoDB deployments track their own catalog version. Its first section says that a migration ledger versions all four catalogs.
  • docs/adr/0023-catalog-migrations.md counts five v1.0 migrations. The migration registry lists nine.
  • docs/cassandra-setup.md describes more than one ExtendDB instance on one Cassandra cluster.
  • docs/manuals/11-deployment-guide.md says that more than one ExtendDB instance can share one backend catalog.
  • docs/getting-started.md names its SQLite section "SQLite backend (developer mode)" and describes SQLite as a backend for local development and CI.
  • docs/rfcs/0002-backend-plugin-policy.md says that all crates follow semantic versioning. The new policy does not cover the crate APIs.
  • The TLS section of README.md says that TLS is mandatory. The new text says that dev mode uses plain HTTP.
  • The Running in Containers section of README.md lists the Cassandra image among the production images. The new text says that the experimental tier is not for production.

Known issues:

  • The stable tier keeps PostgreSQL 14 as the minimum, as the README states it today. CI tests PostgreSQL 16 only, and PostgreSQL 14 reaches its upstream end of life on 2026-11-12. Issue docs: the PostgreSQL minimum says 14, and CI tests 16 only #898 decides between a CI job on 14 and a higher minimum in every file that states it.

Why

Issue #662 asks for three texts before 1.0: a scope statement, backend tiers in the README, and a versioning, compatibility, support and deprecation policy. An operator who chooses a backend reads the README first, so the scope and the tiers go there. The policy has six sections, so it gets a file of its own.

Closes #662

Testing done

  • cargo test --locked -p extenddb-storage-postgres --test doc_version_literals passes. The test fails when a three-part version after the word ExtendDB in README.md or docs/ is not the compiled version.
  • cargo fmt --all -- --check passes.
  • Each link that this change adds resolves, anchors included.

Checklist

  • I have read CONTRIBUTING.md
  • All tests pass (cargo test --workspace)
  • Code is formatted (cargo fmt --check)
  • Clippy is clean (cargo clippy -- -W clippy::pedantic)
  • I have added or updated tests for new functionality
  • I have updated documentation if behavior changed
  • Breaking changes are noted below (if any)
  • If this changes the wire protocol, Storage trait, auth model, on-disk
    format, 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.

The README gets a Scope section and a Storage backends section. The
second section gives each backend a tier: PostgreSQL stable, SQLite
stable on a single node, MongoDB beta and Cassandra experimental.

The new docs/versioning.md says what each kind of release can change
and what stays compatible within a major version. It also gives the
support periods and the deprecation steps.

The comment in crates/bin/src/main.rs now describes SQLite as a
production backend on a single node. The dev-mode comments in main.rs
and in the Cargo.toml files of crates/bin and crates/app say that
dev-mode compiles only with SQLite. The upgrade manual now says that a
later major release refuses CreateBackup without a backup store.

Refs #662

Signed-off-by: Lee Hannigan <29015211+LeeroyHannigan@users.noreply.github.com>
This resolves the conflict in the upgrade manual. #474 added the
migration 9 paragraph, and #740 rewrote the backup store paragraph
after it. The merge keeps both paragraphs as main has them. In the
backup store paragraph, it applies the one change of this branch: a
later major release refuses CreateBackup without a store.

Signed-off-by: Lee Hannigan <29015211+LeeroyHannigan@users.noreply.github.com>
Main requires the cassandra-integration check. That job needs the
Cassandra catalog checks job, which runs the checks of migrated
catalogs. The Cassandra row of the README table said that neither
check is required. The row now names cassandra-integration as
required and drops the sentence about the catalog checks.

The row also said that Cassandra has no backup store. A Cassandra
deployment can configure a store, and a restore can read a backup
from it. CreateBackup on Cassandra still keeps each backup in the
catalog, and the row now says that.

Signed-off-by: Lee Hannigan <29015211+LeeroyHannigan@users.noreply.github.com>
Comment thread crates/bin/Cargo.toml
@@ -26,9 +26,10 @@ mongodb-test-hooks = [
"extenddb-app/mongodb-test-hooks",
]
# Developer mode: plain HTTP on loopback, open authorization (SigV4 still

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere it might help to clarify this with "SigV4 authentication" versus just "SigV4".

Migration 9, `warm_throughput`, adds a nullable `warm_throughput` column to the `tables` and `indexes` catalog tables on PostgreSQL, SQLite and Cassandra. It holds the `WarmThroughput` a client sets on a table or a global secondary index, which `DescribeTable` reports; nothing acts on the value (see [Differences from DynamoDB](../differences-from-dynamodb.md)). Tables created before the migration report the defaults for their billing mode. On MongoDB it changes nothing, since the catalog collections have no validator, and the ledger records it as not applicable. Migration 9 is breaking: to go back to a release without it, restore the backup taken before the upgrade (see [Rollback Procedure](#rollback-procedure)).

Configure `[backup.store]` before the release that stops creating in-catalog backups. From this release CreateBackup writes to the configured store on PostgreSQL, SQLite and MongoDB, and is asynchronous there as on the service: the response is `CREATING` and the backup becomes `AVAILABLE` once the upload ends, so a client that deletes a backup it just created has to poll DescribeBackup first (the previous release returned `AVAILABLE`). A store-backed backup is listed, described, deleted and restored like any other. `RestoreTableFromBackup` of a backup in the configured store runs the restore job from the store on every backend (PostgreSQL, SQLite, MongoDB and Cassandra); in-catalog backups, those of the previous release included, restore as before. An S3 store needs `spool_path` set (the restore's download area) on PostgreSQL, MongoDB and Cassandra, and that must be a directory of its own, not the database directory; a filesystem store needs no spool. Migration 8 adds the `restore_progress` table to the PostgreSQL data database; run `extenddb migrate --yes` before starting the new binary. The progress rows of a restore in flight live in the data database, so a data database restored from a dump taken before migration 8 has none of them and no `restore_progress` table: the new binary refuses to serve it until `extenddb migrate --yes` has run again, and its reconciler then finds no progress for the restore and resumes it from its first data file into the (empty) target, or `FAILED`s it as `target_not_empty` if the target already holds items, in which case `extenddb backup remove-target --account <account> <table>` (the admin guide's `backup` section) removes the target and its record and the backup restores again. The same `target_not_empty` state is what a server stopped inside a restore's first data file on MongoDB, or inside its first batch of 10,000 items or 32 MiB on Cassandra, leaves on restart, since both record progress after the write; the command is the remedy there too. `backup remove-target` writes the catalog, so on SQLite the server is stopped for it, as for `purge`. The previous binary resumes no restore (a store-backed restore in flight at a rollback is lost with the catalog rows, as the rollback paragraph below says). Quiesce restores before a downgrade. `kind = "disabled"` is no longer needed to keep a deployment able to restore the backups it takes. Without a store CreateBackup writes the in-catalog format and logs a deprecation warning naming `[backup.store]` on each call; a later release's CreateBackup refuses without one. Cassandra writes the in-catalog format with or without a store. `kind = "disabled"` under `[backup.store]` is the opt-out that keeps a deployment on the in-catalog format on purpose and silences the warning. On file-backed SQLite an absent section is a filesystem store at `backups/` beside the database file, created `0700` at startup; that root must be owned by the extenddb user with mode `0700`, under directories owned by root or that user which others cannot write to. A default root that breaks the rule (a database file in a group-writable directory, say), or whose `extenddb-store.json` names another catalog (a catalog re-created by `destroy` and `init` under the old root), is logged at startup as a warning (`backup store: the default root ... failed its check`) and leaves the store unconfigured, not unhealthy, so the upgrade never stops a server that never configured backups and CreateBackup keeps working on it; the same root configured explicitly is a startup error or an unhealthy store. The warning applies only while the catalog has no backup rows for the default root (an upgrade, a fresh deployment, a catalog re-created by `destroy` and `init`): once a server has written rows for it, the default root is handled as a configured one, so its backups never vanish behind a warning (a failed identity check is an unhealthy store, reported on `/health`; a root that can no longer be opened is a startup error naming it). Instances that share a catalog must share one store root, an S3 bucket and prefix or a shared mount: a second instance pointed at a host-local directory under the same configured path starts with the store unhealthy (`/health` reports `backup_store` unhealthy; the admin guide's `[backup.store]` section says what the identity file and the manifest probe check). `extenddb destroy` leaves the store; `extenddb backup purge --confirm` is what deletes its backups, and a catalog re-created with `init` needs the old root's `extenddb-store.json` removed or a new root, since the file names the old catalog.
Configure `[backup.store]` before the release that stops creating in-catalog backups. From this release CreateBackup writes to the configured store on PostgreSQL, SQLite and MongoDB, and is asynchronous there as on the service: the response is `CREATING` and the backup becomes `AVAILABLE` once the upload ends, so a client that deletes a backup it just created has to poll DescribeBackup first (the previous release returned `AVAILABLE`). A store-backed backup is listed, described, deleted and restored like any other. `RestoreTableFromBackup` of a backup in the configured store runs the restore job from the store on every backend (PostgreSQL, SQLite, MongoDB and Cassandra); in-catalog backups, those of the previous release included, restore as before. An S3 store needs `spool_path` set (the restore's download area) on PostgreSQL, MongoDB and Cassandra, and that must be a directory of its own, not the database directory; a filesystem store needs no spool. Migration 8 adds the `restore_progress` table to the PostgreSQL data database; run `extenddb migrate --yes` before starting the new binary. The progress rows of a restore in flight live in the data database, so a data database restored from a dump taken before migration 8 has none of them and no `restore_progress` table: the new binary refuses to serve it until `extenddb migrate --yes` has run again, and its reconciler then finds no progress for the restore and resumes it from its first data file into the (empty) target, or `FAILED`s it as `target_not_empty` if the target already holds items, in which case `extenddb backup remove-target --account <account> <table>` (the admin guide's `backup` section) removes the target and its record and the backup restores again. The same `target_not_empty` state is what a server stopped inside a restore's first data file on MongoDB, or inside its first batch of 10,000 items or 32 MiB on Cassandra, leaves on restart, since both record progress after the write; the command is the remedy there too. `backup remove-target` writes the catalog, so on SQLite the server is stopped for it, as for `purge`. The previous binary resumes no restore (a store-backed restore in flight at a rollback is lost with the catalog rows, as the rollback paragraph below says). Quiesce restores before a downgrade. `kind = "disabled"` is no longer needed to keep a deployment able to restore the backups it takes. Without a store CreateBackup writes the in-catalog format and logs a deprecation warning naming `[backup.store]` on each call. A later major release's CreateBackup refuses without one. Cassandra writes the in-catalog format with or without a store. `kind = "disabled"` under `[backup.store]` is the opt-out that keeps a deployment on the in-catalog format on purpose and silences the warning. On file-backed SQLite an absent section is a filesystem store at `backups/` beside the database file, created `0700` at startup; that root must be owned by the extenddb user with mode `0700`, under directories owned by root or that user which others cannot write to. A default root that breaks the rule (a database file in a group-writable directory, say), or whose `extenddb-store.json` names another catalog (a catalog re-created by `destroy` and `init` under the old root), is logged at startup as a warning (`backup store: the default root ... failed its check`) and leaves the store unconfigured, not unhealthy, so the upgrade never stops a server that never configured backups and CreateBackup keeps working on it; the same root configured explicitly is a startup error or an unhealthy store. The warning applies only while the catalog has no backup rows for the default root (an upgrade, a fresh deployment, a catalog re-created by `destroy` and `init`): once a server has written rows for it, the default root is handled as a configured one, so its backups never vanish behind a warning (a failed identity check is an unhealthy store, reported on `/health`; a root that can no longer be opened is a startup error naming it). Instances that share a catalog must share one store root, an S3 bucket and prefix or a shared mount: a second instance pointed at a host-local directory under the same configured path starts with the store unhealthy (`/health` reports `backup_store` unhealthy; the admin guide's `[backup.store]` section says what the identity file and the manifest probe check). `extenddb destroy` leaves the store; `extenddb backup purge --confirm` is what deletes its backups, and a catalog re-created with `init` needs the old root's `extenddb-store.json` removed or a new root, since the file names the old catalog.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph could use some luv, especially if we expect humans to read it. It's a wall of text, has run-on sentences (at least one is paragraph-long in itself), contains a fair amount of implementation detail which may not help someone just trying to figure out the commands to run and what they do, and extraneous AI detail as in "a root that can no longer be opened is a startup error naming it" (just stop at "error").

Comment thread README.md

## Storage backends

An ExtendDB binary contains one storage backend, which the build selects. The tier of a backend says what the 1.x releases promise for it:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reluctant to introduce tiers unless we have a process/policy for how a backend moves from one tier to another. I also thought that last week we'd converged on "a backend is production (or stable) if the ExtendDB CI is reliably green for it".

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-blocker Must ship for its milestone's release v1 Needed for the 1.0 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Launch-prep: scope statement + backend tiers + version policy

2 participants