# Recovery (PITR and snapshot restore)

Tools for recovering a TiDB Cloud cluster after data loss: point-in-time recovery (PITR), snapshot
restore, and the read tools that support them. Verified live against an Essential cluster; see
[spikes/recovery-api.md](spikes/recovery-api.md) for the raw findings.

## The one thing to know first

**A restore ALWAYS creates a new, billable cluster. The source cluster is never changed in place.**
So a restore is safe to run against production (it will not touch production), but it costs money
until you delete the new cluster. Every restore tool is a preflight: it shows you what it will do
and takes no action until you re-run with `confirm: true`.

The two restore tools also require a `confirmTarget` that must match exactly, as a guard against
resolving a different resource between preflight and execute: for `tidb_restore_cluster` it is the
**source cluster id**, and for `tidb_restore_backup` it is the **backupId**. A mismatch executes
nothing.

## Tools

| Tool                      | Group       | What it does                                                                                                                           |
| ------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `tidb_get_recovery_info`  | read        | Tier, whether PITR applies, the snapshot schedule, and how to choose a `pointInTime`. Start here.                                      |
| `tidb_recent_ddl`         | read (data) | Recent schema changes (DROP/TRUNCATE/ALTER) from `information_schema.DDL_JOBS`, with times. Use it to find WHEN a bad change happened. |
| `tidb_list_backups`       | read        | Snapshot backups of a cluster (id, size, time).                                                                                        |
| `tidb_create_backup`      | admin       | Create a manual snapshot ("snapshot before surgery"). Additive; bills for storage. On Starter, the only backup mechanism.              |
| `tidb_restore_cluster`    | admin       | PITR: restore to a past UTC timestamp. Creates a new cluster.                                                                          |
| `tidb_restore_backup`     | admin       | Restore from a snapshot backup id (e.g. a deleted cluster from the Recycle Bin). Creates a new cluster.                                |
| `tidb_get_restore_status` | read        | Progress of a restore, read from the new cluster's state.                                                                              |

The restore and create-backup tools require the `admin` write group (they are cluster-level
mutations). The read tools are available in read-only mode. `tidb_recent_ddl` needs a SQL connection,
so it is disabled by `--disable-data-tools`; the other recovery tools are management-plane and stay
available.

**Deleting a backup is done in the TiDB Cloud console, by design.** This server can create and
restore backups but deliberately does not expose deleting one: an agent that can delete both backups
and clusters is the ransomware sequence (destroy the safety net, then the data). Backup deletion
stays a human console action.

## Tiers

PITR and snapshot recovery are available on **TiDB Cloud Essential and Premium** only. **Starter has
neither.** If you point a recovery tool at a Starter cluster (or a wrong id), you get a plain message
saying so, not an opaque 404.

Note: recovery is the one area where this server reaches Premium. The cluster-management and data
tools (list/get/query/execute) are still Serverless-only (Starter/Essential).

## Choosing a `pointInTime` (the important gotcha)

There is **no API that returns the exact restorable window**, and the **latest restorable point lags
real time** (about 15 minutes behind on a fresh Essential cluster). Consequences:

- Do **not** pass `now` or a near-now timestamp. It will be rejected with
  `"Point in time is not in the pitr time range."`
- A rejected restore creates **nothing** (it is free to retry). If you get that message, step the
  timestamp **further back** and try again.
- `pointInTime` is UTC RFC3339, e.g. `2026-07-22T19:03:06Z`.
- The server does not try to compute the window; the restore call's server-side validation is the
  real gate, and its message is surfaced to you verbatim.

## Typical recovery flow

1. Something bad happened (a table was dropped, rows deleted by a runaway migration).
2. `tidb_recent_ddl` on the cluster to find the DROP/TRUNCATE/ALTER and its `CREATE_TIME`.
3. `tidb_get_recovery_info` to confirm PITR applies and read the timestamp guidance.
4. `tidb_restore_cluster` with `pointInTime` set just **before** the bad change. Run it once to see
   the preflight (target project, region, RCU, billable, reversible), then again with
   `confirm: true`.
5. `tidb_get_restore_status` with the new cluster id until state is `ACTIVE`.
6. Verify the recovered data on the new cluster, then move it into place (repoint your app, or copy
   the data back).
7. Delete the new cluster with `tidb_delete_cluster` when you are done (it bills until deleted). A
   PITR restore is reversible in exactly this sense: delete the new cluster to undo it.

## Where the restored cluster lands (project)

By default the new cluster is placed in the **same project as the source** cluster (verified: the
restore honors the `tidb.cloud/project` label). Override with `projectId` if you want it elsewhere.
When the server is confined with `--project`, the restore is refused if the source is in another
project, and the new cluster is forced into the confined project. This keeps a recovery from
silently landing in the "default virtual project".

## Cost

A restored cluster is a normal billable cluster of its tier. Essential is usage-based (RCU); an idle
restored cluster that takes no traffic costs very little, but it is not free, and storage is billed.
**Delete the new cluster when you are finished.** For a recovery drill, treat "create -> verify ->
delete" as one unit of work.

## Snapshot restore (verified)

`tidb_restore_backup` is verified live (2026-07-22): it restores from a backup id and creates a new
cluster in the source's project, same as PITR. It is the path for a fully deleted cluster whose PITR
window is gone. Prefer `tidb_restore_cluster` (PITR) when a timestamp works, since PITR reaches any
moment in the window, not just a snapshot point.

Notes on backups (verified live):

- Only a **`SUCCEEDED`** backup can be restored (`tidb_list_backups` flags which are restorable). The
  terminal success state is `SUCCEEDED`, not `COMPLETED`; a `RUNNING` or `FAILED` backup cannot be
  restored.
- A manual snapshot of a small (~1.6 MB) cluster took about **6 minutes**; it scales with data size.
- Scheduled auto-backups can fail (one showed `FAILED`), so do not assume a usable backup exists.
- API quirk: the list endpoint returns the backup id as `id`, while creating one returns it as
  `backupId`. The tool handles both; you always pass the value as `backupId`.

Creating a backup is exposed as `tidb_create_backup` (`POST /tidbs/{id}/backups {"name": ...}`).
Deleting a backup (`DELETE /tidbs/{id}/backups/{backupId}`) is a verified endpoint but deliberately
NOT exposed as a tool: it is a console-only action by design (see above).
