Clústering de bases de dades
Què és el clústering de bases de dades
Un clúster de bases de dades és un conjunt de dos o més nodes (servidors) de SGBD que treballen conjuntament per oferir el servei de base de dades amb alta disponibilitat i, en alguns casos, escalabilitat horitzontal.
La diferència clau respecte a la replicació simple és que el clústering inclou:
- Detecció automàtica de fallades: un sistema de monitoratge (watchdog o health check) detecta quan un node no respon.
- Failover automàtic: sense intervenció humana, el sistema promou un node secundari a primari.
- Gestió del quòrum: per evitar el split-brain (dos nodes creuent que cadascun és el primari).
Actiu-actiu vs actiu-passiu
| Arquitectura | Descripció | Avantatge | Desavantatge |
|---|---|---|---|
| Actiu-passiu | Un node processa totes les peticions; els altres estan en standby | Simplicitat, garantia de consistència | Recursos dels nodes secundaris inutilitzats |
| Actiu-actiu | Múltiples nodes accepten peticions d'escriptura simultàniament | Millor utilització de recursos, latència baixa | Molt complex; requereix gestió de conflictes |
Solucions de clústering per motor
Patroni + etcd/Consul/ZooKeeper:
─────────────────────────────────
Patroni és l'estàndard de facto per a HA de PostgreSQL. Cada node del clúster executa:
- PostgreSQL (el SGBD)
- Patroni (l'agent que gestiona el rol del node i el failover)
El DCS (Distributed Configuration Store) — etcd, Consul o ZooKeeper — actua com
a àrbitre per determinar quin node és el líder i evitar el split-brain.
# patroni.yml — configuració exemple per a un node
scope: cluster_prod
namespace: /postgresql/
name: node1
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.101:8008
etcd3:
hosts: 192.168.1.10:2379,192.168.1.11:2379,192.168.1.12:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576 # 1 MB de lag màxim per ser candidat
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
max_wal_senders: 10
max_replication_slots: 10
wal_log_hints: 'on'
archive_mode: 'on'
archive_command: 'cp %p /var/lib/postgresql/wal_archive/%f'
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.101:5432
data_dir: /var/lib/postgresql/data
pg_hba:
- host replication replicant 192.168.1.0/24 scram-sha-256
- host all all 0.0.0.0/0 scram-sha-256
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
# Comandes Patronictl (gestió del clúster)
patronictl -c /etc/patroni/patroni.yml list # estat del clúster
patronictl -c /etc/patroni/patroni.yml switchover # switchover planificat
patronictl -c /etc/patroni/patroni.yml failover # failover manual
patronictl -c /etc/patroni/patroni.yml reinit node2 # reiniciar un node com a rèplica
Galera Cluster (actiu-actiu):
─────────────────────────────
Galera és un plugin de replicació síncrona multi-master per a MySQL i MariaDB.
- Tots els nodes accepten escriptures (actiu-actiu).
- Les escriptures es repliquen síncrona a tots els nodes via el protocol wsrep.
- Si un node es desconnecta, es retira automàticament del clúster.
- El quòrum (majoria de nodes) evita el split-brain.
# my.cnf — configuració Galera per a node 1 de 3
[mysqld]
server_id = 1
binlog_format = ROW
default_storage_engine = InnoDB
innodb_autoinc_lock_mode = 2
# Galera wsrep
wsrep_on = ON
wsrep_provider = /usr/lib/galera/libgalera_smm.so
wsrep_cluster_name = "cluster_prod"
wsrep_cluster_address = "gcomm://192.168.1.101,192.168.1.102,192.168.1.103"
wsrep_node_address = "192.168.1.101"
wsrep_node_name = "node1"
wsrep_sst_method = rsync # o xtrabackup-v2 per a clústers grans
-- Monitorar l'estat del clúster Galera
SHOW STATUS LIKE 'wsrep_%';
-- Camps clau:
-- wsrep_cluster_size: 3 (nombre de nodes)
-- wsrep_cluster_status: Primary
-- wsrep_connected: ON
-- wsrep_ready: ON
-- wsrep_local_state_comment: Synced
MySQL InnoDB Cluster (actiu-actiu o actiu-passiu):
───────────────────────────────────────────────────
Solució oficial de MySQL que combina:
- MySQL Group Replication (replicació multi-master o single-primary)
- MySQL Router (proxy que enruta les connexions al node actiu)
- MySQL Shell (per a la gestió del clúster)
Windows Server Failover Cluster (WSFC) + Always On FCI:
────────────────────────────────────────────────────────
Una FCI (Failover Cluster Instance) presenta als clients una instancia SQL Server
amb un nom virtual (VNN — Virtual Network Name) i una IP virtual.
- L'emmagatzematge és compartit (SAN, SMB, Storage Spaces Direct).
- Si el node actiu falla, el WSFC transfereix l'emmagatzematge i els recursos
al node passiu en segon/minuts.
- Els clients es reconnecten automàticament al nom virtual.
Requeriments:
- Entorn Windows Server amb Active Directory.
- Llicències de Windows Server + SQL Server Enterprise.
- Emmagatzematge compartit (o S2D en Windows Server 2016+).
# Instal·lació del rol WSFC (PowerShell, com a administrador)
Install-WindowsFeature -Name Failover-Clustering -IncludeManagementTools
# Crear el clúster
New-Cluster -Name "ClusterSQL" -Node "NODE1","NODE2" -StaticAddress "192.168.1.50"
# Verificar l'estat del clúster
Get-ClusterNode
Get-ClusterResource
Oracle RAC (Real Application Clusters):
────────────────────────────────────────
Oracle RAC és la solució de clústering actiu-actiu d'Oracle. Múltiples nodes
accedeixen simultàniament a la mateixa base de dades emmagatzemada en
emmagatzematge compartit (Oracle ASM — Automatic Storage Management).
- Tots els nodes processen escriptures i lectures simultàniament.
- Oracle Cache Fusion sincronitza els blocs de dades de la memòria cau
entre els nodes via interconnexió privada d'alta velocitat.
- Clusterware gestiona el quòrum, els failovers i els recursos.
- SCAN (Single Client Access Name): un nom DNS que resol a múltiples IPs
(una per node), per on els clients es connecten.
Components:
- Grid Infrastructure (Clusterware + ASM)
- Oracle Database amb RAC option
- Emmagatzematge compartit (NFS, iSCSI, FC SAN, o Oracle ACFS)
- Interconnexió privada d'alta velocitat (InfiniBand o 10GbE)
-- Veure l'estat del clúster RAC
SELECT inst_id, instance_name, host_name, status, active_state
FROM gv$instance;
-- Estadístiques de Cache Fusion (transferències de blocs entre nodes)
SELECT class, cr_received, cr_sent, current_received, current_sent
FROM gv$cr_block_server
ORDER BY inst_id;
-- Bloquejos globals entre nodes (Global Enqueue Services)
SELECT inst_id, name, gets, misses, sleeps
FROM gv$latch
WHERE name LIKE '%ges%'
ORDER BY inst_id, sleeps DESC;
Balanceig de càrrega
En un entorn amb múltiples nodes, cal un mecanisme per distribuir les connexions dels clients entre els nodes disponibles i redirigir-les automàticament en cas de fallada.
graph TD
A1["Aplicació 1"] --> LB
A2["Aplicació 2"] --> LB
A3["Aplicació 3"] --> LB
LB["Load Balancer\n(HAProxy / ProxySQL / pgBouncer)"]
LB --> |"Escriptures → port 5432"| P[("Primari / Actiu\n192.168.1.101")]
LB --> |"Lectures → port 5433"| R1[("Rèplica 1\n192.168.1.102")]
LB --> |"Lectures → port 5433"| R2[("Rèplica 2\n192.168.1.103")]
P -.->|"Health check"| LB
R1 -.->|"Health check"| LB
R2 -.->|"Health check"| LB
style LB fill:#f0ad4e,color:#333
style P fill:#337ab7,color:#fff
style R1 fill:#5cb85c,color:#fff
style R2 fill:#5cb85c,color:#fff
HAProxy per a PostgreSQL/MySQL
# haproxy.cfg — exemple per a PostgreSQL amb Patroni
global
maxconn 4096
log /dev/log local0
defaults
mode tcp
timeout connect 5s
timeout client 30s
timeout server 30s
# Frontend principal (port 5000 → primari)
frontend fe_postgres_write
bind *:5000
default_backend be_postgres_primary
# Frontend de lectura (port 5001 → rèpliques)
frontend fe_postgres_read
bind *:5001
default_backend be_postgres_replicas
# Backend primari (Patroni exposa un endpoint REST per saber qui és el primari)
backend be_postgres_primary
option httpchk GET /primary
http-check expect status 200
server node1 192.168.1.101:5432 check port 8008 inter 2s rise 2 fall 3
server node2 192.168.1.102:5432 check port 8008 inter 2s rise 2 fall 3 backup
server node3 192.168.1.103:5432 check port 8008 inter 2s rise 2 fall 3 backup
# Backend rèpliques (Patroni endpoint /replica)
backend be_postgres_replicas
balance roundrobin
option httpchk GET /replica
http-check expect status 200
server node1 192.168.1.101:5432 check port 8008 inter 2s rise 2 fall 3
server node2 192.168.1.102:5432 check port 8008 inter 2s rise 2 fall 3
server node3 192.168.1.103:5432 check port 8008 inter 2s rise 2 fall 3
# Estadístiques HAProxy
listen stats
bind *:7000
stats enable
stats uri /
stats refresh 5s
ProxySQL per a MySQL
-- Configuració de ProxySQL per a MySQL InnoDB Cluster / Galera
-- Afegir els servidors al grup d'escriptura (hostgroup 0) i lectura (hostgroup 1)
INSERT INTO mysql_servers (hostgroup_id, hostname, port, weight, max_connections) VALUES
(0, '192.168.1.101', 3306, 1000, 200), -- Primary (escriptura)
(1, '192.168.1.101', 3306, 100, 200), -- Primary (lectura baixa prioritat)
(1, '192.168.1.102', 3306, 1000, 200), -- Replica 1 (lectura)
(1, '192.168.1.103', 3306, 1000, 200); -- Replica 2 (lectura)
-- Usuari de monitoratge (per als health checks)
UPDATE global_variables SET variable_value = 'monitor_user'
WHERE variable_name = 'mysql-monitor_username';
-- Regles de routing: SELECTs van al grup de lectura (1)
INSERT INTO mysql_query_rules (rule_id, active, match_digest, destination_hostgroup, apply)
VALUES
(1, 1, '^SELECT.*FOR UPDATE', 0, 1), -- SELECT ... FOR UPDATE → escriptura
(2, 1, '^SELECT', 1, 1); -- Altres SELECTs → lectura
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
Failover automàtic: conceptes i mecanismes
Detecció de fallades i quòrum
El mecanisme de quòrum evita el split-brain, el problema on dos nodes creuen que tots dos son el primari i escriuen dades independentment (causant divergència de dades).
flowchart TD
A["Node A perd connexió amb Node B"] --> Q{"Node A pot contactar\nel DCS (etcd/Consul)?"}
Q --> |"Sí: manté el lideratge"| AL["Node A continua com a primari\n(B és qui ha caigut)"]
Q --> |"No: perd el quòrum"| AS["Node A s'atura (STONITH)\nper evitar split-brain"]
AS --> BL["Node B detecta que A no respon\ni es promou a primari"]
style AS fill:#d9534f,color:#fff
style AL fill:#5cb85c,color:#fff
style BL fill:#337ab7,color:#fff
STONITH (Shoot The Other Node In The Head): mecanisme per apagar forçosament un node sospitós. Pot ser via IPMI, PDU intel·ligent, o l'API del hypervisor/cloud.
Temps de failover típics
| Solució | Temps de detecció | Temps de failover total |
|---|---|---|
| Patroni (PostgreSQL) | 10-30 s | 20-60 s |
| Galera (MySQL) | 2-5 s | 5-15 s |
| WSFC + FCI (SQL Server) | 15-30 s | 30-120 s |
| Oracle RAC | < 10 s | < 30 s |
| Always On AG síncron | 10-20 s | 20-40 s |
Health checks i monitoratge en clúster
Endpoints de salut (Patroni)
# Patroni exposa una API REST per verificar el rol de cada node
# Node primari
curl -s http://192.168.1.101:8008/primary
# Retorna 200 OK si és el primari, 503 si no ho és
# Node rèplica
curl -s http://192.168.1.102:8008/replica
# Retorna 200 OK si és rèplica síncrona o asíncrona
# Estat complet del clúster
curl -s http://192.168.1.101:8008/cluster | python3 -m json.tool
Mètriques de clúster amb Prometheus
# Configuració de prometheus per a Patroni
scrape_configs:
- job_name: 'patroni'
static_configs:
- targets:
- '192.168.1.101:8008'
- '192.168.1.102:8008'
- '192.168.1.103:8008'
# Alertes crítiques recomanades (alertmanager)
groups:
- name: postgresql_ha
rules:
- alert: PatroniNodeDown
expr: patroni_master_running == 0
for: 30s
annotations:
summary: "Patroni: no hi ha primari actiu"
- alert: PatroniReplicationLag
expr: patroni_replication_lag > 30
for: 60s
annotations:
summary: "Patroni: lag de replicació > 30s"
- alert: PostgreSQLClusterSizeLow
expr: patroni_cluster_size < 3
for: 30s
annotations:
summary: "Clúster degradat: menys de 3 nodes actius"
Miniactivitat — AC0609
Objectiu: Desplegar un clúster Patroni mínim amb 3 nodes PostgreSQL i etcd usant Docker Compose i verificar el failover automàtic.
Prerequisits: Docker i Docker Compose instal·lats.
Arquitectura:
Passos:
- Crea un directori
patroni-clusteri undocker-compose.ymlamb: - 3 serveis etcd (
etcd1,etcd2,etcd3) - 3 serveis patroni+postgresql (
pg1,pg2,pg3) -
1 servei haproxy
-
Configura els fitxers
patroni1.yml,patroni2.yml,patroni3.ymlamb configuració de clúster. -
Arrenca el clúster:
docker compose up -d -
Verifica l'estat:
docker exec patroni1 patronictl -c /etc/patroni/patroni.yml list -
Connecta't al primari (via HAProxy port 5000) i crea una taula amb dades.
-
Simulació de fallada:
docker stop [contenidor_primari] -
Espera 30-60 s i torna a verificar
patronictl list. Un dels secundaris ha d'haver pres el relleu. -
Reconnecta't via HAProxy i verifica que les dades son accessibles.
-
Torna a arrencar el contenidor aturat:
docker start [contenidor_aturat] -
Verifica que el node aturat es reincorpora com a rèplica.
Lliura: Captures de patronictl list (estat normal), patronictl list (durant la fallada, mostrant el nou primari), i les dades accessibles via HAProxy durant el failover.