Módulo 10Trimestre 3 · Fiabilidad y escala12 puntos del póster

Corrección en sistemas distribuidos

Inicia sesión para guardar tu progreso en todos tus dispositivos. Mientras tanto, se guarda en este navegador.

Por qué importa

Los bugs de concurrencia no aparecen en las demos. Aparecen como un cliente que pagó dos veces, un cupón canjeado tres veces, un asiento vendido a dos personas o un saldo que no cuadra a fin de mes. En fintech, esto no son simples bugs, son incidentes regulatorios.

Resultados de aprendizaje

  1. Reconocer las race conditions y corregirlas con operaciones atómicas, restricciones y locks.
  2. Entender la replicación, el teorema CAP y la consistencia eventual lo suficiente como para diseñar la UX en torno a ellos.
  3. Saber cuándo hacen falta los locks distribuidos y la elección de líder, y cuáles son sus trampas.
  4. Evitar los deadlocks.
  5. Manejar el tiempo correctamente a pesar del desfase de relojes.
  6. Diagnosticar fugas de memoria y pausas del recolector de basura.

Lecciones

10.1 Race conditions (39, 43)

La forma clásica es comprobar y luego actuar (check-then-act): lees un valor, decides y escribes. Entre la lectura y la escritura, otro actúa. Soluciones, por orden de preferencia:

  1. Sentencia atómica: UPDATE accounts SET balance = balance - 100 WHERE id = $1 AND balance >= 100 y comprueba las filas afectadas.
  2. Restricción de base de datos: un índice único hace que la segunda inserción falle.
  3. Optimistic locking (columna de versión) cuando hay poca contención.
  4. Pessimistic lock (SELECT ... FOR UPDATE) cuando hay mucha contención. En el código de la app: el estado mutable compartido en un proceso Node de larga duración se comparte entre peticiones; las variables a nivel de módulo no son por usuario. La thread safety (43) también importa en Go, Java, Python async y en los workers.

10.2 Deadlocks (40)

Dos transacciones tienen cada una un lock que la otra necesita. Se previenen bloqueando siempre los recursos en un orden consistente (por ejemplo, por ID ascendente), manteniendo las transacciones cortas y configurando timeouts de lock. Postgres detecta los deadlocks y aborta una de las transacciones; tu código debe reintentar.

10.3 Replicación, CAP y consistencia eventual (32, 34, 35, 95)

  • Replicación (32): copias en varios nodos para durabilidad y escalado de lecturas. Las réplicas asíncronas van con retraso.
  • Las particiones de red (95) ocurren. Durante una, CAP dice que el sistema debe elegir: rechazar algunas peticiones (consistencia) o responder con datos posiblemente desactualizados (disponibilidad).
  • Consistencia eventual (35): las réplicas convergen con el tiempo. Implicaciones de diseño: read-your-own-writes (envía las lecturas de un usuario a la primaria justo después de que escriba), muestra estados de "procesando" y nunca tomes decisiones sobre dinero con una réplica retrasada.
  • Libros contables y saldos: consistencia fuerte, una única fuente de verdad y registros de partida doble de solo inserción (append-only).

10.4 Locks distribuidos y elección de líder (33, 38)

  • Hacen falta cuando varias instancias no deben hacer lo mismo a la vez (un solo ejecutor de cron, un solo consumidor por partición).
  • Prioriza las opciones nativas de la base de datos: advisory locks de Postgres, colas de jobs con SKIP LOCKED.
  • Los locks en Redis necesitan expiración (TTL) y fencing tokens: un proceso pausado puede despertar después de que su lock haya expirado y seguir escribiendo. La capa de almacenamiento debe rechazar los tokens obsoletos.
  • La elección de líder (Raft, etcd, ZooKeeper, leases de Kubernetes) es sobre todo algo que consumes de la infraestructura, no algo que construyes.

10.5 El tiempo y el desfase de relojes (96)

  • Los relojes de las máquinas derivan; nunca uses la hora de reloj de distintos servidores para ordenar eventos.
  • Usa timestamps generados por la base de datos o números de secuencia para ordenar; relojes monótonos para medir duraciones.
  • La expiración de tokens y la comprobación de timestamps en webhooks necesitan una pequeña tolerancia al desfase.

10.6 Fugas de memoria y recolección de basura (41, 42)

  • En los lenguajes gestionados, las fugas suelen ser referencias que conservas: cachés que crecen sin límite, event listeners que nunca se eliminan, arrays globales, streams sin cerrar.
  • Síntomas: la memoria sube de forma constante y luego hay reinicios por OOM; picos de latencia por pausas largas del GC.
  • Herramientas: heap snapshots (Chrome DevTools para Node), --inspect, métricas de memoria por instancia, cachés LRU acotadas.

Lab 10: El reto del doble gasto

  1. Construye un monedero: recarga, transferencia, retirada. Escribe un test que lance 200 transferencias concurrentes. Observa cómo la versión ingenua generada por la IA pierde o crea dinero.
  2. Corrígelo con actualizaciones atómicas y restricciones; impleméntalo como un libro contable de partida doble en el que la suma de todos los asientos sea siempre cero.
  3. Provoca un deadlock con dos transferencias en orden opuesto; corrígelo ordenando los locks.
  4. Lee de una réplica retrasada (con retraso simulado) y muestra el bug de read-your-writes; corrígelo.
  5. Encuentra una fuga de memoria plantada usando heap snapshots.

Definition of Done

Paquete de prompts de IA

  • "Encuentra todas las secuencias de check-then-act en este código. Para cada una, muestra la race condition y la corrección atómica."
  • "Reescribe esta lógica de saldos como un libro contable de partida doble con un test de invariante."