Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

MySQL & MariaDB in Depth  Module 10 – Backups, restores and point-in-time recovery

Backup strategy, RPO, RTO and restore drills

Set a target RPO and RTO, choose logical, physical or snapshot backups, size binary log retention, and run a timed restore drill that verifies the data.

  • Intermediate
  • 15 minutes
  • Examples run with MySQL 8.4.11, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Choose logical, physical and snapshot backups for a stated RPO and RTO
  • Capture a consistent copy with one snapshot or a backup lock, not a read lock held for the whole copy
  • Set binary log retention to cover the point-in-time window you promise
  • Measure a restore drill and verify the restored data with checksums

Before you start

What you’ll learn · Before you start

What you will learn

  • Choose logical, physical and snapshot backups for a stated RPO and RTO
  • Capture a consistent copy with one snapshot or a backup lock, not a read lock held for the whole copy
  • Set binary log retention to cover the point-in-time window you promise
  • Measure a restore drill and verify the restored data with checksums

Before you start

On this page

A restore drill on a MySQL 8.4.11 server turned a 65,317-byte dump back into 2,500 rows and proved, by checksum, that not one of them had changed. The copy you take is never the point; the restore you can finish in time, and trust, is.

A backup strategy answers two questions with numbers. How much recent data may a failure cost, the recovery point objective: The most recent data a restore may lose, stated as a time span: an RPO of five minutes means a failure may cost up to the last five minutes of committed changes. or RPO? And how long may the service stay down while you restore, the recovery time objective: The longest a service may stay down while it is restored: an RTO of one hour means the data must be back and serving within an hour of the failure. or RTO? The usual baseline is a daily full backup plus binary log: The server's log of committed changes, a numbered series of files plus an index file, written as events in commit order. Replicas apply it, and a restore replays it to bring a backup up to date. More in “The binary log and mysqlbinlog” copied away continuously: the full sets how far back you begin, the logs decide how little you lose. The sections below pick between logical, physical and snapshot backups, keep the logs long enough, take a consistent copy, and time a restore drill: A rehearsed restore of a real backup into a throwaway server, timed and checked against the source, so the backup and its recovery time are proven before a real failure. that checks every row.

RPO and RTO decide the backup plan

RPO and RTO pull in different directions, and knowing which one is tight shapes everything else.

An RPO is a promise about loss. A daily full backup on its own gives an RPO of up to a day: a failure at 14:35 that destroys the data directory leaves you with last night’s dump and nothing since. The binary log closes that gap, because it records every committed change in order. Once last night’s dump is restored, replaying the log carries the data forward to any moment whose events you still hold; point-in-time recovery with binary logs walks through it step by step. Your real RPO is then how much of that stream has reached safety off the server when it dies: shipped to another host every few seconds, the exposure is seconds; copied once a night, it is a day.

An RTO is a promise about downtime, counted from the failure to the moment the service answers again. It is dominated by restore speed: fetching the backup, reloading or copying it, replaying the logs and rebuilding whatever the engine needs. A plan can meet a tight RPO and still miss its RTO when the restore itself takes hours. Step through one recovery from last night’s full and its logs, and note which stage settles the loss and where the downtime ends:

Interactive · stage by stage A recovery from a full backup and the binary logs Step through a recovery after a failure, and watch where the RPO (data lost) and the RTO (time down) are measured.
A recovery from a full backup and the binary logs, step 5 of 5. Showing Restore the full backup, Replay the binary logs, Stop at the safe point, Serve traffic again. In focus: Serve traffic again.Restore the full backuplast night's dumpReplay the binary logsfrom the dump's positionStop at the safe pointjust before the troubleServe traffic againthe data is back

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Two clocks, not one. A failure starts two clocks. One measures the data behind it that you cannot get back, the RPO; the other measures the time ahead until the service answers again, the RTO. A plan is judged against both.
  2. Start from the last full. Restore last night's full backup first. On its own it leaves the data as it was when the backup ran, so if you stopped here the RPO would be a whole day of commits.
  3. Replay the binary logs. The binary log holds every change committed after the dump, in order. Replaying it from the position the dump recorded carries the data forward, and the RPO shrinks to however much of the stream you still hold.
  4. Stop before the trouble. For a wrong statement or a bad deploy, the replay stops just before that point. Work committed after it is not in the copy yet, and whether to bring it back is a decision of its own.
  5. The time to here is the RTO. The wall-clock from the failure to serving again is the RTO. Fetching the backup, reloading it and replaying the logs all add to it, so restore speed and log length decide whether the promise holds.

