Runbooks
Each runbook is a procedure. Read the whole procedure before you start it.
Planned failover
Use this before maintenance on the active member.
- Stop the active member with
Ctrl-C, or withSIGINT. It releases its leases on the way out. - Watch the log of the standby for
standby took over write lease. Takeover happens within one third of the lease time-to-live. - Do the maintenance.
- Start the member again with the same
--ownerand--ha. It rejoins as standby.
If the supervisor sends SIGTERM, the process dies without releasing the
lease. Takeover then costs one full lease time-to-live.
Crashed active
Nothing is required for service. The standby takes over within the time-to-live plus one third.
- Confirm the takeover. Watch the basis advance with
corium db stats, and read the lease owner in theMetricspanel ofcorium tui. - Start the crashed member again under its supervisor with
--ha. It rejoins as standby. - Investigate the crash afterward, not before.
Split-brain suspicion
Both members print ownership messages in their logs.
This is not possible for durable state. The root record is owned by exactly one lease version, and every publish and acknowledgement is fenced by it.
- A member that logs
deposedis the loser. It stands down on its own. - Trust the root record, not the process logs.
corium tuireads the lease owner from theStatuscall. - Take no other action.
Both members down
- Start either member. Prefer the one with the newest data-directory modification times if storage is not shared.
- The member waits out any unexpired lease. Without
--hait waits up to--lease-wait-ms. With--hait waits without limit. - It recovers by log replay, and it serves.
- Start the second member. It becomes standby.
Recovery from a backup
- Stop the affected transactor, and preserve its data directory. Do not delete it.
- Restore the newest backup into an empty directory, or under a new name:
corium restore <file> --data-dir <empty-dir> --as-db <name>. - Start a transactor on the restored directory.
- Wait until
:index-lagincorium db statsreaches zero. - Compare the basis, the datom count, the entity count, and the attribute count with the backup report.
- Run a known query, and compare the result.
- Redirect peers only after those checks pass.
Transactor will not start
Read the error first. Four causes are common.
| Error names | Cause | Fix |
|---|---|---|
| A lease holder | Another transactor holds the lease. | Add --ha to stand by, or stop the other member. |
| A storage key | The process cannot resolve a named KEK. | Give it a --storage-key that resolves. |
| A Cargo feature | The binary lacks the storage or OIDC feature. | Rebuild with the feature. |
| A missing data directory | --data-dir is absent. | Pass it, or set :data-dir in the configuration file. |
The process fails at startup for a key it cannot resolve. That is by design. It does not fail later at the first read.
Writes refuse with FAILED_PRECONDITION
Two causes give this status.
A standby. The message names the current lease holder. Point the client at that endpoint, or pass the whole endpoint list.
A fenced key. Run corium keys status <db>. If :keys-fenced is true,
the manifest opened an epoch that this node cannot load.
- Give the transactor a
--storage-keythat resolves the KEK that the manifest names. - Restart the transactor.
- Confirm that
:keys-fencedisfalse.
See encryption at rest.
An ambiguous transaction
The result of a transact call whose connection died mid-request is unknown.
The transaction is committed, or it is absent.
- Do not resubmit yet.
- Run
syncon the connection. - Read the data back and decide from the result.
- Resubmit only if the write is absent.
Changing the schema of a live database
Writes continue throughout. Only a blocked change stops the procedure.
- Edit the schema file. Keep every attribute that must survive, because a file that omits one reports it as unmanaged.
- Plan it:
corium schema update <db> --schema <file>. Nothing is written. - Read every execution class, every count, and every acknowledgement code.
- Run the invocation that the last line of the plan prints, and add the path of the schema file.
- Confirm the new schema generation in the output of the apply.
- Compare
:attributesincorium db statswith the expected count.
A plan is invalidated by a schema change or a failed condition, never by ordinary data writes. Re-plan if the digest is refused.
See schema management.
A schema plan is blocked or refused
Read the reason that the plan prints under the change.
| Reason | Meaning | Fix |
|---|---|---|
value-type-mutation | A value type cannot change in place. | Follow the replacement-attribute recipe that the plan prints. |
unique-duplicates | Duplicate values exist. | Retract the duplicates, then plan again. |
cardinality-conflicts | An entity holds several values where the file asks for one. | Choose a winner per entity and retract the rest. |
ever-protected | The attribute has been protected at some time. | It can never gain index or unique. Drop them from the file. |
protection-conflict | The declaration combines protection with index, unique, or ref. | Drop the conflicting property from the declaration. |
An apply can also fail after a clean plan.
| Error code | Meaning | Fix |
|---|---|---|
plan-mismatch | The schema changed between the plan and the apply. | Plan again and read the new plan. |
allow-required | A change needs --allow <class>. | Add the exact allowance the plan names. |
ack-required | A change needs --ack <code>. | Add the exact code the plan names. |
Every request is denied
The policy denies, or the policy is unreadable.
- Run
corium authz status. A missing basis means the policy is unreadable. - If it is unreadable, a break-glass role admits an operator. See authorization.
- If the policy denies, stop the transactor.
- Start it again without
--authz-db. - Fix the tuples with
corium authz grant. Test each one withcorium authz check. - Restart with
--authz-db.
Index lag grows without limit
- Read
:index-lagincorium db stats, and the publication duration in the metrics endpoint. - Lower
--index-backoff, so publication takes a larger share of wall-clock time. - Lower
--index-tail-thresholdif a large threshold defers the work. - If neither helps, the storage backend is the limit. Give it faster storage, or reduce the write rate.
Index lag never risks durability. It lengthens cold-peer bootstrap and it makes a backup less fresh.
A peer uses too much memory
A peer holds every datom that it has seen, including retractions.
- Confirm the cause. Memory tracks total history, not the size of the live database.
- Restart the peer with
--peer-bootstrap, so it starts from the published snapshot rather than replaying the log from basis 0. - Avoid opening many distinct time views in one process. Each distinct view costs a fold of the whole history.
- Split the workload across more peer processes.
See indexes and storage.
Backing up an encrypted database
corium backup refuses an encrypted database.
- Stop the transactor, or accept a crash-consistent copy.
- Copy the underlying storage with its own tool. Use a filesystem snapshot, a PostgreSQL dump, or S3 replication.
- Copy the KEK separately, and keep it in a different system.
- Test the restore path on a separate host before you rely on it.
Storage is full
- Run a manual sweep:
corium gc --transactor <url> --window 72h. - If that reclaims little, read
corium_transactor_gc_retained_blobs_total. A large retained count means that the window is holding the blobs. - Lower
--gc-windowonly when no reader holds a root older than the new window. - Delete finished forks and staging clones with
corium db delete.
Emergency: recreate a database from the log
The log is the source of truth. A data directory with an intact log recovers by replay.
- Stop every transactor that touches the directory.
- Preserve a copy of the whole directory.
- Start one transactor on the directory. Startup replays the log tail after the last published index basis.
- Compare
corium db statswith the last known values.
Never edit or delete files under <data-dir>/logs by hand. Old lease-version
files are inert history that readers must merge.