Lilith Metaverse · Operations

V3 Data-Layer DR and Backup Plan

Why these numbers: Redis session loss is survivable by design — the reconnect gate (RECONNECT_VALIDATION.md) and login path rebuild session state, so 60 s of session-state RPO costs reconnects, not data.

5sections6 minread2tables

On this page

Plan id: v3-dr-backup-plan.v1

Verifier: pnpm verify:v3 dr-backup (proposed)

Evidence artifacts: V3/operations/dr-backup-release-gate.json, V3/operations/dr-restore-drill-<quarter>.json (proposed)

Owner: Capacity engineering (data on-call), joint with Lilith-Privacy lead for consent/audit classes and residency constraints

The POP-loss path is drilled (PIXEL_STREAMING_POP_CAPACITY_MANAGEMENT.md multi-cloud failover), but compute failover is not data protection: nothing to date sets recovery objectives for the data layer itself. This plan sets RPO/RTO per data class, the backup cadence that achieves them, a quarterly restore drill with pass criteria, and the region-loss runbook.

Residency boundary (binding): backups and replicas of residency-pinned data must stay inside the residency zone signed off in V3/PER_REGION_RULES.md (us-west, eu-de, uk, in, br). The pop-capacity residency drill already establishes that India-to-eu-west-1 fallback requires explicit consent — the same rule applies to data: we accept longer downtime over a residency violation.

RPO/RTO Targets per Data Class#

Data class Store RPO RTO Mechanism
World/persistence DBs (avatar history, attendance, holdings, tickets, commerce ledger) Postgres 5 min 60 min Continuous WAL archiving + nightly base backup (pgBackRest), PITR restore
Consent and audit ledgers (consent decisions, privacy-seat ledger, operator audit events, C2PA refs) Postgres (dedicated DB) 0 (committed txns) 30 min Synchronous in-zone standby (synchronous_commit=remote_apply), plus WAL archiving; daily immutable export to object-lock bucket
Session/queue state (gateway sessions, surge queue, rate counters) Redis 60 s 15 min AOF everysec + in-zone replica + 15-min RDB snapshots to object storage
Media/object storage (recordings, signed-edition masters, asset bundles, pak chunks) MinIO / object storage 15 min 4 h (origin rebuild); 0 for already-cached CDN reads Versioned bucket replication to a second in-zone site; CDN serves stale during origin loss
Live capture segments (mid-concert, pre-publication) Capture pipeline + MinIO one segment (≤ 10 s) 3 min Hot-standby capture worker (existing runbook) + segment-level replication on write

Why these numbers: Redis session loss is survivable by design — the reconnect gate (RECONNECT_VALIDATION.md) and login path rebuild session state, so 60 s of session-state RPO costs reconnects, not data. The consent ledger is the opposite extreme: a consent decision that exists in a user's memory but not in our ledger is a legal exposure (RECORDING_CONSENT_FLOW.md publication gate depends on it), so committed consent transactions get RPO 0 via synchronous replication. Commerce/holdings sit at RPO 5 min because Stripe is an independent source of truth for payments — a ≤5-min gap is reconcilable from provider records via the existing refund/reversal machinery.

Backup Cadence and Retention#

Action Cadence Retention
Postgres base backup (pgbackrest backup --type=full, per stanza per zone) nightly 03:00 zone-local 35 days PITR window
Postgres WAL archiving (archive_command → pgBackRest repo) continuous 35 days
Postgres monthly archival full 1st of month 12 months
Consent/audit immutable export (signed JSONL to object-lock bucket, compliance mode) daily 7 years
Redis RDB snapshot to object storage every 15 min 72 h
MinIO bucket replication (versioned, delete-marker replication off) continuous (≤15 min lag alarmed) versions kept 90 days
Off-provider copy (Postgres monthly full + consent exports → Azure in-zone region) monthly 12 months

All backups are encrypted with per-zone KMS keys; backup-repo credentials are write-only from production (no production role can delete or overwrite the object-lock buckets — ransomware/insider containment). Backup success and replication lag are alarmed: a missed nightly base backup or WAL-archive lag

5 min (the RPO) pages the data on-call — an unalarmed backup failure silently converts RPO 5 min into RPO 24 h.

Quarterly Restore Drill#

Owner: data on-call engineer of the week; witness: one engineer not on the data rota; evidence: V3/operations/dr-restore-drill-<quarter>.json, reviewed in the weekly ops review.

Procedure (per zone, rotating so every zone is drilled at least annually; eu-de and us-west every quarter):

  1. Pick the drill target time T = now − 24 h ± a random offset (the drill must not always restore to a tidy midnight boundary).
  2. Provision an isolated restore instance (no production network routes; separate security group) in the same zone.
  3. Postgres PITR: pgbackrest --stanza=v3-world-<zone> --type=time --target="<T>" restore then start and let recovery complete. Record wall-clock from command to accepting-connections.
  4. Validation, all required:
    • pg_amcheck clean on restored indexes/heap;
    • row-count deltas vs production-at-T (from the hourly stats snapshot) within 0.1% per audited table;
    • ledger coherence: consent-ledger entries for a sampled set of 100 recorded sessions are complete and ordered (the same invariant the publication gate checks);
    • application smoke: a DSAR export (DSAR_V1_PIPELINE.md category set) builds successfully against the restored instance for a synthetic user.
  5. Redis: restore the latest RDB snapshot to an isolated instance; verify the surge-queue and session keyspaces deserialize and TTLs are sane.
  6. MinIO: restore 3 sampled objects per bucket class from the replica site, verify checksums against the C2PA/manifest hashes where applicable.
  7. Consent immutable export: fetch one daily export from the object-lock bucket, verify its signature chain.
  8. Write the drill evidence file: timings per step, validation results, restored-to point, deviations.

Pass criteria (all required, fail-closed):

  • Postgres accepting-connections within 45 min and validation complete within 60 min (the RTO) — measured, not estimated;
  • achieved restore point within 5 min of T (proves the RPO is real);
  • consent-ledger DB restore within 30 min;
  • zero validation failures in step 4;
  • Redis and MinIO restores complete within their RTOs;
  • evidence file written and reviewed within 7 days.

A failed drill is a GA-severity finding: the gate goes red, and the fix plus a re-drill must complete within 30 days.

Region-Loss Runbook#

  • Owner: Capacity incident commander; severity SEV-1.
  • Scenario: total loss of a primary cloud region hosting a residency zone's data plane (not just a POP — the POP-only case is already drilled).

What fails over:

  • Pixel Streaming sessions: per the existing multi-cloud drills — secondary cloud capacity in-zone (e.g., Azure centralindia for ap-south-1), re-establishment ~30 s.
  • Edge/web/BFF: multi-region edge stays up; BFF traffic shifts to the surviving region serving non-residency-pinned routes.
  • Global (non-pinned) Postgres data: promote the cross-region async replica; accept up to RPO 5 min of loss; reconcile commerce gaps against Stripe records before re-enabling payouts.

What is sacrificed (pre-decided, so nobody improvises under pressure):

  • All in-flight sessions in the lost region: resume tokens die with the region; users re-login (surge queue armed first — see V3/launch/launch-surge-plan.md, the re-login stampede is a surge).
  • Up to 5 min of world/persistence writes (RPO), and up to 60 s of session state.
  • Residency-pinned data does not fail over out of zone. If the in zone is lost, India users see a regional outage page until in-zone restore completes (in-zone secondary site or cloud-secondary in-country region); we do not promote an out-of-country replica without the explicit-consent basis established in the residency drill. Same rule for eu-de, uk, br.
  • Free-tier capacity in surviving regions is shed first (85% backpressure ladder) to absorb displaced paid/booked users.

Order of operations: declare SEV-1 and assign commander → arm surge queue → fail over pxstream per capacity drills → promote non-pinned replicas → verify consent-ledger synchronous standby state before accepting new recordings anywhere → begin in-zone restore for pinned data from backups → status-page updates every 30 min → commerce reconciliation before payouts re-enable → post-incident review with measured RPO/RTO vs targets.

Fail-Closed Criteria#

The gate fails closed when any of the following holds:

  • any data class above lacks a configured, alarmed backup mechanism matching its cadence row;
  • WAL-archive or replication lag alarms are missing or routed to no pager;
  • the most recent quarterly restore drill is missing, failed, or older than 100 days;
  • backup repositories are writable-or-deletable by production roles, or object-lock is absent on consent/audit exports;
  • any backup or replica of residency-pinned data exists outside its residency zone;
  • the region-loss runbook has no named commander role or has not had a tabletop walk-through before GA (evidence: drill-region-loss-tabletop-ga).