Replaying the logs and stopping at the right point both lean on the binary logs, and the logs only help while they still exist, so retention is part of the plan, not an afterthought.

When the logs expire too soon

binlog_expire_logs_seconds is 2,592,000 on a new 8.4.11 server, which is 30 days. It must stay longer than the gap between full backups plus the point-in-time window you promise. Set it to two days while you keep only weekly fulls taken on Sunday, and the Monday log that a Saturday restore needs is already gone: the full still loads, but the logs after it have a gap, so nothing past the full can be replayed and the real RPO silently becomes nearly a week.

Checkpoint

Check: RPO, RTO and the binlog window

  1. Question 1 of 2 A service ships its binary log to another host every 5 seconds and takes a full backup each night. A crash destroys the data directory. What bounds the RPO?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The few seconds of log not yet shipped off the server

    RPO is set by how much of the change stream has reached safety off the server. With the log shipped every few seconds, only that short tail is at risk, not the whole day since the last full.

  2. Question 2 of 2 binlog_expire_logs_seconds is 2,592,000 by default on MySQL 8.4.11. How many days is that?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 30 days

    2,592,000 divided by 86,400 seconds in a day is 30 days, the default binary log expiration period.

Three kinds of backup trade speed for portability

Three kinds of backup copy a MySQL database, and the strategy’s first choice is between them.

A logical backup, which mysqldump writes, is SQL text: the CREATE TABLE and INSERT statements that rebuild the data on any server. A physical backup is a raw copy of the data directory’s files, which a tool such as Percona XtraBackup or the CLONE plugin takes while the server runs. A file-system snapshot freezes a whole volume at an instant, through the storage layer or LVM, and copies the frozen image; the manual’s recipe holds FLUSH TABLES WITH READ LOCK only while the snapshot is being made.

They trade speed against portability. A logical dump is slow in both directions: the server turns every row into SQL text to write it, and a reload replays the rows and rebuilds every index, but the text is the easiest to carry to a new version or a different machine. A physical copy or a snapshot copies files without converting them and restores by putting them back, which is fast, but the files only fit a server of a similar build and layout. Answer the table’s two questions for a database you look after, and it marks which kinds fit:

Interactive · answer and compare Which backup method fits the service? Answer two questions, about the size of your data and the server you will restore onto. The table marks the methods that fit and says why the others do not.
How large is the database you must restore?
Will you restore onto a server of the same build and file layout?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Three backup methods against what decides between them, from the MySQL 8.4 manual
Method Restore speed Backup speed Portability Point-in-time ready
Logical dump What it copies: SQL text, via mysqldump Poor reloads row by row and rebuilds indexes, so it is the slowest to restore Poor the server converts every row to SQL text, slower than copying files Good SQL text that loads on another version or machine Good with --source-data it records the binlog position to replay from
Physical copy What it copies: raw data-directory files Good copies the files back, so a restore is fast Good copies the files without converting them, the faster way to take a copy Poor fits only a server of a similar build and layout Good the tool records the position with the copy
Volume snapshot What it copies: a frozen file system Good mount the frozen volume, so a restore is fast Good freezes the volume under a brief read lock; the image is copied after Poor tied to the volume and its file system Mixed note the binlog position yourself while the read lock is held
How the answers decide
  • “How large is the database you must restore?” Hundreds of gigabytes or more: rules out Logical dump. A logical reload rebuilds every index row by row, so for hundreds of gigabytes it is the slowest restore; the manual points large databases that must recover quickly to physical backups.
  • “How large is the database you must restore?” Tens of gigabytes or less: adds a caveat to Logical dump. For smaller data a logical dump is often quick enough to reload and stays portable across versions; a timed drill tells you whether it fits the RTO.
  • “Will you restore onto a server of the same build and file layout?” No, another version or host: rules out Physical copy and Volume snapshot. A physical copy or a volume snapshot restores only onto a server of a matching build and file layout.
  • “Will you restore onto a server of the same build and file layout?” Yes, a matching build and “How large is the database you must restore?” Hundreds of gigabytes or more: adds a caveat to Physical copy and Volume snapshot. On matching hardware a physical copy or a snapshot restores far faster than a reload for large data.

