# MariaDB desde cero (V): replicación, Galera y producción

*Quinta y última entrega de la serie **[MariaDB desde cero a pro](/search?tag=mariadb-desde-cero)**. Tiempo de lectura estimado: 14 minutos.*

Último tramo. Has aprendido a instalar MariaDB ([I](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)), diseñar esquemas con storage engines y tipos adecuados ([II](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)), escribir consultas modernas ([III](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)) y diagnosticar rendimiento ([IV](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)). Ahora toca lo que define "estar en producción": **replicación**, **Galera**, **backups**, **monitorización** y **seguridad**.

Comparativa honesta: ponerlo todo a funcionar en MariaDB es más artesanal que en un PostgreSQL con Patroni o un RDS gestionado. No es más difícil; es distinto. Te paga en flexibilidad.

## Binlog: la base de todo

MariaDB mantiene un **binary log** (binlog): un log secuencial de cambios que sirve para replicación y *point-in-time recovery*. Activarlo es requisito para todo lo serio.

```ini
[mariadb]
server_id = 1
log_bin = /var/log/mysql/mariadb-bin
binlog_format = ROW            # ROW es el estándar moderno
binlog_row_image = MINIMAL     # menos espacio en replicación
expire_logs_days = 7
sync_binlog = 1                # durable, a costa de algo de rendimiento
```

Formatos de binlog:

- **STATEMENT**: guarda el SQL. Compacto, pero problemático con funciones no deterministas.
- **ROW**: guarda los cambios fila a fila. Grande, pero completamente determinista. **Usa este.**
- **MIXED**: mezcla. Evitable.

## Replicación asíncrona

La replicación "clásica": una primaria y una o varias réplicas que aplican los cambios después. Desde hace años, lo estándar es usar **GTID** (Global Transaction ID), que identifica cada transacción globalmente y hace trivial el failover y el reposicionamiento.

### Config en la primaria

```ini
[mariadb]
server_id = 1
log_bin = /var/log/mysql/mariadb-bin
binlog_format = ROW
gtid_domain_id = 1
gtid_strict_mode = ON
log_slave_updates = ON
```

Crear el usuario para las réplicas:

```sql
CREATE USER 'repl'@'10.0.%.%' IDENTIFIED BY 'xxx';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'10.0.%.%';
```

### Config en la réplica

```ini
[mariadb]
server_id = 2
log_bin = /var/log/mysql/mariadb-bin   # también en réplicas
binlog_format = ROW
gtid_domain_id = 1
gtid_strict_mode = ON
log_slave_updates = ON
read_only = ON
```

Y desde SQL:

```sql
CHANGE MASTER TO
  MASTER_HOST='primary.internal',
  MASTER_USER='repl',
  MASTER_PASSWORD='xxx',
  MASTER_USE_GTID=current_pos;

START SLAVE;

SHOW SLAVE STATUS\G
```

Las líneas clave en `SHOW SLAVE STATUS`:

- `Slave_IO_Running` y `Slave_SQL_Running`: deben ser `Yes`.
- `Seconds_Behind_Master`: lag aproximado.
- `Gtid_IO_Pos`: posición actual en términos de GTID.
- `Last_Error` y `Last_SQL_Error`: si algo ha roto.

### Replicación paralela

Desde MariaDB 10, la réplica puede aplicar cambios en paralelo. En `postgresql.conf` de la réplica:

```ini
slave_parallel_mode = optimistic
slave_parallel_threads = 8
slave_parallel_workers = 8
```

Esto ayuda enormemente cuando la réplica iba quedándose atrás con cargas de escritura fuertes.

### Semi-sync

Asíncrona pura tiene un riesgo claro: si la primaria cae antes de que el binlog llegue a las réplicas, los commits recientes se pierden. **Semi-sync** mitiga: la primaria espera al ACK de al menos una réplica antes de confirmar al cliente.

```ini
# En primaria
rpl_semi_sync_master_enabled = ON
rpl_semi_sync_master_timeout = 10000   # 10s antes de caer a async

# En réplicas
rpl_semi_sync_slave_enabled = ON
```

No es garantía total, pero reduce muchísimo la ventana de pérdida.

## Galera Cluster: replicación síncrona multi-master

Galera es una tecnología de replicación síncrona integrada en MariaDB (paquete `mariadb-server` incluye el *wsrep provider* de serie). Características:

- **Multi-master**: todos los nodos aceptan escrituras.
- **Síncrona**: un commit no se confirma hasta que todos los nodos lo han aceptado.
- **Consistencia**: no hay lag de replicación (sí algo de latencia de commit).
- **Tolerancia**: con quórum (N/2 + 1), el cluster sigue funcionando.

Ideal para:

- Aplicaciones que exigen HA síncrona entre dos o tres data centers cercanos.
- Escenarios donde el downtime es inaceptable y toleras algo de latencia añadida en escritura.

### Config mínima

