← dev-notes
DIST · PARTE 2 DE 2

Consenso, Quórum
y Relojes Lógicos

Cómo los nodos se ponen de acuerdo — la maquinaria detrás de Kafka, etcd y ZooKeeper

DIST · P2
🕰 Relojes de Lamport 🗳 Quórum R+W>N 🤝 Consenso Raft 🔒 Locks distribuidos 🏗 Sistemas reales
01 Por qué el consenso es difícil
EL PROBLEMA DE LOS DOS GENERALES · 1975
FUNDAMENTO TEÓRICO
El Problema de los Dos Generales
Dos ejércitos necesitan coordinarse para atacar simultáneamente. El único canal de comunicación es un mensajero que puede ser capturado. Es imposible lograr consenso garantizado con un canal poco confiable — y toda red real es un canal poco confiable.
🏰
General A
Envía: "Ataca a las 6am"
→?→
red no confiable
mensaje puede perderse
🏰
General B
¿Recibió el mensaje?
Si B confirma recepción, A necesita saber si B recibió la confirmación.
Si A confirma que recibió la confirmación… B necesita saber que A recibió su confirmación.
El ciclo de confirmaciones es infinito. No hay solución perfecta.
🔗
Implicación práctica: por qué TCP no basta
TCP garantiza entrega en una conexión estable, pero no resuelve el problema de consenso entre múltiples nodos independientes. Si el nodo A hace commit y muere antes de notificar a B, ¿B hizo commit o no? Los algoritmos de consenso (Paxos, Raft) son la respuesta — con mayorías en lugar de unanimidad, reducen el problema a algo manejable.
02 Relojes lógicos
ORDENAR EVENTOS SIN RELOJ FÍSICO
En sistemas distribuidos, los relojes físicos tienen skew inevitable (NTP solo lo reduce, no lo elimina). Para ordenar eventos de forma correcta se usan relojes lógicos: contadores que capturan relaciones causales, no tiempo real.
⏱ Relojes de Lamport (1978)
Capturan la relación happened-before (→). Si A → B entonces timestamp(A) < timestamp(B). Si no hay relación causal, el orden es arbitrario pero consistente.
Regla 1: Al enviar un mensaje,
  timestamp += 1
Regla 2: Al recibir un mensaje,
  timestamp = max(local, recibido) + 1
Regla 3: Para cualquier evento interno,
  timestamp += 1
Limitación: si ts(A) < ts(B), A pudo haber ocurrido antes — pero no necesariamente. No captura concurrencia.
📐 Vector Clocks (1988)
Cada nodo mantiene un vector de contadores, uno por nodo. Permiten detectar si dos eventos son causalmente relacionados o concurrentes.
Nodo A: [A:1, B:0, C:0]
Nodo B recibe de A: [A:1, B:1, C:0]
Nodo C (independiente): [A:0, B:0, C:1]

V1 ≤ V2 si todo V1[i] ≤ V2[i]
→ causal. Si no: eventos concurrentes.
Usado por: DynamoDB (para detectar conflictos), Riak, sistemas CRDTs. Costo: O(N) por mensaje.
Usar System.currentTimeMillis() para ordenar eventos distribuidos. Dos nodos pueden tener el mismo timestamp o un nodo lento puede tener un timestamp anterior al de un evento que ocurrió después. Siempre usa relojes lógicos o IDs ordenados (UUID v7, Snowflake IDs).
Snowflake ID — timestamp + nodo + secuencia → orden total PATRÓN PRÁCTICO
// Twitter Snowflake (2010) — ID de 64 bits ordenable
// | 41 bits timestamp ms | 10 bits node ID | 12 bits sequence |
// → ~4100 años · 1024 nodos · 4096 IDs/ms/nodo

long snowflakeId = ((long) timestampMs << 22)
                  | ((long) nodeId     << 12)
                  | sequence;

// UUID v7 (2022, RFC 9562) — equivalente estándar moderno
// | 48 bits unix_ts_ms | 4 bits version | 12 bits rand | 62 bits rand |
// → lexicográficamente ordenable + incluye timestamp

// En Java: use UUID7 via com.fasterxml.uuid
UUID id = Generators.timeBasedEpochGenerator().generate();
// Ordenable en BD: INSERT ORDER ≈ CHRONOLOGICAL ORDER → menos fragmentación
03 Quórum — R + W > N
GARANTIZAR CONSISTENCIA SIN COORDINACIÓN TOTAL
En lugar de exigir que todos los nodos confirmen una operación (costoso), se exige que una mayoría (quórum) lo haga. Si el quórum de lecturas y el de escrituras se solapan, siempre habrá al menos un nodo que tiene el dato más reciente.
LA FÓRMULA
R
réplicas leídas
+
W
réplicas escritas
>
N
réplicas totales
→ siempre hay solapamiento
→ lecturas siempre ven la escritura más reciente
N=3, R=2, W=2
R+W = 4 > 3 ✓
Strong consistency
Cassandra QUORUM
DynamoDB strongly consistent
N=3, R=1, W=3
R+W = 4 > 3 ✓
Write-heavy
Lecturas rápidas
Escrituras lentas (all)
N=3, R=1, W=1
R+W = 2 ≤ 3 ✗
Eventual only
Cassandra ONE
Sin garantía de consistencia
Cassandra permite configurar R y W por operación (ONE, QUORUM, ALL). DynamoDB tiene lecturas eventualmente consistentes (R=1) y fuertemente consistentes (R=mayoría). Kafka usa quórum de ISR (In-Sync Replicas) para decidir qué tan durable es un mensaje.
04 Consenso distribuido — Raft
CÓMO LOS NODOS ELIGEN UN LÍDER Y REPLICAN LOG
Paxos (1989) fue el primer algoritmo de consenso práctico pero es notoriamente difícil de implementar. Raft (2014, Diego Ongaro) fue diseñado para ser equivalente a Paxos pero comprensible. Es el algoritmo detrás de etcd (Kubernetes), CockroachDB y TiKV.
FASE 01
Leader Election
Cada nodo empieza como Follower. Si no recibe heartbeat del líder en un timeout aleatorio, se convierte en Candidate y pide votos. El que obtiene mayoría gana.
FASE 02
Log Replication
El líder recibe todas las escrituras, las añade a su log y las replica a los followers. Un entry está committed cuando la mayoría (quórum) lo confirma.
FASE 03
Safety
Solo se puede elegir líder un nodo cuyo log está al menos tan actualizado como la mayoría. Garantiza que ningún entry committed se pierde al cambiar de líder.
FASE 04
Fencing
Cada elección incrementa el term. Mensajes de términos anteriores son ignorados. Previene split-brain: el líder antiguo es automáticamente descartado.
📌
Raft en Kafka (KRaft mode desde 2.8)
Kafka reemplazó ZooKeeper (que usa ZAB, similar a Raft) por KRaft — su propia implementación de consenso. El Controller elige un líder de partición; los ISR (In-Sync Replicas) actúan como quórum. min.insync.replicas define cuántas réplicas deben confirmar para que un mensaje se considere committed — exactamente el W del quórum.
05 Locks distribuidos y sistemas reales
APLICACIÓN PRÁCTICA · JAVA Y SPRING
Los conceptos anteriores se materializan en herramientas concretas. Un arquitecto senior debe saber cuándo usar cada una y cuáles son sus garantías reales.
Sistema Algoritmo/Modelo Garantías Cuándo usarlo
ZooKeeper ZAB (Zookeeper Atomic Broadcast) ≈ Raft CP · Linearizable · Quórum de mayoría Configuración distribuida, leader election, service discovery (legado)
etcd Raft CP · Linearizable · MVCC Config store de Kubernetes, distributed locks, feature flags críticos
Redis (Redlock) Quórum sobre N instancias independientes Eventual · No linearizable bajo fallo asimétrico Locks de baja criticidad, idempotency keys, rate limiting. No usar para casos donde la correctitud es crítica.
Kafka KRaft (Raft) · ISR quórum CP por partición · At-least-once por defecto · Exactly-once con transacciones Log de eventos, streaming, event sourcing. Orden garantizado dentro de partición.
Cassandra Gossip + Dynamo-style quórum AP por defecto · Tunable (ONE/QUORUM/ALL) Escrituras masivas, series de tiempo, disponibilidad > consistencia
Distributed lock con Redisson (Spring Boot) — fencing token implícito JAVA EJEMPLO
// Redlock sobre Redis — útil para secciones críticas de baja duración
// No usar para decisiones de alta integridad (ej: pagos) → usar etcd/ZooKeeper

@Service
public class InventoryService {
    private final RedissonClient redisson;

    public void reserveItem(String itemId) {
        RLock lock = redisson.getLock("inventory:lock:" + itemId);
        boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
        // waitTime=3s, leaseTime=10s (auto-release ante crash)
        if (!acquired) throw new ConcurrentModificationException();
        try {
            updateInventory(itemId); // sección crítica
        } finally {
            if (lock.isHeldByCurrentThread()) lock.unlock();
        }
    }
}
// leaseTime previene deadlock si el proceso muere sin hacer unlock
// isHeldByCurrentThread() previene unlock de lock expirado (race condition)
Preguntas frecuentes en entrevistas de arquitecto
¿Cuándo elegirías CP sobre AP?
Cuando la correctitud es más importante que la disponibilidad: inventario financiero, contadores de licencias, locks de exclusión mutua, configuración crítica. Si mostrar un dato viejo puede causar pérdida de dinero o inconsistencia irreparable → CP. Si mostrar un tweet desactualizado por 100ms es aceptable → AP.
¿Qué pasa si configuras W=1 en Kafka?
acks=1: el broker líder confirma pero no espera replicas. Si el líder muere antes de replicar, el mensaje se pierde. acks=all con min.insync.replicas=2: al menos 2 ISR deben confirmar → mensaje no se pierde mientras haya quórum. El costo es latencia adicional de replicación.
¿Por qué Redlock no es seguro para locks críticos?
Martin Kleppmann (2016) demostró que bajo fallo asimétrico (un nodo lento que responde tarde) dos clientes pueden adquirir el lock simultáneamente. La solución: usar fencing tokens (número monotónico del lock) que el recurso protegido valida. Si el token es inferior al visto, la operación es rechazada.
¿Cuál es la diferencia entre Paxos y Raft?
Ambos logran el mismo objetivo. Paxos es más flexible (multi-master posible) pero extremadamente difícil de implementar correctamente. Raft fue diseñado explícitamente para ser entendible — tiene un líder único, separación clara de elección y replicación. En producción casi todo usa Raft o variantes (Zab, Multi-Paxos).
¿Qué es "split brain" y cómo lo previene etcd?
Split brain: dos nodos creen ser el líder. etcd previene con Raft: un candidato solo gana si obtiene votos de la mayoría absoluta (N/2+1). Es imposible que dos candidatos ganen la mayoría en el mismo term. El term actúa como fencing token — mensajes de términos anteriores son descartados.
¿Qué modelo de consistencia usa DynamoDB?
Por defecto: eventual consistency (R=1 de 3 réplicas). Con ConsistentRead=true: strongly consistent (R=mayoría, doble costo de lectura). Para transacciones multi-item: serializable via 2-phase commit interno. Ninguna es linearizable — DynamoDB prioriza latencia baja.
🗺
El mapa completo — dónde encaja todo esto
CAP + PACELC → marco de decisión al elegir base de datos o broker.
Modelos de consistencia → qué garantías puedes ofrecer en tu API.
Relojes lógicos → cómo ordenar eventos en Kafka, audit logs, CQRS.
Quórum → cómo configurar Kafka (acks, min.insync.replicas), Cassandra (CL), DynamoDB (ConsistentRead).
Raft/consenso → cómo funciona etcd detrás de Kubernetes y por qué ZooKeeper era el cuello de botella de Kafka.
Locks distribuidos → Redisson para baja criticidad, etcd para alta. Siempre con leaseTime y fencing.