The MySQL manual draws the same line: physical backups suit large, important databases that must be recovered quickly, logical ones smaller data or a move to a different machine. For tens of gigabytes that must move across versions, a logical dump is the portable choice, and a drill shows whether its reload fits the RTO. For hundreds of gigabytes on matching hardware, a physical copy or a snapshot keeps the restore short.

Checkpoint

Check: Logical, physical and snapshot backups

  1. Question 1 of 2 A 20 GB database must be restored onto a different MySQL version. Which backup method fits best?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A logical dump

    For data that is small enough to reload quickly and must move across versions or machines, a logical dump is the portable choice; physical copies and snapshots restore only onto a matching build.

  2. Question 2 of 2 Why does a physical copy usually restore faster than a logical dump of the same database?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It puts the data files back, while a logical reload rebuilds every index row by row

    A logical reload runs the INSERTs and rebuilds each index as it goes, the slowest part for large tables. A physical copy restores the already-built files, so it is faster on matching hardware.

A consistent copy needs one snapshot, not a frozen server

A backup is only useful if it is consistent: every table at one instant, not a smear across a busy minute.

InnoDB gives logical dumps that at almost no cost. mysqldump --single-transaction starts a transaction and takes one consistent read: A SELECT that returns the version of each row the transaction's read view may see, without taking any lock on the rows it touches; the default read mode in InnoDB under REPEATABLE READ and READ COMMITTED., so the whole dump sees the database as it stood when the transaction began, even while other clients keep committing. Reading it blocks no reader and no writer. Other clients should still not run ALTER, CREATE, DROP, RENAME or TRUNCATE TABLE meanwhile: a consistent read does not shield the dump from those statements, which can make it read wrong contents or fail. With --source-data the dump also records the binary-log position of that instant, where a later replay begins. To read that position, or the GTID set of a server with GTIDs on, mysqldump holds FLUSH TABLES WITH READ LOCK for a moment at the start; the pause stays short unless a long-running update makes the lock, and every write queued behind it, wait.

A physical tool cannot open a read view over the files it copies, so Percona XtraBackup, on a MySQL server, holds a backup lock: A light instance-level lock taken by LOCK INSTANCE FOR BACKUP that permits DML during an online physical backup while it blocks file changes and most DDL that would spoil the copy. instead. LOCK INSTANCE FOR BACKUP takes an instance-level lock that “permits DML during an online backup”, as the manual puts it, yet stops the operations that would leave the copy inconsistent: it blocks files from being created, renamed or removed and most DDL, and it needs the BACKUP_ADMIN privilege. It is far lighter than FLUSH TABLES WITH READ LOCK, which stops every write for the length of the copy. Step through both on a live server, the dump with its moment of locking first:

Interactive · step by step Taking a consistent copy while the server runs Step through a mysqldump that locks for a moment, then reads one snapshot while other clients keep writing, and see what a physical tool does instead.
Taking a consistent copy while the server runs, step 4 of 4. Showing mysqldump, Live server (backup lock), Other clients (still writing). In focus: Live server.mysqldumpone snapshotLive serverbackup lockOther clientsstill writing

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. A moment's global lock. With --single-transaction and --source-data, mysqldump first takes FLUSH TABLES WITH READ LOCK. Every write waits while it is held, so it must stay a moment; a long-running update at this point would make the lock, and everyone behind it, wait.
  2. One read view, one position. Under the lock it starts a transaction with a consistent snapshot and reads the binary-log position of that instant, the point a later replay starts from. Then it releases the lock.
  3. Writes keep flowing. Other clients commit again while the dump reads every table as it stood at that instant. It neither blocks them nor sees their changes, as long as no one alters, drops, renames or truncates a table being dumped.
  4. Files need a backup lock. A physical tool copies files, not a read view, so XtraBackup holds LOCK INSTANCE FOR BACKUP: an instance lock that lets DML continue while it blocks DDL and file changes that would spoil the copy.

Locking the whole server to be safe

