---
title: "Recovery when a node or a cluster breaks"
description: "Start with a write, find your case, and follow its steps: replace a node, a majority down, a full disk, a majority of disks gone, every node stopped on the same entry."
---

> Queen MQ documentation, for AI agents
> Complete self-contained summary of Queen MQ: https://queenmq.com/llms-brief.txt
> Fetch that first when the question is about the product rather than about this page.
> Index of all pages: https://queenmq.com/llms.txt

# Recovery when a node or a cluster breaks

Most failures need nothing from you. A node that crashes catches up from the leader when it starts again. A leader that gets SIGTERM hands its job to a peer before it stops. A node that was away too long gets a snapshot from the leader, exits with code 75, and loads it when it starts again. This section is for the rest: a lost disk, a lost majority, a full disk, a bad day.

## Start with a write

The question that decides everything is whether the cluster can still commit. Ask it with a write, through two different nodes, because a node that is itself broken can hang while the cluster is fine:

```bash
curl -s -m 10 -X PUT http://10.0.0.11:6632/api/v1/kv/ops/probe \
  -H 'content-type: application/json' -d '{"value":"probe","ttlSeconds":60}'
```

An answer within a second means the cluster commits, and you are looking for one sick node. No answer within ten seconds means it does not. Then ask every node what it thinks, and the leader who the members are:

```bash
for n in 10.0.0.11 10.0.0.12 10.0.0.13; do
  echo "$n $(curl -s -m 2 http://$n:6632/health | jq -c '{status, role: .raft.role,
    term: .raft.term, applied: .raft.applied, commit: .raft.commit}')"
done
curl -s http://10.0.0.11:6632/api/v1/system/raft/membership \
  | jq -c '.membership | {leader, voters, members: [.members[] | {nodeId, live, lag}]}'
```

```json
{"leader":2,"voters":[1,2,3],"members":[{"nodeId":1,"live":true,"lag":0},
{"nodeId":2,"live":true,"lag":0},{"nodeId":3,"live":true,"lag":0}]}
```

On a healthy cluster every node reports the same term, and an `applied` close to the leader's `commit`. A node that will not start says why in its last `FATAL` line, in `docker logs` or `kubectl logs --previous`.

## Rules for a bad day

Each of these comes from a test where breaking it made things worse.

1. **One node at a time.** Change a node, wait until it is healthy, then touch the next.
2. **Remove a node from the members before you wipe its disk.** A node that comes back empty under its old number is never repaired, and requests sent to it hang.
3. **Never put an old copy back on one node of a running cluster.** Its log went backwards, which is the same trap. Replace the node instead.
4. **Never delete files in a data directory to make room.** A missing file is not noticed at start, and the node then serves holes. Grow the disk, or replace the node.
5. **Force a recovery on exactly one node, and only when a majority is gone for good.** On two nodes it makes two clusters.
6. **Keep `QUEEN_ENCRYPTION_KEY` safe and unchanged.** With another key, consumers of encrypted queues get the encrypted envelope as their data, and nothing reports an error.

## Find your case

| What you see | What to do |
| --- | --- |
| A node restarted, and is healthy again | Nothing |
| A node exited once with code 75 | Nothing: it loaded a snapshot |
| A node will not start: a `FATAL` line names a damaged file, or it exits with code 139 and no line | Replace the node |
| A node answers, but its `applied` stays at 0 or far behind | Replace the node, starting with its removal |
| Writes to one node answer `507`, or it is `stopped` with no space left | Grow its disk |
| A node logs `refused this node's QUEEN_RAFT_TOKEN` | Give it the same token, restart it |
| A majority of the nodes is down, their disks intact | Bring them back |
| A majority of the disks is gone for good | Force a recovery on the survivor |
| Every node stopped on the same entry (`APPLY FAILED`) | Skip the entry |
| Every disk is gone | Promote the standby, or restore a backup |

**Figure.** Start with one KV write through two nodes, with a 10-second timeout. If it is answered, the cluster commits and one node is sick: a restart or an exit code 75 needs nothing; a damaged file, an exit 139, or an applied index stuck at zero means replace the node, removing it first; a full disk means grow it; a refused token means give it the same token. If there is no answer, the cluster does not commit: a majority down with disks intact, bring them back; every node stopped on the same entry, skip the entry; a majority of disks gone for good, force a recovery on the survivor; every disk gone, promote the standby or restore a backup.

Start with a write, then find your case.

## Replace a node

This is the procedure you will use most. The node comes back empty and copies everything from the leader, so nothing is lost while the other two commit. Check first that the probe commits through another node.

```text
# 1. Remove node 3 from the members, through a healthy node.
curl -s -X DELETE http://10.0.0.11:6632/api/v1/system/raft/membership/members/3
# 2. On node 3: stop it and move its data away.
docker stop -t 60 queen && docker rm queen
mv /var/lib/queen /var/lib/queen.broken
# 3. Start it on an empty directory: the same docker run, plus -e QUEEN_RAFT_JOIN=true
# 4. Add it back as a learner, then make it a voter.
curl -s -X POST http://10.0.0.11:6632/api/v1/system/raft/membership/learners \
  -H 'content-type: application/json' \
  -d '{"id":3,"raft":"10.0.0.13:7400","http":"10.0.0.13:6632"}'
curl -s -X POST http://10.0.0.11:6632/api/v1/system/raft/membership/promote \
  -H 'content-type: application/json' -d '{"ids":[3]}'
```