En `/etc/mysql/mariadb.conf.d/60-galera.cnf`:

```ini
[galera]
wsrep_on = ON
wsrep_provider = /usr/lib/galera/libgalera_smm.so
wsrep_cluster_name = "blog_cluster"
wsrep_cluster_address = "gcomm://10.0.0.1,10.0.0.2,10.0.0.3"
wsrep_node_address = "10.0.0.1"
wsrep_node_name = "ch-01"
wsrep_sst_method = mariabackup
wsrep_sst_auth = "sst:xxx"

binlog_format = ROW
default_storage_engine = InnoDB
innodb_autoinc_lock_mode = 2
bind_address = 0.0.0.0
```

Bootstrap del primer nodo (solo la **primera vez**):

```bash
galera_new_cluster
```

Y en el resto de nodos, arranque normal:

```bash
systemctl start mariadb
```

Verificar estado del cluster:

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

Señales de salud:

- `wsrep_cluster_size` = número de nodos esperado.
- `wsrep_cluster_status` = `Primary`.
- `wsrep_ready` = `ON`.
- `wsrep_local_state_comment` = `Synced`.

### Consideraciones de Galera

- **Latencia de red** entre nodos: Galera hace un *consensus* por transacción. Si los nodos están en DCs distantes, cada commit paga ese RTT.
- **Write conflicts**: si dos nodos modifican la misma fila a la vez, uno recibe error 1213 (deadlock). La app debe reintentar.
- **Tablas sin PK**: no se replican bien. **Todas las tablas necesitan PK.** InnoDB ya lo recomienda igualmente.
- **DDL**: Galera soporta dos métodos de aplicación, Total Order Isolation (TOI, bloquea el cluster) y Rolling Schema Upgrade (RSU, nodo por nodo). TOI es el default y más seguro.
- **Escalado de escritura**: Galera NO escala escrituras (todas las escrituras tienen que aplicarse en todos los nodos). Lo que te da es HA y lecturas distribuidas.

Para escalar escrituras necesitas sharding, que en MariaDB se hace o con Spider o con lógica de aplicación.

## Backups

### mariabackup

La herramienta moderna, derivada de Percona XtraBackup. Backup físico en caliente, sin bloquear escrituras. Soporta Galera.

Instalación:

```bash
sudo apt install -y mariadb-backup
```

Backup:

```bash
mariabackup --backup --target-dir=/backups/$(date +%Y%m%d-%H%M) \
  --user=bkp --password=xxx
```

Backup incremental (requiere un full previo):

```bash
mariabackup --backup \
  --target-dir=/backups/inc-$(date +%Y%m%d-%H%M) \
  --incremental-basedir=/backups/full-20260518 \
  --user=bkp --password=xxx
```

Restore:

```bash
# Preparar (aplica log)
mariabackup --prepare --target-dir=/backups/full-20260518

# Copiar al datadir (con el servicio parado y datadir vacío)
systemctl stop mariadb
rm -rf /var/lib/mysql/*
mariabackup --copy-back --target-dir=/backups/full-20260518
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb
```

Para restore a un instante concreto, aplicas los binlogs con `mysqlbinlog`:

```bash
mysqlbinlog --start-position=... --stop-datetime='2026-05-18 12:34:56' \
  /var/log/mysql/mariadb-bin.00* | mariadb
```

### mysqldump (lógico)

Para migraciones y respaldos pequeños:

```bash
mariadb-dump --single-transaction --routines --triggers --events \
  --databases blog > blog.sql
```

`--single-transaction` hace el dump dentro de una transacción REPEATABLE READ, evitando bloqueos. Para tablas no-InnoDB (MyISAM, Aria), esto no sirve y hay que usar `--lock-tables`.

### Estrategia de backup sensata

- **Full** diario (mariabackup).
- **Incremental** cada hora o cada 6 h.
- **Binlog** copiado a almacenamiento externo en tiempo real (puedes hacer `mysqlbinlog --read-from-remote-server --raw ...` en un script con systemd timer).
- **Retención**: 7–30 días según compliance.
- **Almacenamiento**: S3, GCS o equivalente. Nunca en la misma máquina.
- **Restore probado** mensualmente. Un backup no probado es una suposición.

## Monitorización

Métricas imprescindibles:

- **QPS / TPS**.
- **Tasa de errores SQL** (1213 deadlocks, 1205 lock wait timeouts).
- **Buffer pool hit ratio**: objetivo > 99%.
- **Replication lag** (`Seconds_Behind_Master`).
- **Threads_connected** vs `max_connections`.
- **Disk I/O y espacio** en datadir y en `pg_wal`-equivalente (`/var/log/mysql`).
- **Galera**: `wsrep_cluster_size`, `wsrep_flow_control_paused`, `wsrep_local_recv_queue_avg`.

Herramientas:

- **Prometheus + mysqld_exporter + Grafana**. Dashboards listos en grafana.com. Mismo stack que el post [Prometheus y Grafana para servicios pequeños](/post/prometheus-y-grafana-para-servicios-pequenos).
- **Percona PMM**: plataforma completa de monitorización, open source, muy visual. Mi elección si quieres lo mejor listo de serie.
- **pt-stalk / pt-summary** (Percona Toolkit): diagnóstico puntual.

### Alertas mínimas

- Replication lag > 30 s.
- Cluster size distinto al esperado (Galera).
- Deadlocks nuevos > 0 en 5 min (según tu tolerancia).
- Disco > 80%.
- Conexiones usadas > 80% del máximo.
- Buffer pool hit ratio < 99% durante 10 min.
- Slow queries nuevas en el top.

## Seguridad en producción

Mínimo aceptable:

1. **TLS** obligatorio en conexiones externas:
   ```ini
   [mariadb]
   ssl_cert = /etc/mysql/server-cert.pem
   ssl_key = /etc/mysql/server-key.pem
   ssl_ca = /etc/mysql/ca.pem
   require_secure_transport = ON
   ```
2. **scram-sha-256 / ed25519** para autenticación moderna (en vez de `mysql_native_password`):
   ```sql
   CREATE USER 'app'@'10.%.%.%'
     IDENTIFIED VIA ed25519 USING PASSWORD('xxx');
   ```
3. **Usuarios con permisos mínimos**. La app nunca como `root`.
4. **Red privada**: MariaDB nunca expuesto a internet.
5. **Audit log** con el plugin `server_audit`:
   ```ini
   plugin_load_add = server_audit
   server_audit_logging = ON
   server_audit_events = CONNECT,QUERY_DDL,QUERY_DCL
   ```
6. **Validación de contraseñas** con `simple_password_check`.
7. **Actualizaciones regulares** de minor.
8. **Roles** (ver [entrega I](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)) para separar permisos.

## Upgrades

- **Minor** (11.4.x → 11.4.y): simple. Parar, actualizar paquete, arrancar, `mariadb-upgrade` por si hay ajustes de metadata.
- **Major** (10.11 → 11.4): varias rutas:
  - **In-place**: parar, reinstalar paquete de la versión nueva, arrancar, `mariadb-upgrade`.
  - **Réplica nueva**: levantar una réplica en la versión nueva, promocionar, retirar la antigua.
  - **mysqldump**: migración lógica, lenta pero segura.

En cluster Galera, los upgrades se hacen **nodo a nodo**, en rolling. Siempre que la versión nueva sea compatible con la del cluster durante el proceso (consulta la guía oficial para cada salto).

## Checklist de producción

- [ ] Backups full + incremental automatizados a almacenamiento externo.
- [ ] Restore probado en entorno limpio.
- [ ] Replicación configurada con GTID; al menos una réplica.
- [ ] Semi-sync o Galera activo si el nivel de durabilidad lo exige.
- [ ] Failover documentado y probado.
- [ ] Connection pooler (ProxySQL / MaxScale / equivalentes).
- [ ] Monitorización con las métricas de arriba + alertas.
- [ ] TLS y autenticación robusta.
- [ ] `innodb_buffer_pool_size` ajustado a la RAM real (ver [entrega IV](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)).
- [ ] Slow query log activado y revisado periódicamente.
- [ ] Logs centralizados.
- [ ] Binlog con expiración razonable y copiado fuera.
- [ ] Plan de upgrades con ventanas definidas.
- [ ] Red privada, sin MariaDB expuesto a internet.

## Cierre de la serie

Fin del recorrido. En cinco posts hemos pasado de no haber tocado MariaDB a tenerlo desplegado en producción con replicación y backups serios.

Una observación sobre el ecosistema: MariaDB tiene una filosofía muy pragmática. Integra lo que funciona (Galera, mariabackup, ColumnStore) en el producto base y mantiene una compatibilidad alta con MySQL para que no te quedes atrás. A cambio, te pide más trabajo manual que un PostgreSQL con Patroni o que un servicio gestionado. Es un trade-off razonable si tu equipo valora el control.

Serie completa:

- [I: instalación y primeros pasos](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)
- [II: storage engines, tipos y restricciones](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)
- [III: consultas, CTEs y window functions](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)
- [IV: índices, EXPLAIN y tuning](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)
- V: replicación, Galera y producción *(estás aquí)*

Y ampliando horizonte de bases de datos:

- [PostgreSQL desde cero a pro](/search?tag=postgresql-desde-cero): la opción por defecto para la mayoría de aplicaciones.
- [ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero): cuando lo tuyo es analítica a escala.
- [MySQL y MariaDB, MariaDB y MySQL](/post/mysql-y-mariadb-mariadb-y-mysql): la comparativa honesta entre los dos *forks*.

Si tienes dudas sobre montar Galera, elegir entre async y Galera, o depurar un bloqueo recurrente, mis DMs siguen abiertos.