The slip is to reach for FLUSH TABLES WITH READ LOCK around every backup. On a busy server that pause blocks writes until the copy finishes, and a logical dump can run for minutes. A consistent read for InnoDB dumps, or a backup lock for a physical copy with XtraBackup, gives the same consistent instant without the outage.

Checkpoint

Check: A consistent copy and its locks

  1. Question 1 of 2 What does mysqldump --single-transaction give an InnoDB dump?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: One consistent read, so every table is dumped at the same instant without blocking writers

    --single-transaction opens a transaction and takes one consistent read, so the whole dump reflects one instant even as writes continue, and reading it blocks no reader or writer. Only recording a binary-log position or a GTID set adds a moment's global read lock at the start.

  2. Question 2 of 2 LOCK INSTANCE FOR BACKUP is held during a physical backup. Which operation is still allowed?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Ordinary INSERTs and UPDATEs

    LOCK INSTANCE FOR BACKUP permits DML during an online backup while preventing operations that could make the copy inconsistent, such as file creation, renaming and removal and most DDL.

A restore drill times the RTO and proves the data

A backup you have never restored is a hope, not a plan. A restore drill turns it into a fact: reload a real backup into a throwaway server, time it, and check the result against the source. The session below does exactly that for a small shop schema, on two fresh 8.4.11 servers:

Dump a schema, reload it into a new server, and compare checksums Python · drill_session.py
"""A restore drill on MySQL 8.4.11: dump a seeded schema with a consistent snapshot, reload it
into a throwaway server, and compare CHECKSUM TABLE ... EXTENDED on both.

The byte count of the dump and the row counts are the real, reproducible figures. The elapsed
times change from run to run, so they are shown through a mask (<elapsed>) and the record stays
the same on every run; run the drill yourself to read the real seconds.
"""

import re
import sys
import time

from mysql_session import Server

# A small, fixed data set so the dump is byte-for-byte the same on every run.
SEED = """
SET SESSION cte_max_recursion_depth = 5000;
CREATE DATABASE shop;
USE shop;
CREATE TABLE customers (
  id   INT PRIMARY KEY,
  name VARCHAR(60) NOT NULL,
  city VARCHAR(40) NOT NULL
) ENGINE=InnoDB;
CREATE TABLE orders (
  id           INT PRIMARY KEY,
  customer_id  INT NOT NULL,
  amount_paise BIGINT NOT NULL,
  status       VARCHAR(12) NOT NULL,
  KEY (customer_id),
  CONSTRAINT fk_cust FOREIGN KEY (customer_id) REFERENCES customers (id)
) ENGINE=InnoDB;
INSERT INTO customers (id, name, city)
WITH RECURSIVE n (seq) AS (SELECT 1 UNION ALL SELECT seq + 1 FROM n WHERE seq < 500)
SELECT seq, CONCAT('Customer ', seq), ELT(1 + seq % 4, 'Pune', 'Delhi', 'Chennai', 'Kochi') FROM n;
INSERT INTO orders (id, customer_id, amount_paise, status)
WITH RECURSIVE n (seq) AS (SELECT 1 UNION ALL SELECT seq + 1 FROM n WHERE seq < 2000)
SELECT seq, 1 + seq % 500, 1000 + seq * 7, ELT(1 + seq % 3, 'new', 'packed', 'shipped') FROM n;
"""

CHECKSUM = "CHECKSUM TABLE shop.customers, shop.orders EXTENDED"


def masked(line):
    """Replace every per-run duration with <elapsed> so two runs print the same line."""
    return re.sub(r"\d+\.\d+ s", "<elapsed>", line)


