How to Build a 3-Node Percona XtraDB Cluster Lab for MySQL High Availability

Learn how to create a Peona XtraDB Cluster with three nodesto test HA, replicación Galera, IST, SST and Disaster Revcovery.

This post is also available in: Spanish

Percona XtraDB Cluster, commonly known as PXC, is a high-availability MySQL solution based on Percona Server for MySQL and Galera replication.

Unlike a traditional MySQL primary-replica architecture, every node in a Percona XtraDB Cluster contains a complete copy of the database and can participate in transaction processing.

This makes PXC especially interesting for DevOps, SRE, and Database Reliability Engineering environments where high availability, automated recovery, rolling maintenance, and failure handling are important.

In this guide, we will build a three-node Percona XtraDB Cluster lab and explore not only how to install it, but also how the cluster behaves when nodes fail, replication falls behind, or quorum is lost.

The goal is to create a lab where we can safely break things and understand how Percona behaves under real operational scenarios.


Percona XtraDB Cluster Architecture

A basic PXC architecture looks like this:

Application
|
HAProxy
|
+----------+----------+
| | |
PXC01 PXC02 PXC03
MySQL MySQL MySQL
\ | /
\------ Galera -----/

Each node contains a full copy of the database.

This differs from traditional MySQL replication, where one primary typically handles writes and replicas asynchronously receive changes:

Primary
|
| asynchronous replication
v
Replica

With Percona XtraDB Cluster, transactions are replicated using Galera's write-set replication.

Although PXC supports writes through multiple nodes, that does not necessarily mean an application should randomly distribute writes across every database server.

A common production architecture is to use a preferred writer while keeping the remaining nodes available for failover:

HAProxy / ProxySQL
|
Writes
|
PXC01
/ \
PXC02 PXC03

This reduces the probability of transaction conflicts while still providing high availability.


How Transactions Work in PXC

Imagine an application executes the following transaction against pxc01:

BEGIN;
UPDATE accounts
SET balance = balance - 100
WHERE id = 10;
COMMIT;

The transaction executes locally first.

Before it is committed, Galera creates a write-set describing the changes made by the transaction.

The write-set is distributed to the other members of the cluster:

PXC01
|
Transaction executes
|
v
Write-set generated
|
+------------+
| |
v v
PXC02 PXC03
| |
Certify Certify
\ /
\----------/
|
Commit

Each node performs a process known as certification.

Certification determines whether another concurrent transaction modified the same data.

For example:

PXC01 PXC02
UPDATE users UPDATE users
SET name='Alice' SET name='Bob'
WHERE id=10 WHERE id=10
\ /
\ /
Certification
|
Conflict detected
|
One transaction aborts

This behavior is one reason multi-primary replication should be used carefully.

Applications generating many concurrent writes against the same rows can experience certification conflicts.


Why a PXC Cluster Should Have Three Nodes

One of the most important concepts in Galera-based clusters is quorum.

Consider a three-node cluster:

PXC01 ---- PXC02 ---- PXC03
1 1 1

The cluster has three voting members.

To remain operational, a majority of the cluster must remain connected.

With three nodes:

3 nodes available → quorum
2 nodes available → quorum
1 node available → no quorum

If one node fails:

PXC01 -------- PXC02 X PXC03
2 / 3 nodes available

The remaining two nodes still represent a majority, so the cluster continues processing transactions.

If another node fails:

PXC01
X PXC02
X PXC03

The remaining node no longer has quorum.

You may see states such as:

wsrep_cluster_status = Non-Primary
wsrep_ready = OFF

The database intentionally stops accepting normal application traffic.

This protects the cluster against one of the most dangerous problems in distributed databases:

split brain.

Without quorum protection, two isolated sides of a network partition could both believe they are authoritative and accept conflicting writes.


Understanding IST and SST

Another important PXC concept is how a node synchronizes after being offline.

Imagine our cluster starts healthy:

PXC01 PXC02 PXC03
✓ ✓ ✓

Now pxc03 goes offline:

