Skip to content

MariaDB Galera Cluster

Introduction

Galera Cluster fournit la réplication synchrone multi-master pour MariaDB. Tous les nœuds sont égaux et peuvent accepter lectures et écritures, avec garantie de cohérence forte (RPO = 0).

Prérequis

  • Concepts HA
  • Compréhension de MySQL/MariaDB
  • Cluster de 3 controllers minimum (nombre impair pour le quorum)

Points à apprendre

Architecture Galera

graph TB
    openstack["OpenStack Services<br/>Nova, Neutron,<br/>Keystone, etc."]
    haproxy["HAProxy<br/>Load Balancer<br/>Round-robin"]

    subgraph galera["Galera Cluster"]
        node1["MariaDB Node 1<br/>Controller-1<br/>Primary<br/>wsrep_local_state = 4"]
        node2["MariaDB Node 2<br/>Controller-2<br/>Primary<br/>wsrep_local_state = 4"]
        node3["MariaDB Node 3<br/>Controller-3<br/>Primary<br/>wsrep_local_state = 4"]
    end

    openstack -->|"SQL<br/>3306"| haproxy
    haproxy -->|Distribute| node1
    haproxy --> node2
    haproxy --> node3

    node1 <-->|"Galera Replication<br/>wsrep (4567)"| node2
    node2 <-->|"Galera Replication"| node3
    node3 <-->|"Galera Replication"| node1

Réplication synchrone

sequenceDiagram
    participant Client
    participant N1 as Node 1<br/>(Writer)
    participant N2 as Node 2
    participant N3 as Node 3

    Client->>N1: BEGIN; INSERT...
    N1->>N1: Write to local binlog
    N1->>N2: Writeset broadcast
    N1->>N3: Writeset broadcast

    N2->>N2: Certification check
    N3->>N3: Certification check

    N2-->>N1: ACK
    N3-->>N1: ACK

    N1->>N1: COMMIT
    N1-->>Client: OK

    Note over N1,N3: Réplication synchrone:<br/>Commit seulement après ACK de tous les nœuds

Configuration Kolla

# /etc/kolla/globals.yml

# Galera activé par défaut en HA
enable_mariadb: "yes"

# Nombre de réplicas (doit correspondre au nombre de controllers)
# Pas de configuration explicite, Kolla utilise les hosts du groupe "control"

Configuration MariaDB/Galera générée

# Voir la configuration
docker exec mariadb cat /etc/mysql/conf.d/galera.cnf
# /etc/mysql/conf.d/galera.cnf

[mysqld]
# Galera settings
wsrep_on = ON
wsrep_provider = /usr/lib/galera/libgalera_smm.so
wsrep_cluster_name = "openstack"
wsrep_cluster_address = "gcomm://10.0.0.11,10.0.0.12,10.0.0.13"
wsrep_node_name = "controller-1"
wsrep_node_address = "10.0.0.11"

# Replication
wsrep_sst_method = mariabackup
wsrep_sst_auth = "sstuser:sstpassword"

# Performance
wsrep_slave_threads = 4
wsrep_log_conflicts = ON
wsrep_certify_nonPK = ON

# Mode strict
binlog_format = ROW
default_storage_engine = InnoDB
innodb_autoinc_lock_mode = 2

# Timeouts
wsrep_provider_options = "gcache.size=1G; gmcast.segment=0"

États du cluster

wsrep_local_state État Description
1 Joining Nœud en cours de jonction
2 Donor/Desynced Fournit SST à un autre nœud
3 Joined Synchronisé, pas encore prêt
4 Synced Pleinement opérationnel
# Vérifier l'état de chaque nœud
docker exec mariadb mysql -e "SHOW STATUS LIKE 'wsrep_%';" | \
    grep -E "(wsrep_local_state|wsrep_cluster_size|wsrep_ready|wsrep_connected)"

Quorum et split-brain

graph TB
    subgraph healthy["Cluster sain (3 nœuds)"]
        h1["Node 1"]
        h2["Node 2"]
        h3["Node 3"]
    end

    subgraph partial["1 nœud down (2/3 = Quorum OK)"]
        p1["Node 1"]
        p2["Node 2"]
        p3["Node 3 ✗"]
        style p3 fill:#f66
    end

    subgraph split["Partition (split-brain évité)"]
        s1["Majorité (2)<br/>Continue"]
        s2["Minorité (1)<br/>S'arrête"]
        style s1 fill:#6f6
        style s2 fill:#f66
    end

    split -.->|"La minorité refuse les requêtes:<br/>WSREP has not yet prepared node for application use"| split

State Snapshot Transfer (SST)

# Méthodes SST disponibles
# 1. mariabackup (recommandé) - Non bloquant
# 2. rsync - Bloquant mais rapide
# 3. mysqldump - Lent, non recommandé

# Vérifier une SST en cours
docker exec mariadb mysql -e "SHOW STATUS LIKE 'wsrep_local_state_comment';"
# Affiche "Donor/Desynced" si SST en cours

