README · by ansango
← Volver al libro

The trouble with distributed systems: clocks y conocimiento

Los relojes de los sistemas distribuidos no son fiables, los procesos se pausan, y 'el conocimiento' entre nodos es más sutil de lo que parece

~5 min de lectura
Resumen

El libro dedica dos capítulos al “trouble with distributed systems” y este segundo nota cubre los relojes, los process pauses y la noción de “conocimiento” en sistemas distribuidos. La idea central: los relojes son imprecisos, los procesos se pausan, y los nodos nunca pueden estar seguros de lo que otros saben.

Por qué los relojes importan

El libro arranca con una observación incómoda: si tu sistema distribuido usa wall clocks (relojes de pared) para ordenar eventos, estás confiando en que todos los nodos tienen el mismo reloj. Y eso no es cierto.

El problema:

Nodo 1 (Madrid):   t = 10:00:00.500
Nodo 2 (Nueva York): t = 10:00:00.503
Nodo 3 (Tokio):    t = 10:00:00.498

¿Cuál fue primero?

[!danger> Asumir sincronización es un error común NTP (Network Time Protocol) sincroniza relojes con precisión de milisegundos, pero en sistemas de alta carga esa precisión no basta.

Por qué los relojes fallan

El libro enumera las causas de la imprecisión:

1. Quartz clocks

Los relojes de los servidores son osciladores de cuarzo. Su frecuencia depende de la temperatura, vibración, y la edad del cristal.

Drift típico:

  Buen reloj: 1 segundo de drift por día
  Reloj malo: 1 segundo de drift por hora

En una red distribuida, los relojes **divergen** constantemente.

2. NTP

NTP sincroniza relojes sobre la red. Pero:

NTP precision:

- Mejor caso:  ~1ms
- Caso típico: ~10-100ms
- Peor caso:  varios segundos

[!tip> NTP no es infalible El libro es claro: asumir NTP como verdad absoluta es un error. Mucho software robusto (Cassandra, DynamoDB) no depende de NTP para ordenación.

3. Falsos positivos

Los relojes pueden saltar hacia atrás o hacia adelante por:

Salto de reloj (NTP correction):

t=10:00:00.000
  NTP: salta a t=10:00:00.001 para corregir
  No fue instantáneo: 1ms perdida

4. Relojes virtuales

Máquinas virtuales, containers, sandboxes: los relojes están mediados por el hypervisor.

VM clock:

Host: 10:00:00.000
VM: 10:00:00.000 (al iniciar)
... 1 hora después:
Host: 11:00:00.000
VM: 10:00:00.000 (si no hay sincronización)

Synchronous vs asynchronous networks

El libro distingue:

Red sincrónica (teórica)

Red asíncrona (real)

[!tip> Diseña para la red asíncrona El libro es claro: asume que la red es asíncrona. Si los algoritmos funcionan en ese modelo, funcionarán en el real.

Relojes monotónicos

El libro recomienda relojes monotónicos cuando sea posible.

# Monotonic clock
import time

t1 = time.monotonic()
# ... tiempo después ...
t2 = time.monotonic()

# Garantía: t2 >= t1

Características

Wall clock vs monotonic:

                       Go backwards?     Sync between nodes?
Wall clock (time.time):    YES (with NTP)        YES (with NTP)
Monotonic (time.monotonic): NO                    NO

[!tip> Para medir duración, usa monotonic El libro insiste: si mides tiempo transcurrido, monotonic. Si necesitas el wall clock (para mostrar al usuario), solo convierte al final.

Lamport timestamps

Leslie Lamport (1978) introdujo una forma de ordenación de eventos en sistemas distribuidos.

Cada evento tiene un timestamp (counter, nodo).
Reglas:
  - Counter local se incrementa en cada evento.
  - Al enviar, se incluye el counter.
  - Al recibir, counter = max(local, received) + 1.
class LamportClock:
    def __init__(self, node_id):
        self.counter = 0
        self.node_id = node_id
    
    def tick(self):
        self.counter += 1
        return self.counter
    
    def update(self, received_counter):
        self.counter = max(self.counter, received_counter)

Limitaciones

No captura causalidad: si un Lamport timestamp de evento A es menor que el de B, no sabemos si A causó B.

Ejemplo:

Nodo 1: evento A (Lamport: 1)
Nodo 2: recibe de 1 (Lamport: 2)
Nodo 2: evento B (sin A relacionado) (Lamport: 3)

¿A causó B? No sabemos. Lamport solo dice "B está después".

Vector clocks

Para capturar causalidad, los vector clocks.

Vector clock = {nodo1: c1, nodo2: c2, ...}

Reglas:
  - En cada evento local, incrementar counter del propio nodo.
  - Al enviar, incluir el vector clock.
  - Al recibir, hacer element-wise max.
class VectorClock:
    def __init__(self, nodes):
        self.clocks = {n: 0 for n in nodes}
    
    def tick(self, node):
        self.clocks[node] += 1
        return self.clocks
    
    def update(self, other):
        for n in self.clocks:
            self.clocks[n] = max(self.clocks[n], other[n])
        self.clocks[node] += 1  # también en mensaje

Detección de conflictos

Dos eventos con vector clocks no comparables son concurrentes.

Conflicto:

Event A: {1: 1, 2: 0}
Event B: {1: 0, 2: 1}

Comparación:
  A.clocks[1] = 1, B.clocks[1] = 0 → A después
  A.clocks[2] = 0, B.clocks[2] = 1 → B después
  ¡Contradicción! → Eventos concurrentes.

[!tip> Vector clocks son el gold standard Dynamo, Riak, Cassandra usan vector clocks. Pero el libro señala que la mayoría de los sistemas actuales prefieren LWW (timestamp) por simplicidad.

Process pauses

El libro introduce un problema sutil: los procesos se pausan.

Process pause:

1. Proceso 1 escribe x = 1 (commit a disco).
2. Proceso 1 se pausa (GC, scheduler, etc.).
3. Proceso 1 se reanuda 30 segundos después.
4. Proceso 1 piensa que "no ha pasado nada", pero...
   - el reloj ha avanzado 30 segundos.
   - el nodo 2 puede haber escrito x = 2.

Causas de pausas

VM clock jumping

El libro describe un caso famoso: VMware corrects the clock después de reanudar una VM. Esto puede romper los timestamps en la aplicación.

VM scenario:

  t1: VM pausada
  t2: 10 minutos después, VM reanudada
  hypervisor: "corrijo" el reloj para que coincida con el real
  aplicación: "¡¿pero si pasaron 10 minutos?!"

[!note> Los relojes no son lo que parecen El libro insiste: cualquier uso de wall clock para ordenación es frágil. La disciplina es diseñar para que los saltos no importen.

Conocimiento distribuido

El libro introduce un punto filosófico: el conocimiento entre nodos es problemático.

El problema

Si el nodo A cree que el nodo B ha procesado un mensaje, ¿cómo lo sabe?

Escenario:

A ──► mensaje ──► B
A ◄── ¿ack? ──

A: "B tiene mi mensaje"
A: "pero no estoy seguro"

La verdad eventual

El libro usa la frase de Lamport: “happens-before” define la causalidad.

happens-before:

  - En un nodo, los eventos se ordenan por tiempo.
  - Entre nodos, un envío "happens-before" su recepción.
  - La transitividad: si a happens-before b, y b happens-before c, entonces a happens-before c.

Knowledge: tests

El libro describe los tests que un nodo puede hacer para saber si tiene conocimiento:

Test de conocimiento:

Para saber si el nodo B recibió un mensaje:
  - Reenviar mensajes hasta que B envíe ack.
  - Asumir que B tiene el mensaje cuando B ya responde con el siguiente.

Synchronous vs asynchronous

El libro cierra con una distinción importante:

[!tip> Asume asynchronous El libro recomienda diseñar siempre para sistemas asíncronos. Si funciona en ese modelo, funcionará en cualquier red.

El conocimiento distribuido y los problemas que crea

El libro enumera los problemas derivados del conocimiento imperfecto:

1. Fencing tokens

Si un nodo cae y se reanuda, no debe escribir sin haber confirmado que su estado es actual.

Fencing:

Nodo A: lease expira
        (nodo A no sabe que otros le dan trabajo)
Nodo B: emite lease con token=42
Nodo B: emite lease con token=43
Nodo A: reanimado, intenta escribir con token=41 (viejo)
Sistema: rechaza, el token es antiguo

2. Lease y renewal

Los leases son locks con tiempo de expiración. El cliente debe renovarlos antes de que expiren.

Lease:

Cliente: "lease por 30s"
Servidor: tok=42, exp=30s
Cliente: "renovar" (cada 10s)
Si no renueva, lease expira, otro pueda tomarlo.

3. Leader election

Si el leader cae, los seguidores necesitan elegir uno nuevo. Esto requiere consensus.

Cómo gastar tiempo y clicar el conocimiento

El libro cierra con una frase que merece recordar:

“El conocimiento en sistemas distribuidos es como el precio: todos hablan de él, pero pocos lo entienden.”

El conocimiento entre nodos no es como el estado local. Es siempre incompleto, temporal y susceptible de revisión cuando llega un mensaje.

El conocimiento es:

  - Incompleto: nunca sabes "todo" sobre otros nodos.
  - Temporal: tu conocimiento puede caducar.
  - Revisable: cuando un mensaje llega, puede cambiar.

[!tip> Diseña asumiendo conocimiento incompleto Cualquier componente que dependa de un estado “estable” en otro nodo es frágil. Diseña para que la incertidumbre sea tolerada.

Resumen en tres frases

Próximos pasos