with Server() as src, Server() as dst:
    src.up()
    src.query(SEED)
    src.sql(
        "SELECT (SELECT COUNT(*) FROM shop.customers) AS customers,"
        " (SELECT COUNT(*) FROM shop.orders) AS orders",
        note="what the backup must protect",
    )

    # Dump with --single-transaction, so InnoDB reads one snapshot while writes could go on,
    # and --source-data=2, so the dump records the binary-log position to replay from.
    args = ["mysqldump", "--single-transaction", "--source-data=2", "--skip-dump-date",
            "--no-tablespaces", "--databases", "shop"]
    t0 = time.perf_counter()
    dump = src.output(*args, connect=True)
    dump_secs = time.perf_counter() - t0
    nbytes = len(dump.encode())
    position = next(l for l in dump.splitlines() if "CHANGE REPLICATION SOURCE" in l)
    print("$ " + " ".join(args) + " > shop.sql")
    print(f"-- the dump is {nbytes} bytes of SQL")
    print("-- " + position.strip().lstrip("- ").rstrip(";"))
    print()

    # Restore into a brand-new server and time it: this is the RTO you are measuring.
    dst.up()
    (dst.base / "shop.sql").write_text(dump)
    t1 = time.perf_counter()
    dst.query((dst.base / "shop.sql").read_text())
    restore_secs = time.perf_counter() - t1
    dst.sql(
        "SELECT (SELECT COUNT(*) FROM shop.customers) AS customers,"
        " (SELECT COUNT(*) FROM shop.orders) AS orders",
        note="reloaded into a throwaway server",
    )

    # The step that turns a copy into a drill: do the restored tables match the source?
    src.sql(CHECKSUM, note="source")
    dst.sql(CHECKSUM, note="restored")
    if src.query(CHECKSUM) != dst.query(CHECKSUM):
        sys.exit("the restored tables do not match the source")

    total = dump_secs + restore_secs
    print(masked(f"-- measured: dump {dump_secs:.3f} s, restore {restore_secs:.3f} s,"
                 f" {total:.3f} s in all for 2,500 rows (times vary per run, masked here)"))

Output

-- what the backup must protect
$ mysql -t -e "SELECT (SELECT COUNT(*) FROM shop.customers) AS customers, (SELECT COUNT(*) FROM shop.orders) AS orders"
+-----------+--------+
| customers | orders |
+-----------+--------+
|       500 |   2000 |
+-----------+--------+

$ mysqldump --single-transaction --source-data=2 --skip-dump-date --no-tablespaces --databases shop > shop.sql
-- the dump is 65317 bytes of SQL
-- CHANGE REPLICATION SOURCE TO SOURCE_LOG_FILE='binlog.000001', SOURCE_LOG_POS=60525

-- reloaded into a throwaway server
$ mysql -t -e "SELECT (SELECT COUNT(*) FROM shop.customers) AS customers, (SELECT COUNT(*) FROM shop.orders) AS orders"
+-----------+--------+
| customers | orders |
+-----------+--------+
|       500 |   2000 |
+-----------+--------+

-- source
$ mysql -t -e "CHECKSUM TABLE shop.customers, shop.orders EXTENDED"
+----------------+------------+
| Table          | Checksum   |
+----------------+------------+
| shop.customers | 2435803798 |
| shop.orders    |  351868791 |
+----------------+------------+

-- restored
$ mysql -t -e "CHECKSUM TABLE shop.customers, shop.orders EXTENDED"
+----------------+------------+
| Table          | Checksum   |
+----------------+------------+
| shop.customers | 2435803798 |
| shop.orders    |  351868791 |
+----------------+------------+

-- measured: dump <elapsed>, restore <elapsed>, <elapsed> in all for 2,500 rows (times vary per run, masked here)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 drill_session.py

The seeded database held 500 customers and 2,000 orders. mysqldump --single-transaction --source-data=2 wrote a 65,317-byte dump and recorded its starting point as binlog.000001 position 60,525, so a point-in-time recovery would know exactly which events to replay next. Reloaded into a brand-new server, the 2,500 rows came back; the check that follows is what makes this a drill rather than a copy.

CHECKSUM TABLE ... EXTENDED reads the whole table row by row and returns one number for its contents; the QUICK form returns only the live checksum that a MyISAM table created with CHECKSUM=1 keeps, so on InnoDB it gives NULL. The source and the restored copy returned the same numbers, 2435803798 for customers and 351868791 for orders, so every row survived the round trip. The comparison is fair only between the same moment’s data on the same version and row format, because the value depends on the row format; here nothing wrote to the source after the dump. The statement also read-locks each table while it runs, so to check a busy source against its replicas, Percona’s pt-table-checksum works in small chunks instead: its checksum queries run on the source and replicate, and it exits non-zero when a replica differs.

The elapsed times the drill printed are masked in the record because they change from run to run. Repeat the drill on your own data and hardware and read the real seconds: the restore figure is what reloading that much data costs you, the part of the RTO you can measure today, and a far more honest number than a guess.

