Javier Valencia Javier Valencia
Banco de leones marinos asomando la cabeza al unísono sobre el mar — la mascota Seal de MariaDB en clave de cluster

Cluster MariaDB master-master en Debian 13: instalación paso a paso desde el repo oficial

Javier Valencia · · 9 min de lectura · 5 visitas · DevOps
bases-de-datos tutorial mariadb galera devops debian alta-disponibilidad

Tiempo de lectura estimado: 16 minutos.

En la quinta entrega de la serie MariaDB desde cero pasé por encima de Galera, replicación asíncrona, backups y producción a vista de pájaro. Esta vez bajo al detalle: montar un cluster de 3 nodos en Debian 13 trixie desde cero, usando el repo oficial de MariaDB Foundation, y demostrar que funciona con pruebas reales (caídas, recuperación, conflictos de escritura, latencia).

Sobre la terminología: en el mundo MySQL/MariaDB master-master significa históricamente "dos primarias asíncronas en círculo". Aquí lo uso en el sentido más amplio y más usado hoy: multi-master síncrono con Galera. Es lo que la gente busca cuando dice "cluster master-master" en 2026 y es lo que tiene sentido montar en producción.

Por qué Galera y por qué tres nodos

Galera resuelve dos problemas de la replicación clásica de un solo plumazo:

  • Cualquier nodo acepta escrituras. No hay rol "primario" fijo, así que el failover deja de ser un procedimiento manual.
  • El commit es síncrono. Cuando la transacción se confirma, los tres nodos ya la tienen. No hay ventana de pérdida de datos.

¿Por qué tres y no dos? Por el quórum. Galera necesita mayoría (N/2 + 1) para seguir aceptando escrituras. Con dos nodos, perder uno te deja con 1 de 2 (sin mayoría) y el cluster se bloquea. Con tres nodos, perder uno te deja con 2 de 3 y el cluster sigue. Es la diferencia entre "alta disponibilidad" y "alta disponibilidad de verdad".

Si solo tienes presupuesto para dos máquinas reales, una solución habitual es añadir un garbd (Galera Arbitrator) en una tercera VM minúscula: aporta voto sin almacenar datos. Pero aquí montamos tres nodos completos.

Topología

Tres VMs Debian 13 trixie en una red privada /24. Todo lo que sigue asume estas IPs y nombres; sustituye por los tuyos:

Hostname IP Rol
db-01.lan 10.10.0.11 bootstrap
db-02.lan 10.10.0.12 nodo
db-03.lan 10.10.0.13 nodo

Recursos por VM (mínimo razonable): 2 vCPU, 4 GB RAM, 40 GB de disco. Para producción con carga real, dimensiona según innodb_buffer_pool_size.

Puertos que usa Galera:

  • 3306/tcp — cliente SQL.
  • 4567/tcp+udp — replicación entre nodos (gcomm).
  • 4568/tcp — transferencia incremental de estado (IST).
  • 4444/tcp — transferencia completa de estado (SST).

Preparación del sistema (en los 3 nodos)

Lo siguiente se hace en cada nodo, idéntico salvo el hostname y la IP.

Hostnames y DNS local

Sin DNS interno, el /etc/hosts es el mejor amigo. En los tres nodos:

sudo hostnamectl set-hostname db-01.lan   # ajusta por nodo

sudo tee -a /etc/hosts <<'EOF'
10.10.0.11  db-01.lan db-01
10.10.0.12  db-02.lan db-02
10.10.0.13  db-03.lan db-03
EOF

Compruébalo:

for n in db-01 db-02 db-03; do ping -c1 -W1 $n >/dev/null && echo "$n OK"; done
# db-01 OK
# db-02 OK
# db-03 OK

Reloj sincronizado

Galera detesta el reloj suelto. Debian 13 trae systemd-timesyncd activado de serie, pero verifícalo:

timedatectl status | grep -E "System clock|NTP service"
# System clock synchronized: yes
#               NTP service: active

Si por lo que sea estuviera en no/inactive, arréglalo antes de continuar:

sudo timedatectl set-ntp true

Firewall

Si tienes nftables o ufw activo, abre los puertos entre los nodos (no al mundo). Con nftables directo:

sudo nft add rule inet filter input ip saddr 10.10.0.0/24 \
  tcp dport { 3306, 4567, 4568, 4444 } accept
sudo nft add rule inet filter input ip saddr 10.10.0.0/24 \
  udp dport 4567 accept

Con ufw:

sudo ufw allow from 10.10.0.0/24 to any port 3306 proto tcp
sudo ufw allow from 10.10.0.0/24 to any port 4567
sudo ufw allow from 10.10.0.0/24 to any port 4568 proto tcp
sudo ufw allow from 10.10.0.0/24 to any port 4444 proto tcp

No abras 3306 al exterior. El acceso de aplicaciones debe pasar por una VIP, ProxySQL o MaxScale, y el tráfico entre nodos vive en la red privada.

AppArmor

Debian 13 mantiene AppArmor activo. El paquete de MariaDB instala su propio perfil, pero ten a mano aa-status por si algún directorio custom (datadir alternativo, por ejemplo) requiere ajuste:

sudo aa-status | grep mariadbd

Repo oficial de MariaDB

Debian 13 trixie incluye MariaDB en sus repos, pero queremos la versión que elijamos nosotros y soporte directo del upstream. La forma limpia es usar el script oficial de MariaDB Foundation, que añade el repo correcto según la distro y la versión.

Vamos a instalar MariaDB 11.4 LTS (soporte hasta 2029, estable y muy probada con Galera):

sudo apt update
sudo apt install -y curl ca-certificates apt-transport-https gnupg

curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup \
  | sudo bash -s -- --mariadb-server-version="mariadb-11.4" --skip-maxscale

Salida esperada:

# [info] Repository file successfully written to /etc/apt/sources.list.d/mariadb.list
# [info] Adding trusted package signing keys...
# [info] Successfully added trusted package signing keys
# [info] Cleaning package cache...

Comprueba el repo añadido:

cat /etc/apt/sources.list.d/mariadb.sources
# X-Repolib-Name: MariaDB
# Types: deb
# URIs: https://deb.mariadb.org/11.4/debian
# Suites: trixie
# Components: main
# Signed-By: /etc/apt/keyrings/mariadb-keyring.pgp

Instalación de MariaDB y Galera

El paquete mariadb-server ya trae el proveedor wsrep (Galera) integrado. Aún así conviene instalar galera-4 y mariadb-backup (necesario para SST con mariabackup):

sudo apt update
sudo apt install -y mariadb-server mariadb-backup galera-4

Verifica versiones:

mariadb --version
# mariadb from 11.4.5-MariaDB, client 15.2 for debian-linux-gnu (x86_64)

dpkg -l | grep -E "mariadb-server|galera-4|mariadb-backup" | awk '{print $2, $3}'
# galera-4 26.4.21-deb13
# mariadb-backup 1:11.4.5+maria~deb13
# mariadb-server 1:11.4.5+maria~deb13

Justo después de instalar, el servicio arranca con la config por defecto en standalone. Lo paramos en los tres nodos antes de configurar Galera:

sudo systemctl stop mariadb
sudo systemctl status mariadb --no-pager | head -3
# ● mariadb.service - MariaDB 11.4.5 database server
#      Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; preset: enabled)
#      Active: inactive (dead)

mariadb-secure-installation (antes del cluster)

Lo hacemos una vez por nodo mientras el servicio está aún en modo standalone (lo levantamos un momento, securizamos, lo paramos):

sudo systemctl start mariadb
sudo mariadb-secure-installation

Responde:

  • Enter current password for root: vacío (autenticación por socket en Debian).
  • Switch to unix_socket authentication?: n (ya está activo desde 10.4; redundante).
  • Change the root password?: n (root local, sin contraseña, autenticación por socket; más seguro que cualquier contraseña).
  • Remove anonymous users?: Y.
  • Disallow root login remotely?: Y.
  • Remove test database?: Y.
  • Reload privilege tables now?: Y.

Para el servicio antes de configurar Galera:

sudo systemctl stop mariadb

Configuración Galera

Crea el fichero /etc/mysql/mariadb.conf.d/60-galera.cnf en los tres nodos. La mayor parte es idéntica; las dos últimas líneas (wsrep_node_address y wsrep_node_name) son específicas de cada nodo.

[galera]
# Activación
wsrep_on                 = ON
wsrep_provider           = /usr/lib/galera/libgalera_smm.so

# Identidad del cluster
wsrep_cluster_name       = "blog_cluster"
wsrep_cluster_address    = "gcomm://10.10.0.11,10.10.0.12,10.10.0.13"

# Identidad del nodo (ajusta en cada máquina)
wsrep_node_address       = "10.10.0.11"
wsrep_node_name          = "db-01"

