For most of my MySQL career, building a new replica meant a ritual: run XtraBackup on a donor, ship the files, run prepare, fix ownership, note the binlog position, start replication, and hope nobody fat-fingered a step. The CLONE plugin, available since MySQL 8.0.17, collapses that ritual into two SQL statements and a restart. I resisted it for a while out of habit. I was wrong to.
These notes cover how I set it up, the gotchas that actually bit me in production, how to monitor a clone while it runs, and the honest boundaries of where clone belongs versus a real backup tool.
What clone actually does
A clone is a physical, page-level copy of the donor's InnoDB data, taken while the donor stays online and transferred either into a local directory or directly over the MySQL protocol to a recipient server. That last part is the operational win: no SSH between database hosts, no rsync of a prepared backup, no intermediate staging disk sized to your dataset. The recipient pulls the data itself, and when it finishes it knows the donor's binlog position and GTID set, so replication setup is deterministic.
It is worth being precise about scope. Clone copies InnoDB data. It does not copy server configuration, and it is a provisioning mechanism rather than a backup strategy; more on that boundary at the end.
There is also a local variant, CLONE LOCAL DATA DIRECTORY, which snapshots the running instance into a directory on the same host without touching the live data. I use it less often than the remote form, but it has a niche worth knowing: taking a quick physical copy immediately before risky maintenance on a host where the full backup pipeline would take too long to invoke. It needs enough free disk for a second copy of the dataset, which is exactly the constraint the remote form was designed to remove, so treat it as a convenience, not a plan.
Setup: plugin, privileges, donor list
Both donor and recipient need the plugin loaded, the donor needs a user the recipient can authenticate as, and the recipient must explicitly allow the donor in its valid donor list. The privilege split is easy to remember: BACKUP_ADMIN to give data (donor side), CLONE_ADMIN to receive it (recipient side).
-- On both servers
INSTALL PLUGIN clone SONAME 'mysql_clone.so';
-- On the donor
CREATE USER 'clone_donor'@'10.0.0.%' IDENTIFIED BY '...';
GRANT BACKUP_ADMIN ON *.* TO 'clone_donor'@'10.0.0.%';
-- On the recipient
SET GLOBAL clone_valid_donor_list = '10.0.0.12:3306';
The donor list check surprises people because it fails after authentication succeeds. If the clone statement errors with a complaint about an invalid donor, it is this variable, not the grant.
Running a remote clone, and what happens to the recipient
The statement itself is one line, and the most important thing to internalize is what it does to the recipient: a remote clone into the recipient's own data directory drops the existing data first. All of it. This is exactly what you want when rebuilding a broken replica and exactly what you do not want when someone runs it on the wrong host. Treat CLONE INSTANCE with the same respect as DROP DATABASE.
CLONE INSTANCE FROM 'clone_donor'@'10.0.0.12':3306
IDENTIFIED BY '...';
When the transfer completes, the recipient restarts itself to recover on top of the cloned data. That restart only works if something supervises the process, systemd with the right unit, mysqld_safe, a container runtime that restarts on exit. On a bare mysqld with no supervisor, the server simply exits after the clone and waits for a human, which makes for a confusing first experience. Check your process supervision before your first clone, not after.
Once the recipient is back, replication setup is two statements because the clone carried the coordinates with it.
SELECT binlog_file, binlog_position, gtid_executed
FROM performance_schema.clone_status;
CHANGE REPLICATION SOURCE TO
SOURCE_HOST = '10.0.0.12',
SOURCE_USER = 'repl',
SOURCE_PASSWORD = '...',
SOURCE_AUTO_POSITION = 1;
START REPLICA;
The version-matching gotchas
Clone is strict about donor and recipient compatibility, and this is where most first attempts fail. Donor and recipient must be the same series, 8.0 with 8.0, 8.4 with 8.4, and on older 8.0 releases they had to be the exact same patch version; MySQL 8.0.37 and the 8.4 line relaxed this to allow different patch releases within a series. If you run a mixed-patch fleet on older binaries, clone will refuse pairs that XtraBackup would have happily handled, so check versions before you plan a provisioning run.
Two more constraints that read as fine print until they stop you: the plugins active on the donor must also be active on the recipient, and character set and collation availability must match. In practice this means recipient servers should be built from the same configuration management role as the donor, which you wanted anyway.
Monitoring clone progress
A clone of a large dataset runs for a long time, and the worst way to spend that time is refreshing a terminal wondering if anything is happening. The plugin exposes two performance_schema tables on the recipient: clone_status for the overall operation and clone_progress for the per-stage breakdown across DROP DATA, FILE COPY, PAGE COPY, REDO COPY, FILE SYNC, RESTART, and RECOVERY.
SELECT stage, state, work_estimated, work_completed
FROM performance_schema.clone_progress;
FILE COPY dominates the wall clock and its work_estimated versus work_completed ratio gives an honest progress percentage. If a clone fails, clone_status keeps the error message, which beats scraping the error log. On the donor side, the levers are clone_max_data_bandwidth to throttle transfer impact and, on 8.0.27 and later, the clone_block_ddl variable, since by default concurrent DDL on the donor is now permitted during a clone rather than blocked behind the backup lock as in early releases. If you are wiring these tables into dashboards, the broader mapping of MySQL instrumentation to what a PostgreSQL operator would expect is in the MySQL to PostgreSQL monitoring translation notes.
Do not forget the donor is a production server while all this runs. A clone reads the entire dataset, which means real I/O against the donor's storage and real competition for the buffer pool's attention. On a donor with headroom, the default autotuned concurrency behaves reasonably; on a donor that is already busy, set clone_max_data_bandwidth deliberately and accept a slower clone. My habit is to clone from a replica rather than the primary whenever the topology allows it, for the same reason we ran XtraBackup against replicas: provisioning traffic and customer traffic should not share a disk if they do not have to. Watch the donor's query latency during the first few clones you run, and write the observed impact into the runbook so the next person schedules clones with evidence instead of optimism.
When clone beats file-copy workflows
For replica provisioning, replacement, and scale-out, clone wins on almost every axis I care about: fewer moving parts, no staging storage, no version skew between a backup tool and the server, coordinates captured automatically, and progress observable in SQL. It is also what Group Replication uses internally for distributed recovery, so trusting it for provisioning is not a leap.
Where it does not belong is your backup strategy. A clone gives you a copy of now. It has no incremental story, no point-in-time recovery, and a remote clone destroys the recipient's previous state by design. The role of full backups plus binary logs, and how that compares to the PostgreSQL equivalent, is territory I covered in MySQL backups vs PostgreSQL backups; clone replaces the provisioning use of XtraBackup, not the recovery use. Run both: clone to build replicas, a real backup pipeline to survive mistakes.
The quiet second-order win is automation. Because the whole workflow is SQL, statements to start it, tables to watch it, coordinates delivered at the end, replica rebuilds become something an orchestration system can own end to end: detect a broken replica, wipe it with a clone from a healthy donor, attach replication, verify lag converges, close the ticket. That loop was always possible with file-copy tooling, but every step crossed a tool boundary where scripts rot. With clone, the failure surface shrinks to MySQL itself, which is the component you were already monitoring.
Where MonPG fits today
Honest positioning: MonPG is a PostgreSQL monitoring platform, and it does not monitor MySQL today. MySQL monitoring (coming soon) is under active development, and the clone_progress and clone_status tables are exactly the kind of operational surface we intend to expose properly, because a replica rebuild is precisely when an operator needs progress and failure evidence in one place. If you also run PostgreSQL, the platform is live for that today and you can start there. Either way, the advice above stands on its own: supervise the process, respect CLONE INSTANCE like a destructive command, and keep a real backup pipeline next to it.