Les tres lliçons anteriors han tractat la replicació com un fet donat: hi havia rèpliques inv-bcn i inv-vlc que s'endarrerien, divergien o es posaven d'acord, però mai no vam explicar com arriben les dades de l'una a l'altra ni què passa pel camí. Aquesta lliçó obre aquesta caixa. La replicació és el mecanisme pel qual una mateixa peça de dades es manté en diversos nodes, i el seu disseny decideix directament quin model de consistència s'obté (03-01), què se sacrifica davant d'una partició (03-02) i on cal consens (03-03).
Veurem primer per a què es replica (disponibilitat, latència i escalat de lectures) i després les tres topologies que existeixen. La replicació líder-seguidor, la més comuna, amb les seves variants síncrona, asíncrona i semisíncrona, el retard de replicació i les anomalies que produeix (que són exactament les garanties de sessió de 03-01 vistes des de l'altre costat), i el failover amb els seus perills. La replicació multilíder, per a diverses regions o clients sense connexió, el problema central de la qual és resoldre conflictes entre escriptures concurrents. I la replicació sense líder a l'estil Dynamo, amb quòrums d'escriptura i lectura, reparació en lectura i hinted handoff. La part pràctica és doble: muntarem una rèplica real de PostgreSQL en streaming per a km0_comandes al docker-compose.yml de Quilòmetre Zero, l'observarem amb pg_stat_replication i la veurem arribar tard; i una simulació en Python amb N=3 mostrarà quan un quòrum retorna lectures estancades. La base de dades Cassandra, que implementa la replicació sense líder en producció, i el particionament (com repartir dades diferents entre nodes, no copiar-hi les mateixes) queden per al Mòdul 4.
Contingut
- Per què replicar: disponibilitat, latència i escalat de lectures
- Replicació líder-seguidor
- Síncrona, asíncrona i semisíncrona
- Retard de replicació i les seves anomalies
- Failover i els seus perills
- Replicació multilíder i resolució de conflictes
- Replicació sense líder: quòrums, read repair i hinted handoff
- Taula de les tres topologies
- Pràctica: PostgreSQL en streaming per a
km0_comandes - Simulació: quòrum W/R amb N=3
- Errors comuns i consells
- Exercicis
- Conclusió
- Per què replicar: disponibilitat, latència i escalat de lectures
Replicar és mantenir còpies de les mateixes dades en diversos nodes connectats per xarxa. Es fa per tres motius, i convé saber quin d'ells es persegueix perquè cadascun empeny el disseny en una direcció diferent:
| Objectiu | Què es busca | Conseqüència de disseny | A Quilòmetre Zero |
|---|---|---|---|
| Disponibilitat (tolerància a fallades) | Que la pèrdua d'un node (disc, màquina, centre de dades) no perdi dades ni aturi el servei | Les còpies han de ser en dominis de fallada diferents; cal un procediment de failover | km0_comandes no pot perdre comandes si mor el servidor primari |
| Latència | Que les dades siguin a prop de qui les llegeix | Còpies geogràficament repartides; escriptures possiblement llunyanes | El catàleg es llegeix des de València sense creuar a Barcelona |
| Escalat de lectures | Repartir la càrrega de lectura entre moltes còpies | Moltes rèpliques de només lectura; el retard entre elles passa a ser visible | La fitxa de tomaquet-rosa es llegeix milers de vegades per cada escriptura |
Fixa't que cap dels tres objectius no és "escalar escriptures": replicar copia les mateixes escriptures a tots els nodes, així que la capacitat d'escriptura no creix. Per a això cal particionar (04-01).
Si les dades no canviessin mai, replicar seria copiar-les una vegada. Tota la dificultat ve del fet que canvien: cada escriptura ha d'arribar a totes les còpies, i entre que arriba a una i a una altra hi ha un interval en què les còpies difereixen. Les tres topologies següents són tres respostes a la pregunta "qui accepta les escriptures i com es propaguen?".
- Replicació líder-seguidor
També anomenada single-leader, primary-replica o master-slave (terme en desús). És la que fan servir PostgreSQL, MySQL, MongoDB, Kafka (per partició) i la majoria de sistemes:
- Un node és el líder (primari). Totes les escriptures hi van. El primer que fa és escriure-les al seu emmagatzematge local.
- Els altres són seguidors (rèpliques, standbys). El líder els envia un flux de canvis (el log de replicació), i cada seguidor els aplica en el mateix ordre, amb la qual cosa el seu estat convergeix al del líder.
- Les lectures poden anar al líder (sempre actuals) o a qualsevol seguidor (possiblement endarrerides).
flowchart LR
C1[Clients que escriuen] -->|INSERT / UPDATE| L[(Líder<br/>km0_comandes primari)]
L -->|log de replicació WAL| S1[(Seguidor 1<br/>rèplica Barcelona)]
L -->|log de replicació WAL| S2[(Seguidor 2<br/>rèplica València)]
C2[Clients que llegeixen] -->|SELECT| S1
C3[Clients que llegeixen] -->|SELECT| S2
C1 -.->|SELECT que necessita<br/>l'últim valor| L
El que viatja pel flux pot ser de tres tipus, i la diferència importa a la pràctica:
| Què es replica | Exemple | Avantatge | Inconvenient |
|---|---|---|---|
| Sentències | UPDATE estoc SET unitats = unitats - 1 WHERE producte = 'formatge-curat' |
Compacte | No determinista: now(), random(), seqüències, triggers donen resultats diferents a cada node; MySQL ho va abandonar per defecte |
| Log físic (WAL) | Els bytes que canvien a les pàgines del disc | Exacte, ja existeix per a la durabilitat | Acobla les rèpliques a la versió exacta del motor i al seu format de disc; és el que fa PostgreSQL a la replicació en streaming |
| Log lògic (files) | "A la taula comandes, la fila amb id P-2026-000123 passa a tenir estat pagada" |
Independent del motor i de la versió; permet replicar taules soltes i alimentar altres sistemes (CDC) | Més costós de generar; és la replicació lògica de PostgreSQL i la base de Debezium, que connecta amb el patró Outbox de 02-05 |
Per afegir un seguidor nou sense aturar el líder es pren una instantània consistent del líder en un punt del log (a PostgreSQL, pg_basebackup), es copia al seguidor, i aquest demana al líder "tot el que ha passat des d'aquest punt" (catch-up). Quan l'atrapa, continua en directe. Ho farem a l'apartat 9.
- Síncrona, asíncrona i semisíncrona
La pregunta clau és quan el líder confirma l'escriptura al client respecte del moment en què el seguidor la rep:
sequenceDiagram
participant C as Client (comandes)
participant L as Líder
participant S as Seguidor
Note over C,S: Síncrona
C->>L: COMMIT
L->>S: WAL
S-->>L: escrit a disc
L-->>C: ok (només després de l'ack del seguidor)
Note over C,S: Asíncrona
C->>L: COMMIT
L-->>C: ok (immediat)
L->>S: WAL (arriba quan arriba)
| Mode | El líder confirma quan... | Garantia si el líder mor just després de confirmar | Latència d'escriptura | Disponibilitat d'escriptura |
|---|---|---|---|---|
| Síncrona | El seguidor (o tots els seguidors) ha escrit la transacció a disc | La transacció és al seguidor: zero pèrdua | + un viatge d'anada i tornada al seguidor més lent, i el seu fsync |
Si el seguidor síncron cau o s'aïlla, les escriptures es bloquegen fins que torni o es reconfiguri |
| Asíncrona | Ha escrit al seu propi disc | La transacció pot no haver arribat al seguidor: pèrdua possible (les últimes transaccions desapareixen en promoure el seguidor) | Mínima | El líder continua escrivint encara que tots els seguidors estiguin caiguts |
| Semisíncrona | Com a mínim un (o un quòrum) dels seguidors ha confirmat; la resta són asíncrons | Com a mínim una còpia té la transacció: zero pèrdua mentre no fallin el líder i aquest seguidor alhora | + el seguidor síncron més ràpid | Es bloqueja només si cap seguidor no pot confirmar |
La replicació completament síncrona amb molts seguidors és impracticable: el seguidor més lent marca la latència de tot el sistema, i qualsevol seguidor caigut el paralitza. Per això "síncrona" a la pràctica gairebé sempre vol dir semisíncrona: un seguidor síncron (que garanteix la còpia) i la resta asíncrons (que donen lectures i seran a mà si el síncron cau). PostgreSQL ho configura amb synchronous_standby_names, i a l'apartat 9 ho canviarem en calent per veure'n l'efecte. En termes de 03-02, la replicació asíncrona és PC/EL (ràpida, amb retard a les rèpliques) i la síncrona és PC/EC (cada commit paga la latència de la coordinació).
- Retard de replicació i les seves anomalies
Amb seguidors asíncrons, entre que el líder confirma i el seguidor aplica hi ha un retard de replicació (replication lag): mil·lisegons en condicions normals, segons o minuts si el seguidor està sobrecarregat, si hi ha una consulta llarga que bloqueja l'aplicació del WAL, o després d'una partició. Si les lectures van als seguidors per escalar, aquest retard es converteix en les anomalies que l'Anna patia a 03-01, ara amb la seva causa mecànica:
| Anomalia (03-01) | Com la produeix el retard | Com s'evita en líder-seguidor |
|---|---|---|
| Violació de read-your-writes | L'Anna escriu al líder i llegeix d'un seguidor que encara no ha aplicat la seva transacció | Llegir del líder allò que el mateix usuari acaba de modificar (per exemple, durant el minut següent a una escriptura); o el client recorda l'LSN del seu commit i el seguidor espera a assolir-lo (pg_last_wal_replay_lsn() >= lsn) |
| Violació de monotonic reads | Dues lectures consecutives cauen en seguidors amb retard diferent | Encaminar cada usuari sempre al mateix seguidor (hash de l'id d'usuari), o LSN mínim al client |
| Violació de consistència causal (resposta abans que la pregunta) | La pregunta d'en Marc i la resposta de la formatgeria s'escriuen al líder en ordre, però un seguidor amb retard de segons encara mostra només la pregunta mentre un altre mostra totes dues; un tercer sistema que llegeixi de tots dos veu l'ordre trencat | En líder-seguidor el log preserva l'ordre, així que un mateix seguidor mai no mostra la resposta sense la pregunta; el problema apareix en combinar lectures de seguidors diferents o en particionar (04-01) |
La conseqüència pràctica és que "escalar lectures amb rèpliques" no és transparent: cal decidir per a cada consulta si tolera retard, i mesurar el retard contínuament (pg_stat_replication a l'apartat 9; alertes a 07-01). Un límit raonable és encaminar a rèpliques només lectures on un retard d'uns segons és innocu (catàleg, historial de comandes de fa dies, informes) i mantenir al líder les lectures que precedeixen una decisió (queda estoc?, està pagada la comanda?).
- Failover i els seus perills
Si el líder mor, un seguidor s'ha de convertir en líder: és el failover. Els seus tres passos (detectar que el líder ha mort, triar el nou líder, redirigir els clients) semblen senzills i cadascun amaga un problema:
- Detectar: no hi ha manera segura de distingir un líder mort d'un de lent o aïllat (01-02, 03-02). Un timeout curt provoca failovers innecessaris; un de llarg allarga la indisponibilitat.
- Triar: amb replicació asíncrona, el seguidor més al dia pot no tenir les últimes transaccions confirmades: es perden. Pitjor: si altres sistemes ja han actuat sobre aquestes transaccions (una
comanda.creadaja publicada pel relay outbox), queden inconsistents amb la base de dades. Amb semisíncrona, el seguidor síncron és el candidat i no perd res. - Redirigir: si l'antic líder torna creient que continua sent-ho, hi ha dos nodes acceptant escriptures: split-brain (cervell dividit). És la situació que els termes de Raft impedien a 03-03, i en una base de dades sense consens integrat cal impedir-la des de fora: apagant l'antic líder per la força (STONITH, shoot the other node in the head) o amb un token de tancat.
Precisament perquè el failover automàtic d'una base de dades és tan delicat, molts equips el deleguen en eines que es recolzen en un sistema de consens (Patroni fa servir etcd per a PostgreSQL; MongoDB i Kafka porten la seva pròpia elecció integrada) i d'altres el fan a mà. Com s'opera un failover, amb quins checkpoints i com s'assaja és matèria de 07-03; aquí n'hi ha prou de saber que l'elecció entre asíncrona i semisíncrona determina quant s'hi perd.
- Replicació multilíder i resolució de conflictes
En líder-seguidor, tota escriptura passa per un únic node. Si Quilòmetre Zero té usuaris a Barcelona i a València i el líder és a Barcelona, cada escriptura des de València creua 350 km; i si la xarxa entre ciutats cau, València no pot escriure. La replicació multilíder (multi-leader, master-master) posa un líder a cada centre de dades, que accepta escriptures localment i les replica asíncronament als altres líders. Els seus casos d'ús legítims són pocs i concrets:
- Diverses regions amb escriptures locals i tolerància a la desconnexió entre elles.
- Clients sense connexió: l'app mòbil del repartidor de
furgoneta-3registra lliuraments sense cobertura i sincronitza en recuperar-la. Cada dispositiu és, en efecte, un líder. - Edició col·laborativa (diversos usuaris editant el mateix document), que és multilíder amb rèpliques molt petites.
I el seu problema central és inevitable: dos líders poden acceptar escriptures concurrents sobre la mateixa dada, i en replicar-se apareix un conflicte. Ja ho vam veure a la simulació AP de 03-02: l'Anna reserva a inv-bcn i en Marc a inv-vlc. Les estratègies, de la més simple a la més elaborada:
| Estratègia | Com funciona | Perd dades | Quan fer-la servir |
|---|---|---|---|
| Evitar el conflicte | Encaminar totes les escriptures d'una mateixa dada al mateix líder (les comandes de l'Anna sempre a Barcelona) | No | Sempre que sigui possible; falla quan cal canviar de líder |
| Last-write-wins (LWW) | Cada escriptura porta una marca de temps; guanya la més gran | Sí, silenciosament (03-02); depèn de rellotges físics desincronitzats (01-05) | Dades on l'última escriptura és realment la bona: posició de furgoneta-3, perfil editat per una sola persona |
| Fusió (merge) determinista | Combinar tots dos valors amb una regla: unió de conjunts, suma d'increments, concatenació ordenada | No, però pot produir estats inesperats | Cistell (unió: mai no perdre un article), llistes de desitjos |
| CRDT (03-01) | Tipus de dada la fusió dels quals és commutativa, associativa i idempotent | No | Comptadors de campanya, conjunts, text col·laboratiu; no serveix per a invariants globals |
| Resolució a l'aplicació | Es desen totes dues versions (siblings) i es demana a l'aplicació, o a l'usuari, que decideixi | No | Quan la regla de negoci ho exigeix: dues reserves de l'últim formatge, amb una compensació (03-05) |
Un detall que acostuma a sorprendre: la topologia en què els líders s'envien els canvis (tots amb tots, en anell, en estrella) afecta l'ordre en què arriben, i amb rellotges físics com a marca de temps LWW pot "guanyar" una escriptura que causalment va ser anterior. Els sistemes multilíder seriosos fan servir rellotges vectorials o version vectors (01-05) per detectar quines escriptures són realment concurrents i quines simplement van arribar tard. Multilíder és potent i perillós a parts iguals; PostgreSQL no l'ofereix de sèrie (hi ha extensions com BDR/pglogical), i la recomanació general és no fer-lo servir llevat que els tres casos d'ús de dalt ho exigeixin.
- Replicació sense líder: quòrums, read repair i hinted handoff
La tercera topologia elimina el líder per complet: qualsevol rèplica accepta escriptures i lectures, i la coordinació se substitueix per aritmètica. La va popularitzar l'article de Dynamo (Amazon, 2007), i la implementen Cassandra, Riak, Voldemort i ScyllaDB.
Quòrums d'escriptura i lectura
Amb N rèpliques de cada dada, el client (o un node coordinador que actua per ell) envia cada escriptura a les N rèpliques en paral·lel i la dona per bona quan W han confirmat; i envia cada lectura a les N rèpliques (o a un subconjunt) i la dona per bona amb R respostes, quedant-se amb el valor de versió més alta. Si
W + R > N
aleshores el conjunt de rèpliques que va confirmar l'escriptura i el conjunt que va respondre a la lectura se solapen com a mínim en una rèplica, que tindrà la versió nova; com que la lectura tria la versió més alta, sempre retorna l'última escriptura confirmada. Amb N=3, les configuracions típiques:
| W | R | W + R > N | Comportament |
|---|---|---|---|
| 1 | 1 | No | Màxima velocitat i disponibilitat; lectures estancades freqüents |
| 2 | 2 | Sí | Equilibri habitual: tolera una rèplica caiguda en escriptura i en lectura |
| 3 | 1 | Sí | Lectures ràpides; escriptures bloquejades si cau una rèplica |
| 1 | 3 | Sí | Escriptures ràpides; lectures bloquejades si cau una rèplica |
flowchart LR
Cl[Client / coordinador] -->|escriure v2| A[(rèplica A: v2)]
Cl -->|escriure v2| B[(rèplica B: v2)]
Cl -.->|escriure v2: no respon| C[(rèplica C: v1)]
Cl2[Client / coordinador] -->|llegir| B
Cl2 -->|llegir| C
B -->|v2| Cl2
C -->|v1| Cl2
Cl2 -->|max versió = v2<br/>read repair: enviar v2 a C| C
La condició W + R > N garanteix que la lectura veu l'última escriptura confirmada, però no dona linealitzabilitat: dues escriptures concurrents amb el mateix W poden quedar aplicades en rèpliques diferents, una lectura durant una escriptura en curs pot veure la versió nova i la següent l'antiga, i les versions depenen de marques de temps (LWW, amb els seus problemes) o de version vectors. És consistència "ajustable" i a la pràctica força forta, però un sistema Dynamo no és un substitut d'un magatzem amb consens per a les decisions de 03-03.
Mantenir les rèpliques al dia
Sense líder no hi ha log que reposi una rèplica que ha estat caiguda; en el seu lloc hi ha dos mecanismes:
- Read repair (reparació en lectura): quan una lectura rep versions diferents de diverses rèpliques, el coordinador retorna la més nova i l'escriu a les rèpliques que tenien l'antiga. Les dades que es llegeixen sovint es reparen soles; les que no es llegeixen, no (per a això hi ha processos d'antientropia en segon pla que comparen rèpliques amb arbres de Merkle).
- Hinted handoff (lliurament diferit): si una rèplica no respon durant una escriptura, un altre node accepta l'escriptura "en nom seu" amb una nota (hint) i l'hi lliura quan torna. Amb això, l'escriptura assoleix W confirmacions fins i tot amb rèpliques caigudes: és el sloppy quorum (quòrum lax), que augmenta la disponibilitat al preu que W + R > N ja no garanteix solapament (les W confirmacions poden venir de nodes que no són les N rèpliques "de debò", i una lectura posterior a les N rèpliques de debò pot no veure l'escriptura fins que es lliuri el hint).
- Taula de les tres topologies
| Aspecte | Líder-seguidor | Multilíder | Sense líder |
|---|---|---|---|
| Qui accepta escriptures | Un node | Un node per regió/dispositiu | Qualsevol rèplica |
| Conflictes d'escriptura | Impossibles (un sol ordre) | Sí; cal resoldre'ls | Sí; versions i LWW o version vectors |
| Consistència que s'obté | Fins a linealitzable llegint del líder; eventual als seguidors | Eventual, causal amb version vectors | Ajustable amb W i R; eventual amb quòrum lax |
| Latència d'escriptura | La del líder (+ seguidor síncron) | Local | La de les W rèpliques més ràpides |
| Disponibilitat davant caiguda d'un node | Failover si cau el líder (segons a minuts, risc de pèrdua i split-brain) | Els altres líders continuen; sense failover | Sense failover; la resta continua mentre hi hagi W i R |
| Tolerància a particions entre regions | La regió sense líder no escriu | Cada regió escriu; conflictes en retrobar-se | Depèn del quòrum: el costat amb W rèpliques escriu |
| Complexitat operativa | Baixa; molt madura | Alta; conflictes i topologies | Mitjana; ajust de N, W, R i antientropia |
| Sistemes | PostgreSQL, MySQL, MongoDB, Kafka, Redis | CouchDB, BDR, apps mòbils amb sincronització | Cassandra, Riak, DynamoDB (internament), ScyllaDB |
| A Quilòmetre Zero | km0_comandes, km0_inventari i la resta de PostgreSQL |
L'app del repartidor (lliuraments sense cobertura) | Comandes històriques i esdeveniments a Cassandra (04-04) |
- Pràctica: PostgreSQL en streaming per a
km0_comandes
km0_comandesDonarem a km0_comandes un seguidor real. PostgreSQL implementa líder-seguidor enviant el seu WAL (log físic) per una connexió de replicació en streaming; el seguidor, en mode hot standby, aplica el WAL i accepta consultes de només lectura. Afegim dos serveis al docker-compose.yml de Quilòmetre Zero (el primari substitueix el servei postgres de 01-06):
# docker-compose.yml (fragment)
services:
comandes-db-primari:
image: postgres:16
environment:
POSTGRES_DB: km0_comandes
POSTGRES_USER: km0
POSTGRES_PASSWORD: km0
volumes:
- comandes_primari_data:/var/lib/postgresql/data
- ./sql/replicacio/01-replicador.sh:/docker-entrypoint-initdb.d/01-replicador.sh
command: >
postgres -c wal_level=replica
-c max_wal_senders=5
-c wal_keep_size=256MB
-c hot_standby=on
ports:
- "5432:5432"
comandes-db-replica:
image: postgres:16
depends_on:
- comandes-db-primari
environment:
PGPASSWORD: replicador
volumes:
- comandes_replica_data:/var/lib/postgresql/data
command: >
bash -c "
if [ ! -s /var/lib/postgresql/data/PG_VERSION ]; then
until pg_isready -h comandes-db-primari -U km0; do sleep 1; done;
pg_basebackup -d 'host=comandes-db-primari user=replicador application_name=replica_vlc'
-D /var/lib/postgresql/data -R -X stream -C -S slot_replica_vlc;
chown -R postgres:postgres /var/lib/postgresql/data;
chmod 700 /var/lib/postgresql/data;
fi;
exec docker-entrypoint.sh postgres -c hot_standby=on"
ports:
- "5433:5432"
volumes:
comandes_primari_data:
comandes_replica_data:I l'script que crea el rol de replicació i autoritza la connexió, a km0/sql/replicacio/01-replicador.sh:
#!/bin/bash
# S'executa una sola vegada, en inicialitzar el primari (docker-entrypoint-initdb.d)
set -e
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" <<-SQL
CREATE ROLE replicador WITH REPLICATION LOGIN PASSWORD 'replicador';
SQL
echo "host replication replicador all scram-sha-256" >> "$PGDATA/pg_hba.conf"Què fa cada peça, línia a línia:
wal_level=replica: el primari escriu al WAL la informació suficient perquè un seguidor l'apliqui (el nivellminimalno n'hi ha prou;logicalinclouria a més el necessari per a la replicació lògica).max_wal_senders=5: nombre màxim de processoswalsender, un per seguidor (o perpg_basebackupen curs).wal_keep_size=256MB: quant WAL conserva el primari encara que ja l'hagi aplicat ell mateix, per si un seguidor s'endarrereix. L'slot de replicació (-C -S slot_replica_vlc) és la versió moderna i més segura d'això: el primari no esborra WAL que l'slot encara no ha consumit (amb el risc, si el seguidor desapareix per sempre, d'omplir el disc del primari).pg_basebackup -R: copia la instantània del primari al directori de dades del seguidor i escriu el fitxerstandby.signal(que li diu "arrenca com a seguidor") i la líniaprimary_conninfo = 'host=comandes-db-primari user=replicador application_name=replica_vlc ...'apostgresql.auto.conf, amb la qual sap a qui connectar-se.-X streamrep el WAL generat durant la còpia per una segona connexió, perquè la instantània sigui consistent sense bloquejar el primari. L'application_nameés el nom amb què el primari identificarà aquest seguidor, i el farem servir per fer-lo síncron.hot_standby=onal seguidor: accepta consultes de lectura mentre aplica el WAL. Sense això, seria un seguidor "calent" només a efectes de failover.host replication replicador all scram-sha-256apg_hba.conf: autoritza el rolreplicadora obrir connexions de replicació des de qualsevol adreça. En producció es restringeix a la xarxa de les rèpliques i es xifra (06-04).
Comprovar la replicació
Arrenquem amb docker compose up -d comandes-db-primari comandes-db-replica i, des del primari, consultem la vista que descriu cada seguidor connectat:
-- Al primari (port 5432)
SELECT application_name, state, sync_state,
sent_lsn, write_lsn, flush_lsn, replay_lsn,
write_lag, flush_lag, replay_lag
FROM pg_stat_replication;application_name | state | sync_state | sent_lsn | write_lsn | flush_lsn | replay_lsn | write_lag | flush_lag | replay_lag ------------------+-----------+------------+------------+------------+------------+------------+-----------------+-----------------+----------------- replica_vlc | streaming | async | 0/3000148 | 0/3000148 | 0/3000148 | 0/3000148 | 00:00:00.000412 | 00:00:00.000891 | 00:00:00.001203
Les columnes expliquen la història de l'apartat 3: sent_lsn és fins on ha enviat el primari; write_lsn, fins on ha rebut el seguidor; flush_lsn, fins on ho ha escrit a disc (el que compta per a la durabilitat); replay_lsn, fins on ho ha aplicat (el que compta perquè una consulta ho vegi). Els *_lag són els temps corresponents: el retard de replicació mesurat. sync_state = async confirma que, de moment, el primari no espera ningú. Des del seguidor es pot veure el mateix des de l'altre costat:
-- A la rèplica (port 5433)
SELECT pg_is_in_recovery() AS soc_replica,
pg_last_wal_receive_lsn() AS rebut,
pg_last_wal_replay_lsn() AS aplicat,
now() - pg_last_xact_replay_timestamp() AS retard_aproximat;Veure una lectura que arriba tard
Amb un retard d'un mil·lisegon és difícil observar l'anomalia, així que la provocarem: el seguidor pot pausar l'aplicació del WAL (continuarà rebent-lo i desant-lo, però no l'aplicarà), que és exactament el que passa quan una consulta llarga a la rèplica endarrereix el replay o quan la rèplica està saturada.
# km0/simulacions/lectura_tardana.py
import time
import psycopg # pip install "psycopg[binary]"
PRIMARI = "host=localhost port=5432 dbname=km0_comandes user=km0 password=km0"
REPLICA = "host=localhost port=5433 dbname=km0_comandes user=km0 password=km0"
with psycopg.connect(PRIMARI, autocommit=True) as p, psycopg.connect(REPLICA, autocommit=True) as r:
r.execute("SELECT pg_wal_replay_pause()") # la rèplica deixa d'aplicar WAL
print("rèplica en pausa:", r.execute("SELECT pg_is_wal_replay_paused()").fetchone()[0])
p.execute("INSERT INTO comandes (id, client, estat, total) VALUES (%s, %s, 'creada', %s)",
("P-2026-000126", "Llúcia", 31.50))
lsn_commit = p.execute("SELECT pg_current_wal_lsn()").fetchone()[0]
print(f"primari: comanda P-2026-000126 confirmada; LSN del primari = {lsn_commit}")
fila = r.execute("SELECT id, estat FROM comandes WHERE id = 'P-2026-000126'").fetchone()
lsn_replica = r.execute("SELECT pg_last_wal_replay_lsn()").fetchone()[0]
print(f"rèplica: lectura immediata -> {fila}; LSN aplicat = {lsn_replica}") # None: la Llúcia no veu la seva comanda
r.execute("SELECT pg_wal_replay_resume()")
inici = time.monotonic()
while r.execute("SELECT pg_last_wal_replay_lsn() >= %s::pg_lsn", (lsn_commit,)).fetchone()[0] is False:
time.sleep(0.01) # esperar que la rèplica assoleixi l'LSN
fila = r.execute("SELECT id, estat FROM comandes WHERE id = 'P-2026-000126'").fetchone()
print(f"rèplica: després d'assolir l'LSN ({(time.monotonic() - inici) * 1000:.1f} ms) -> {fila}")Sortida:
rèplica en pausa: True
primari: comanda P-2026-000126 confirmada; LSN del primari = 0/30001F8
rèplica: lectura immediata -> None; LSN aplicat = 0/3000148
rèplica: després d'assolir l'LSN (12.3 ms) -> ('P-2026-000126', 'creada')La tercera línia és la violació de read-your-writes amb la causa a la vista: el primari és a l'LSN 0/30001F8 i la rèplica continua a 0/3000148. I el bucle final és la tècnica de "versió mínima al client" de 03-01 amb la versió real de PostgreSQL: el client desa l'LSN del seu commit i no accepta llegir d'una rèplica que no l'hagi assolit. Moltes biblioteques d'accés a dades i proxies (pgpool, alguns ORM) implementen exactament això.
Fer la rèplica síncrona
Amb un canvi de paràmetre en calent, el primari passa a esperar la confirmació de replica_vlc a cada commit:
-- Al primari
ALTER SYSTEM SET synchronous_standby_names = 'replica_vlc';
SELECT pg_reload_conf();
SELECT application_name, sync_state FROM pg_stat_replication; -- ara: syncsynchronous_commit (per defecte on) decideix què espera el primari: remote_write (el seguidor l'ha rebut), on (l'ha escrit a disc: zero pèrdua) o remote_apply (l'ha aplicat: una lectura posterior a la rèplica ja el veu, és a dir, read-your-writes garantit a canvi de la latència màxima). Per a un quòrum de diversos seguidors, la sintaxi és ANY 1 (replica_vlc, replica_bcn): la semisíncrona de l'apartat 3.
Ara fes l'experiment que en revela el preu: atura la rèplica (docker compose stop comandes-db-replica) i intenta un INSERT al primari. Es queda bloquejat: el primari no pot confirmar sense la rèplica síncrona. És la fila "síncrona" de la taula de l'apartat 3 en carn i ossos, i la raó per la qual en producció es configura ANY 1 d'almenys dos seguidors, o s'accepta l'asíncrona amb el seu risc de pèrdua acotat (Ctrl+C a l'INSERT, torna a synchronous_standby_names = '' i recarrega per desbloquejar).
- Simulació: quòrum W/R amb N=3
Per acabar, la replicació sense líder en Python: tres rèpliques, escriptures a totes amb W confirmacions, lectures a R amb elecció de la versió més alta i reparació en lectura. Una rèplica, node-c, està temporalment caiguda durant l'escriptura, que és el cas en què l'elecció de W i R importa.
# km0/simulacions/quorum_wr.py
from dataclasses import dataclass, field
class SenseQuorum(Exception):
pass
@dataclass
class Replica:
nom: str
dades: dict[str, tuple[int, int]] = field(default_factory=dict) # clau -> (valor, versió)
caiguda: bool = False
def escriure(self, clau: str, valor: int, versio: int) -> bool:
if self.caiguda:
return False
actual = self.dades.get(clau, (None, 0))
if versio > actual[1]: # mai no retrocedir de versió
self.dades[clau] = (valor, versio)
return True
def llegir(self, clau: str) -> tuple[int, int] | None:
return None if self.caiguda else self.dades.get(clau, (None, 0))
class MagatzemSenseLider:
def __init__(self, noms: list[str], W: int, R: int):
self.repliques = {n: Replica(n) for n in noms}
self.N, self.W, self.R = len(noms), W, R
self.versio = 0
print(f"\n=== N={self.N}, W={W}, R={R} -> W+R{'>' if W + R > self.N else '<='}N ===")
def escriure(self, clau: str, valor: int) -> None:
self.versio += 1
acks = [r.nom for r in self.repliques.values() if r.escriure(clau, valor, self.versio)]
if len(acks) < self.W:
raise SenseQuorum(f"escriptura {clau}={valor}: només {len(acks)} acks, W={self.W}")
print(f"escriure {clau}={valor} (v{self.versio}): confirmada per {acks} ({len(acks)} >= W={self.W})")
def llegir(self, clau: str, des_de: list[str]) -> int | None:
"""Llegeix de les rèpliques indicades (simula a quines va arribar abans el coordinador)."""
respostes = {n: self.repliques[n].llegir(clau) for n in des_de}
respostes = {n: v for n, v in respostes.items() if v is not None}
if len(respostes) < self.R:
raise SenseQuorum(f"lectura {clau}: només {len(respostes)} respostes, R={self.R}")
millor_nom, (valor, versio) = max(respostes.items(), key=lambda kv: kv[1][1])
reparades = []
for n, (_, v) in respostes.items(): # read repair
if v < versio and self.repliques[n].escriure(clau, valor, versio):
reparades.append(n)
detall = ", ".join(f"{n}=v{v[1]}" for n, v in respostes.items())
print(f"llegir {clau} des de {des_de}: [{detall}] -> retorna v{versio} de {millor_nom}"
+ (f"; read repair a {reparades}" if reparades else ""))
return valor
def escenari(W: int, R: int) -> None:
mag = MagatzemSenseLider(["node-a", "node-b", "node-c"], W, R)
mag.escriure("estoc:formatge-curat", 3) # estat inicial, totes al dia
mag.repliques["node-c"].caiguda = True
try:
mag.escriure("estoc:formatge-curat", 2) # l'Anna reserva; node-c no respon
except SenseQuorum as e:
print("ERROR:", e)
mag.repliques["node-c"].caiguda = False # node-c torna, però està endarrerit
for des_de in (["node-c"], ["node-b", "node-c"], ["node-a", "node-c"], ["node-c"]):
try:
valor = mag.llegir("estoc:formatge-curat", des_de)
if valor == 3:
print(" !!! LECTURA ESTANCADA: en Marc veu 3 unitats quan en queden 2")
except SenseQuorum as e:
print("ERROR:", e)
if __name__ == "__main__":
escenari(W=1, R=1)
escenari(W=2, R=1)
escenari(W=2, R=2)Sortida:
=== N=3, W=1, R=1 -> W+R<=N === escriure estoc:formatge-curat=3 (v1): confirmada per ['node-a', 'node-b', 'node-c'] (3 >= W=1) escriure estoc:formatge-curat=2 (v2): confirmada per ['node-a', 'node-b'] (2 >= W=1) llegir estoc:formatge-curat des de ['node-c']: [node-c=v1] -> retorna v1 de node-c !!! LECTURA ESTANCADA: en Marc veu 3 unitats quan en queden 2 llegir estoc:formatge-curat des de ['node-b', 'node-c']: [node-b=v2, node-c=v1] -> retorna v2 de node-b; read repair a ['node-c'] llegir estoc:formatge-curat des de ['node-a', 'node-c']: [node-a=v2, node-c=v2] -> retorna v2 de node-a llegir estoc:formatge-curat des de ['node-c']: [node-c=v2] -> retorna v2 de node-c === N=3, W=2, R=1 -> W+R<=N === escriure estoc:formatge-curat=3 (v1): confirmada per ['node-a', 'node-b', 'node-c'] (3 >= W=2) escriure estoc:formatge-curat=2 (v2): confirmada per ['node-a', 'node-b'] (2 >= W=2) llegir estoc:formatge-curat des de ['node-c']: [node-c=v1] -> retorna v1 de node-c !!! LECTURA ESTANCADA: en Marc veu 3 unitats quan en queden 2 llegir estoc:formatge-curat des de ['node-b', 'node-c']: [node-b=v2, node-c=v1] -> retorna v2 de node-b; read repair a ['node-c'] llegir estoc:formatge-curat des de ['node-a', 'node-c']: [node-a=v2, node-c=v2] -> retorna v2 de node-a llegir estoc:formatge-curat des de ['node-c']: [node-c=v2] -> retorna v2 de node-c === N=3, W=2, R=2 -> W+R>N === escriure estoc:formatge-curat=3 (v1): confirmada per ['node-a', 'node-b', 'node-c'] (3 >= W=2) escriure estoc:formatge-curat=2 (v2): confirmada per ['node-a', 'node-b'] (2 >= W=2) ERROR: lectura estoc:formatge-curat: només 1 respostes, R=2 llegir estoc:formatge-curat des de ['node-b', 'node-c']: [node-b=v2, node-c=v1] -> retorna v2 de node-b; read repair a ['node-c'] llegir estoc:formatge-curat des de ['node-a', 'node-c']: [node-a=v2, node-c=v2] -> retorna v2 de node-a ERROR: lectura estoc:formatge-curat: només 1 respostes, R=2
El que mostra:
- Amb W=1, R=1 i amb W=2, R=1 (totes dues W + R ≤ N), l'escriptura de l'Anna es confirma sense
node-c, i la primera lectura, que cau només anode-c, retorna la versió 1: en Marc veu tres formatges quan en queden dos. És una lectura estancada legal per a aquesta configuració. La segona lectura, que tocanode-binode-c, obté totes dues versions, retorna la 2 i reparanode-c; a partir d'aquínode-cestà al dia i les lectures següents són correctes. Amb W=1, a més, l'escriptura hauria tingut èxit encara que només una rèplica respongués, amb tot el que això implica per a la durabilitat. - Amb W=2, R=2 (W + R > N), una lectura que només obté resposta de
node-cno assoleix R i falla en lloc de mentir, tant la primera com l'última (el coordinador real preguntaria a una altra rèplica en lloc de fallar). Qualsevol lectura amb dues respostes inclou necessàriamentnode-aonode-b, que tenen la versió 2: mai no hi ha lectura estancada. Aquest és el solapament garantit per l'aritmètica del quòrum, al preu que cada lectura espera dues rèpliques i que, si dues rèpliques caiguessin, ni escriptures ni lectures serien possibles.
Aquest és el mecanisme que Cassandra exposa amb els nivells de consistència ONE, QUORUM i ALL, i amb el qual a 04-04 configurarem el magatzem de comandes històriques de Quilòmetre Zero.
Errors Comuns i Consells
- Afegir rèpliques de lectura sense classificar les consultes. La consulta "queda estoc?" que precedeix una reserva no pot anar a una rèplica asíncrona. Marca al codi de cada servei quines lectures van al líder i quines a les rèpliques, i per què.
- Creure que la replicació síncrona és "més segura" i activar-la sense més. Amb un sol seguidor síncron, la seva caiguda bloqueja totes les escriptures del sistema. Si necessites zero pèrdua, configura
ANY 1sobre almenys dos seguidors i monitora que siguin vius. - Confiar en el failover automàtic sense protecció contra split-brain. Un antic líder que torna i continua acceptant escriptures és pitjor que una caiguda. Fes servir una eina amb consens (Patroni + etcd) o un token de tancat, i assaja el failover (07-03, 07-06).
- Slots de replicació oblidats. Un slot el seguidor del qual ha desaparegut reté WAL per sempre i omple el disc del primari. Monitora
pg_replication_slotsi elimina els que no es fan servir. - Multilíder amb LWW i rellotges físics "perquè és el que ve per defecte". És la combinació que perd dades amb més seguretat (03-02). Si necessites multilíder, decideix la resolució de conflictes per tipus de dada i fes servir version vectors per detectar la concurrència real.
- Quòrum lax sense saber-ho. Cassandra i Riak tenen l'sloppy quorum i el hinted handoff activats per defecte en algunes configuracions; W + R > N deixa de garantir solapament. Llegeix la documentació de la teva versió abans de raonar amb l'aritmètica de l'apartat 7.
- Consell: mesura el retard de replicació com una mètrica de primer nivell (
replay_lag, o la diferència d'LSN convertida ambpg_wal_lsn_diff) i defineix un llindar a partir del qual el balancejador deixa d'enviar lectures a aquesta rèplica. - Consell: per a read-your-writes sobre PostgreSQL, l'LSN de commit és el millor "número de versió" que existeix: és monòton, ja està calculat i les rèpliques saben exactament on són respecte d'ell.
Exercicis
Exercici 1: Triar el mode de replicació per base de dades
Quilòmetre Zero té una base de dades PostgreSQL per servei. Per a km0_comandes, km0_inventari, km0_pagaments i la base de dades del catàleg, decideix entre asíncrona, semisíncrona (ANY 1 de dos seguidors) i remote_apply, indica quines lectures enviaries a les rèpliques i quina pèrdua màxima de dades acceptes en un failover. Justifica cada elecció amb els conceptes d'aquesta lliçó i de 03-02.
Exercici 2: Read-your-writes amb LSN a comandes
Escriu una funció llegir_comanda(id_comanda, lsn_minim) per al servei comandes que rebi l'LSN de l'últim commit de l'usuari (desat a la seva sessió) i retorni la comanda des de la rèplica si aquesta ha assolit l'LSN, o des del primari si no, registrant quin dels dos ha fet servir. Fes servir psycopg i les funcions de PostgreSQL vistes a l'apartat 9. Com obté i desa el servei l'LSN després de cada escriptura de l'usuari?
Exercici 3: Quòrum lax a la simulació
Afegeix a MagatzemSenseLider un mode de quòrum lax: si una rèplica està caiguda durant l'escriptura, un node auxiliar node-h accepta l'escriptura amb un hint per a ella, i l'hi lliura quan la rèplica torna (lliurar_hints()). Reprodueix l'escenari amb W=2, R=2 i mostra que, entre la tornada de node-c i el lliurament del hint, una lectura des de node-b i node-c... pot estar estancada? Raona què garanteix i què no garanteix W + R > N amb quòrum lax, i què es guanya a canvi.
Solucions
Solució 1:
km0_pagaments: semisíncronaANY 1ambsynchronous_commit = on. Un cobrament registrat i perdut en un failover són diners cobrats sense comanda (o a l'inrevés); la pèrdua acceptable és zero. Lectures a rèpliques: només informes i conciliació comptable (retard innocu); l'estat d'un pagament sempre del primari. És la fila "CP" de 03-02.km0_comandes: semisíncronaANY 1. Una comanda confirmada a l'Anna que desapareix al failover, amb la sevacomanda.creadaja publicada pel relay outbox, genera un esdeveniment orfe queinventariipagamentsprocessarien per a una comanda inexistent. Pèrdua acceptable: zero. Lectures a rèpliques: "les meves comandes" amb LSN mínim (exercici 2) i l'historial de més d'un dia.km0_inventari: semisíncronaANY 1per a les reserves (l'última unitat, 03-02); les lectures de l'estoc per al catàleg poden anar a rèpliques asíncrones (el catàleg ja és eventual). Pèrdua acceptable: cap reserva confirmada; els esdevenimentsestoc.actualitzatpassen per outbox i segueixen el mateix raonament que les comandes.- Catàleg: asíncrona. Un canvi de preu o de descripció perdut en un failover el reintrodueix el productor; no hi ha cap esdeveniment irreversible aigües avall. Pèrdua acceptable: els últims segons de canvis. Totes les lectures a rèpliques, sense LSN (el preu efectiu es fixa a
comandes).
remote_apply no compensa en cap: duplica la latència d'escriptura per obtenir read-your-writes a la rèplica, que s'aconsegueix més barat amb l'LSN al client.
Solució 2:
# km0/serveis/comandes/lectures.py
import psycopg
PRIMARI = "host=comandes-db-primari dbname=km0_comandes user=km0 password=km0"
REPLICA = "host=comandes-db-replica dbname=km0_comandes user=km0 password=km0"
def escriure_i_anotar_lsn(sessio: dict, sql: str, params: tuple) -> None:
"""Tota escriptura de l'usuari desa a la seva sessió l'LSN assolit després del commit."""
with psycopg.connect(PRIMARI) as conn:
with conn.transaction():
conn.execute(sql, params)
sessio["lsn"] = conn.execute("SELECT pg_current_wal_lsn()::text").fetchone()[0]
def llegir_comanda(id_comanda: str, lsn_minim: str | None) -> tuple[dict | None, str]:
if lsn_minim is not None:
with psycopg.connect(REPLICA) as r:
al_dia = r.execute("SELECT pg_last_wal_replay_lsn() >= %s::pg_lsn", (lsn_minim,)).fetchone()[0]
if al_dia:
fila = r.execute("SELECT id, client, estat, total FROM comandes WHERE id = %s", (id_comanda,)).fetchone()
return fila, "replica"
with psycopg.connect(PRIMARI) as p: # sense LSN o rèplica endarrerida: al primari
fila = p.execute("SELECT id, client, estat, total FROM comandes WHERE id = %s", (id_comanda,)).fetchone()
return fila, "primari"L'LSN s'obté amb pg_current_wal_lsn() després del commit, a la mateixa connexió, i es desa a la sessió de l'usuari (galeta signada, Redis de sessions o el token de sessió). Un usuari que mai no ha escrit (lsn_minim None) va al primari per prudència, o a la rèplica si la lectura tolera retard; una millora és fer servir pg_last_wal_replay_lsn() comparat amb l'LSN en una sola consulta i fer la lectura només si la rèplica està al dia, com aquí. Fixa't que l'LSN de la sessió ha de ser el màxim dels LSN de totes les escriptures de l'usuari (monotonic reads), i que un failover a una rèplica l'historial de la qual s'ha bifurcat invalida els LSN anteriors, un altre motiu per a la semisíncrona.
Solució 3:
Implementació esquemàtica: a escriure, quan una rèplica retorna False, es desa (clau, valor, versio) a self.hints[nom_replica] i es compta l'ack del node auxiliar; lliurar_hints() recorre els hints i crida escriure a les rèpliques ja disponibles. Amb W=2, R=2: l'escriptura de l'Anna es confirma amb node-a, node-b i el hint per a node-c (tres acks, encara que només dos són rèpliques reals). En tornar node-c i abans de lliurar_hints(), una lectura des de node-b i node-c obté v2 de node-b i v1 de node-c: no estancada, perquè node-b és una rèplica real que té l'escriptura. Però imagina que també node-b hagués estat caiguda: els acks serien node-a més dos hints, W=2 es compliria, i una lectura posterior des de node-b i node-c (totes dues tornades, sense hints lliurats) retornaria v1 amb R=2 satisfet: lectura estancada amb W + R > N. El que garanteix W + R > N amb quòrum lax és que l'escriptura és en W nodes d'algun tipus (durabilitat) i que es lliurarà a les rèpliques reals quan tornin (convergència); el que ja no garanteix és el solapament entre les rèpliques que van confirmar i les que es llegeixen. A canvi, l'escriptura té èxit fins i tot amb la majoria de les rèpliques reals caigudes: disponibilitat màxima, l'elecció AP en la seva forma més pura.
Conclusió
Replicar és mantenir còpies de les mateixes dades en diversos nodes per guanyar disponibilitat, latència i escalat de lectures (mai d'escriptures), i aquesta lliçó ha recorregut les tres maneres de fer-ho. En líder-seguidor, totes les escriptures passen per un node que les envia en ordre als altres; l'elecció entre síncrona, asíncrona i semisíncrona decideix quant es perd si el líder mor i quant costa cada commit, i el retard de replicació dels seguidors és la causa mecànica de les anomalies de sessió de 03-01, que s'eviten encaminant al líder o esperant l'LSN. El failover converteix un seguidor en líder amb tres perills (detecció, pèrdua de transaccions, split-brain) que deixem per a 07-03. En multilíder, cada regió accepta escriptures i el problema és el conflicte, amb un repertori de solucions que va d'evitar-lo a LWW, fusions, CRDT i resolució a l'aplicació. En la replicació sense líder, l'aritmètica W + R > N substitueix la coordinació, amb read repair i hinted handoff per mantenir les rèpliques al dia i el quòrum lax com a concessió a la disponibilitat. La pràctica ha estat real: km0_comandes té ara un seguidor PostgreSQL en streaming creat amb pg_basebackup -R, observat amb pg_stat_replication, pausat per veure la Llúcia no trobar la seva comanda i esperat fins a l'LSN correcte; i la simulació amb N=3 ha mostrat en Marc llegint tres formatges quan en quedaven dos sempre que W + R ≤ N.
Amb això sabem copiar les dades d'un servei. Però l'operació més important de Quilòmetre Zero, crear una comanda, ja no toca una sola base de dades: insereix a km0_comandes, descompta a km0_inventari i cobra a km0_pagaments, tres bases de dades de tres serveis, cadascuna replicada com acabem de veure, i cap transacció de PostgreSQL no abasta les tres. Si el cobrament falla després de descomptar l'estoc, qui el retorna? Si comandes cau entre el descompte i el cobrament, en quin estat queda la comanda d'en Marc? El que el monòlit resolia amb un BEGIN i un COMMIT necessita ara un protocol de commit atòmic o, més habitualment en microserveis, una saga amb compensacions. És el tema de l'última lliçó del mòdul: transaccions distribuïdes i sagues.
Curs d'Arquitectures Distribuïdes
Mòdul 1: Introducció als Sistemes Distribuïts
- Conceptes Bàsics de Sistemes Distribuïts
- Models de Sistemes Distribuïts
- Avantatges i Desafiaments dels Sistemes Distribuïts
- Les Fal·làcies de la Computació Distribuïda
- Temps, Rellotges i Ordenació d'Esdeveniments
- Del Monòlit a la Plataforma Distribuïda: el Cas Quilòmetre Zero
Mòdul 2: Comunicació en Sistemes Distribuïts
- Protocols de Comunicació
- RPC i RMI
- gRPC i Serialització de Dades
- Missatgeria i Cues de Missatges
- Patrons de Comunicació Asíncrona
Mòdul 3: Consistència i Replicació
- Models de Consistència
- El Teorema CAP i PACELC
- Algorismes de Consens
- Replicació de Dades
- Transaccions Distribuïdes i Sagues
Mòdul 4: Emmagatzematge Distribuït
- Particionament de Dades i Hashing Consistent
- Sistemes de Fitxers Distribuïts
- Emmagatzematge d'Objectes
- Bases de Dades Distribuïdes
- Memòries Cau Distribuïdes
Mòdul 5: Computació Distribuïda
- Models de Computació Distribuïda
- MapReduce i Hadoop
- Spark i Computació en Memòria
- Processament de Fluxos de Dades
- Planificació de Treballs i Pipelines de Dades
Mòdul 6: Seguretat en Sistemes Distribuïts
- Autenticació i Autorització
- Xifratge i Protecció de Dades
- Gestió d'Identitats
- Seguretat entre Serveis: mTLS i Gestió de Secrets
- Passarel·les d'API, Limitació de Taxa i Auditoria
Mòdul 7: Monitoratge i Manteniment
- Monitoratge de Sistemes Distribuïts
- Logs Centralitzats i Traçabilitat Distribuïda
- Gestió de Fallades i Recuperació
- Patrons de Resiliència: Timeouts, Reintents i Circuit Breaker
- Automatització i Orquestració
- Proves en Sistemes Distribuïts i Enginyeria del Caos