# SST (State Snapshot Transfer)
wsrep_sst_method         = mariabackup
wsrep_sst_auth           = "sstuser:CAMBIA_ESTO"

# Requisitos de Galera
binlog_format            = ROW
default_storage_engine   = InnoDB
innodb_autoinc_lock_mode = 2

# Aceptar conexiones de otros nodos
bind_address             = 0.0.0.0

# Tuning razonable de partida
wsrep_slave_threads      = 4
wsrep_provider_options   = "gcache.size=512M; gcs.fc_limit=128"

Notas:

  • gcomm://... con todas las IPs es lo correcto en arranque normal. En el bootstrap se usa una variante distinta (la veremos en un momento).
  • innodb_autoinc_lock_mode = 2 es obligatorio en Galera. Con valor 1 las escrituras concurrentes en tablas con AUTO_INCREMENT se serializan de forma que rompe el cluster.
  • gcache.size=512M permite que un nodo que estuvo caído pueda recuperarse con IST (transferencia incremental) si vuelve dentro de la ventana. Si tarda más, hará SST completa.

En db-02.lan:

wsrep_node_address       = "10.10.0.12"
wsrep_node_name          = "db-02"

En db-03.lan:

wsrep_node_address       = "10.10.0.13"
wsrep_node_name          = "db-03"

Usuario SST

mariabackup necesita un usuario en MariaDB para hacer las transferencias de estado entre nodos. Lo creamos solo en el primer nodo (después se replica solo); arrancamos brevemente standalone, lo creamos, y paramos:

sudo systemctl start mariadb
sudo mariadb <<'SQL'
CREATE USER 'sstuser'@'localhost' IDENTIFIED BY 'CAMBIA_ESTO';
GRANT PROCESS, RELOAD, LOCK TABLES, BINLOG MONITOR, REPLICA MONITOR
  ON *.* TO 'sstuser'@'localhost';
GRANT SELECT ON mysql.* TO 'sstuser'@'localhost';
FLUSH PRIVILEGES;
SQL
sudo systemctl stop mariadb

La contraseña tiene que coincidir con la de wsrep_sst_auth del fichero 60-galera.cnf.

Bootstrap del cluster

El bootstrap es solo la primera vez en la vida del cluster, y solo en uno de los nodos. El elegido inicia el cluster en estado Primary con wsrep_cluster_address = gcomm:// (sin IPs, indicando que es la semilla).

En db-01.lan:

sudo galera_new_cluster

Este wrapper de Debian lanza mariadbd con --wsrep-new-cluster. Verifica:

sudo systemctl status mariadb --no-pager | head -5
# ● mariadb.service - MariaDB 11.4.5 database server
#      Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; preset: enabled)
#      Active: active (running) since Fri 2026-05-15 09:42:11 CEST; 4s ago

sudo mariadb -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
# +--------------------+-------+
# | Variable_name      | Value |
# +--------------------+-------+
# | wsrep_cluster_size | 1     |
# +--------------------+-------+

Un nodo solo, en estado primary. Ahora levantamos los otros dos como un servicio normal.

En db-02.lan y db-03.lan (uno tras otro, no en paralelo):

sudo systemctl start mariadb

La primera vez harán SST desde db-01 usando mariabackup. En el log lo ves claro:

sudo journalctl -u mariadb -n 30 --no-pager | grep -E "WSREP|SST"
# ... WSREP: Member 0.0 (db-02) requested state transfer from '*any*'
# ... WSREP: Running: 'wsrep_sst_mariabackup --role 'joiner' ...'
# ... WSREP: SST received: ...
# ... WSREP: Member 0.0 (db-02) synced with group.

Cuando los tres están arriba:

sudo mariadb -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
# +--------------------+-------+
# | Variable_name      | Value |
# +--------------------+-------+
# | wsrep_cluster_size | 3     |
# +--------------------+-------+

Verificación del cluster

Las cuatro variables que miro siempre, en cada nodo:

sudo mariadb -e "
  SHOW STATUS WHERE Variable_name IN (
    'wsrep_cluster_size',
    'wsrep_cluster_status',
    'wsrep_ready',
    'wsrep_local_state_comment'
  );"
# +---------------------------+----------+
# | Variable_name             | Value    |
# +---------------------------+----------+
# | wsrep_cluster_size        | 3        |
# | wsrep_cluster_status      | Primary  |
# | wsrep_local_state_comment | Synced   |
# | wsrep_ready               | ON       |
# +---------------------------+----------+

