In this page
This post is also available in: Español
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.
Keep reading
Related articles
Upgrading my Five-Node Proxmox VE homelab from 8 to 9: Failures, Recovery, and Lessons Learned
A real Proxmox VE 8-to-9 upgrade across five lab servers: systemd-boot, GRUB, duplicate repositories, Ceph conflicts, and recovery.
Implementing Open-Source PAM with JumpServer for Proxmox and MicroK8s
Learn how to design an open-source PAM architecture with JumpServer to secure and audit privileged access to Proxmox, Linux, MicroK8s, and databases.
Recent articles
Dynamic Jenkins Parameters with Groovy: GitHub Branches, Builds, and Dependencies
Learn how to create dynamic Jenkins parameters with Groovy and shared libraries, retrieve GitHub branches, select builds from other pipelines, and define parameter dependencies with Active Choices. Includes practical Jenkinsfile examples and security best
Comments