Disciplines · Runbooks

Runbook — Yemaya study workspace: recovery from a store outage

Most of this outage is already handled, and knowing that changes what you should do.

7sections3 minread

On this page

Checklist: YSD-22058 (recovery) · drill kind recovery Decision owner role: the on-call engineer for svc-study-workspace Machinery: apps/yemaya/svc-study-workspace/src/persistence/chaos.integration.spec.ts

There was a real plan for this — detection, three steps with owners and durations, two audiences and a 60-minute objective — written as a fixture inside a spec, to exercise the plan assessor. It and a key-compromise plan in the same file were the only continuity plans in the workspace, and a plan that exists to be graded is not a plan anybody can follow at 3am.

Running this document IS the drill. Record it in procedure-exercise-register.json.

What the system does on its own — read this first#

Most of this outage is already handled, and knowing that changes what you should do. Driven on every change against real PostgreSQL and object storage:

  • The saga waits durably on the dead store and heals when it returns. It does not fail, and it does not lose the work. Do not "clear the queue".
  • A redelivered outbox row re-processes idempotently, so a store that comes back and replays does not double-apply.
  • A step never acks while a stale copy is still served — a failed cache invalidation holds the step rather than declaring success.
  • A partial delete resumes exactly once after a crash: bytes gone, records intact, and the resume finishes the job rather than restarting it.

⚠️ The main way to turn this outage into data loss is to intervene. Restarting workers, draining outboxes by hand, or re-running a deletion because it "looked stuck" all fight machinery designed to wait. The default action is to restore the store and let the saga heal.

Detection#

  • Readiness reports the store unavailable (readiness.ts).
  • Outbox depth climbing without an error rate to match — the signature of durable waiting, not of failure.
  • Not "a customer tells us": by the time a learner notices, the queue has been waiting for some time.

The steps#

# Action Owner role Expected
1 Confirm which store is gone and that the queue is waiting rather than erroring on-call engineer 5 min
2 Restore the store, or fail over to its replica on-call engineer 30 min
3 Watch the outbox drain and confirm the depth returns to baseline on-call engineer 20 min
4 Reconcile: confirm no step acked while the store was gone on-call engineer 10 min

Recovery time objective: 75 minutes. Recovery point objective: 0 — the saga waits rather than dropping, so an outage inside the window should lose nothing.

Why the number changed on 2026-08-15#

It read sixty minutes until then, and the four steps above are budgeted at 5 + 30 + 20 + 10 = 65. A run that went exactly to plan would have missed it, so the number was not a commitment anybody could keep by following this document — it was a commitment to depart from it. The programme already refuses that shape (assessDisasterRecoveryPlan: "the objective is contradicted by the plan meant to meet it") and had never put this runbook through the rule. Seventy-five is the step budget plus ten minutes of headroom, and a number with no headroom over the plan meant to meet it is missed by any run that hits one snag. Owner decision: raise it rather than trim a step, because the budgets are the considered estimate of the work.

Communication#

Audience Within
The on-call channel immediately
Affected tenants, if the outage passes 30 minutes 30 min

Done means#

  • The store answers, readiness passes, outbox depth is back to baseline.
  • No step acked during the outage window.
  • Recorded in the procedure exercise register with the real elapsed time — which is the number the 60-minute objective is a claim about.

Known shortfall#

An outage of a store is exercised. A regional outage is not, and the scenario list names it (regional-outage in continuity/disaster-recovery.ts) with recovery as its drill kind. Promoting a secondary region to primary appears in this workspace only as a fixture string. Do not read this document as covering that case.