🌐 Sistemas distribuidos
Qué es un sistema distribuido, el teorema CAP, fallos y parcial, replicación, particionado, consistencia, consenso (Raft/Paxos), relojes y qué leer a continuación. Con ejemplos y práctica.
Sistemas distribuidos
Un sistema distribuido es un conjunto de computadoras independientes que colaboran y que al usuario le parecen una sola máquina. Google, tus bases de datos en la nube, un backend con varios nodos, Kafka, Kubernetes: todos son sistemas distribuidos.
Lo que cambia todo es una frase: cualquier cosa puede fallar en cualquier momento. Una máquina se cae, la red se corta, un mensaje llega tarde o se pierde. El ingeniero de distribuidos diseña sistemas que siguen funcionando bien a pesar de los fallos — no asumiendo que no pasarán.
💡 La frase de Leslie Lamport: «Un sistema distribuido es aquel en el que el fallo de una computadora que no sabías que existía puede inutilizar la tuya». Diseñar bien = hacer que eso no pase.
El problema fundamental: los fallos
En una sola máquina, todo es instantáneo y consistente (una variable siempre tiene su valor). Distribuido, aparece lo imposible de saber:
- ¿El nodo B está caído, o solo lento? (No hay forma de distinguir sin esperar).
- ¿Mi mensaje llegó? ¿En qué orden? (La red no garantiza entrega ni orden).
- ¿Qué versión de los datos es la correcta? (Cada nodo ve una parte distinta).
| Fallo | Descripción | Ejemplo |
|---|---|---|
| Fallos parciales | Parte del sistema funciona, otra no | Un nodo de 5 se cae |
| Partición de red | Nodos que no pueden hablar entre sí | Dos centros de datos desconectados |
| Fallos de proceso | El programa muere (crash) | El worker se muere a mitad |
| Fallos byzantinos | Nodos que se comportan mal (mienten, corrompen) | Nodo comprometido (malicioso) |
| Fallos temporales / tiempo de espera | Un nodo “desaparece” y reaparece | Timeout mal configurado |
⚠️ Fallos byzantinos son los de la seguridad: nodos que mienten. La mayoría de sistemas (Raft, etc.) asumen fallos crash (el nodo se cae pero no miente). Los byzantinos se resuelven con consenso bizantino (blockchain).
El teorema CAP
CAP es la afirmación más famosa de los distribuidos: de las tres propiedades, un sistema distribuido solo puede garantizar dos a la vez:
| Letra | Propiedad | Significado |
|---|---|---|
| C | Consistencia | Todos los nodos ven los mismos datos al mismo tiempo |
| A | Disponibilidad | El sistema responde siempre (cada petición tiene respuesta) |
| P | Tolerancia a particiones | El sistema sigue funcionando aunque se parta la red |
La regla de oro: cuando la red se parte (P ocurre), tienes que elegir entre C y A:
- CP (consistencia): durante una partición, prefieres negar el servicio (o devolver error) a devolver datos viejos. → Eliges datos correctos. PostgreSQL replicado, ZooKeeper, MongoDB (por defecto).
- AP (disponibilidad): durante una partición, prefieres seguir respondiendo, aunque cada lado vea datos distintos (que se reconciliarán después). → Eliges que siga funcionando. Cassandra, DynamoDB, sistemas de mensajería.
⚠️ CAP no es «elegir 2 de 3»: en condiciones normales (sin partición) un sistema puede dar C y A. CAP solo importa cuando hay una partición (que tarde o temprano pasa). Por eso todos los sistemas modernos son eventually consistent en algún nivel y usan P fijo: la decisión real es CP vs AP.
Consistencia: de fuerte a eventual
La consistencia describe qué tan actualizados están los datos que lees.
| Nivel | Qué ve el lector | Ejemplo |
|---|---|---|
| Fuerte (linearizable) | Siempre el último dato escrito | Sistema bancario, elecciones de líder |
| Lecturas consistentes | Ve su propia escritura (read-your-writes) | Perfil de usuario |
| Lectura-caída (stale reads) | Puede ver datos viejos un tiempo | Caché |
| Eventual | Los datos se actualizan… eventualmente | DNS, réplicas, feeds |
Consistencia fuerte:
Escribe ▶ [nodo1] ──▶ [nodo2] (espera confirmación antes de responder)
Lee desde cualquier nodo → siempre lo último
Consistencia eventual:
Escribe ▶ [nodo1] ──▶ (async) ──▶ [nodo2]
Leo de nodo2 → puede estar un paso atrás unos milisegundos
💡 El precio de la consistencia fuerte es latencia (esperas a que los demás nodos confirmen) y menos disponibilidad durante particiones. Por eso los sistemas reales mezclan: fuerte para lo crítico, eventual para lo que puede esperar.
Replicación
Replicar es tener copias de los datos en varios nodos. Motivos: disponibilidad (si un nodo muere, otros sirven) y escala de lectura (más nodos = más lecturas por segundo).
Líder-seguidor (primary-replica)
El líder recibe todas las escrituras; los seguidores replican y atienden lecturas.
Escribe ──▶ [Líder]
│ (replica)
┌────────────┼────────────┐
▼ ▼ ▼
[Follower] [Follower] [Follower]
Lee Lee Lee
- Escrituras: todas al líder (escalable para lectura, un solo punto para escritura).
- Síncrono vs asíncrono: con replicación síncrona, el líder espera a que el seguidor confirme (más seguro, más lento). Con asíncrona, el líder responde sin esperar (más rápido, riesgo de perder datos si cae el líder).
⚠️ Failover: si el líder muere, un seguidor debe promoverse a líder. Decidir cuál (sin que haya dos líderes a la vez — split-brain) es un problema de consenso.
Multi-líder y sin líder
- Multi-líder: varias réplicas aceptan escrituras (útil con múltiples regiones, pero aparecen conflictos de escritura que hay que resolver).
- Sin líder (peer-to-peer): cualquier nodo acepta escrituras (DynamoDB, Cassandra). Conflictos resueltos con quórum: escribir en N nodos, leer de R nodos; si
R + W > N, siempre lees el dato más reciente.
Particionado (sharding)
Cuando los datos ya no caben o no aguantan el tráfico en un solo nodo, se particiona: cada nodo guarda una parte de los datos.
Usuarios por ID:
[0-1000] [1001-2000] [2001-3000]
nodo A nodo B nodo C
- Por rango: IDs 0-1000 en A, etc. Simple, pero puede sesgar (hot spots) y el rango puede moverse.
- Por hash:
hash(user_id) % Ndecide el nodo. Distribución uniforme, pero difícil de rebalancear (por eso se usa consistent hashing). - Por claves naturales: por región, por tenant (multi-tenancy).
⚠️ El precio del sharding: las consultas que cruzan particiones (JOINs, transacciones multi-nodo) se complican. El diseño del clave de partición es una de las decisiones más importantes (y difíciles de cambiar después) de system design.
Consenso: ponerse de acuerdo
El problema del consenso: varios nodos deben acordar un mismo valor (por ejemplo, quién es el líder) aunque haya fallos y red lenta. Sin consenso, dos nodos podrían creer cada uno que es líder → split-brain (dos líderes, datos que se contradicen).
Raft y Paxos
- Paxos (Lamport): el algoritmo teórico original, famoso por difícil de entender.
- Raft: la versión práctica y legible (lo usan etcd, Consul, CockroachDB, MongoDB). Separa el consenso en: elección de líder, replicación de log, seguridad.
Cómo decide Raft quién es líder (resumen):
- Cada nodo tiene un término (contador de elecciones) y un timeout de elección aleatorio.
- El nodo cuyo timeout expira se candidato y pide votos a los demás.
- Gana si consigue mayoría (más de la mitad) de los votos.
- El líder replica su log a los seguidores; si la mayoría confirma, el valor está committed.
Candidato ──▶ pide voto a todos
Mayoría de votos ──▶ LÍDER (solo puede haber uno por término)
💡 Por qué «mayoría»: con más de la mitad, no puede haber dos líderes en el mismo término (dos mayorías se solapan en al menos un nodo). Esa propiedad es el corazón del consenso: mayoría = exclusión mutua garantizada.
Uso real: etcd y ZooKeeper guardan la configuración de Kubernetes y otros sistemas; PostgreSQL y MongoDB usan consenso para el failover del líder. Cuando veas «distributed consensus», piensa: elegir un líder / acordar un orden de forma segura.
Relojes y ordenamiento
En distribuidos, no hay un reloj global. Cada máquina tiene su reloj, y se desincronizan. ¿Cómo sabes qué escritura fue «después» de cuál?
- Relojes físicos (NTP): se sincronizan por red, pero con deriva (ms de error). No sirven para ordenar con exactitud.
- Relojes lógicos (Lamport): un contador por nodo; cada evento incrementa el reloj. Si
e1 < e2en los relojes,e1pasó antes quee2. Da un orden parcial. - Vector clocks: generalizan a Lamport: cada nodo sabe «qué han visto los demás». Permiten detectar conflictos de escritura concurrentes (los usa Cassandra/DynamoDB).
- Timestamps híbridos: combinan físico + lógico (los usa CockroachDB, Spanner).
⚠️ Frase para recordar: «el que escribió el timestamp no sabe nada del que lo leyó». En distribuidos, no ordenas por hora de reloj: ordenas por orden causal (Lamport, vector clocks) o por consenso (orden total del log).
Cheatsheet de conceptos
| Término | En una frase |
|---|---|
| Sistema distribuido | Varias máquinas que parecen una sola |
| Nodo | Una máquina del sistema |
| Partición | Corte de red entre nodos |
| Replicación | Copias de los datos en varios nodos |
| Particionado (sharding) | Cada nodo guarda una parte de los datos |
| Líder / seguidor | Quién acepta escrituras / quién replica |
| Failover | Cambio automático al nuevo líder |
| Split-brain | Dos líderes a la vez (muy malo) |
| Quórum | Mayoría necesaria para decidir |
| Consenso | Nodos acordando un valor (Raft, Paxos) |
| Consistencia fuerte | Todos ven lo último inmediatamente |
| Consistencia eventual | Los datos convergen… eventualmente |
| CAP | Consistencia / Disponibilidad / Particiones |
| CP vs AP | Elegir entre correcto o disponible durante partición |
| Reloj lógico | Ordenar eventos sin reloj global |
| Timeout / retry | Esperar y reintentar ante fallos |
Práctica propuesta
- Levanta 3 instancias de Redis (o PostgreSQL) con replicación. Escribe en el líder y lee de un seguidor. Observa la eventual consistency.
- Simula una partición de red (bloquea un puerto con firewall o desconecta un contenedor) y observa qué pasa con las peticiones: ¿CP o AP es tu sistema?
- Ejecuta un clúster de etcd (o un Raft simple con Go) y mata el líder. Observa cómo los seguidores eligen otro (consenso).
- Lee el paper de Raft (raft.github.io) con la animación interactiva. Explica el split-brain en una frase.
- Diseña (en papel) un sistema de likes que deba funcionar ante una partición: ¿CP o AP? ¿Cómo lo reconcilias después?
Para profundizar
- MIT 6.5840 / 6.824 — Distributed Systems: el curso graduado de MIT; labs de MapReduce, Raft y KV store en Go. La formación hands-on.
- Raft — the interactive visualization: la animación que hace entendible el consenso.
- Designing Data-Intensive Applications: el libro #1 de sistemas de datos (replicación, particionado, consistencia). Parte de la ruta de System Design.
- Distributed Systems (van Steen & Tanenbaum): el libro universitario gratis en PDF.
- Papers We Love: la comunidad para leer papers en grupo (Dynamo, Spanner, Raft…).
- System Design: la sección del sitio que aplica todo esto a casos reales.
- Sigue con 🏗️ System Design o vuelve al 🗺️ Plan.