Step 1 answers `"ok":true` with the voters left, `[1,2]`. A `409 no_quorum` means too few nodes are live: stop, and look again. At step 3 the empty node answers 503 and logs `this fresh node joins an existing cluster`: `QUEEN_RAFT_JOIN` makes it wait to be added instead of starting a cluster of its own. A learner copies the log but does not vote; if the promotion answers `409 learner_behind`, it is still copying, so try again a few seconds later. It is done when the members list three voters with `lag` 0. On the operator, one annotation does all four steps (pod `queen-2` is node 3): `kubectl -n queen annotate queencluster queen queenmq.com/replace-pod=queen-2`.

**Why:** the order keeps the cluster whole: out of the members first, then the disk, then back in as a learner that votes only once it has caught up. A node wiped while still a member would count in the majority with an empty log.

**Figure.** Replacing node 3 in four steps. The voters start as 1, 2 and 3. Step 1 removes node 3: voters 1 and 2. Step 2 stops node 3 and moves its data away. Step 3 starts it empty with QUEEN_RAFT_JOIN: it answers 503 and waits to be added. Step 4 adds it as a learner, which copies the log but does not vote, and then promotes it: voters 1, 2 and 3 again, with lag 0.

Replacing a node: out of the members, wipe, back in as a learner.

## A majority is down, the disks are fine

Two nodes of three are gone for a while: a lost machine, a zone outage, an image that will not pull. Nothing commits, and the survivor answers 503. Bring the missing nodes back, and do nothing else: do not delete a volume, and do not force a recovery. As soon as two nodes run, they elect a leader. When we stopped two nodes of three and started one of them again, the very next write committed.

## A disk is full

At 85% a node refuses its own clients' writes, but entries accepted by the other nodes keep arriving. At 100% it logs `No space left on device` and stops; the other nodes keep serving. Grow its volume and restart it: it catches up on its own. Then grow the other volumes too, because they hold the same data, and turn on retention for the biggest queues.

## A majority of the disks is gone for good

The majority will never come back, so the cluster can never elect a leader again. `QUEEN_RAFT_FORCE_RECOVER` is the way out. Set on the one survivor, it makes that node the only voter, and it leads alone. The others are rebuilt empty and join it.

1. Restart the survivor with `QUEEN_RAFT_FORCE_RECOVER=1` (its own node number). Its log says `UNSAFE RECOVERY`, and the members list it as the only voter.
2. Restart it once more without the setting.
3. Start the other nodes on empty directories with `QUEEN_RAFT_JOIN=true`, add them as learners and promote them, as in steps 3 and 4 of Replace a node.

The cost is the writes the lost nodes had committed without the survivor: usually nothing, at worst the last moment before the failure. If more than one node survived, pick the one with the highest `commit`, and rebuild the others empty.

## Every node stopped on the same entry

When every node refuses the same entry, it is a bug in Queen, and restarting never helps: each restart stops on it again. Every node shows the way out in `/health`, under `apply.failure`, and in its `APPLY FAILED` log line: the entry's `index` and `digest`. Check that two nodes agree, and that the `class` is `deterministic`. Then set `QUEEN_RAFT_APPLY_SKIP=<index>:<digest>` on every node and restart them all. The entry becomes a no-op: none of its effects apply, and nothing else is lost. Keep the log line for the bug report.

## Backups

Three nodes protect you from losing a machine, not from a mistake: a deleted queue reaches every node as faithfully as a good write. A backup is the copy that lives outside the cluster. A node keeps everything it knows in one directory, and one copy of one node is enough to rebuild a whole cluster.

The copy must be taken at one instant, like the state a power cut leaves behind, which Queen survives. Pause the container, copy, and resume. On a cluster, pause a follower: the other two keep committing without it.

```bash
docker pause queen
tar -C /var/lib/queen -cf /backup/queen-$(date -u +%Y%m%dT%H%MZ).tar .
docker unpause queen
```

A snapshot of the volume (a cloud disk snapshot, LVM, ZFS) is also taken at one instant, and works while the node runs. A plain `cp` or `rsync` of a running node does not: its files are copied at different moments.

To restore a single node, stop it, unpack the copy into an empty directory, and start it. To restore a whole cluster, unpack the copy as node 1's directory, start node 1 alone with `QUEEN_RAFT_FORCE_RECOVER=1`, restart it without, and let the others join it empty, as above. We did exactly that: every message pushed before the copy was back, and the one pushed after it was not. If the copy comes from a node that was force-recovered once, move `raft/force_recovered.json` out of the restored directory first, or node 1 refuses a second recovery.

**Figure.** Backup: pause a follower, copy its data directory with tar, resume it; the other two nodes keep committing. Restore after every disk is lost: unpack the copy as node 1's directory, start node 1 alone with QUEEN_RAFT_FORCE_RECOVER=1, restart it without, then start nodes 2 and 3 empty, add them as learners and promote them. The cluster comes back at the moment of the copy: a message pushed after it is gone, consumers repeat what they acked after it.

One copy of one node rebuilds the whole cluster.

**Why:** a restore goes back to the moment of the copy, consumers included: what they acked after it is delivered again. Producers can push what was lost again, and deduplication writes only what is missing, within the queue's dedup window. Take copies as often as you can afford to lose, keep several, and restore one now and then on a spare machine: the first restore you run should not be the one during an outage.

Two things are never in a backup: ephemeral queues, which live in memory, and the settings, among them `QUEEN_RAFT_TOKEN` and `QUEEN_ENCRYPTION_KEY`. Keep those apart, with the same care.

Source: https://queenmq.com/manage/recovery/index.mdx