Checkpoint

Check: The restore drill and its checks

  1. Question 1 of 2 What does CHECKSUM TABLE ... EXTENDED do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Reads the whole table row by row and returns one checksum of its contents

    With EXTENDED the entire table is read row by row and a checksum of its contents is returned. QUICK instead returns a stored live checksum, or NULL when none is available.

  2. Question 2 of 2 In the recorded drill the source and restored tables returned the same CHECKSUM TABLE ... EXTENDED values. What does that establish?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The backup reloaded with every row intact

    The matching checksums, 2435803798 for customers and 351868791 for orders on both servers, prove the data survived the dump and reload. The RTO is the measured time, a separate number.

Practise: plan a backup for a service

A backup strategy is a set of numbers before it is a set of commands: how often to take a full, how long to keep the logs, and whether the two together meet the RPO and RTO a service was promised. The exercise hands your function a service’s needs as a dictionary of numbers and asks for a plan that meets them.

Exercise · Easy · Python

Plan a backup that meets an RPO and RTO

A service hands you its needs and asks for a backup plan. Turn the numbers into a plan that meets them, using the baseline from the lesson: a daily full backup, binary logs shipped off the server, and a retention long enough for point-in-time recovery.

Write plan_backup(needs) in backup_plan.py. The argument needs is a dictionary with four integer keys:

  • "data_gb": the size of the database, in gigabytes.
  • "rpo_minutes": the most recent data a failure may cost.
  • "rto_minutes": the longest the service may be down during a restore.
  • "pitr_window_days": how far back a point-in-time recovery must be able to reach.

Return a dictionary with exactly these four keys:

  • "method": "physical" when the data is large (data_gb over 50) or the RTO is tight (rto_minutes under 30), because a physical copy restores files quickly; otherwise "logical", which is portable across versions and machines.
  • "full_backup": always "daily" — the baseline is one full backup a day.
  • "ship_binlog_seconds": how often to copy the binary log off the server, in seconds. To meet the RPO, ship at least as often as the allowed loss: use rpo_minutes * 60, but never less than 1 (ship continuously when the RPO is 0).
  • "binlog_retention_days": how long to keep the binary logs. It must exceed the point-in-time window plus the one-day gap between full backups, so use pitr_window_days + 2.

For {"data_gb": 20, "rpo_minutes": 5, "rto_minutes": 60, "pitr_window_days": 7}, plan_backup() returns {"method": "logical", "full_backup": "daily", "ship_binlog_seconds": 300, "binlog_retention_days": 9}.

Starter code · backup_plan.py

def plan_backup(needs):
    """Turn a service's needs into a backup plan, as the prompt describes.

    needs has the integer keys "data_gb", "rpo_minutes", "rto_minutes" and "pitr_window_days".
    Return a dict with "method", "full_backup", "ship_binlog_seconds" and "binlog_retention_days".
    """
    # Replace these lines with your plan.
    return {
        "method": "logical",
        "full_backup": "daily",
        "ship_binlog_seconds": 1,
        "binlog_retention_days": 0,
    }
The sample tests · test_backup_plan.py
"""Sample tests: five services with different needs, each wanting a plan that meets its RPO and RTO."""

from backup_plan import plan_backup


def test_small_portable_service():
    """small data, loose RTO: a logical dump, binlogs shipped every 5 minutes"""
    needs = {"data_gb": 20, "rpo_minutes": 5, "rto_minutes": 60, "pitr_window_days": 7}
    assert plan_backup(needs) == {
        "method": "logical",
        "full_backup": "daily",
        "ship_binlog_seconds": 300,
        "binlog_retention_days": 9,
    }


def test_large_service_needs_physical():
    """over 50 GB: a physical copy so the restore fits the RTO"""
    needs = {"data_gb": 400, "rpo_minutes": 2, "rto_minutes": 90, "pitr_window_days": 14}
    assert plan_backup(needs) == {
        "method": "physical",
        "full_backup": "daily",
        "ship_binlog_seconds": 120,
        "binlog_retention_days": 16,
    }