# Logs SST
docker logs mariadb | grep -i sst

Monitoring Galera

#!/bin/bash
# galera-status.sh

echo "=== Cluster Status ==="
docker exec mariadb mysql -e "
SHOW STATUS WHERE Variable_name IN (
    'wsrep_cluster_size',
    'wsrep_cluster_status',
    'wsrep_ready',
    'wsrep_connected',
    'wsrep_local_state_comment'
);"

echo -e "\n=== Node Status ==="
docker exec mariadb mysql -e "
SHOW STATUS WHERE Variable_name IN (
    'wsrep_node_name',
    'wsrep_local_recv_queue',
    'wsrep_local_send_queue',
    'wsrep_flow_control_paused'
);"

echo -e "\n=== Replication Health ==="
docker exec mariadb mysql -e "
SHOW STATUS WHERE Variable_name LIKE 'wsrep_cert%'
   OR Variable_name LIKE 'wsrep_commit%'
   OR Variable_name LIKE 'wsrep_apply%';"

Récupération après panne

# Cas 1: Un nœud redémarre
# → Rejoint automatiquement via IST (Incremental State Transfer)

# Cas 2: Cluster complet down
# → Identifier le nœud le plus avancé
for host in controller-{1,2,3}; do
    echo "$host:"
    ssh $host "docker exec mariadb cat /var/lib/mysql/grastate.dat" | grep seqno
done

# Bootstrap depuis le nœud avec le seqno le plus élevé
ssh controller-1 "docker exec mariadb galera_new_cluster"

# Démarrer les autres nœuds normalement
ssh controller-2 "docker restart mariadb"
ssh controller-3 "docker restart mariadb"

# Cas 3: Quorum perdu (tous les nœuds down sauf un)
# Forcer le bootstrap sur le nœud restant
docker exec mariadb mysql -e "SET GLOBAL wsrep_provider_options='pc.bootstrap=YES';"

Configuration HAProxy pour Galera

# Configuration HAProxy pour Galera (générée par Kolla)

listen mariadb
    bind 10.0.0.10:3306
    mode tcp
    option tcplog
    option clitcpka
    option srvtcpka
    balance leastconn
    option httpchk GET /

    # Check via xinetd mysqlchk (port 9200)
    server controller-1 10.0.0.11:3306 check port 9200 inter 5s rise 2 fall 3
    server controller-2 10.0.0.12:3306 check port 9200 inter 5s rise 2 fall 3 backup
    server controller-3 10.0.0.13:3306 check port 9200 inter 5s rise 2 fall 3 backup

Performance et optimisation

# Paramètres de performance importants

# Taille du cache de certification
wsrep_provider_options = "gcache.size=1G"

# Threads pour appliquer les writesets
wsrep_slave_threads = 4  # Typiquement 2-4x nombre de CPU

# Flow control (éviter saturation)
wsrep_provider_options = "gcs.fc_limit=64; gcs.fc_factor=0.8"

# Retry en cas de deadlock
wsrep_retry_autocommit = 3

Exemples pratiques

Test de réplication

# Sur Node 1: créer une table de test
docker exec mariadb mysql -e "
CREATE DATABASE IF NOT EXISTS test_galera;
USE test_galera;
CREATE TABLE IF NOT EXISTS repl_test (
    id INT AUTO_INCREMENT PRIMARY KEY,
    data VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO repl_test (data) VALUES ('test from node 1');
"

# Sur Node 2: vérifier la réplication
ssh controller-2 "docker exec mariadb mysql -e 'SELECT * FROM test_galera.repl_test;'"

# Nettoyer
docker exec mariadb mysql -e "DROP DATABASE test_galera;"

Simulation de panne

# Arrêter un nœud
ssh controller-3 "docker stop mariadb"

# Vérifier que le cluster continue
docker exec mariadb mysql -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
# Doit afficher: 2

# Insérer des données (doit fonctionner)
docker exec mariadb mysql -e "INSERT INTO keystone.test VALUES(1);" || echo "OK expected to fail - table doesn't exist"

# Redémarrer le nœud
ssh controller-3 "docker start mariadb"

# Vérifier le rejoin
sleep 30
ssh controller-3 "docker exec mariadb mysql -e \"SHOW STATUS LIKE 'wsrep_local_state_comment';\""
# Doit afficher: Synced

Backup Galera

# Backup à chaud avec mariabackup
docker exec mariadb mariabackup --backup \
    --target-dir=/backup/$(date +%Y%m%d) \
    --user=root --password=$MYSQL_ROOT_PASSWORD

# Ou depuis l'extérieur
mysqldump --single-transaction --all-databases \
    -h 10.0.0.10 -u root -p > galera_backup.sql

Ressources

Checkpoint

  • Cluster Galera avec 3 nœuds en état "Synced"
  • wsrep_cluster_size = 3
  • Réplication synchrone fonctionnelle
  • Failover testé (arrêt d'un nœud)
  • Rejoin automatique après redémarrage
  • HAProxy distribue les requêtes correctement