About this session
| Session | Details |
|---|---|
| Duration | 2–3 hours, including exercises |
| You will build | A three-member InnoDB Cluster running as sandbox instances on one machine, with MySQL Router in front and a small test database |
| You will learn | Group Replication basics, primary and secondary members, quorum, MySQL Shell's AdminAPI, cluster status, MySQL Router ports, automatic failover, planned switchover, rejoining a member |
| Prerequisites | Basic SQL and MySQL administration (connecting with the mysql client, creating databases and users); permission to install software on the machine |
By the end you will understand how InnoDB Cluster keeps MySQL available and be able to build and operate a lab cluster on your own. The advanced session takes this to real servers in production.
Note
Sandbox instances are for learning and testing only. All three members run on one machine, so the lab shows how the cluster behaves, but it gives no protection against that machine failing.
Module 1: the ideas you need first
The problem InnoDB Cluster solves
A single MySQL server is a single point of failure. If it stops, the application stops. Classic asynchronous replication gives you a copy, but someone (or a script) has to promote the replica, repoint the application and deal with transactions that may not have reached the replica.
InnoDB Cluster packages three pieces so that failover is automatic and safe:
| Piece | What it does |
|---|---|
| Group Replication | A MySQL plugin that keeps a group of servers in sync. Each transaction is agreed by a majority before it commits |
MySQL Shell (mysqlsh) | The admin tool. Its AdminAPI (dba and cluster objects) creates and manages the cluster with a few commands |
| MySQL Router | A lightweight proxy. Applications connect to Router, and Router sends them to the right member, even after a failover |
Primary and secondaries
In the default single-primary mode:
- One member is the primary. It accepts reads and writes.
- The other members are secondaries. They're read-only and apply the primary's changes.
- If the primary fails, the remaining members elect a new primary automatically, usually within seconds.
Application
│
MySQL Router
6446 (read/write) │ 6447 (read-only)
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ member 1 │◄─►│ member 2 │◄─►│ member 3 │
│ PRIMARY │ │ SECONDARY │ │ SECONDARY │
└────────────┘ └────────────┘ └────────────┘
Group Replication (majority agreement)
Quorum: why three members
Group Replication needs a majority of members to agree before it commits anything. This protects you from two halves of a cluster both accepting writes ("split brain").
| Members | Majority | Failures tolerated |
|---|---|---|
| 1 | 1 | 0 |
| 2 | 2 | 0 (losing either one stops writes) |
| 3 | 2 | 1 |
| 5 | 3 | 2 |
So the minimum useful cluster is three members. Two members give you no more availability than one.
Rules your data must follow
- Tables must use the InnoDB storage engine.
- Every table needs a primary key (or a non-null unique key).
The cluster setup checks these and tells you what to fix.
Module 2: install the software
Install the same version (8.4 LTS) of all three tools.
Ubuntu / Debian (after adding the MySQL APT repository from dev.mysql.com):
sudo apt-get update
sudo apt-get install -y mysql-server mysql-shell mysql-router
mysqlsh --version
mysqlrouter --version
Oracle Linux / RHEL (after adding the MySQL Yum repository):
sudo dnf install -y mysql-community-server mysql-shell mysql-router-community
Windows: use the MySQL Installer (or the MSI/ZIP packages) and select MySQL Server, MySQL Shell and MySQL Router.
For the sandbox lab you don't need the system MySQL service to be running. MySQL Shell starts its own instances using the installed server binaries. If a MySQL service is already running on port 3306, leave it alone; the lab uses other ports.
Module 3: create three sandbox instances
Start MySQL Shell in JavaScript mode:
mysqlsh --js
Deploy three instances on ports 3310, 3320 and 3330. Use the same root password for all three (this is a lab):
dba.deploySandboxInstance(3310, {password: 'Lab#Pass2026'})
dba.deploySandboxInstance(3320, {password: 'Lab#Pass2026'})
dba.deploySandboxInstance(3330, {password: 'Lab#Pass2026'})
Each command creates a data directory (under ~/mysql-sandboxes on Linux and macOS, or %APPDATA%\MySQL\mysql-sandboxes on Windows), writes a configuration file and starts a mysqld process.
Check they're running:
\connect root@localhost:3310
\sql SELECT @@port, @@version;
\js
Shell switches between languages with \js, \sql and \py.
Module 4: create the cluster
Connect to the first instance and create the cluster:
\connect root@localhost:3310
var cluster = dba.createCluster('labCluster')
Shell checks the instance, enables Group Replication and makes this member the primary. Now add the other two:
cluster.addInstance('root@localhost:3320', {recoveryMethod: 'clone'})
cluster.addInstance('root@localhost:3330', {recoveryMethod: 'clone'})
recoveryMethod: 'clone' copies a full snapshot of the primary's data to the new member, then the member catches up. It's the simplest and most reliable way to add members. (The alternative, incremental, replays binary logs and only works when the new member is already almost in sync.)
The new member restarts during cloning; Shell waits and reports progress.
Module 5: read the cluster status
cluster.status()
Shortened output:
{
"clusterName": "labCluster",
"defaultReplicaSet": {
"primary": "127.0.0.1:3310",
"ssl": "REQUIRED",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"127.0.0.1:3310": { "memberRole": "PRIMARY", "mode": "R/W", "status": "ONLINE" },
"127.0.0.1:3320": { "memberRole": "SECONDARY", "mode": "R/O", "status": "ONLINE" },
"127.0.0.1:3330": { "memberRole": "SECONDARY", "mode": "R/O", "status": "ONLINE" }
}
}
}
What to look at:
| Field | Healthy value | Meaning |
|---|---|---|
status | OK | All members online, can survive a failure |
statusText | "can tolerate up to ONE failure" | How many members you can lose |
memberRole | One PRIMARY | Who takes writes |
member status | ONLINE | Others you may see: RECOVERING (catching up), UNREACHABLE, (MISSING) |
Other statuses you'll meet: OK_NO_TOLERANCE (working, but one more failure stops it) and NO_QUORUM (writes stopped).
You can see the same thing in plain SQL on any member:
SELECT MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE
FROM performance_schema.replication_group_members;
Module 6: put MySQL Router in front
Router "bootstraps" from the cluster: it reads the cluster's metadata and writes its own configuration.
Open a normal terminal (not Shell):
mysqlrouter --bootstrap root@localhost:3310 --directory ~/lab-router
~/lab-router/start.sh
On Windows, bootstrap into a folder such as C:\lab-router and run mysqlrouter -c C:\lab-router\mysqlrouter.conf in its own window.
The bootstrap output lists the ports. The two to remember:
| Port | Goes to | Use for |
|---|---|---|
| 6446 | The primary | Reads and writes |
| 6447 | Secondaries (round robin) | Read-only queries |
Recent versions also offer port 6450 for read/write splitting in a single connection; we won't use it in this lab.
Test it:
mysql -h 127.0.0.1 -P 6446 -u root -p -e "SELECT @@port AS served_by;"
mysql -h 127.0.0.1 -P 6447 -u root -p -e "SELECT @@port AS served_by;"
mysql -h 127.0.0.1 -P 6447 -u root -p -e "SELECT @@port AS served_by;"
Port 6446 always shows 3310 (the primary). Port 6447 alternates between 3320 and 3330.
Tip
Always use
-h 127.0.0.1, notlocalhost, when testing Router on Linux. Withlocalhostthemysqlclient uses a Unix socket and skips Router entirely.
Module 7: write data and watch it replicate
Create a test database through Router's read/write port:
-- mysql -h 127.0.0.1 -P 6446 -u root -p
CREATE DATABASE shop;
CREATE TABLE shop.orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO shop.orders (customer, amount) VALUES ('Asha', 120.50), ('Ben', 75.00), ('Chen', 310.20);
Read it through the read-only port:
-- mysql -h 127.0.0.1 -P 6447 -u root -p
SELECT @@port, COUNT(*) FROM shop.orders;
The secondary already has the rows. Now try to write on the read-only port:
INSERT INTO shop.orders (customer, amount) VALUES ('Dev', 10);
-- ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option ...
That's the protection that stops anyone writing to a secondary by mistake.
Try a table without a primary key:
-- on port 6446
CREATE TABLE shop.no_pk (note VARCHAR(20));
INSERT INTO shop.no_pk VALUES ('test');
-- fails: Group Replication requires a primary key
Module 8: break the primary and watch failover
Keep a loop running in a second terminal that writes through Router once a second:
while true; do
mysql -h 127.0.0.1 -P 6446 -u root -p'Lab#Pass2026' -N -e \
"INSERT INTO shop.orders (customer, amount) VALUES ('loop', 1); SELECT @@port, NOW();" 2>&1 | tail -1
sleep 1
done
In MySQL Shell, kill the primary (this simulates a crash):
dba.killSandboxInstance(3310)
Watch the loop: a few inserts fail with a connection error, then they continue, now served by 3320 or 3330. Check the cluster from a surviving member:
\connect root@localhost:3320
var cluster = dba.getCluster()
cluster.status()
You'll see a new primary, the old one shown as (MISSING), and statusText saying the cluster can't tolerate any more failures (OK_NO_TOLERANCE).
Bring the old member back:
dba.startSandboxInstance(3310)
cluster.status()
It usually rejoins by itself as a secondary (it doesn't take the primary role back). If it stays (MISSING), rejoin it:
cluster.rejoinInstance('root@localhost:3310')
What you learned: the application only noticed a few seconds of errors, because it connects to Router, and applications must retry failed statements.
Module 9: planned switchover
For maintenance you choose which member becomes primary, with no crash involved:
cluster.setPrimaryInstance('root@localhost:3310')
cluster.status()
Your write loop sees at most a brief error. Use this before patching the current primary.
Module 10: clean up
# stop Router (Linux/macOS)
~/lab-router/stop.sh
// in mysqlsh
dba.stopSandboxInstance(3310, {password: 'Lab#Pass2026'})
dba.stopSandboxInstance(3320, {password: 'Lab#Pass2026'})
dba.stopSandboxInstance(3330, {password: 'Lab#Pass2026'})
dba.deleteSandboxInstance(3310)
dba.deleteSandboxInstance(3320)
dba.deleteSandboxInstance(3330)
Then delete the ~/lab-router folder.
Exercises
- Kill two sandbox members. What does
cluster.status()say, and can you still write through port 6446? Start them again. Does the cluster recover by itself? If not, find the AdminAPI command that restores quorum (the advanced session covers it). - Add a fourth sandbox instance (port 3340) to the cluster. How many failures can it tolerate now? Is four members better than three?
- Run
cluster.describe()andcluster.options(). Find theconsistencyandexitStateActionsettings and note their values. - Connect with the
mysqlclient directly to port 3320 (not through Router) while it's a secondary, and try anUPDATE. What error do you get? - Stop Router while your write loop is running. What happens to the application? What does this tell you about where to run Router in production?
Knowledge check
- What are the three components of an InnoDB Cluster, and what does each do?
- Why does a cluster need at least three members?
- What's the difference between ports 6446 and 6447?
- Why must every table have a primary key?
- After a failover, does the old primary become primary again when it comes back?
- Why should the application retry failed statements?
Answers
- Group Replication keeps the members' data in sync with majority agreement; MySQL Shell's AdminAPI creates and manages the cluster; MySQL Router sends application connections to the right member.
- Writes need a majority. With two members, losing one leaves no majority, so writes stop. Three members keep a majority (two) after one failure.
- 6446 always connects to the primary for reads and writes. 6447 spreads read-only connections over the secondaries.
- Group Replication uses the primary key to detect conflicting changes and to apply rows on other members. Without one it can't certify the transaction.
- No. It rejoins as a secondary. You move the primary role back yourself with
setPrimaryInstance()if you want to. - During a failover, connections to the failed primary are dropped and new ones are briefly refused. Retrying (with a short delay) hides this from users.
Summary
- InnoDB Cluster = Group Replication + MySQL Shell + MySQL Router.
- Single-primary mode: one read/write primary, read-only secondaries, automatic election on failure.
- Three members tolerate one failure; writes need a majority.
- Applications connect to Router: port 6446 for writes, 6447 for reads, and they must retry.
- Sandbox instances are a quick, free way to learn and test the behaviour.
- Next step: the advanced session - real servers, configuration, security, monitoring, failure recovery, backups, upgrades and ClusterSet for disaster recovery.
Further reading
- MySQL documentation: MySQL InnoDB Cluster (in the MySQL Shell manual), Group Replication, MySQL Router.
- On this site: choosing MySQL high availability and the InnoDB Cluster setup quick reference.