def test_tight_rto_chooses_physical_even_when_small():
    """small data but a sub-30-minute RTO still picks the faster physical restore"""
    needs = {"data_gb": 10, "rpo_minutes": 1, "rto_minutes": 20, "pitr_window_days": 3}
    assert plan_backup(needs) == {
        "method": "physical",
        "full_backup": "daily",
        "ship_binlog_seconds": 60,
        "binlog_retention_days": 5,
    }


def test_zero_rpo_ships_continuously():
    """an RPO of 0 means ship the binlog continuously, never slower than every second"""
    needs = {"data_gb": 30, "rpo_minutes": 0, "rto_minutes": 45, "pitr_window_days": 1}
    assert plan_backup(needs) == {
        "method": "logical",
        "full_backup": "daily",
        "ship_binlog_seconds": 1,
        "binlog_retention_days": 3,
    }


def test_values_at_the_limits_stay_logical():
    """exactly 50 GB is not over 50 and exactly 30 minutes is not under 30, so the plan stays logical"""
    needs = {"data_gb": 50, "rpo_minutes": 10, "rto_minutes": 30, "pitr_window_days": 2}
    assert plan_backup(needs) == {
        "method": "logical",
        "full_backup": "daily",
        "ship_binlog_seconds": 600,
        "binlog_retention_days": 4,
    }
A hint

Build the plan one key at a time and check each against its rule in the prompt. Three cases are easy to miss: an RPO of 0 still needs a shipping interval of at least one second, either condition alone makes the method physical, and a value exactly at a limit is neither over it nor under it.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then test yourself on the four ideas above, RPO and RTO, the three kinds of backup, a consistent copy and the drill:

Practice that adapts to you

Practise this lesson

9 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 What does an RPO of five minutes promise?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: A failure may cost at most the last five minutes of committed data

    An RPO is a promise about loss, measured as a time span of recent changes a failure may cost. How you meet it, by shipping logs or keeping a replica, is a separate decision.

  2. Question 2 of 9 Two plans meet the same one-minute RPO, but one restores in 10 minutes and the other in 3 hours. What separates them?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: The RTO: restore speed and log length decide the downtime

    RPO and RTO are separate promises. Equal RPO plans can have very different RTOs because fetching, reloading and replaying take different amounts of time.

  3. Question 3 of 9 Full backups run every Sunday night, and you promise point-in-time recovery to any moment of the last 3 days. What is the smallest binlog_expire_logs_seconds that keeps every log such a restore needs?

    Type a number.

    A hint

    A restore starts from the last full before the moment you recover to. How long ago, at worst, did that full run?

    Show the answer: Question 3 of 9

    Answer: 864000 seconds

    The worst case is a moment 3 days ago whose last full ran almost 7 days before it, so the logs must reach back 7 + 3 = 10 days from now: 10 × 86,400 = 864,000 seconds. Any shorter setting leaves a gap after some full.

  4. Question 4 of 9 What does a logical backup written by mysqldump contain?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: SQL statements such as CREATE TABLE and INSERT that rebuild the data

    A logical backup saves the data as SQL or delimited text that recreates it on any server, which is what makes it portable but slower to take and to restore.

  5. Question 5 of 9 A team wants to restore a volume snapshot onto a server with a different file system and version. What is the risk?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: The snapshot is tied to its volume and layout, so it may not restore cleanly

    A snapshot copies the raw file system, so it restores onto a matching volume and build. To move data across versions, a logical dump is the portable choice.

  6. Question 6 of 9 Why must a backup be consistent?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: So every table reflects one instant, not a mix from before and after other commits

    A consistent backup captures the database at a single instant, so foreign keys and related rows agree. A copy taken while writes land unevenly can restore a state the database never actually held.

  7. Question 7 of 9 What is the cost of wrapping a backup in FLUSH TABLES WITH READ LOCK on a busy server?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: It blocks every write until the copy finishes

    FLUSH TABLES WITH READ LOCK gives a consistent instant but stops every write while the backup runs. A consistent read or a backup lock gives the same instant without the outage; mysqldump with --source-data holds the global read lock only for a moment at the start, to read the binary-log position.

  8. Question 8 of 9 Why run a restore drill instead of trusting that the backups work?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: Only a real restore proves the backup loads and measures its RTO

    A restore drill reloads a real backup into a throwaway server, so it proves the backup is loadable and gives a measured RTO instead of a guess.

  9. Question 9 of 9 Why does mysqldump --source-data=2 record the binary-log position?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: So a point-in-time recovery knows which events to replay after the dump

    With --source-data the dump records the binary-log file and position of its snapshot, in the recorded run binlog.000001 at 60,525, so replay starts exactly where the backup left off.

