Salta el contingut

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
Pgpool-II:
──────────
Middleware que ofereix:
- Connection pooling
- Load balancing de lectures entre nodes
- Detecció de fallades i failover
- Replicació en mode "statement replication" (no recomanat per a producció moderna)
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)
# Configuració amb MySQL Shell
# mysqlsh --uri root@192.168.1.101

# Crear el clúster
# cluster = dba.createCluster('clusterProd')
# cluster.addInstance('root@192.168.1.102')
# cluster.addInstance('root@192.168.1.103')
# cluster.status()
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
-- Veure l'estat de la FCI
SELECT
    NodeName,
    status_description,
    is_current_owner
FROM sys.dm_os_cluster_nodes;
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:

etcd (3 nodes) + Patroni/PostgreSQL (3 nodes) + HAProxy (1 node)

Passos:

  1. Crea un directori patroni-cluster i un docker-compose.yml amb:
  2. 3 serveis etcd (etcd1, etcd2, etcd3)
  3. 3 serveis patroni+postgresql (pg1, pg2, pg3)
  4. 1 servei haproxy

  5. Configura els fitxers patroni1.yml, patroni2.yml, patroni3.yml amb configuració de clúster.

  6. Arrenca el clúster: docker compose up -d

  7. Verifica l'estat: docker exec patroni1 patronictl -c /etc/patroni/patroni.yml list

  8. Connecta't al primari (via HAProxy port 5000) i crea una taula amb dades.

  9. Simulació de fallada: docker stop [contenidor_primari]

  10. Espera 30-60 s i torna a verificar patronictl list. Un dels secundaris ha d'haver pres el relleu.

  11. Reconnecta't via HAProxy i verifica que les dades son accessibles.

  12. Torna a arrencar el contenidor aturat: docker start [contenidor_aturat]

  13. 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.