Los cuatro valores tienen que ser los de arriba en los tres nodos. Si uno se queda en Donor/Desynced un rato es normal mientras envía SST a otro; debe volver a Synced en segundos.

También útil:

sudo mariadb -e "SHOW STATUS LIKE 'wsrep_incoming_addresses';"
# +--------------------------+-----------------------------------------------------+
# | Variable_name            | Value                                               |
# +--------------------------+-----------------------------------------------------+
# | wsrep_incoming_addresses | 10.10.0.11:3306,10.10.0.12:3306,10.10.0.13:3306     |
# +--------------------------+-----------------------------------------------------+

Pruebas reales de funcionamiento

Aquí viene lo interesante: demostrar que el cluster hace lo que dice. Todas las pruebas son reproducibles con la instalación de arriba.

Prueba 1: escritura en un nodo, lectura en los otros dos

En db-01:

CREATE DATABASE demo;
USE demo;

CREATE TABLE notas (
  id        BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  contenido VARCHAR(200) NOT NULL,
  origen    VARCHAR(32)  NOT NULL,
  ts        TIMESTAMP    DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO notas (contenido, origen) VALUES ('hola desde db-01', @@hostname);

Inmediatamente en db-02:

SELECT * FROM demo.notas;
-- +----+------------------+--------+---------------------+
-- | id | contenido        | origen | ts                  |
-- +----+------------------+--------+---------------------+
-- |  1 | hola desde db-01 | db-01  | 2026-05-15 09:51:02 |
-- +----+------------------+--------+---------------------+

Y en db-03:

SELECT * FROM demo.notas;
-- (mismo resultado)

Prueba 2: escritura en cada nodo

Para que sea master-master de verdad, todos los nodos tienen que aceptar escrituras:

# Desde el host de control, lanzando contra cada nodo
for n in db-01 db-02 db-03; do
  mariadb -h $n -uroot -e \
    "INSERT INTO demo.notas (contenido, origen) VALUES ('eco', @@hostname);"
done

Lectura en cualquier nodo:

SELECT id, origen, ts FROM demo.notas ORDER BY id;
-- +----+--------+---------------------+
-- | id | origen | ts                  |
-- +----+--------+---------------------+
-- |  1 | db-01  | 2026-05-15 09:51:02 |
-- |  4 | db-01  | 2026-05-15 09:52:31 |
-- |  7 | db-02  | 2026-05-15 09:52:31 |
-- | 10 | db-03  | 2026-05-15 09:52:31 |
-- +----+--------+---------------------+

Fíjate en los id: saltan de 3 en 3 (1, 4, 7, 10). Es lo esperado: Galera asigna auto_increment_increment = 3 (número de nodos) y un auto_increment_offset distinto por nodo. Así dos nodos nunca generan el mismo id sin coordinarse. Verifica:

SHOW VARIABLES LIKE 'auto_increment_%';
-- +--------------------------+-------+
-- | Variable_name            | Value |
-- +--------------------------+-------+
-- | auto_increment_increment | 3     |
-- | auto_increment_offset    | 1     |   -- en db-01 (2 en db-02, 3 en db-03)
-- +--------------------------+-------+

Prueba 3: conflicto de escritura

Galera resuelve los conflictos con first commit wins (certificación optimista). Si dos nodos actualizan la misma fila a la vez, uno gana y el otro recibe error 1213 (deadlock) en el COMMIT.

En dos terminales en paralelo, una contra db-01 y otra contra db-02:

-- Terminal A (db-01)                   -- Terminal B (db-02)
START TRANSACTION;                       START TRANSACTION;
UPDATE demo.notas                        UPDATE demo.notas
  SET contenido='gana A' WHERE id=1;       SET contenido='gana B' WHERE id=1;
COMMIT;                                  COMMIT;
-- Query OK, 1 row affected              -- ERROR 1213 (40001): Deadlock found
--                                       --   when trying to get lock; try
--                                       --   restarting transaction

La fila queda con el valor de A. La aplicación tiene que reintentar las transacciones que reciben 1213. Es la misma disciplina que ya aplicarías con SERIALIZABLE en PostgreSQL.

Prueba 4: caída de un nodo (2/3 sigue con quórum)

Tirar db-02 por las bravas:

ssh db-02 sudo systemctl stop mariadb

Desde db-01:

SHOW STATUS LIKE 'wsrep_cluster_size';
-- +--------------------+-------+
-- | Variable_name      | Value |
-- +--------------------+-------+
-- | wsrep_cluster_size | 2     |
-- +--------------------+-------+

SHOW STATUS LIKE 'wsrep_cluster_status';
-- | wsrep_cluster_status | Primary |

INSERT INTO demo.notas (contenido, origen) VALUES ('aún vivo', @@hostname);
-- Query OK, 1 row affected (0.003 sec)

Sigue aceptando escrituras. Quórum 2/3 = mayoría.

Prueba 5: recuperación con IST

Vuelve a levantar db-02:

ssh db-02 sudo systemctl start mariadb
ssh db-02 sudo journalctl -u mariadb -n 20 --no-pager | grep WSREP | tail -5
# ... WSREP: Member 1.0 (db-02) requested state transfer from '*any*'
# ... WSREP: IST receiver addr using tcp://10.10.0.12:4568
# ... WSREP: IST received: 8023-8047
# ... WSREP: 0.0 (db-01): State transfer to 1.0 (db-02) complete.
# ... WSREP: Member 1.0 (db-02) synced with group.

IST: solo se transfirieron las transacciones que se perdió mientras estaba caído. Rapidísimo. Si el nodo hubiera estado caído más tiempo del que cubre gcache.size, Galera caería automáticamente a SST completa (más lenta pero igual de automática).

-- Desde db-02, ya sincronizado:
SELECT contenido FROM demo.notas WHERE contenido='aún vivo';
-- +----------+
-- | contenido|
-- +----------+
-- | aún vivo |
-- +----------+

Recibió la fila que se insertó mientras estaba abajo. Sin intervención manual.

Prueba 6: caída de dos nodos (sin quórum)

Tira db-02 y db-03:

ssh db-02 sudo systemctl stop mariadb
ssh db-03 sudo systemctl stop mariadb

Desde db-01:

SHOW STATUS LIKE 'wsrep_cluster_status';
-- | wsrep_cluster_status | non-Primary |

INSERT INTO demo.notas (contenido, origen) VALUES ('???', @@hostname);
-- ERROR 1047 (08S01): WSREP has not yet prepared node for application use

Galera bloquea las escrituras al perder mayoría. Es lo que quieres: prefiere parar a permitir un split-brain. Las lecturas también se bloquean por defecto; si quieres permitirlas se puede ajustar wsrep_dirty_reads = ON, pero piensatelo dos veces.

Para recuperar, lo correcto es levantar primero los nodos que apagaste (no rebootstrappear db-01 a lo loco — eso fuerza una primaria nueva y puede provocar pérdida si las otras tienen datos más recientes):

ssh db-02 sudo systemctl start mariadb
ssh db-03 sudo systemctl start mariadb

En cuanto cualquiera de los dos vuelve y forma quórum con db-01, el cluster vuelve a estado Primary y db-01 recupera escrituras. Verifica:

SHOW STATUS LIKE 'wsrep_cluster_size';
-- | wsrep_cluster_size | 3 |

SHOW STATUS LIKE 'wsrep_cluster_status';
-- | wsrep_cluster_status | Primary |

Prueba 7: latencia de escritura con sysbench

Lo síncrono cuesta. Vamos a medir cuánto. Instala sysbench:

sudo apt install -y sysbench

Prepara la carga (10 tablas, 100 000 filas cada una):

sysbench oltp_write_only \
  --mysql-host=db-01 --mysql-user=root \
  --tables=10 --table-size=100000 prepare

Lanza 60 segundos de escrituras puras con 16 hilos:

sysbench oltp_write_only \
  --mysql-host=db-01 --mysql-user=root \
  --tables=10 --table-size=100000 \
  --threads=16 --time=60 --report-interval=10 run

Salida típica en 3 nodos en la misma LAN (1 Gb/s, sin saturar nada):

[ 10s ] thds: 16 tps: 412.3 qps: 2473.8 (r/w/o: 0.0/2061.5/412.3)
[ 20s ] thds: 16 tps: 418.9 qps: 2513.4 (r/w/o: 0.0/2094.5/418.9)
...
SQL statistics:
    transactions:                        25018  (416.85 per sec.)
    queries:                             150108 (2501.10 per sec.)

Latency (ms):
         min:                                    8.42
         avg:                                   38.34
         95th percentile:                       58.99
         max:                                  148.27

Compáralo con la misma prueba contra un nodo standalone:

[ 10s ] thds: 16 tps: 1827.4 qps: 10964.3
...
Latency (ms):
         avg:                                    8.74
         95th percentile:                       13.46

Cuatro o cinco veces más latencia y un quinto del throughput. Es el coste de la durabilidad multi-DC síncrona. Si esto te asusta, replanteate si necesitas Galera de verdad o si te basta con replicación asíncrona + failover automático. Y si lo necesitas, dimensiona en consecuencia: red baja latencia, discos NVMe y gcache grande.

Limpia:

sysbench oltp_write_only --mysql-host=db-01 --mysql-user=root \
  --tables=10 cleanup

Operaciones del día a día

Rolling restart (sin downtime)

Reinicia un nodo cada vez, esperando a que vuelva a Synced antes del siguiente:

for n in db-01 db-02 db-03; do
  ssh $n sudo systemctl restart mariadb
  until ssh $n "sudo mariadb -BNe \"SHOW STATUS LIKE 'wsrep_local_state_comment';\" \
    | grep -q Synced"; do sleep 2; done
  echo "$n OK"
done

Añadir un cuarto nodo

  1. Instala MariaDB y galera-4 igual que en los primeros tres.
  2. Pon su IP en wsrep_cluster_address de todos los nodos (y añade el nuevo wsrep_node_address/wsrep_node_name en el nuevo).
  3. Arranca con systemctl start mariadb. Hará SST desde uno de los nodos existentes.

Recuerda mantener el cluster con número impar de nodos (3, 5, 7) si quieres preservar quórum claro. Con 4 nodos, perder 2 te deja 2/4 (sin mayoría).

Retirar un nodo

Para retirar db-03 definitivamente:

ssh db-03 sudo systemctl stop mariadb
ssh db-03 sudo systemctl disable mariadb

Quita su IP de wsrep_cluster_address en los nodos restantes. No requiere reinicio del cluster; el cambio se aplica en el próximo restart de cada nodo.

Backups

mariabackup funciona perfectamente con Galera. Hazlo desde un solo nodo (idealmente uno no productivo o uno marcado como backup donor):

sudo mariabackup --backup \
  --target-dir=/var/backups/mariadb/$(date +%F-%H%M) \
  --user=sstuser --password=CAMBIA_ESTO

# Después, "preparar" el backup
sudo mariabackup --prepare \
  --target-dir=/var/backups/mariadb/2026-05-15-1042

Sube los .xb a almacenamiento externo (S3, B2, lo que tengas). Restore probado mensualmente, como siempre.

Frontal: ProxySQL o MaxScale

Galera te da el cluster, pero la app necesita un punto único de entrada. Las dos opciones razonables:

  • ProxySQL: muy ligero, configuración en runtime vía SQL, lo que prefiero para la mayoría.
  • MaxScale: producto de MariaDB Corporation, más integrado pero con licencia BSL para producción a partir de cierto tamaño.

Una config mínima de ProxySQL para repartir lecturas entre los tres y mandar escrituras al "writer principal" (un nodo elegido para reducir conflictos de certificación):

INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES
  (10, '10.10.0.11', 3306),
  (20, '10.10.0.12', 3306),
  (20, '10.10.0.13', 3306);

INSERT INTO mysql_galera_hostgroups
  (writer_hostgroup, reader_hostgroup, max_writers, writer_is_also_reader, active)
VALUES (10, 20, 1, 1, 1);

LOAD MYSQL SERVERS TO RUNTIME;
SAVE  MYSQL SERVERS TO DISK;

ProxySQL monitoriza Galera con mysql_galera_hostgroups y reasigna escrituras automáticamente si el writer cae.

Lo que no te he dicho (y tienes que mirar)

  • TLS entre nodos (wsrep_provider_options="socket.ssl_..."). Imprescindible si los nodos van entre data centers por enlace público o compartido.
  • Backups encriptados con --encrypt.
  • Audit log (plugin server_audit) para cumplimiento.
  • Monitorización con Prometheus + mysqld_exporter (el exporter tiene métricas específicas de wsrep).
  • Particionado de tablas grandes para que el SST sea más manejable.
  • Selección de versión LTS según tu ventana de soporte. 11.4 LTS cubre hasta 2029; 11.8 LTS llegará más lejos cuando salga.

Para el panorama amplio (replicación asíncrona, GTID, semi-sync, checklist de producción) consulta la quinta entrega de la serie MariaDB desde cero. Para el resto de la serie:

Si te montas el cluster siguiendo esto y se atasca en algo (típicamente el SST inicial entre nodos por temas de firewall o de versión), escríbeme. Es de esas cosas donde un par de pares de ojos ahorran horas.