Interview questions

Warm-up, for freshers: why can a daily full backup alone never meet a one-minute RPO?

A full backup captures the database only at the instant it runs. With one full a night, everything committed between that run and a failure the next afternoon lives nowhere else, so a crash can cost many hours of data: the RPO is the age of the last full, not one minute. To promise a one-minute RPO you also need the change stream off the server at least that often, the binary log shipped continuously to another host, or a replica kept nearly in step. The full backup bounds how far back a restore starts; the copied logs bound how little it loses.

Two questions of this module start where the drill above stops. Work out a first move for each, then open it:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    On a MySQL 8.4 server, someone ran DELETE FROM orders with no WHERE clause on a Friday afternoon and committed it. You have last night's full backup and every binary log since. How do you recover the table to the moment just before that statement, and what do you do about the writes that came after it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    Your team promises a 30-minute RTO for a 200 GB MySQL 8.4 database, but no one has measured a real restore. How do you measure the current RTO, where does the time usually go, and what would you change to bring it under 30 minutes?

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which two numbers judge a backup plan?

    The RPO, how much data a failure may cost, and the RTO, how long the service may stay down, both counted from the moment of failure.

  2. What is the common baseline backup plan, and what does each half decide?

    A daily full backup plus binary logs copied away continuously. The full backup is where a restore starts; the shipped logs decide how little the restore loses.

  3. How do logical dumps, physical copies and file-system snapshots trade off?

    Logical dumps are portable but the slowest to take and to restore. Physical copies and snapshots copy and restore fast, but only onto a server of a similar build.

  4. How do you get a consistent backup without blocking writes for the whole copy?

    A consistent read for an InnoDB logical dump, with a global read lock held only for a moment to note the binary-log position or GTID set, or a backup lock for a physical tool such as XtraBackup. A FLUSH TABLES WITH READ LOCK held for the whole copy blocks writes until it ends.

  5. How long must binary logs stay on the server for point-in-time recovery to work?

    Longer than the gap between full backups plus the point-in-time window. binlog_expire_logs_seconds defaults to 2,592,000 (30 days); set it shorter than that sum and the logs a restore needs are gone before it runs.

  6. What is the only real proof that a backup can be restored?

    A drill that reloads it into a throwaway server and compares CHECKSUM TABLE ... EXTENDED with the source. The time the drill takes is the honest starting point for the RTO.

Key takeaways

  • A backup plan is judged by two numbers: the RPO, how much data a failure may cost, and the RTO, how long the service may be down, both measured from the moment of failure.
  • The common baseline is a daily full backup plus binary logs copied away continuously; the full sets where a restore starts and the shipped logs set how little it loses.
  • Logical dumps are the portable kind but the slowest to take and to restore; physical copies and file-system snapshots copy and restore fast, but only onto a server of a similar build.
  • A consistent copy needs a consistent read for InnoDB logical dumps, with at most a moment’s global read lock to note the binary-log position or GTID set, or a backup lock for a physical tool such as XtraBackup, never a server-wide read lock held for the whole copy.
  • binlog_expire_logs_seconds defaults to 2,592,000 (30 days) and must exceed the gap between fulls plus the point-in-time window, or the logs a restore needs are deleted before it runs.
  • A restore drill that reloads into a throwaway server and compares CHECKSUM TABLE ... EXTENDED is the only proof that a backup restores, and its restore time is the honest starting point for the RTO.

Terms in this lesson

backup lock
A light instance-level lock taken by LOCK INSTANCE FOR BACKUP that permits DML during an online physical backup while it blocks file changes and most DDL that would spoil the copy.
recovery point objective
The most recent data a restore may lose, stated as a time span: an RPO of five minutes means a failure may cost up to the last five minutes of committed changes.
recovery time objective
The longest a service may stay down while it is restored: an RTO of one hour means the data must be back and serving within an hour of the failure.
restore drill
A rehearsed restore of a real backup into a throwaway server, timed and checked against the source, so the backup and its recovery time are proven before a real failure.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.