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.dumpA 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_restoredpg_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.
-
Dump both schemas from the source, as above.
-
Restore into a fresh database on the target cluster with
--no-owner --no-privileges. -
Start one broker against it and read the
config: postgresboot line to confirm it is talking to the clone and not to production. -
Review the clone’s mesh configuration before it serves anything. Do not leave
QUEEN_MESH_PEERSpointing 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. -
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/seekrather 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.