PXC01 PXC02 PXC03
✓ ✓ X

Applications continue generating transactions while the node is unavailable.

When pxc03 returns, it must synchronize with the cluster.

There are two primary mechanisms for this.


Incremental State Transfer — IST

If another cluster member still has the missing write-sets in its Galera cache, the returning node can receive only the transactions it missed.

PXC01 GCache
TX1001
TX1002
TX1003
TX1004
TX1005
|
| Missing transactions
v
PXC03

This process is called:

Incremental State Transfer, or IST.

IST is normally much faster because it does not require transferring the entire database.


State Snapshot Transfer — SST

If the returning node has been offline long enough that the required transactions are no longer available in GCache, a complete copy of the database must be transferred.

PXC01
Full database
|
|
v
PXC03

This process is called:

State Snapshot Transfer, or SST.

SST can place considerably more load on storage, networking, and the donor node than IST.

Understanding the relationship between:

IST
SST
GCache
Donor
Joiner

is important when operating Percona clusters.


Building the Lab

For this lab we will use four virtual machines:

Server IP Address Purpose
pxc01 192.168.50.11 Percona node
pxc02 192.168.50.12 Percona node
pxc03 192.168.50.13 Percona node
mysql-proxy 192.168.50.10 HAProxy

A reasonable configuration for each database VM is:

2 vCPU
4 GB RAM
30–50 GB disk
Ubuntu 24.04

For learning purposes, virtual machines are preferable to containers because they allow us to reproduce infrastructure-level failures such as:

  • Network partitions

  • VM crashes

  • Disk exhaustion

  • Storage latency

  • Firewall problems

  • Rolling operating-system maintenance


Required Network Ports

PXC nodes need to communicate over several ports.

3306 MySQL client connections
4444 State Snapshot Transfer
4567 Galera replication
4568 Incremental State Transfer

If UFW is enabled:

sudo ufw allow 3306/tcp
sudo ufw allow 4444/tcp
sudo ufw allow 4567/tcp
sudo ufw allow 4567/udp
sudo ufw allow 4568/tcp

In a real environment, these rules should be restricted to the database network instead of being accessible from every source.


Configure the Hostnames

On the first node:

sudo hostnamectl set-hostname pxc01

On the second:

sudo hostnamectl set-hostname pxc02

And on the third:

sudo hostnamectl set-hostname pxc03

Add the nodes to /etc/hosts:

192.168.50.11 pxc01
192.168.50.12 pxc02
192.168.50.13 pxc03

Verify connectivity between all three nodes before continuing.


Install Percona XtraDB Cluster

Perform the installation on every database node.

sudo apt update
sudo apt install -y \
wget \
gnupg2 \
lsb-release \
curl

Download the Percona repository package:

wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb

Install it:

sudo dpkg -i percona-release_latest.generic_all.deb

Enable the PXC repository:

sudo percona-release setup pxc-84-lts

Update the package list:

sudo apt update

Install Percona XtraDB Cluster:

sudo apt install -y percona-xtradb-cluster

After installation, stop MySQL on all three nodes:

sudo systemctl stop mysql

We want to configure the cluster before starting it.


Configure PXC01

First inspect the MySQL configuration directories:

ls -la /etc/mysql/
ls -la /etc/mysql/mysql.conf.d/

Create a PXC configuration file:

sudo nano /etc/mysql/mysql.conf.d/pxc.cnf

For pxc01:

[mysqld]
server-id=1
datadir=/var/lib/mysql
user=mysql
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
wsrep_provider=/usr/lib/libgalera_smm.so
wsrep_cluster_name=pxc-lab
wsrep_cluster_address=gcomm://192.168.50.11,192.168.50.12,192.168.50.13
wsrep_node_name=pxc01
wsrep_node_address=192.168.50.11
wsrep_sst_method=clone

The most important settings here are:

wsrep_cluster_name
wsrep_cluster_address
wsrep_node_name
wsrep_node_address

These tell Galera which cluster the server belongs to and how to discover the other nodes.


Configure PXC02

Use the same configuration with node-specific values:

[mysqld]
server-id=2
datadir=/var/lib/mysql
user=mysql
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
wsrep_provider=/usr/lib/libgalera_smm.so
wsrep_cluster_name=pxc-lab
wsrep_cluster_address=gcomm://192.168.50.11,192.168.50.12,192.168.50.13
wsrep_node_name=pxc02
wsrep_node_address=192.168.50.12
wsrep_sst_method=clone

Configure PXC03

For the third server:

[mysqld]
server-id=3
datadir=/var/lib/mysql
user=mysql
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
wsrep_provider=/usr/lib/libgalera_smm.so
wsrep_cluster_name=pxc-lab
wsrep_cluster_address=gcomm://192.168.50.11,192.168.50.12,192.168.50.13
wsrep_node_name=pxc03
wsrep_node_address=192.168.50.13
wsrep_sst_method=clone

Bootstrap the Cluster

The first node must be started differently from the other members.

On pxc01:

sudo systemctl start mysql@bootstrap

Bootstrapping tells Galera that this server is creating a new Primary Component rather than attempting to join an existing cluster.

Connect to MySQL:

mysql -uroot -p

Check the cluster status:

SHOW STATUS LIKE 'wsrep_cluster_size';
SHOW STATUS LIKE 'wsrep_cluster_status';
SHOW STATUS LIKE 'wsrep_local_state_comment';
SHOW STATUS LIKE 'wsrep_ready';

A healthy first node should report something similar to:

wsrep_cluster_size 1
wsrep_cluster_status Primary
wsrep_local_state_comment Synced
wsrep_ready ON

Add PXC02

Now start MySQL normally on the second node:

sudo systemctl start mysql

Watch the logs:

journalctl -u mysql -f

The node should discover pxc01 and synchronize its state.

Check the cluster:

SHOW STATUS LIKE 'wsrep_cluster_size';

The expected result is:

2

Also check:

SHOW STATUS LIKE 'wsrep_local_state_comment';

The node should eventually report:

Synced

Add PXC03

Start MySQL normally:

sudo systemctl start mysql

Check the cluster again:

SHOW STATUS LIKE 'wsrep_cluster_size';

You should now have:

3

Verify:

SHOW STATUS LIKE 'wsrep_cluster_status';

The expected state is:

Primary

Our three-node cluster is now operational.


Test Replication

Connect to pxc01 and create a database:

CREATE DATABASE dbre_lab;
USE dbre_lab;

Create a table:

CREATE TABLE transactions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
description VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Insert a record:

INSERT INTO transactions(description)
VALUES ('Inserted through pxc01');

Now connect to pxc02:

SELECT *
FROM dbre_lab.transactions;

The row should already exist.

Insert another transaction through pxc02:

INSERT INTO dbre_lab.transactions(description)
VALUES ('Inserted through pxc02');

Connect to pxc03:

SELECT *
FROM dbre_lab.transactions;

Both transactions should appear.

This demonstrates one of the fundamental characteristics of PXC: every cluster member maintains the database state.


Important WSREP Metrics

Running:

SHOW STATUS LIKE 'wsrep%';

returns a very large number of metrics.

For day-to-day troubleshooting, a smaller group is particularly useful:

SHOW STATUS WHERE Variable_name IN (
'wsrep_cluster_size',
'wsrep_cluster_status',
'wsrep_connected',
'wsrep_ready',
'wsrep_local_state_comment',
'wsrep_local_recv_queue',
'wsrep_flow_control_paused',
'wsrep_local_cert_failures',
'wsrep_local_bf_aborts'
);

For a healthy three-node cluster we normally expect:

wsrep_cluster_size 3
wsrep_cluster_status Primary
wsrep_connected ON
wsrep_ready ON
wsrep_local_state_comment Synced

These are excellent metrics to include in monitoring and alerting.


Failure Exercise 1: Lose One Node

Now the interesting part begins.

Stop MySQL on pxc03:

sudo systemctl stop mysql

Check the cluster from another node:

SHOW STATUS LIKE 'wsrep_cluster_size';

The cluster should report:

2

Because two of the three voting members remain available, quorum still exists.

Writes should continue succeeding:

INSERT INTO dbre_lab.transactions(description)
VALUES ('Transaction while PXC03 is unavailable');

Bring the node back:

sudo systemctl start mysql

Watch its synchronization process:

journalctl -u mysql -f

Failure Exercise 2: Observe an IST

Stop pxc03 again:

sudo systemctl stop mysql

Generate additional transactions through pxc01:

INSERT INTO dbre_lab.transactions(description)
VALUES ('PXC03 is offline');

Now restart the third node:

sudo systemctl start mysql

Review the logs:

journalctl -u mysql

Search for references to:

IST

If the required write-sets remain available in GCache, Galera should perform an Incremental State Transfer.

The important relationship is:

Donor
|
| Missing write-sets
v
Joiner

The returning server receives only the transactions it missed.


Failure Exercise 3: Force an SST

Next we can intentionally make IST impossible.

The idea is:

1. Stop PXC03
2. Generate large amounts of data
3. Overflow the available GCache
4. Restart PXC03

Because the donor can no longer provide all of the missing write-sets, PXC must perform a full State Snapshot Transfer.

This is useful because SST introduces very different operational concerns.

Monitor:

SST duration
CPU utilization
Disk throughput
Network utilization
Donor performance
Joiner status
Application latency

This is the type of scenario DBRE teams should understand before it happens in production.


Failure Exercise 4: Lose Quorum

Start from a healthy cluster:

PXC01 ✓
PXC02 ✓
PXC03 ✓

Stop the second node:

sudo systemctl stop mysql

The cluster still has quorum.

Now stop the third node:

sudo systemctl stop mysql

Only pxc01 remains.

Check:

SHOW STATUS LIKE 'wsrep_cluster_status';
SHOW STATUS LIKE 'wsrep_ready';

The remaining server should no longer behave like a normal healthy cluster member.

This is an important demonstration of the difference between:

MySQL is running

and:

The database is safe to receive traffic

A process being alive does not necessarily mean the database should be included in the application load balancer.


Failure Exercise 5: Create a Network Partition

Database processes are not the only things that fail.

Networks fail too.

Block Galera traffic on one node:

sudo iptables -A INPUT -p tcp --dport 4567 -j DROP
sudo iptables -A OUTPUT -p tcp --dport 4567 -j DROP

Observe:

SHOW STATUS LIKE 'wsrep%';

Pay particular attention to:

wsrep_cluster_size
wsrep_cluster_status
wsrep_connected
wsrep_ready

Remove the firewall rules afterward:

sudo iptables -D INPUT -p tcp --dport 4567 -j DROP
sudo iptables -D OUTPUT -p tcp --dport 4567 -j DROP

This exercise demonstrates that:

mysqld crash
VM crash
network partition
slow network
disk failure

are very different failure modes.

A reliable database platform needs to detect and respond appropriately to each of them.


Add HAProxy

Our next improvement is to stop applications from connecting directly to individual database nodes.

The architecture becomes:

Application
|
v
+---------------+
| HAProxy |
| 192.168.50.10 |
+-------+-------+
|
+------------+------------+
| | |
v v v
+-------+ +-------+ +-------+
| PXC01 | | PXC02 | | PXC03 |
+-------+ +-------+ +-------+

Install HAProxy:

sudo apt update
sudo apt install -y haproxy

One particularly important consideration is the health check.

Checking only:

TCP port 3306

is not sufficient.

For example, a database can have:

mysqld running
3306 listening

while simultaneously reporting:

wsrep_ready = OFF

That server should not receive application traffic.

A PXC-aware health check should therefore evaluate cluster state before declaring a node healthy.


Monitoring the Cluster

Once the basic architecture works, add monitoring.

Percona Monitoring and Management, or PMM, is a natural choice for a Percona lab.

The environment could eventually look like this:

Application
|
HAProxy
|
+------------+------------+
| | |
PXC01 PXC02 PXC03
| | |
+------------+------------+
|
PMM

