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.
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:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- 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
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:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| 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
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:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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
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:
"""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
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_gbover 50) or the RTO is tight (rto_minutesunder 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: userpo_minutes * 60, but never less than1(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 usepitr_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.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
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.
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.
-
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?
-
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?
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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_secondsdefaults 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 ... EXTENDEDis 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
- MySQL 8.4 Reference Manual: Backup and Recovery Types (Oracle Corporation)
- MySQL 8.4 Reference Manual: Database Backup Methods (Oracle Corporation)
- MySQL 8.4 Reference Manual: Example Backup and Recovery Strategy (Oracle Corporation)
- MySQL 8.4 Reference Manual: LOCK INSTANCE FOR BACKUP and UNLOCK INSTANCE Statements (Oracle Corporation)
- MySQL 8.4 Reference Manual: Binary Logging Options and Variables (Oracle Corporation)
- MySQL 8.4 Reference Manual: CHECKSUM TABLE Statement (Oracle Corporation)
- Percona Toolkit Documentation: pt-table-checksum (Percona)
- MySQL 8.4 Reference Manual: mysqldump, A Database Backup Program (Oracle Corporation)
- MySQL 8.4 Reference Manual: The Clone Plugin (Oracle Corporation)
- Percona XtraBackup 8.4 Documentation (Percona)
- Percona XtraBackup 8.4 Documentation: Reduced backup lock time (Percona)
- SP 800-34 Rev. 1: Contingency Planning Guide for Federal Information Systems (section 3.2.1, RTO and RPO) (National Institute of Standards and Technology (NIST))
Report a problem with this lesson
Kept only in this browser. Your Learn progress