Runbooks
Concrete, ordered procedures for the operations you run on a live deployment. They assume a node started as:
merod --home data/ --node node1 runso its home directory is data/node1/, holding config.toml, the RocksDB
datastore (data/node1/data/), and the blob store (data/node1/blobs/).
Upgrade a node binary
Section titled “Upgrade a node binary”Replacing the merod binary in place. Because a node that runs an older
binary safely buffers newer-schema deltas in its absorb buffer (see
stale-schema deltas),
a rolling upgrade across a fleet is tolerated — but each node still needs a
clean stop/replace/restart.
-
Confirm the running build. Scrape
merod_build_info(the constant-1beacon carriesversionandpeer_id) so you know what you are replacing:Terminal window curl -s http://127.0.0.1:2528/metrics | grep merod_build_info -
Quiesce client traffic. Stop new JSON-RPC / admin calls against this node at your load balancer or proxy. There is no in-process “drain” command; draining means cutting client traffic upstream and letting in-flight syncs settle.
-
Stop the node. Send
SIGINT/SIGTERMto themerodprocess (e.g.Ctrl-C,systemctl stop, ordocker stop) and wait for it to exit. Do notkill -9unless it hangs — an abrupt kill risks leaving RocksDB to recover on next start. -
Replace the binary. Install the new
merod(andmeroctl) over the old one. The home directory is untouched. -
Restart. Start the node with the same home and node name:
Terminal window merod --home data/ --node node1 run -
Verify. Confirm the new version is live and the node re-converges:
Terminal window curl -s http://127.0.0.1:2528/metrics | grep merod_build_infomeroctl --node node1 peersWatch
governance_pending_queue_depthand any absorbed-delta counters drain toward zero — an upgraded reader can now decode what the old binary buffered.
Recover a stuck upgrade
Section titled “Recover a stuck upgrade”Use this when an application upgrade (a new bytecode version) does not finish on a node: a context stays on its old code, or a peer never reports as migrated. The recovery walks the node-local migration & recovery markers — inspect what they say, request a resync, then verify they clear.
The endpoints below are on the loopback HTTP server (default 127.0.0.1:2528);
reach them the same way you reach the rest of the admin API for your auth setup.
-
Inspect — find the stuck members. The migration-status rollup is the operator-facing “have all peers migrated?” view. It snapshots each member’s migration heartbeat, including any failure:
Terminal window curl -s http://127.0.0.1:2528/admin-api/groups/<namespace_id>/migration-statusRead
rollup.allMigrated(the go/no-go) androllup.failed. Any member whosemigrationFailedis set names why it stalled — these map directly onto theContextMigrationFailedmarker:migrationFailedvalueWhat happened check_abortedThe app’s migration check rejected the upgrade; the migrate was rolled back (zero residue). apply_failedThe migrate apply itself errored (e.g. the target wasm would not run). no_migration_pathThe context cannot replay its next upgrade rung — the intermediate bytecode is unobtainable. Recovery is a resync (below) or reinstalling the missing version. Under the hood, a context that aborted its migrate is pinned to its pre-upgrade code by
ContextExecutingBlob, whileContextActivatedBloblags the group’s current app key — that gap is the “running old code” symptom. See the marker reference for what each column means. -
Decide the recovery. A
check_aborted/apply_failedmember usually recovers by re-driving the upgrade once the underlying cause is fixed (a bad migration function, a missing dependency). Ano_migration_path(stranded) context cannot replay its ladder locally — its recovery is a full-state resync from a healthy peer that already holds the upgraded state. -
Request the resync. This sets the
ContextResyncRequestedmarker and pulls a fresh snapshot, overwriting local state for that context:Terminal window curl -s -X POST http://127.0.0.1:2528/admin-api/contexts/<context_id>/resync \-H 'content-type: application/json' \-d '{"force": false}'A successful call returns
{"contextId": "...", "resyncStarted": true}. -
Verify the markers clear. Re-run the migration-status rollup. As the snapshot lands,
ContextResyncRequestedis cleared on completion,ContextActivatedBlobadvances to the group’s current app key (so the context now runs the new code), and theContextMigrationFailedmarker self-clears — the member should move out offailedtowardmigrated:Terminal window curl -s http://127.0.0.1:2528/admin-api/groups/<namespace_id>/migration-statusrollup.allMigrated == truemeans the cohort converged. If a member is stillfailedafter the resync settles, the cause is upstream (the upgraded state isn’t actually available on the peers you resynced from) — fix that before retrying.
Back up & restore the datastore
Section titled “Back up & restore the datastore”The datastore (RocksDB) and blob store are plain directories under the node home. A consistent backup is a cold copy taken while the node is stopped.
Back up
Section titled “Back up”-
Stop the node (
SIGINT/SIGTERM, wait for clean exit) so RocksDB flushes and releases its lock. -
Copy the whole node home. This captures config, identity keypair, datastore, and blobs together — they must be restored as a set:
Terminal window tar czf node1-backup-$(date +%Y%m%d).tar.gz -C data node1 -
Restart the node and confirm it is healthy (
meroctl --node node1 peers).
Restore
Section titled “Restore”-
Stop the node if it is running.
-
Replace the home directory with the backed-up copy (keep the original aside until you have verified the restore):
Terminal window mv data/node1 data/node1.brokentar xzf node1-backup-YYYYMMDD.tar.gz -C data -
Restart with the same
--home/--node:Terminal window merod --home data/ --node node1 run -
Let it re-converge. A restored node may be behind the live group; the reconciler and sync will catch it up from peers once it reconnects.
Recover a context from divergence
Section titled “Recover a context from divergence”Use this when a context will not converge on its own — a scope_root / root-hash
mismatch the reconciler keeps reporting, sync_verification_failures_total
incrementing, or snapshot selection climbing without resolution. Escalate from
gentlest to most forceful.
-
Confirm divergence, don’t guess. Check the sync counters (Observability) and logs for the context before acting. Transient lag after a partition is normal and self-heals.
-
Force a targeted sync. This is the first, non-destructive lever:
Terminal window meroctl --node node1 context sync --context <context_id> -
Drive governance convergence if the deltas are buffered awaiting authorisation (growing
governance_pending_queue_depth). Sync the owning group’s state so the governance DAG catches up:Terminal window meroctl --node node1 group sync <group_id> -
Sync everything if multiple contexts drifted (e.g. after a long partition):
Terminal window meroctl --node node1 context sync --all -
Last resort — force a clean resync from peers. If the local copy is genuinely corrupt and will not reconcile, opt the node out of the context and re-join so it pulls a fresh snapshot from a healthy peer:
Terminal window meroctl --node node1 context leave-context <context_id># then rejoin via the owning group / namespacemeroctl --node node1 group join-context <context_id>
Rotate & leave safely
Section titled “Rotate & leave safely”Removing this node (or identity) from a group, namespace, or context in a way that the rest of the fleet records correctly.
Publishes a MemberLeft operation to the group DAG, so peers record the
departure:
meroctl --node node1 group leave <group_id>A namespace is a root group; leaving cascades through its descendant groups:
meroctl --node node1 namespace leave <namespace_id>Local-only opt-out (no DAG op) — drop this node’s copy of a context without leaving the group:
meroctl --node node1 context leave-context <context_id>Signing-key rotation
Section titled “Signing-key rotation”Group signing keys are managed via:
meroctl --node node1 group signing-key register <group_id> <hex_signing_key>