Useful metrics include:

CPU utilization
Memory consumption
Disk latency
Buffer pool utilization
Connections
Query latency
Slow queries
Replication queues
Galera flow control
Certification failures
SST activity
IST activity

In particular, metrics such as:

wsrep_local_recv_queue
wsrep_flow_control_paused
wsrep_local_cert_failures
wsrep_local_bf_aborts

can tell us much more about the health of the cluster than simply verifying whether MySQL is running.


Turning the Lab Into a DBRE Training Environment

Installing Percona is only the beginning.

The real value comes from operating and breaking it.

A useful learning progression would be:

Stage 1 — Build

Deploy three PXC nodes manually.

Understand every configuration parameter instead of immediately automating everything.

Stage 2 — Operate

Add:

HAProxy
ProxySQL
PMM
Backups
Monitoring
Alerts

Stage 3 — Break

Simulate:

Node failures
VM failures
Network partitions
Disk full conditions
High latency
Firewall failures
Configuration errors
Replication problems

Stage 4 — Recover

Practice:

IST recovery
SST recovery
Quorum recovery
Node replacement
Full-cluster recovery

Stage 5 — Maintain

Perform:

Rolling restarts
Operating-system maintenance
Database upgrades
Configuration changes
Certificate rotation

without taking the entire database offline.

Stage 6 — Automate

After understanding the manual process, rebuild the environment using tools such as:

Terraform
Ansible
GitHub Actions

The objective should be to make the cluster reproducible.

Stage 7 — Observe

Build alerts and dashboards around:

WSREP state
Flow control
Certification conflicts
Node synchronization
Query latency
Disk latency
SST and IST events

Stage 8 — Document

Finally, write operational runbooks.

Examples:

PXC node failed
Cluster is Non-Primary
Node cannot rejoin
SST is stuck
Disk is full
Flow control is high
Rolling restart procedure
Rolling upgrade procedure
Complete cluster outage

This turns a simple home lab into something that closely resembles the operational work performed by Database Reliability Engineering teams.


Full-Cluster Recovery

One scenario deserves special attention:

all cluster nodes are down.

Imagine:

PXC01 X
PXC02 X
PXC03 X

At this point, we should not simply select a random node and bootstrap the cluster.

Each server may contain a different Galera sequence position depending on which transactions it successfully processed before the outage.

During recovery, administrators need to determine which node contains the most advanced trustworthy state.

A typical recovery investigation includes tools such as:

mysqld --wsrep-recover

The objective is to determine the Galera position associated with each node and bootstrap from the correct database state.

Choosing the wrong node can potentially mean starting the new cluster from an older state.

That makes full-cluster recovery one of the most valuable exercises to practice in a PXC lab.


What This Lab Teaches

A Percona XtraDB Cluster lab teaches much more than MySQL installation.

It provides hands-on experience with distributed database concepts such as:

Quorum
Consensus
Write-set replication
Transaction certification
Split-brain prevention
Synchronous replication
Failure domains
State synchronization
Flow control
Automated failover

It also highlights an important reliability engineering principle:

A running database process is not necessarily a healthy database service.

A node may have MySQL running and port 3306 listening while being disconnected from the Primary Component or unable to safely process application traffic.

That distinction is why health checks, observability, quorum awareness, and automated failover are critical parts of database infrastructure.


Conclusion

Percona XtraDB Cluster provides an excellent platform for learning how highly available MySQL environments operate.

A three-node lab gives us a safe environment to explore behaviors that are difficult—or dangerous—to experiment with in production.

Instead of stopping after a successful installation, intentionally break the cluster.

Kill a node.

Disconnect the network.

Force an SST.

Lose quorum.

Fill the disk.

Restart every server.

Recover the cluster.

Then automate everything and repeat the exercises.

The objective is not simply to learn how to install Percona.

The objective is to understand what happens when the database infrastructure stops behaving the way we expect.

That is where the lab starts becoming valuable from a Database Reliability Engineering perspective.

Comments