Skip to content

Backup and restore

Dump both schemas with pg_dump, restore with pg_restore, and know what a restore does to consumer cursors and in-flight leases.

Updated View as Markdown

Queen has no backup feature. The broker’s built-in migration surface was removed, and the migrate subcommand that remains only reports that offline migration is unsupported and exits non-zero. Backup is therefore a PostgreSQL operation, done with the standard tools, and there is nothing broker-specific to install.

What makes it simple is that the broker is stateless. Everything it persists lives in two schemas (queen and queen_streams) plus the node-local push spool, which no database backup can capture. No table lives in public, and no extension is required: the schema needs CREATE SCHEMA and nothing else.

Dumping both schemas

One command, two -n flags, custom format so the restore can be parallel and selective:

pg_dump -Fc -n queen -n queen_streams -f queen-2026-07-30.dump "postgresql://user@host:5432/queen"

Omitting queen_streams is the most common mistake. It holds every registered streaming query and all per-key operator state; without it a restored deployment loses its stream definitions and every aggregate they had accumulated, and the loss is silent: the broker boots happily and recreates an empty schema.

To check what you captured before you trust it:

pg_restore -l queen-2026-07-30.dump

A dump can be taken against a running broker. pg_dump works from a single transaction snapshot, so the result is internally consistent: a consistent picture of a moving system, which is what the next section is about.

Schema-only and data-only dumps are both viable, but note that the broker re-applies its full DDL at every boot under an advisory lock, and every statement is idempotent. A schema-only dump is therefore redundant for recovery: the binary carries the schema. Dump the data and let the broker own the shape.

Restoring

Restore into an empty database, with the broker stopped.

createdb -h host -U postgres queen_restored
pg_restore --no-owner --no-privileges -d "postgresql://postgres@host:5432/queen_restored" queen-2026-07-30.dump

--no-owner and --no-privileges are deliberate: the boot-time schema applier re-issues the GRANT statements the broker needs, so you do not have to reproduce the source cluster’s roles. Start a broker against the restored database with QUEEN_APPLY_SCHEMA at its default of true and its idempotent re-apply verifies the restored schema and re-asserts the per-table parameters.

Restore into the same broker version that produced the dump. The deployment model is always-virgin: the boot DDL only creates, it never reshapes existing tables, so a restored older-version schema is exactly the “database built by a different version” case that is unsupported. Moving data across versions is a cutover, not a restore; see Upgrades.

What a restore does to consumers

A restore rolls the entire consumption state back to the dump instant, and the consequences are mechanical rather than mysterious.

Cursors go backwards, so messages are redelivered. Consumption state is one committed offset per (partition, consumer group). Restoring sets it back to its dump-time value, so every message acked between the dump and the restore is delivered again. The volume of duplicate delivery is proportional to the age of the dump: an hour-old dump means an hour of reprocessing. Consumers must be idempotent for this to be a recovery rather than an incident.

Leases come back expired, and nothing is handed back. A lease is a set of columns on the consumer row (worker_id, batch_end, lease_expires_at), not a session. A dump captures whichever leases were open at that instant. After the restore, those timestamps are in the past, the lease predicate treats the row as free, and the next pop re-leases the span from committed. Nothing is stuck.

The one edge to know: if you restore quickly, a captured lease_expires_at may still be in the future, and that (partition, group) is invisible to pops until it passes. Wait it out; the wait is bounded by the queue’s lease time.

Deduplication is restored, partially. queen.log_txns, the hash sidecar the push path probes, is in the dump, so producers replaying messages that were already stored before the dump are still deduplicated inside the window. Hashes written after the dump are gone, so a producer retrying one of those pushes will be accepted as new. Combined with the cursor rollback, plan for both duplicate delivery and, for post-dump traffic, duplicate storage.

Retry budgets and dead letters roll back too. batch_retry_count returns to its dump value, so a batch that had exhausted its retries gets its budget back. queen.log_dlq rows written after the dump are gone.

The maintenance flag is in the dump. Both flags live in queen.system_state, and the broker reads them at boot. A dump taken during a maintenance window restores with push maintenance on, which means every push goes straight to the spool and nothing reaches PostgreSQL. Check GET /api/v1/system/maintenance immediately after any restore.

The spool is not in the dump. Buffered pushes are files under FILE_BUFFER_DIR on whichever node accepted them. A database restore neither captures nor replays them. If you are rebuilding a deployment, drain the spools first. See Surviving a PostgreSQL outage.

Metrics and stats recover on their own. queen.stats is recomputed by the reconciler within one interval, and the metrics tables refill from the collectors. Restoring them is harmless but not required, so a data-only restore that skips queen.system_metrics, queen.worker_metrics and queen.queue_lag_metrics is a legitimate way to make a large dump smaller.

Cloning a deployment

The same two-schema dump is how you build a staging copy from production.

  1. Dump both schemas from the source, as above.

  2. Restore into a fresh database on the target cluster with --no-owner --no-privileges.

  3. Start one broker against it and read the config: postgres boot line to confirm it is talking to the clone and not to production.

  4. Review the clone’s mesh configuration before it serves anything. Do not leave QUEEN_MESH_PEERS pointing at the source deployment’s instances: mesh frames wake parked pops and include a maintenance-mode set, so a clone that joins the original mesh can affect production. The mesh port must be firewalled in both deployments regardless.

  5. Decide what the clone’s consumers should see. The restored cursors are production’s cursors. To replay history in the clone, seek the consumer groups back with POST /api/v1/consumer-groups/:group/queues/:queue/seek rather than editing rows by hand.

A clone is a separate database, so the advisory locks that gate retention and the stats reconciler are separate too; the clone sweeps its own data and cannot interfere with the source’s maintenance.

Physical backups

Nothing about the broker argues against a physical backup: a base backup plus WAL archiving, or your provider’s snapshot and point-in-time recovery. It covers both schemas by construction, which removes the “forgot queen_streams” failure mode entirely, and it is the right choice once a logical dump stops finishing in a sensible window.

The restore semantics above are unchanged: recovering to a point in time rolls consumer cursors back to that point, with exactly the same redelivery consequences. The spool remains outside the picture either way.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close