Las tres lecciones anteriores han tratado la replicación como un hecho dado: había réplicas inv-bcn e inv-vlc que se retrasaban, divergían o se ponían de acuerdo, pero nunca explicamos cómo llegan los datos de una a otra ni qué pasa por el camino. Esta lección abre esa caja. La replicación es el mecanismo por el que una misma pieza de datos se mantiene en varios nodos, y su diseño decide directamente qué modelo de consistencia se obtiene (03-01), qué se sacrifica ante una partición (03-02) y dónde hace falta consenso (03-03).
Veremos primero para qué se replica (disponibilidad, latencia y escalado de lecturas) y después las tres topologías que existen. La replicación líder-seguidor, la más común, con sus variantes síncrona, asíncrona y semisíncrona, el retraso de replicación y las anomalías que produce (que son exactamente las garantías de sesión de 03-01 vistas desde el otro lado), y el failover con sus peligros. La replicación multilíder, para varias regiones o clientes sin conexión, cuyo problema central es resolver conflictos entre escrituras concurrentes. Y la replicación sin líder al estilo Dynamo, con quórums de escritura y lectura, reparación en lectura y hinted handoff. La parte práctica es doble: montaremos una réplica real de PostgreSQL en streaming para km0_pedidos en el docker-compose.yml de Kilómetro Cero, la observaremos con pg_stat_replication y la veremos llegar tarde; y una simulación en Python con N=3 mostrará cuándo un quórum devuelve lecturas estancadas. La base de datos Cassandra, que implementa la replicación sin líder en producción, y el particionado (cómo repartir datos distintos entre nodos, no copiar los mismos) quedan para el Módulo 4.
Contenido
- Por qué replicar: disponibilidad, latencia y escalado de lecturas
- Replicación líder-seguidor
- Síncrona, asíncrona y semisíncrona
- Retraso de replicación y sus anomalías
- Failover y sus peligros
- Replicación multilíder y resolución de conflictos
- Replicación sin líder: quórums, read repair y hinted handoff
- Tabla de las tres topologías
- Práctica: PostgreSQL en streaming para
km0_pedidos - Simulación: quórum W/R con N=3
- Errores comunes y consejos
- Ejercicios
- Conclusión
- Por qué replicar: disponibilidad, latencia y escalado de lecturas
Replicar es mantener copias de los mismos datos en varios nodos conectados por red. Se hace por tres motivos, y conviene saber cuál de ellos se persigue porque cada uno empuja el diseño en una dirección distinta:
| Objetivo | Qué se busca | Consecuencia de diseño | En Kilómetro Cero |
|---|---|---|---|
| Disponibilidad (tolerancia a fallos) | Que la pérdida de un nodo (disco, máquina, centro de datos) no pierda datos ni pare el servicio | Las copias deben estar en dominios de fallo distintos; hace falta un procedimiento de failover | km0_pedidos no puede perder pedidos si muere el servidor primario |
| Latencia | Que los datos estén cerca de quien los lee | Copias geográficamente repartidas; escrituras posiblemente lejanas | El catálogo se lee desde Valencia sin cruzar a Barcelona |
| Escalado de lecturas | Repartir la carga de lectura entre muchas copias | Muchas réplicas de solo lectura; el retraso entre ellas pasa a ser visible | La ficha de tomate-rosa se lee miles de veces por cada escritura |
Fíjate en que ninguno de los tres objetivos es "escalar escrituras": replicar copia las mismas escrituras a todos los nodos, así que la capacidad de escritura no crece. Para eso hace falta particionar (04-01).
Si los datos nunca cambiaran, replicar sería copiarlos una vez. Toda la dificultad viene de que cambian: cada escritura tiene que llegar a todas las copias, y entre que llega a una y a otra hay un intervalo en el que las copias difieren. Las tres topologías siguientes son tres respuestas a la pregunta "¿quién acepta las escrituras y cómo se propagan?".
- Replicación líder-seguidor
También llamada single-leader, primary-replica o master-slave (término en desuso). Es la que usan PostgreSQL, MySQL, MongoDB, Kafka (por partición) y la mayoría de sistemas:
- Un nodo es el líder (primario). Todas las escrituras van a él. Lo primero que hace es escribirlas en su almacenamiento local.
- Los demás son seguidores (réplicas, standbys). El líder les envía un flujo de cambios (el log de replicación), y cada seguidor los aplica en el mismo orden, con lo que su estado converge al del líder.
- Las lecturas pueden ir al líder (siempre actuales) o a cualquier seguidor (posiblemente retrasadas).
flowchart LR
C1[Clientes que escriben] -->|INSERT / UPDATE| L[(Líder<br/>km0_pedidos primario)]
L -->|log de replicación WAL| S1[(Seguidor 1<br/>réplica Barcelona)]
L -->|log de replicación WAL| S2[(Seguidor 2<br/>réplica Valencia)]
C2[Clientes que leen] -->|SELECT| S1
C3[Clientes que leen] -->|SELECT| S2
C1 -.->|SELECT que necesita<br/>el último valor| L
Lo que viaja por el flujo puede ser de tres tipos, y la diferencia importa en la práctica:
| Qué se replica | Ejemplo | Ventaja | Inconveniente |
|---|---|---|---|
| Sentencias | UPDATE stock SET unidades = unidades - 1 WHERE producto = 'queso-curado' |
Compacto | No determinista: now(), random(), secuencias, triggers dan resultados distintos en cada nodo; MySQL lo abandonó por defecto |
| Log físico (WAL) | Los bytes que cambian en las páginas del disco | Exacto, ya existe para la durabilidad | Acopla las réplicas a la versión exacta del motor y a su formato de disco; es lo que hace PostgreSQL en la replicación en streaming |
| Log lógico (filas) | "En la tabla pedidos, la fila con id P-2026-000123 pasa a tener estado pagado" |
Independiente del motor y de la versión; permite replicar tablas sueltas y alimentar otros sistemas (CDC) | Más costoso de generar; es la replicación lógica de PostgreSQL y la base de Debezium, que conecta con el patrón Outbox de 02-05 |
Para añadir un seguidor nuevo sin parar el líder se toma una instantánea consistente del líder en un punto del log (en PostgreSQL, pg_basebackup), se copia al seguidor, y este pide al líder "todo lo que ha pasado desde ese punto" (catch-up). Cuando lo alcanza, sigue en directo. Lo haremos en el apartado 9.
- Síncrona, asíncrona y semisíncrona
La pregunta clave es cuándo el líder confirma la escritura al cliente respecto al momento en que el seguidor la recibe:
sequenceDiagram
participant C as Cliente (pedidos)
participant L as Líder
participant S as Seguidor
Note over C,S: Síncrona
C->>L: COMMIT
L->>S: WAL
S-->>L: escrito en disco
L-->>C: ok (solo tras el ack del seguidor)
Note over C,S: Asíncrona
C->>L: COMMIT
L-->>C: ok (inmediato)
L->>S: WAL (llega cuando llega)
| Modo | El líder confirma cuando... | Garantía si el líder muere justo después de confirmar | Latencia de escritura | Disponibilidad de escritura |
|---|---|---|---|---|
| Síncrona | El seguidor (o todos los seguidores) ha escrito la transacción en disco | La transacción está en el seguidor: cero pérdida | + un viaje de ida y vuelta al seguidor más lento, y su fsync |
Si el seguidor síncrono cae o se aísla, las escrituras se bloquean hasta que vuelva o se reconfigure |
| Asíncrona | Ha escrito en su propio disco | La transacción puede no haber llegado al seguidor: pérdida posible (las últimas transacciones desaparecen al promover el seguidor) | Mínima | El líder sigue escribiendo aunque todos los seguidores estén caídos |
| Semisíncrona | Al menos uno (o un quórum) de los seguidores ha confirmado; el resto son asíncronos | Al menos una copia tiene la transacción: cero pérdida mientras no fallen el líder y ese seguidor a la vez | + el seguidor síncrono más rápido | Se bloquea solo si ningún seguidor puede confirmar |
La replicación completamente síncrona con muchos seguidores es impracticable: el seguidor más lento marca la latencia de todo el sistema, y cualquier seguidor caído lo paraliza. Por eso "síncrona" en la práctica casi siempre significa semisíncrona: un seguidor síncrono (que garantiza la copia) y el resto asíncronos (que dan lecturas y estarán a mano si el síncrono cae). PostgreSQL lo configura con synchronous_standby_names, y en el apartado 9 lo cambiaremos en caliente para ver el efecto. En términos de 03-02, la replicación asíncrona es PC/EL (rápida, con retraso en las réplicas) y la síncrona es PC/EC (cada commit paga la latencia de la coordinación).
- Retraso de replicación y sus anomalías
Con seguidores asíncronos, entre que el líder confirma y el seguidor aplica hay un retraso de replicación (replication lag): milisegundos en condiciones normales, segundos o minutos si el seguidor está sobrecargado, si hay una consulta larga que bloquea la aplicación del WAL, o tras una partición. Si las lecturas van a los seguidores para escalar, ese retraso se convierte en las anomalías que Ana sufría en 03-01, ahora con su causa mecánica:
| Anomalía (03-01) | Cómo la produce el retraso | Cómo se evita en líder-seguidor |
|---|---|---|
| Violación de read-your-writes | Ana escribe en el líder y lee de un seguidor que aún no ha aplicado su transacción | Leer del líder lo que el propio usuario acaba de modificar (por ejemplo, durante el minuto siguiente a una escritura); o el cliente recuerda el LSN de su commit y el seguidor espera a alcanzarlo (pg_last_wal_replay_lsn() >= lsn) |
| Violación de monotonic reads | Dos lecturas consecutivas caen en seguidores con distinto retraso | Enrutar cada usuario siempre al mismo seguidor (hash del id de usuario), o LSN mínimo en el cliente |
| Violación de consistencia causal (respuesta antes que la pregunta) | La pregunta de Marc y la respuesta de la quesería se escriben en el líder en orden, pero un seguidor con retraso de segundos aún muestra solo la pregunta mientras otro muestra ambas; un tercer sistema que lea de ambos ve el orden roto | En líder-seguidor el log preserva el orden, así que un mismo seguidor nunca muestra la respuesta sin la pregunta; el problema aparece al combinar lecturas de seguidores distintos o al particionar (04-01) |
La consecuencia práctica es que "escalar lecturas con réplicas" no es transparente: hay que decidir para cada consulta si tolera retraso, y medir el retraso continuamente (pg_stat_replication en el apartado 9; alertas en 07-01). Un límite razonable es enrutar a réplicas solo lecturas donde un retraso de unos segundos es inocuo (catálogo, historial de pedidos de hace días, informes) y mantener en el líder las lecturas que preceden a una decisión (¿queda stock?, ¿está pagado el pedido?).
- Failover y sus peligros
Si el líder muere, un seguidor debe convertirse en líder: es el failover. Sus tres pasos (detectar que el líder ha muerto, elegir el nuevo líder, redirigir a los clientes) parecen sencillos y cada uno esconde un problema:
- Detectar: no hay forma segura de distinguir un líder muerto de uno lento o aislado (01-02, 03-02). Un timeout corto provoca failovers innecesarios; uno largo alarga la indisponibilidad.
- Elegir: con replicación asíncrona, el seguidor más al día puede no tener las últimas transacciones confirmadas: se pierden. Peor: si otros sistemas ya han actuado sobre esas transacciones (un
pedido.creadoya publicado por el relay outbox), quedan inconsistentes con la base de datos. Con semisíncrona, el seguidor síncrono es el candidato y no pierde nada. - Redirigir: si el antiguo líder vuelve creyendo que sigue siéndolo, hay dos nodos aceptando escrituras: split-brain (cerebro dividido). Es la situación que los términos de Raft impedían en 03-03, y en una base de datos sin consenso integrado hay que impedirla desde fuera: apagando al antiguo líder por la fuerza (STONITH, shoot the other node in the head) o con un token de cercado.
Precisamente porque el failover automático de una base de datos es tan delicado, muchos equipos lo delegan en herramientas que se apoyan en un sistema de consenso (Patroni usa etcd para PostgreSQL; MongoDB y Kafka llevan su propia elección integrada) y otros lo hacen a mano. Cómo se opera un failover, con qué checkpoints y cómo se ensaya es materia de 07-03; aquí basta con saber que la elección entre asíncrona y semisíncrona determina cuánto se pierde en él.
- Replicación multilíder y resolución de conflictos
En líder-seguidor, toda escritura pasa por un único nodo. Si Kilómetro Cero tiene usuarios en Barcelona y en Valencia y el líder está en Barcelona, cada escritura desde Valencia cruza 350 km; y si la red entre ciudades cae, Valencia no puede escribir. La replicación multilíder (multi-leader, master-master) pone un líder en cada centro de datos, que acepta escrituras localmente y las replica asíncronamente a los otros líderes. Sus casos de uso legítimos son pocos y concretos:
- Varias regiones con escrituras locales y tolerancia a la desconexión entre ellas.
- Clientes sin conexión: la app móvil del repartidor de
furgoneta-3registra entregas sin cobertura y sincroniza al recuperarla. Cada dispositivo es, en efecto, un líder. - Edición colaborativa (varios usuarios editando el mismo documento), que es multilíder con réplicas muy pequeñas.
Y su problema central es inevitable: dos líderes pueden aceptar escrituras concurrentes sobre el mismo dato, y al replicarse aparece un conflicto. Ya lo vimos en la simulación AP de 03-02: Ana reserva en inv-bcn y Marc en inv-vlc. Las estrategias, de la más simple a la más elaborada:
| Estrategia | Cómo funciona | Pierde datos | Cuándo usarla |
|---|---|---|---|
| Evitar el conflicto | Enrutar todas las escrituras de un mismo dato al mismo líder (los pedidos de Ana siempre a Barcelona) | No | Siempre que sea posible; falla cuando hay que cambiar de líder |
| Last-write-wins (LWW) | Cada escritura lleva una marca de tiempo; gana la mayor | Sí, silenciosamente (03-02); depende de relojes físicos desincronizados (01-05) | Datos donde la última escritura es realmente la buena: posición de furgoneta-3, perfil editado por una sola persona |
| Fusión (merge) determinista | Combinar ambos valores con una regla: unión de conjuntos, suma de incrementos, concatenación ordenada | No, pero puede producir estados inesperados | Carrito (unión: nunca perder un artículo), listas de deseos |
| CRDT (03-01) | Tipos de dato cuya fusión es conmutativa, asociativa e idempotente | No | Contadores de campaña, conjuntos, texto colaborativo; no sirve para invariantes globales |
| Resolución en la aplicación | Se guardan ambas versiones (siblings) y se pide a la aplicación, o al usuario, que decida | No | Cuando la regla de negocio lo exige: dos reservas del último queso, con una compensación (03-05) |
Un detalle que suele sorprender: la topología en la que los líderes se envían los cambios (todos con todos, en anillo, en estrella) afecta al orden en que llegan, y con relojes físicos como marca de tiempo LWW puede "ganar" una escritura que causalmente fue anterior. Los sistemas multilíder serios usan relojes vectoriales o version vectors (01-05) para detectar qué escrituras son realmente concurrentes y cuáles simplemente llegaron tarde. Multilíder es potente y peligroso a partes iguales; PostgreSQL no lo ofrece de serie (existen extensiones como BDR/pglogical), y la recomendación general es no usarlo salvo que los tres casos de uso de arriba lo exijan.
- Replicación sin líder: quórums, read repair y hinted handoff
La tercera topología elimina el líder por completo: cualquier réplica acepta escrituras y lecturas, y la coordinación se sustituye por aritmética. La popularizó el artículo de Dynamo (Amazon, 2007), y la implementan Cassandra, Riak, Voldemort y ScyllaDB.
Quórums de escritura y lectura
Con N réplicas de cada dato, el cliente (o un nodo coordinador que actúa por él) envía cada escritura a las N réplicas en paralelo y la da por buena cuando W han confirmado; y envía cada lectura a las N réplicas (o a un subconjunto) y la da por buena con R respuestas, quedándose con el valor de versión más alta. Si
W + R > N
entonces el conjunto de réplicas que confirmó la escritura y el conjunto que respondió a la lectura se solapan en al menos una réplica, que tendrá la versión nueva; como la lectura elige la versión más alta, siempre devuelve la última escritura confirmada. Con N=3, las configuraciones típicas:
| W | R | W + R > N | Comportamiento |
|---|---|---|---|
| 1 | 1 | No | Máxima velocidad y disponibilidad; lecturas estancadas frecuentes |
| 2 | 2 | Sí | Equilibrio habitual: tolera una réplica caída en escritura y en lectura |
| 3 | 1 | Sí | Lecturas rápidas; escrituras bloqueadas si cae una réplica |
| 1 | 3 | Sí | Escrituras rápidas; lecturas bloqueadas si cae una réplica |
flowchart LR
Cl[Cliente / coordinador] -->|escribir v2| A[(réplica A: v2)]
Cl -->|escribir v2| B[(réplica B: v2)]
Cl -.->|escribir v2: no responde| C[(réplica C: v1)]
Cl2[Cliente / coordinador] -->|leer| B
Cl2 -->|leer| C
B -->|v2| Cl2
C -->|v1| Cl2
Cl2 -->|max versión = v2<br/>read repair: enviar v2 a C| C
La condición W + R > N garantiza que la lectura ve la última escritura confirmada, pero no da linealizabilidad: dos escrituras concurrentes con el mismo W pueden quedar aplicadas en réplicas distintas, una lectura durante una escritura en curso puede ver la versión nueva y la siguiente la antigua, y las versiones dependen de marcas de tiempo (LWW, con sus problemas) o de version vectors. Es consistencia "ajustable" y en la práctica bastante fuerte, pero un sistema Dynamo no es un sustituto de un almacén con consenso para las decisiones de 03-03.
Mantener las réplicas al día
Sin líder no hay log que reponga a una réplica que ha estado caída; en su lugar hay dos mecanismos:
- Read repair (reparación en lectura): cuando una lectura recibe versiones distintas de varias réplicas, el coordinador devuelve la más nueva y la escribe en las réplicas que tenían la antigua. Los datos que se leen a menudo se reparan solos; los que no se leen, no (para eso hay procesos de anti-entropía en segundo plano que comparan réplicas con árboles de Merkle).
- Hinted handoff (entrega diferida): si una réplica no responde durante una escritura, otro nodo acepta la escritura "en su nombre" con una nota (hint) y se la entrega cuando vuelve. Con esto, la escritura alcanza W confirmaciones incluso con réplicas caídas: es el sloppy quorum (quórum laxo), que aumenta la disponibilidad al precio de que W + R > N ya no garantiza solape (las W confirmaciones pueden venir de nodos que no son las N réplicas "de verdad", y una lectura posterior a las N réplicas de verdad puede no ver la escritura hasta que se entregue el hint).
- Tabla de las tres topologías
| Aspecto | Líder-seguidor | Multilíder | Sin líder |
|---|---|---|---|
| Quién acepta escrituras | Un nodo | Un nodo por región/dispositivo | Cualquier réplica |
| Conflictos de escritura | Imposibles (un solo orden) | Sí; hay que resolverlos | Sí; versiones y LWW o version vectors |
| Consistencia obtenible | Hasta linealizable leyendo del líder; eventual en seguidores | Eventual, causal con version vectors | Ajustable con W y R; eventual con quórum laxo |
| Latencia de escritura | La del líder (+ seguidor síncrono) | Local | La de las W réplicas más rápidas |
| Disponibilidad ante caída de un nodo | Failover si cae el líder (segundos a minutos, riesgo de pérdida y split-brain) | Los otros líderes siguen; sin failover | Sin failover; el resto sigue mientras haya W y R |
| Tolerancia a particiones entre regiones | La región sin líder no escribe | Cada región escribe; conflictos al reunirse | Depende del quórum: el lado con W réplicas escribe |
| Complejidad operativa | Baja; muy madura | Alta; conflictos y topologías | Media; ajuste de N, W, R y anti-entropía |
| Sistemas | PostgreSQL, MySQL, MongoDB, Kafka, Redis | CouchDB, BDR, apps móviles con sincronización | Cassandra, Riak, DynamoDB (internamente), ScyllaDB |
| En Kilómetro Cero | km0_pedidos, km0_inventario y el resto de PostgreSQL |
La app del repartidor (entregas sin cobertura) | Pedidos históricos y eventos en Cassandra (04-04) |
- Práctica: PostgreSQL en streaming para
km0_pedidos
km0_pedidosVamos a dar a km0_pedidos un seguidor real. PostgreSQL implementa líder-seguidor enviando su WAL (log físico) por una conexión de replicación en streaming; el seguidor, en modo hot standby, aplica el WAL y acepta consultas de solo lectura. Añadimos dos servicios al docker-compose.yml de Kilómetro Cero (el primario sustituye al servicio postgres de 01-06):
# docker-compose.yml (fragmento)
services:
pedidos-db-primario:
image: postgres:16
environment:
POSTGRES_DB: km0_pedidos
POSTGRES_USER: km0
POSTGRES_PASSWORD: km0
volumes:
- pedidos_primario_data:/var/lib/postgresql/data
- ./sql/replicacion/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"
pedidos-db-replica:
image: postgres:16
depends_on:
- pedidos-db-primario
environment:
PGPASSWORD: replicador
volumes:
- pedidos_replica_data:/var/lib/postgresql/data
command: >
bash -c "
if [ ! -s /var/lib/postgresql/data/PG_VERSION ]; then
until pg_isready -h pedidos-db-primario -U km0; do sleep 1; done;
pg_basebackup -d 'host=pedidos-db-primario 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:
pedidos_primario_data:
pedidos_replica_data:Y el script que crea el rol de replicación y autoriza la conexión, en km0/sql/replicacion/01-replicador.sh:
#!/bin/bash
# Se ejecuta una sola vez, al inicializar el primario (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é hace cada pieza, línea a línea:
wal_level=replica: el primario escribe en el WAL la información suficiente para que un seguidor lo aplique (el nivelminimalno basta;logicalincluiría además lo necesario para la replicación lógica).max_wal_senders=5: número máximo de procesoswalsender, uno por seguidor (o porpg_basebackupen curso).wal_keep_size=256MB: cuánto WAL conserva el primario aunque ya lo haya aplicado él mismo, por si un seguidor se retrasa. El slot de replicación (-C -S slot_replica_vlc) es la versión moderna y más segura de esto: el primario no borra WAL que el slot aún no ha consumido (con el riesgo, si el seguidor desaparece para siempre, de llenar el disco del primario).pg_basebackup -R: copia la instantánea del primario al directorio de datos del seguidor y escribe el ficherostandby.signal(que le dice "arranca como seguidor") y la líneaprimary_conninfo = 'host=pedidos-db-primario user=replicador application_name=replica_vlc ...'enpostgresql.auto.conf, con la que sabe a quién conectarse.-X streamrecibe el WAL generado durante la copia por una segunda conexión, para que la instantánea sea consistente sin bloquear el primario. Elapplication_namees el nombre con el que el primario identificará a este seguidor, y lo usaremos para hacerlo síncrono.hot_standby=onen el seguidor: acepta consultas de lectura mientras aplica el WAL. Sin ello, sería un seguidor "caliente" solo a efectos de failover.host replication replicador all scram-sha-256enpg_hba.conf: autoriza al rolreplicadora abrir conexiones de replicación desde cualquier dirección. En producción se restringe a la red de las réplicas y se cifra (06-04).
Comprobar la replicación
Arrancamos con docker compose up -d pedidos-db-primario pedidos-db-replica y, desde el primario, consultamos la vista que describe a cada seguidor conectado:
-- En el primario (puerto 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
Las columnas cuentan la historia del apartado 3: sent_lsn es hasta dónde ha enviado el primario; write_lsn, hasta dónde ha recibido el seguidor; flush_lsn, hasta dónde lo ha escrito en disco (lo que cuenta para la durabilidad); replay_lsn, hasta dónde lo ha aplicado (lo que cuenta para que una consulta lo vea). Los *_lag son los tiempos correspondientes: el retraso de replicación medido. sync_state = async confirma que, por ahora, el primario no espera a nadie. Desde el seguidor se puede ver lo mismo desde el otro lado:
-- En la réplica (puerto 5433)
SELECT pg_is_in_recovery() AS soy_replica,
pg_last_wal_receive_lsn() AS recibido,
pg_last_wal_replay_lsn() AS aplicado,
now() - pg_last_xact_replay_timestamp() AS retraso_aproximado;Ver una lectura que llega tarde
Con un retraso de un milisegundo es difícil observar la anomalía, así que la vamos a provocar: el seguidor puede pausar la aplicación del WAL (seguirá recibiéndolo y guardándolo, pero no lo aplicará), que es exactamente lo que ocurre cuando una consulta larga en la réplica retrasa el replay o cuando la réplica está saturada.
# km0/simulaciones/lectura_tardia.py
import time
import psycopg # pip install "psycopg[binary]"
PRIMARIO = "host=localhost port=5432 dbname=km0_pedidos user=km0 password=km0"
REPLICA = "host=localhost port=5433 dbname=km0_pedidos user=km0 password=km0"
with psycopg.connect(PRIMARIO, autocommit=True) as p, psycopg.connect(REPLICA, autocommit=True) as r:
r.execute("SELECT pg_wal_replay_pause()") # la réplica deja de aplicar WAL
print("réplica en pausa:", r.execute("SELECT pg_is_wal_replay_paused()").fetchone()[0])
p.execute("INSERT INTO pedidos (id, cliente, estado, total) VALUES (%s, %s, 'creado', %s)",
("P-2026-000126", "Lucía", 31.50))
lsn_commit = p.execute("SELECT pg_current_wal_lsn()").fetchone()[0]
print(f"primario: pedido P-2026-000126 confirmado; LSN del primario = {lsn_commit}")
fila = r.execute("SELECT id, estado FROM pedidos WHERE id = 'P-2026-000126'").fetchone()
lsn_replica = r.execute("SELECT pg_last_wal_replay_lsn()").fetchone()[0]
print(f"réplica: lectura inmediata -> {fila}; LSN aplicado = {lsn_replica}") # None: Lucía no ve su pedido
r.execute("SELECT pg_wal_replay_resume()")
inicio = 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 a que la réplica alcance el LSN
fila = r.execute("SELECT id, estado FROM pedidos WHERE id = 'P-2026-000126'").fetchone()
print(f"réplica: tras alcanzar el LSN ({(time.monotonic() - inicio) * 1000:.1f} ms) -> {fila}")Salida:
réplica en pausa: True
primario: pedido P-2026-000126 confirmado; LSN del primario = 0/30001F8
réplica: lectura inmediata -> None; LSN aplicado = 0/3000148
réplica: tras alcanzar el LSN (12.3 ms) -> ('P-2026-000126', 'creado')La tercera línea es la violación de read-your-writes con su causa a la vista: el primario está en el LSN 0/30001F8 y la réplica sigue en 0/3000148. Y el bucle final es la técnica de "versión mínima en el cliente" de 03-01 con la versión real de PostgreSQL: el cliente guarda el LSN de su commit y no acepta leer de una réplica que no lo haya alcanzado. Muchas bibliotecas de acceso a datos y proxies (pgpool, algunos ORM) implementan exactamente esto.
Hacer la réplica síncrona
Con un cambio de parámetro en caliente, el primario pasa a esperar la confirmación de replica_vlc en cada commit:
-- En el primario
ALTER SYSTEM SET synchronous_standby_names = 'replica_vlc';
SELECT pg_reload_conf();
SELECT application_name, sync_state FROM pg_stat_replication; -- ahora: syncsynchronous_commit (por defecto on) decide qué espera el primario: remote_write (el seguidor lo ha recibido), on (lo ha escrito en disco: cero pérdida) o remote_apply (lo ha aplicado: una lectura posterior en la réplica ya lo ve, es decir, read-your-writes garantizado a cambio de la latencia máxima). Para un quórum de varios seguidores, la sintaxis es ANY 1 (replica_vlc, replica_bcn): la semisíncrona del apartado 3.
Ahora haz el experimento que revela el precio: detén la réplica (docker compose stop pedidos-db-replica) e intenta un INSERT en el primario. Se queda bloqueado: el primario no puede confirmar sin la réplica síncrona. Es la fila "síncrona" de la tabla del apartado 3 en carne y hueso, y la razón por la que en producción se configura ANY 1 de al menos dos seguidores, o se acepta la asíncrona con su riesgo de pérdida acotado (Ctrl+C en el INSERT, vuelve a synchronous_standby_names = '' y recarga para desbloquear).
- Simulación: quórum W/R con N=3
Para terminar, la replicación sin líder en Python: tres réplicas, escrituras a todas con W confirmaciones, lecturas a R con elección de la versión más alta y reparación en lectura. Una réplica, nodo-c, está temporalmente caída durante la escritura, que es el caso en el que la elección de W y R importa.
# km0/simulaciones/quorum_wr.py
from dataclasses import dataclass, field
class SinQuorum(Exception):
pass
@dataclass
class Replica:
nombre: str
datos: dict[str, tuple[int, int]] = field(default_factory=dict) # clave -> (valor, versión)
caida: bool = False
def escribir(self, clave: str, valor: int, version: int) -> bool:
if self.caida:
return False
actual = self.datos.get(clave, (None, 0))
if version > actual[1]: # nunca retroceder de versión
self.datos[clave] = (valor, version)
return True
def leer(self, clave: str) -> tuple[int, int] | None:
return None if self.caida else self.datos.get(clave, (None, 0))
class AlmacenSinLider:
def __init__(self, nombres: list[str], W: int, R: int):
self.replicas = {n: Replica(n) for n in nombres}
self.N, self.W, self.R = len(nombres), W, R
self.version = 0
print(f"\n=== N={self.N}, W={W}, R={R} -> W+R{'>' if W + R > self.N else '<='}N ===")
def escribir(self, clave: str, valor: int) -> None:
self.version += 1
acks = [r.nombre for r in self.replicas.values() if r.escribir(clave, valor, self.version)]
if len(acks) < self.W:
raise SinQuorum(f"escritura {clave}={valor}: solo {len(acks)} acks, W={self.W}")
print(f"escribir {clave}={valor} (v{self.version}): confirmada por {acks} ({len(acks)} >= W={self.W})")
def leer(self, clave: str, desde: list[str]) -> int | None:
"""Lee de las réplicas indicadas (simula a cuáles llegó antes el coordinador)."""
respuestas = {n: self.replicas[n].leer(clave) for n in desde}
respuestas = {n: v for n, v in respuestas.items() if v is not None}
if len(respuestas) < self.R:
raise SinQuorum(f"lectura {clave}: solo {len(respuestas)} respuestas, R={self.R}")
mejor_nombre, (valor, version) = max(respuestas.items(), key=lambda kv: kv[1][1])
reparadas = []
for n, (_, v) in respuestas.items(): # read repair
if v < version and self.replicas[n].escribir(clave, valor, version):
reparadas.append(n)
detalle = ", ".join(f"{n}=v{v[1]}" for n, v in respuestas.items())
print(f"leer {clave} desde {desde}: [{detalle}] -> devuelve v{version} de {mejor_nombre}"
+ (f"; read repair en {reparadas}" if reparadas else ""))
return valor
def escenario(W: int, R: int) -> None:
alm = AlmacenSinLider(["nodo-a", "nodo-b", "nodo-c"], W, R)
alm.escribir("stock:queso-curado", 3) # estado inicial, todas al día
alm.replicas["nodo-c"].caida = True
try:
alm.escribir("stock:queso-curado", 2) # Ana reserva; nodo-c no responde
except SinQuorum as e:
print("ERROR:", e)
alm.replicas["nodo-c"].caida = False # nodo-c vuelve, pero está atrasado
for desde in (["nodo-c"], ["nodo-b", "nodo-c"], ["nodo-a", "nodo-c"], ["nodo-c"]):
try:
valor = alm.leer("stock:queso-curado", desde)
if valor == 3:
print(" !!! LECTURA ESTANCADA: Marc ve 3 unidades cuando quedan 2")
except SinQuorum as e:
print("ERROR:", e)
if __name__ == "__main__":
escenario(W=1, R=1)
escenario(W=2, R=1)
escenario(W=2, R=2)Salida:
=== N=3, W=1, R=1 -> W+R<=N === escribir stock:queso-curado=3 (v1): confirmada por ['nodo-a', 'nodo-b', 'nodo-c'] (3 >= W=1) escribir stock:queso-curado=2 (v2): confirmada por ['nodo-a', 'nodo-b'] (2 >= W=1) leer stock:queso-curado desde ['nodo-c']: [nodo-c=v1] -> devuelve v1 de nodo-c !!! LECTURA ESTANCADA: Marc ve 3 unidades cuando quedan 2 leer stock:queso-curado desde ['nodo-b', 'nodo-c']: [nodo-b=v2, nodo-c=v1] -> devuelve v2 de nodo-b; read repair en ['nodo-c'] leer stock:queso-curado desde ['nodo-a', 'nodo-c']: [nodo-a=v2, nodo-c=v2] -> devuelve v2 de nodo-a leer stock:queso-curado desde ['nodo-c']: [nodo-c=v2] -> devuelve v2 de nodo-c === N=3, W=2, R=1 -> W+R<=N === escribir stock:queso-curado=3 (v1): confirmada por ['nodo-a', 'nodo-b', 'nodo-c'] (3 >= W=2) escribir stock:queso-curado=2 (v2): confirmada por ['nodo-a', 'nodo-b'] (2 >= W=2) leer stock:queso-curado desde ['nodo-c']: [nodo-c=v1] -> devuelve v1 de nodo-c !!! LECTURA ESTANCADA: Marc ve 3 unidades cuando quedan 2 leer stock:queso-curado desde ['nodo-b', 'nodo-c']: [nodo-b=v2, nodo-c=v1] -> devuelve v2 de nodo-b; read repair en ['nodo-c'] leer stock:queso-curado desde ['nodo-a', 'nodo-c']: [nodo-a=v2, nodo-c=v2] -> devuelve v2 de nodo-a leer stock:queso-curado desde ['nodo-c']: [nodo-c=v2] -> devuelve v2 de nodo-c === N=3, W=2, R=2 -> W+R>N === escribir stock:queso-curado=3 (v1): confirmada por ['nodo-a', 'nodo-b', 'nodo-c'] (3 >= W=2) escribir stock:queso-curado=2 (v2): confirmada por ['nodo-a', 'nodo-b'] (2 >= W=2) ERROR: lectura stock:queso-curado: solo 1 respuestas, R=2 leer stock:queso-curado desde ['nodo-b', 'nodo-c']: [nodo-b=v2, nodo-c=v1] -> devuelve v2 de nodo-b; read repair en ['nodo-c'] leer stock:queso-curado desde ['nodo-a', 'nodo-c']: [nodo-a=v2, nodo-c=v2] -> devuelve v2 de nodo-a ERROR: lectura stock:queso-curado: solo 1 respuestas, R=2
Lo que muestra:
- Con W=1, R=1 y con W=2, R=1 (ambas W + R ≤ N), la escritura de Ana se confirma sin
nodo-c, y la primera lectura, que cae solo ennodo-c, devuelve la versión 1: Marc ve tres quesos cuando quedan dos. Es una lectura estancada legal para esa configuración. La segunda lectura, que tocanodo-bynodo-c, obtiene ambas versiones, devuelve la 2 y reparanodo-c; a partir de ahínodo-cestá al día y las lecturas siguientes son correctas. Con W=1, además, la escritura habría tenido éxito aunque solo una réplica respondiera, con todo lo que eso implica para la durabilidad. - Con W=2, R=2 (W + R > N), una lectura que solo obtiene respuesta de
nodo-cno alcanza R y falla en lugar de mentir, tanto la primera como la última (el coordinador real preguntaría a otra réplica en vez de fallar). Cualquier lectura con dos respuestas incluye necesariamente anodo-aonodo-b, que tienen la versión 2: nunca hay lectura estancada. Ese es el solape garantizado por la aritmética del quórum, al precio de que cada lectura espera a dos réplicas y de que, si dos réplicas cayeran, ni escrituras ni lecturas serían posibles.
Este es el mecanismo que Cassandra expone con los niveles de consistencia ONE, QUORUM y ALL, y con el que en 04-04 configuraremos el almacén de pedidos históricos de Kilómetro Cero.
Errores Comunes y Consejos
- Añadir réplicas de lectura sin clasificar las consultas. La consulta "¿queda stock?" que precede a una reserva no puede ir a una réplica asíncrona. Marca en el código de cada servicio qué lecturas van al líder y cuáles a las réplicas, y por qué.
- Creer que la replicación síncrona es "más segura" y activarla sin más. Con un solo seguidor síncrono, su caída bloquea todas las escrituras del sistema. Si necesitas cero pérdida, configura
ANY 1sobre al menos dos seguidores y monitoriza que estén vivos. - Confiar en el failover automático sin protección contra split-brain. Un antiguo líder que vuelve y sigue aceptando escrituras es peor que una caída. Usa una herramienta con consenso (Patroni + etcd) o un token de cercado, y ensaya el failover (07-03, 07-06).
- Slots de replicación olvidados. Un slot cuyo seguidor ha desaparecido retiene WAL para siempre y llena el disco del primario. Monitoriza
pg_replication_slotsy elimina los que no se usan. - Multilíder con LWW y relojes físicos "porque es lo que trae por defecto". Es la combinación que pierde datos con más seguridad (03-02). Si necesitas multilíder, decide la resolución de conflictos por tipo de dato y usa version vectors para detectar la concurrencia real.
- Quórum laxo sin saberlo. Cassandra y Riak tienen el sloppy quorum y el hinted handoff activados por defecto en algunas configuraciones; W + R > N deja de garantizar solape. Lee la documentación de tu versión antes de razonar con la aritmética del apartado 7.
- Consejo: mide el retraso de replicación como una métrica de primer nivel (
replay_lag, o la diferencia de LSN convertida conpg_wal_lsn_diff) y define un umbral a partir del cual el balanceador deja de enviar lecturas a esa réplica. - Consejo: para read-your-writes sobre PostgreSQL, el LSN de commit es el mejor "número de versión" que existe: es monótono, ya está calculado y las réplicas saben exactamente dónde están respecto a él.
Ejercicios
Ejercicio 1: Elegir el modo de replicación por base de datos
Kilómetro Cero tiene una base de datos PostgreSQL por servicio. Para km0_pedidos, km0_inventario, km0_pagos y la base de datos del catálogo, decide entre asíncrona, semisíncrona (ANY 1 de dos seguidores) y remote_apply, indica qué lecturas enviarías a las réplicas y qué pérdida máxima de datos aceptas en un failover. Justifica cada elección con los conceptos de esta lección y de 03-02.
Ejercicio 2: Read-your-writes con LSN en pedidos
Escribe una función leer_pedido(id_pedido, lsn_minimo) para el servicio pedidos que reciba el LSN del último commit del usuario (guardado en su sesión) y devuelva el pedido desde la réplica si esta ha alcanzado el LSN, o desde el primario si no, registrando cuál de los dos ha usado. Usa psycopg y las funciones de PostgreSQL vistas en el apartado 9. ¿Cómo obtiene y guarda el servicio el LSN tras cada escritura del usuario?
Ejercicio 3: Quórum laxo en la simulación
Añade a AlmacenSinLider un modo de quórum laxo: si una réplica está caída durante la escritura, un nodo auxiliar nodo-h acepta la escritura con un hint para ella, y la entrega cuando la réplica vuelve (entregar_hints()). Reproduce el escenario con W=2, R=2 y muestra que, entre la vuelta de nodo-c y la entrega del hint, una lectura desde nodo-b y nodo-c... ¿puede estar estancada? Razona qué garantiza y qué no garantiza W + R > N con quórum laxo, y qué se gana a cambio.
Soluciones
Solución 1:
km0_pagos: semisíncronaANY 1consynchronous_commit = on. Un cobro registrado y perdido en un failover es dinero cobrado sin pedido (o al revés); la pérdida aceptable es cero. Lecturas a réplicas: solo informes y conciliación contable (retraso inocuo); el estado de un pago siempre del primario. Es la fila "CP" de 03-02.km0_pedidos: semisíncronaANY 1. Un pedido confirmado a Ana que desaparece en el failover, con supedido.creadoya publicado por el relay outbox, genera un evento huérfano queinventarioypagosprocesarían para un pedido inexistente. Pérdida aceptable: cero. Lecturas a réplicas: "mis pedidos" con LSN mínimo (ejercicio 2) y el historial de más de un día.km0_inventario: semisíncronaANY 1para las reservas (la última unidad, 03-02); las lecturas del stock para el catálogo pueden ir a réplicas asíncronas (el catálogo ya es eventual). Pérdida aceptable: ninguna reserva confirmada; los eventosstock.actualizadopasan por outbox y siguen el mismo razonamiento que los pedidos.- Catálogo: asíncrona. Un cambio de precio o de descripción perdido en un failover se reintroduce por el productor; no hay evento irreversible aguas abajo. Pérdida aceptable: los últimos segundos de cambios. Todas las lecturas a réplicas, sin LSN (el precio efectivo se fija en
pedidos).
remote_apply no compensa en ninguna: duplica la latencia de escritura para obtener read-your-writes en la réplica, que se consigue más barato con el LSN en el cliente.
Solución 2:
# km0/servicios/pedidos/lecturas.py
import psycopg
PRIMARIO = "host=pedidos-db-primario dbname=km0_pedidos user=km0 password=km0"
REPLICA = "host=pedidos-db-replica dbname=km0_pedidos user=km0 password=km0"
def escribir_y_anotar_lsn(sesion: dict, sql: str, params: tuple) -> None:
"""Toda escritura del usuario guarda en su sesión el LSN alcanzado tras el commit."""
with psycopg.connect(PRIMARIO) as conn:
with conn.transaction():
conn.execute(sql, params)
sesion["lsn"] = conn.execute("SELECT pg_current_wal_lsn()::text").fetchone()[0]
def leer_pedido(id_pedido: str, lsn_minimo: str | None) -> tuple[dict | None, str]:
if lsn_minimo is not None:
with psycopg.connect(REPLICA) as r:
al_dia = r.execute("SELECT pg_last_wal_replay_lsn() >= %s::pg_lsn", (lsn_minimo,)).fetchone()[0]
if al_dia:
fila = r.execute("SELECT id, cliente, estado, total FROM pedidos WHERE id = %s", (id_pedido,)).fetchone()
return fila, "replica"
with psycopg.connect(PRIMARIO) as p: # sin LSN o réplica atrasada: al primario
fila = p.execute("SELECT id, cliente, estado, total FROM pedidos WHERE id = %s", (id_pedido,)).fetchone()
return fila, "primario"El LSN se obtiene con pg_current_wal_lsn() después del commit, en la misma conexión, y se guarda en la sesión del usuario (cookie firmada, Redis de sesiones o el token de sesión). Un usuario que nunca ha escrito (lsn_minimo None) va al primario por prudencia, o a la réplica si la lectura tolera retraso; una mejora es usar pg_last_wal_replay_lsn() comparado con el LSN en una sola consulta y hacer la lectura solo si la réplica está al día, como aquí. Nota que el LSN de la sesión debe ser el máximo de los LSN de todas las escrituras del usuario (monotonic reads), y que un failover a una réplica cuyo historial se bifurcó invalida los LSN anteriores, otro motivo para la semisíncrona.
Solución 3:
Implementación esquemática: en escribir, cuando una réplica devuelve False, se guarda (clave, valor, version) en self.hints[nombre_replica] y se cuenta el ack del nodo auxiliar; entregar_hints() recorre los hints y llama a escribir en las réplicas ya disponibles. Con W=2, R=2: la escritura de Ana se confirma con nodo-a, nodo-b y el hint para nodo-c (tres acks, aunque solo dos son réplicas reales). Al volver nodo-c y antes de entregar_hints(), una lectura desde nodo-b y nodo-c obtiene v2 de nodo-b y v1 de nodo-c: no estancada, porque nodo-b es una réplica real que tiene la escritura. Pero imagina que también nodo-b hubiera estado caída: los acks serían nodo-a más dos hints, W=2 se cumpliría, y una lectura posterior desde nodo-b y nodo-c (ambas vueltas, sin hints entregados) devolvería v1 con R=2 satisfecho: lectura estancada con W + R > N. Lo que garantiza W + R > N con quórum laxo es que la escritura está en W nodos de algún tipo (durabilidad) y que se entregará a las réplicas reales cuando vuelvan (convergencia); lo que ya no garantiza es el solape entre las réplicas que confirmaron y las que se leen. A cambio, la escritura tiene éxito incluso con la mayoría de las réplicas reales caídas: disponibilidad máxima, la elección AP en su forma más pura.
Conclusión
Replicar es mantener copias de los mismos datos en varios nodos para ganar disponibilidad, latencia y escalado de lecturas (nunca de escrituras), y esta lección ha recorrido las tres formas de hacerlo. En líder-seguidor, todas las escrituras pasan por un nodo que las envía en orden a los demás; la elección entre síncrona, asíncrona y semisíncrona decide cuánto se pierde si el líder muere y cuánto cuesta cada commit, y el retraso de replicación de los seguidores es la causa mecánica de las anomalías de sesión de 03-01, que se evitan enrutando al líder o esperando al LSN. El failover convierte a un seguidor en líder con tres peligros (detección, pérdida de transacciones, split-brain) que dejamos para 07-03. En multilíder, cada región acepta escrituras y el problema es el conflicto, con un repertorio de soluciones que va de evitarlo a LWW, fusiones, CRDTs y resolución en la aplicación. En la replicación sin líder, la aritmética W + R > N sustituye a la coordinación, con read repair y hinted handoff para mantener las réplicas al día y el quórum laxo como concesión a la disponibilidad. La práctica ha sido real: km0_pedidos tiene ahora un seguidor PostgreSQL en streaming creado con pg_basebackup -R, observado con pg_stat_replication, pausado para ver a Lucía no encontrar su pedido y esperado hasta el LSN correcto; y la simulación con N=3 ha mostrado a Marc leyendo tres quesos cuando quedaban dos siempre que W + R ≤ N.
Con esto sabemos copiar los datos de un servicio. Pero la operación más importante de Kilómetro Cero, crear un pedido, ya no toca una sola base de datos: inserta en km0_pedidos, descuenta en km0_inventario y cobra en km0_pagos, tres bases de datos de tres servicios, cada una replicada como acabamos de ver, y ninguna transacción de PostgreSQL abarca las tres. Si el cobro falla después de descontar el stock, ¿quién lo devuelve? Si pedidos se cae entre el descuento y el cobro, ¿en qué estado queda el pedido de Marc? Lo que el monolito resolvía con un BEGIN y un COMMIT necesita ahora un protocolo de commit atómico o, más habitualmente en microservicios, una saga con compensaciones. Es el tema de la última lección del módulo: transacciones distribuidas y sagas.
Curso de Arquitecturas Distribuidas
Módulo 1: Introducción a los Sistemas Distribuidos
- Conceptos Básicos de Sistemas Distribuidos
- Modelos de Sistemas Distribuidos
- Ventajas y Desafíos de los Sistemas Distribuidos
- Las Falacias de la Computación Distribuida
- Tiempo, Relojes y Ordenación de Eventos
- Del Monolito a la Plataforma Distribuida: el Caso Kilómetro Cero
Módulo 2: Comunicación en Sistemas Distribuidos
- Protocolos de Comunicación
- RPC y RMI
- gRPC y Serialización de Datos
- Mensajería y Colas de Mensajes
- Patrones de Comunicación Asíncrona
Módulo 3: Consistencia y Replicación
- Modelos de Consistencia
- El Teorema CAP y PACELC
- Algoritmos de Consenso
- Replicación de Datos
- Transacciones Distribuidas y Sagas
Módulo 4: Almacenamiento Distribuido
- Particionado de Datos y Hashing Consistente
- Sistemas de Archivos Distribuidos
- Almacenamiento de Objetos
- Bases de Datos Distribuidas
- Cachés Distribuidos
Módulo 5: Computación Distribuida
- Modelos de Computación Distribuida
- MapReduce y Hadoop
- Spark y Computación en Memoria
- Procesamiento de Flujos de Datos
- Planificación de Trabajos y Pipelines de Datos
Módulo 6: Seguridad en Sistemas Distribuidos
- Autenticación y Autorización
- Cifrado y Protección de Datos
- Gestión de Identidades
- Seguridad entre Servicios: mTLS y Gestión de Secretos
- Puertas de Enlace, Limitación de Tasa y Auditoría
Módulo 7: Monitoreo y Mantenimiento
- Monitoreo de Sistemas Distribuidos
- Logs Centralizados y Trazabilidad Distribuida
- Gestión de Fallos y Recuperación
- Patrones de Resiliencia: Timeouts, Reintentos y Circuit Breaker
- Automatización y Orquestación
- Pruebas en Sistemas Distribuidos e Ingeniería del Caos
