Operator runbooks for running ScrivaDB in production. Start with getting-started.md for setup and architecture.md for how the internals work.
ScrivaDB replicates a leader to one or more read-only followers (see
Replication). Replication is
asynchronous and there is no automatic leader election — recovering write
availability after a leader loss is a deliberate operator action: promote a
caught-up follower to leader with the admin Promote RPC.
Promotion is one-way. A promoted node becomes an ordinary leader; the old leader must not come back as a leader against the same data (see Preventing split-brain).
--replicate-from <old-leader>.Promote is an admin operation).1. Detect leader loss.
Confirm the leader is actually down (not a transient network blip) before failing over — promoting while the old leader still takes writes causes split-brain. Check liveness/readiness and that clients are erroring:
curl -sf http://<leader>:8080/healthz # liveness; fails/refuses when down
curl -sf http://<leader>:8080/readyz # readiness
2. Pick the most caught-up follower and verify it is current.
If you run several followers, promote the one with the highest applied LSN (the least data loss). Read each follower's applied LSN:
curl -s -H 'x-api-key: <key>' http://<follower>:8080/v1/replication/status
# {"appliedLsn":"1234", ...}
Ideally the chosen follower's appliedLsn equals the leader's last-known
leaderLsn (lag 0 — no data loss). The follower tracks the last leader LSN it
observed; the Promote call itself reports the lag it measured.
3. Promote.
scriva-cli --host <follower>:5433 --api-key <key> promote
# promoted: role=leader lsn=1234 lag=0
or over REST:
curl -s -H 'x-api-key: <key>' http://<follower>:8080/v1/replication/promote -d '{}'
# {"role":"leader","lsn":"1234","lag":"0"}
On success the follower stops replicating from its old upstream, lifts its read-only guard, and begins accepting writes at the LSN reported in the response (new writes are tagged with strictly greater LSNs, so no LSN is ever reused).
4. Repoint writers and surviving followers.
--replicate-from <new-leader>. A follower that was tailing the old leader will
not automatically follow the new one. If a follower's data has diverged from
the new leader (it applied writes the new leader never had, or vice-versa),
re-bootstrap it: stop it, wipe its data directory, and restart it against the
new leader so it takes a fresh snapshot.5. Rebuild the old leader as a follower (optional).
When the old node returns, do not let it serve as a leader. Wipe its data
directory and restart it with --replicate-from <new-leader> so it re-bootstraps
and rejoins as a follower.
Promotion refuses to silently discard data. If the chosen follower's replication
lag (last-known leader LSN − applied LSN) exceeds the configured ceiling, the
call fails with FAILED_PRECONDITION and reports the lag:
FAILED_PRECONDITION: replica lag exceeds promotion threshold: applied_lsn=1200 last_known_leader_lsn=1234 lag=34 threshold=0
The ceiling is --promote-max-lag (config promote_max_lag), default 0 — the
follower must be fully caught up with every write it knows the leader committed.
Raise it if you can tolerate a bounded amount of lost tail, or wait for the
follower to catch up and retry.
When the leader is unrecoverable and you accept losing its un-replicated tail, force the promotion past the guard:
scriva-cli --host <follower>:5433 --api-key <key> promote --force
# or REST: curl ... /v1/replication/promote -d '{"force":true}'
A forced promotion of a lagging replica permanently discards the writes the old leader committed but never shipped. Use it only when the alternative (no leader at all) is worse.
There is no coordinator to fence the old leader, so you must ensure only one leader takes writes at a time:
Promote on a node that is already a leader is refused
(FAILED_PRECONDITION, nothing to promote).Promote requires a read-write key. Finer per-key admin ACLs (an
admin scope) arrive with the S3 milestone; until then a read-write key is the
admin boundary. The replication link itself uses plain gRPC — run it inside a
trusted network (mutual TLS is a later milestone).--audit-log <path> turns on a durable, append-only trail of every
state-mutating and admin RPC and every rejected authentication attempt. It is the
system of record for "who did what" — including the Promote, Compact,
CreateCollection, and DropCollection admin actions — and is kept deliberately
separate from the request log so it can have its own retention and be shipped to
an append-only or write-once store. It is off by default. See
Audit log for the record format and field
reference.
The audit trail is a single gRPC interceptor chained outside the auth
interceptor, so a call rejected by auth is still recorded (an inner interceptor
would never run for a rejected call). Because the resolved principal lives on a
context the auth interceptor derives downstream, the audit interceptor installs
a small principal "sink" that the auth interceptor fills in on a successful
authentication; on a rejected call the sink stays empty and the record is
attributed to unauthenticated. Writes to the file are serialized by the JSON
handler, so records are whole lines even under concurrent RPCs. One record is
emitted per RPC — never zero, never two — and it is written before the RPC's
response reaches the client.
--audit-log at a file on a volume with its own retention
and, ideally, append-only/immutable semantics (e.g. a WORM mount or a shipper
that tails the file into a tamper-evident store). Keeping it off the data
directory avoids coupling audit retention to database backups.O_APPEND and holds it open for the
process lifetime; it does not rotate the file itself. Use copytruncate-style
rotation (e.g. logrotate --copytruncate) so the open descriptor keeps writing
to the same inode, or rotate during a restart. A plain rename+create leaves
ScrivaDB writing to the renamed (still-open) file until the next restart.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |