DRDATABASE-SPECIFICCLOUD-SPECIFICGENERAL

Partial and Logical Data Recovery

Most real data loss is partial and logical. Restoring the whole database over a live system is usually the wrong tool and often makes it worse.

The question, the obvious approach, and why it breaks

Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.

The production question

One table is wrong and the rest of the database is fine — now what?

The problem

Backups are designed for total loss, and total loss is rare. The common event is a subset of data made wrong by code or by a person, discovered later, while correct writes continue arriving on top of it.

What teams do first

We have backups. Restore last night's backup over production and we are back to a known-good state.

How it breaks

That discards every correct write made since the backup — usually far more data than the incident damaged. The cure is larger than the disease.

How it breaks in production
  • That discards every correct write made since the backup — usually far more data than the incident damaged. The cure is larger than the disease.
  • The damage often predates the most recent backup. Restoring the newest copy restores the corruption too.
  • A full restore takes as long as the data volume demands, so a problem affecting one table becomes a full outage for the entire system.
  • Downstream systems already consumed the bad data: caches, search indexes, analytics, invoices, emails already sent. Rolling back the database does not roll those back (Operating a Cache).
  • Without knowing the exact scope and start time of the damage, any recovery is a guess, and a confident-looking one is worse than an honest uncertain one.
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

What is actually happening

Underneath the tooling, which is the part that survives a change of tool.

  • Logical corruption is not a storage failure. The database did exactly what it was told; the instruction was wrong. Nothing at the storage layer will detect it, which is why it survives replication and snapshots.
  • The recovery shape is almost always: restore a copy aside, extract the correct subset, reconcile into the live system — not restore over the top.
  • Two questions bound the whole operation: what is the blast radius in rows, and when did it start. Everything else is procedure.
  • Point-in-time recovery is the tool that makes this tractable: a side restore to just before the damage gives you a queryable copy of the truth to diff against (Backup Operations).
  • Reconciliation is application logic, not a database operation. Deciding which of two versions of a row wins requires knowing what the rows mean.
  • Stopping the bleeding comes first. If the bad writer is still running, every minute enlarges the scope you will later have to reconcile (Stop the Harm Before You Understand It).

Not every data problem is a restore problem

GENERALThe classification holds across engines. What varies is how cheap the side restore is: with continuous archival it is routine, with periodic snapshots only you are restoring to the nearest available point and reconciling a wider window.

The first job is classification, because the classes need different tools and choosing the wrong one wastes the time when scope is still growing.

What happenedIs a full restore right?What actually works
Storage or instance lostYesRestore or promote a replica; this is the case backups were designed for
Table dropped, nothing else touchedRarelySide restore, extract the table, reload it, then reconcile rows written since
Column overwritten by a bad migration or jobNoSide restore to just before; diff and repair the affected column in batches
Rows deleted for one tenantNoSide restore; extract that tenant's rows; reinsert with the original keys
Slow drift from a logic bug over weeksNoRecompute from source of truth or event history; restore points may all contain the bug
Data leaked or wrongly retainedNoA deletion and audit problem, not a recovery problem (Audit Logs for Privileged Actions in Security)

The partial recovery procedure

Every step exists because skipping it has caused a second, worse incident somewhere. The ordering matters most at the start: stopping the writer and preserving evidence come before anything that feels like progress.

Stop, scope, restore aside, reconcile
  1. 1
    Stop the bleeding

    Disable the job, revert the deploy, revoke the credential, flip the flag off.

    fails by Recovery starts while damage continues, so the scope keeps moving.

    evidence The write rate of the damaging pattern drops to zero on a dashboard.

  2. 2
    Preserve evidence

    Snapshot the current, damaged state before touching it.

    fails by A hasty repair destroys the only record of what the damage looked like.

    evidence A snapshot identifier recorded in the incident timeline.

  3. 3
    Scope

    Determine which rows, which columns, and the start instant, from the data and the change record.

    fails by Guessing; a correction that is too wide damages good data, too narrow leaves the incident half-fixed.

    evidence A query that returns the affected set with a count, agreed by two people.

  4. 4
    Side restore

    Restore to a separate instance at a point just before the damage began.

    fails by Chosen point falls inside the damage window; or quota, network or key problems block the restore (Restore Drills).

    evidence A spot check on the side copy confirms known-good values for affected rows.

  5. 5
    Diff

    Compare side copy and live for the scoped set; classify differences into damage and legitimate later writes.

    fails by Treating every difference as damage, which overwrites correct new work.

    evidence A reviewed diff summary with counts per category.

  6. 6
    Dry run

    Execute the correction with writes disabled and report what it would change.

    fails by Skipped under time pressure — the most expensive shortcut in this procedure.

    evidence Dry-run output matching the expected count from the scope step.

  7. 7
    Repair in batches

    Apply the correction in bounded, resumable, idempotent batches.

    fails by A single unbounded statement that locks the table, saturates the database, or cannot be stopped.

    evidence Per-batch records of rows changed; live error and latency signals stay flat (Operating a Production Database).

  8. 8
    Repair downstream

    Invalidate caches, reindex search, recompute derived data, handle external side effects.

    fails by Database correct, product still wrong, users still seeing stale values.

    evidence Reads through the application return corrected values.

  9. 9
    Assert and record

    Run invariants, then write down exactly what changed.

    fails by Nobody can reconstruct the final state during the postmortem.

    evidence Invariant checks pass; a change record exists (Postmortems).

The correction is the dangerous part

By the time you are repairing, the original bug is understood and the pressure is to move fast. This is where a data incident becomes an outage, because a correction is a large write against a production database with a very human author.

Recovery operations that made it worse
TriggerSymptomCauseResponse
Unbounded UPDATE on a large tableLocks and replication lag; the application times outA single long transaction touching far more rows than intendedBatch with explicit bounds and pauses; watch lag and error rate between batches
Restore over the live databaseHours of correct writes goneTotal restore used for partial damageSide restore and reconcile; keep the live system serving
Recovery point chosen from memoryThe "clean" copy contains the corruptionDamage start time was assumed rather than established from dataBracket the start from the data, verify known-good values on the side copy before diffing
Database repaired, cache untouchedUsers still see wrong values after the all-clearDerived state holds the bad values with its own lifetimeInvalidate or flush the affected keys as an explicit step (Operating a Cache)
Repair script re-run after an interruptionDouble-applied correctionsThe correction was not idempotentTrack repaired rows explicitly; make re-running a no-op
Two ways to run the same repair
One statement, at 3am
UPDATE orders
   SET status = 'paid'
 WHERE status = 'pending';
-- no bound, no dry run, no batches, no record of prior values
Scoped, dry-run, batched, reversible
-- 1. scope, agreed and counted first
CREATE TABLE repair_scope AS
SELECT o.id, o.status AS old_status, s.status AS good_status
  FROM orders o
  JOIN side_restore.orders s USING (id)
 WHERE o.updated_at >= :damage_start
   AND o.updated_at <  :damage_end
   AND o.status <> s.status;

-- 2. dry run: this count must match the agreed scope
SELECT count(*) FROM repair_scope;

-- 3. repair in bounded, resumable batches
UPDATE orders o
   SET status = r.good_status, updated_at = now()
  FROM (SELECT * FROM repair_scope
         WHERE NOT repaired ORDER BY id LIMIT :batch) r
 WHERE o.id = r.id;
-- mark repaired, pause, re-check live signals, continue

The left statement has no bound, no record of what it changed, no way to stop halfway and no way to undo. The right one keeps the prior value for every touched row, so the repair itself is reversible; it matches a count agreed in advance, so a scope error is caught before any write; and it yields between batches, so the database keeps serving traffic while the repair runs.

How to do it properly

Most important first.

  • Stop the source of damage before anything else — disable the job, revert the deploy, revoke the credential, turn off the flag.
  • Establish scope and start time from the data itself and the change record: which rows, which columns, from which timestamp, by which actor (The Audit Trail).
  • Restore to a side copy at a point just before the damage began. Never restore over the live system while a partial recovery is still viable.
  • Diff and reconcile deliberately, in batches, with a dry run first and a preserved record of what each batch changed.
  • Repair downstream consumers explicitly: invalidate caches, reindex search, recompute derived data, and decide what to do about actions already taken in the outside world.
  • Prefer soft deletes and append-only history for high-value data — it converts a recovery operation into a query (Event Sourcing in Architecture).

How much can this affect

Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.

Blast radius if this is wrongOne tenant
One testEveryone
What contains it

Scope discipline contains it: a bounded, dry-run, batched correction affects only the rows you named. An unbounded UPDATE issued in a hurry does not stop at the tenant, and the recovery becomes the larger incident.

What can go wrong

Failure modes, including of the mitigation
  • The recovery script itself corrupts more data, because it ran against the wrong scope with no dry run.
  • Reconciliation overwrites correct new writes with restored old values, creating a second, harder-to-detect incident.
  • The chosen recovery point is inside the damage window, so the "clean" copy is not clean.
  • The damage is fixed in the database and not in the search index, cache or warehouse, so the system continues to serve wrong answers.
Misreads this invites
  • "We have backups, so any data problem is solved." Backups solve total loss. Partial logical damage needs a scope, a point in time, a side copy and reconciliation.
  • "Restore the latest backup" is the reflex and is usually wrong twice: it may contain the damage, and it discards correct writes made since.
  • "The database is fixed, so the incident is over." Derived stores and external side effects are part of the system's state.
  • "Point-in-time recovery means we can undo anything." It means you can reconstruct state at an instant. Merging that with everything correct that happened since is still your problem.

Operating it

Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.

How you know it worked
  • A row count and a scope definition for the damage, agreed before the correction runs.
  • A dry run whose output was reviewed, followed by batch-by-batch records of what changed.
  • Post-recovery assertions that pass on the live system: referential integrity, invariants, and spot checks against the side copy.
  • Downstream systems confirmed consistent — cache invalidated, index rebuilt, derived data recomputed.
How you get back
  • The preserved snapshot of the damaged state is the rollback. Take it before you start; without it, a bad reconciliation is unrecoverable.
  • Some of it cannot be rolled back at all — emails sent, payments captured, webhooks delivered. Those need a business remedy, not a technical one, and should be named early (Telling People What Is Happening).
What to automate, and what stays human
  • Automate: side restores to a point in time, diff generation between the side copy and live, batched and resumable correction jobs, and post-recovery assertions.
  • Automate the guardrails hardest — dry-run-by-default, a required row-count bound, and a refusal to run without an explicit scope.
  • Keep human: the choice of recovery point, the reconciliation policy where old and new disagree, and the decision to accept remaining inconsistency rather than chase it.
What this costs
  • A side restore costs infrastructure and time before any repair begins, and is almost always faster overall than a full restore plus re-doing lost work.
  • Batched, reviewed correction is slower than a single statement and is the difference between a recoverable mistake and an unrecoverable one.
  • Append-only and soft-delete designs make recovery dramatically easier and cost storage, query complexity and privacy-deletion work forever after.

Where this applies

This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.

  • DATABASE-SPECIFICWhether you can restore to an arbitrary instant depends on continuous log archival being configured and unbroken; some engines and managed tiers offer only periodic snapshots, which forces you to the nearest copy and widens the reconciliation work. Row-level history via versioned tables or change data capture, where available, changes this lesson from a restore into a query.
  • CLOUD-SPECIFICManaged point-in-time recovery typically restores into a *new* instance, which is exactly what you want here — but it means paying for a second instance during recovery and having the quota and network configuration to launch it. Verify that path in a drill; it is often where the recovery stalls.
  • GENERALThe shape — stop the bleeding, preserve evidence, scope, side restore, reconcile in batches, repair downstream — applies to object storage, search indexes and message state as well as to relational data.

Where the depth lives

This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.

Securityaudit-logs
Domains that do not exist yet
  • Distributed Systems — reconstructing a consistent state across shards or services when the damage crossed a boundary, and why a per-service restore point is not a system-wide one.