Módulo 7: Fiabilidad y el tradeoff de consistencia

5. PACELC: más allá de CAP

Descripción

Al terminar esta lección vas a entender PACELC, la extensión de CAP que Daniel Abadi propuso para tapar el hueco más grande del teorema: CAP solo habla del caso raro —la partición—, y calla sobre el caso común —cuando la red está sana—. PACELC se lee como una frase con dos ramas: if Partition, elige entre Availability y Consistency; Else (sin partición), elige entre Latency y Consistency. Vas a entender por qué existe ese segundo tradeoff (latencia contra consistencia) que CAP ignora: la consistencia fuerte cuesta latencia siempre, haya partición o no, porque mantener las copias linealizables exige coordinación —replicación síncrona, quórum, esperar confirmaciones— y esa coordinación toma tiempo. Vas a conocer las cuatro clases que resultan (PA/EL, PC/EC, PA/EC, PC/EL) con ejemplos de sistemas reales, y vas a clasificar a Enlace como PA/EL: disponible bajo partición (lo decidiste en la lección 4) y de baja latencia el resto del tiempo —justo el perfil que necesita un redirect servido 4,000 veces por segundo—.

Esto importa porque CAP, por sí solo, deja una impresión falsa: que fuera de las particiones "tienes C y A gratis" y no hay nada más que decidir. Pero los sistemas pasan el 99.99% del tiempo sin partición, y en todo ese tiempo hay un tradeoff que sí opera y que define el comportamiento cotidiano del sistema: ¿pago el costo de coordinación para que toda lectura vea la última escritura (consistencia fuerte, más latencia), o dejo que las réplicas respondan sin coordinarse (consistencia eventual, menos latencia)? PACELC pone nombre a esa decisión y la vuelve parte del diseño. Entenderla te da una herramienta de clasificación que usan las bases de datos distribuidas reales para describirse a sí mismas, y te permite tomar la decisión de Enlace de forma completa —no solo "qué hago bajo partición" (CAP) sino "qué hago el resto del tiempo" (el Else de PACELC)—, que es donde de verdad se juega su latencia.

Conexión con el módulo: esta lección completa el marco que la 4 dejó a medias. CAP te dio la rama "if Partition" (Enlace es AP); PACELC agrega la rama "Else" (Enlace es EL), y juntas dan la clasificación completa PA/EL. Es también el puente directo a la lección 6: el "EL" de PACELC —preferir baja latencia a consistencia fuerte cuando la red está sana— es exactamente lo que, bajado a la práctica, se llama consistencia eventual, que la lección 6 formaliza y mide con los números de Enlace. La frontera se mantiene: aquí clasificamos el tradeoff; cómo se implementa la coordinación que da consistencia fuerte (protocolos de quórum, consenso, two-phase commit) es la guía de resiliencia y la de sistemas distribuidos. Nosotros decidimos en qué lado del tradeoff vive Enlace, no construimos el protocolo.

Confirmar cada venta por teléfono cuesta tiempo, aunque el teléfono funcione

Piénsalo así, retomando las dos sucursales de la lección 4. Allá la línea telefónica se caía (la partición) y las sucursales tenían que elegir entre atender o coordinarse. Pero ahora quiero que mires el caso en que la línea funciona perfectamente, porque ahí también hay una decisión, y CAP la ignora por completo.

Imagina que la tienda adopta una regla de máxima coordinación: "antes de vender la última unidad de cualquier producto, la sucursal debe llamar a la otra para confirmar que sigue disponible". Con la línea sana, esta regla funciona —las dos sucursales están siempre perfectamente coordinadas, nunca venden de más—. Pero mira el costo: cada venta ahora incluye una llamada telefónica. El cliente espera mientras el empleado marca, saluda, pregunta "¿queda?", escucha la respuesta y cuelga. La coordinación no es gratis; se paga en tiempo, en cada operación, incluso cuando todo funciona.

Ahora imagina la regla contraria: "vende con la información que tengas en tu propio mostrador; no llames a nadie". Las ventas son instantáneas —el cliente no espera ninguna llamada—, pero de vez en cuando las dos sucursales quedan levemente descoordinadas (venden la misma última unidad casi a la vez) y se reconcilian después. Aquí sacrificaste coordinación (consistencia) a cambio de rapidez (latencia), sin que haya ninguna partición —la línea está perfecta, simplemente elegiste no usarla en cada venta—.

Esa es la decisión que PACELC pone sobre la mesa y que CAP no ve: incluso cuando la red funciona (Else), hay un tradeoff entre consistencia y latencia, porque la consistencia fuerte requiere la llamada de coordinación, y la llamada cuesta tiempo. CAP solo mira el caso en que la línea se cae; PACELC mira los dos casos —la línea caída (P) y la línea sana (E)— y en cada uno hay un tradeoff distinto.

El enunciado de PACELC

PACELC (se pronuncia "pas-elk") se lee como una frase condicional con dos ramas. Daniel Abadi lo formuló en 2012 así:

If there is a Partition, how does the system trade off Availability and Consistency? Else (when running normally, no partition), how does the system trade off Latency and Consistency?

En español: si hay una partición (P), ¿cómo negocia el sistema entre disponibilidad (A) y consistencia (C)? Si no (Else), operando normalmente, ¿cómo negocia entre latencia (L) y consistencia (C)?

Las dos ramas y sus opciones:

                    ¿hay particion?
                          │
          ┌───────────────┴───────────────┐
      P (particion)                    E (else: red sana)
          │                                │
   elige A o C                       elige L o C
          │                                │
    ┌─────┴─────┐                    ┌─────┴─────┐
   PA          PC                   EL          EC
 disponible  consistente        baja latencia  consistente
 bajo        bajo               (no coordina    (coordina en
 particion   particion          en cada op)     cada op, mas lento)

Fíjate en que la C aparece en las dos ramas, y no es casualidad: la consistencia fuerte es la que cuesta en ambos casos. Bajo partición, cuesta disponibilidad (para ser consistente, te niegas a responder → CP). Sin partición, cuesta latencia (para ser consistente, coordinas en cada operación → EC). La consistencia fuerte siempre se paga; PACELC solo aclara con qué se paga en cada situación. La rama "if P" de PACELC es exactamente el teorema CAP; la contribución de Abadi es la rama "Else", que CAP nunca tuvo.

Por qué la consistencia fuerte cuesta latencia (aunque no haya partición)

Esta es la idea central de la lección, y conviene entenderla bien porque no es obvia. ¿Por qué la consistencia fuerte cuesta latencia incluso con la red sana?

Recuerda de la lección 4 que consistencia fuerte = linearizabilidad = toda lectura ve la escritura más reciente, como si hubiera una sola copia. Ahora piensa en cómo se logra eso con varias copias (réplicas). Cuando escribes un valor, para que cualquier réplica que se lea a continuación devuelva el valor nuevo (nunca el viejo), la escritura tiene que haberse propagado y confirmado en las réplicas antes de darse por exitosa. Eso es replicación síncrona: la escritura espera a que suficientes réplicas confirmen "ya lo tengo" antes de responderle "listo" al cliente. Y esperar esas confirmaciones —que viajan por la red, aunque la red esté sana— toma tiempo: es latencia extra en cada escritura, y a veces en cada lectura (si lees de un quórum para garantizar el dato más nuevo).

La alternativa es replicación asíncrona: la escritura se confirma en cuanto la acepta una copia (el primary), y se propaga a las réplicas después, en segundo plano. La escritura es rápida (no espera a nadie), pero durante un ratito las réplicas van atrasadas —el replication lag del módulo 5—, así que una lectura desde una réplica puede devolver el valor viejo. Eso es consistencia eventual, y su premio es la baja latencia: nadie espera coordinación.

  CONSISTENCIA FUERTE (EC)            CONSISTENCIA EVENTUAL (EL)
  escritura ──► primary               escritura ──► primary ──► "listo" (rapido)
                  │  espera                            │
                  ▼  confirmaciones                    ▼  propaga despues
             replica 1  ✓                         replica 1  (llega luego)
             replica 2  ✓                         replica 2  (llega luego)
                  │                                   
             "listo" (lento: espero a todas)      lecturas pueden ver dato viejo
                                                  por un ratito (replication lag)

Aquí está el nudo: la coordinación que da consistencia fuerte es intrínsecamente más lenta, y esa lentitud existe siempre que hay réplicas, con o sin partición. Por eso PACELC necesita la rama "Else": el tradeoff latencia-vs-consistencia no aparece solo cuando la red falla; aparece cada vez que un sistema replicado decide si esperar la coordinación (consistente, lento) o no esperarla (eventual, rápido). CAP, al mirar solo las particiones, no podía ni nombrar este tradeoff.

Las cuatro clases, con ejemplos reales

Combinando las dos ramas salen cuatro clases. Cada sistema distribuido cae en una, y describirse con PACELC es hoy una forma común de que las bases de datos declaren su carácter.

ClaseBajo particiónSin partición (Else)Ejemplos y perfil
PA/ELdisponible (AP)baja latencia (EL)Cassandra, Dynamo (el diseño original de Amazon), Riak. "Prioriza responder siempre y rápido; acepta consistencia eventual." El perfil de alta disponibilidad y baja latencia. (Ojo: el producto gestionado DynamoDB suele clasificarse PA/EC, no como el Dynamo original.)
PC/ECconsistente (CP)consistente (EC)RDBMS clásico con replicación síncrona, VoltDB, HBase. "Prioriza la exactitud siempre, aunque cueste disponibilidad bajo partición y latencia el resto del tiempo."
PA/ECdisponible (AP)consistente (EC)MongoDB (en su configuración por defecto: bajo partición sigue disponible, pero con red sana garantiza lecturas/escrituras consistentes) y algunas bases de datos ajustables. Menos común que las dos puras.
PC/ELconsistente (CP)baja latencia (EL)Bajo partición se detienen para ser exactos, pero con red sana priorizan latencia. PNUTS de Yahoo es el ejemplo canónico.

Las dos clases "puras" —PA/EL y PC/EC— son las más comunes y las más fáciles de razonar, porque toman la misma actitud en las dos ramas: PA/EL siempre prefiere responder (disponible bajo partición, rápido el resto del tiempo), y PC/EC siempre prefiere ser exacto (consistente bajo partición, coordinado el resto del tiempo). Las dos mixtas (PA/EC, PC/EL) existen y son válidas, pero son casos de sistemas que cambian de prioridad según haya o no partición. Muchas bases de datos modernas son ajustables: te dejan elegir la clase por operación o por configuración, en vez de fijarla para todo el sistema.

Ejemplo trabajado: Enlace es PA/EL

Clasifiquemos a Enlace con las dos ramas, apoyándonos en lo que ya decidimos y en sus números.

Rama "if Partition" → PA (ya lo decidimos en la lección 4). Bajo una partición, resolve debe seguir respondiendo con la copia que tenga, aunque pueda estar unos segundos vieja, porque negarse a redirigir es peor que una URL levemente desactualizada. Enlace elige A sobre C bajo partición: es PA.

Rama "Else" → EL (la decisión nueva de esta lección). Con la red sana —el 99.99% del tiempo— Enlace sirve ~4,000 lecturas por segundo, y cada una es un usuario esperando ser redirigido. La pregunta del Else es: ¿pago coordinación en cada resolve para garantizar que devuelvo la URL más reciente (EC, más lento), o dejo que la réplica más cercana responda sin coordinarse (EL, más rápido)? La respuesta cae por su propio peso al mirar los números:

# El costo de coordinar (EC) contra no coordinar (EL) en el camino de lectura de Enlace
qps_read = 4000                 # ~4,000 lecturas/s (numeros-ancla)

lat_local_ms  = 1               # leer de la replica mas cercana, sin coordinar (EL)
lat_quorum_ms = 15              # leer de un quorum para garantizar el dato mas nuevo (EC)

extra_ms = lat_quorum_ms - lat_local_ms
print(f"latencia por lectura:  EL = {lat_local_ms} ms   EC = {lat_quorum_ms} ms")
print(f"coordinar (EC) agrega {extra_ms} ms a CADA una de {qps_read:,} lecturas/s")
print(f"a cambio de garantizar algo que a un redirect casi nunca le importa")

Qué esperar. Al correrlo:

latencia por lectura:  EL = 1 ms   EC = 15 ms
coordinar (EC) agrega 14 ms a CADA una de 4,000 lecturas/s
a cambio de garantizar algo que a un redirect casi nunca le importa

Enlace elige EL: no paga coordinación en cada resolve, deja que la réplica más cercana responda en ~1 ms, y acepta que muy de vez en cuando el dato vaya unos segundos atrás. Pagar 15 ms por lectura —multiplicar por 15 la latencia de la operación más frecuente del sistema— para garantizar una exactitud que un redirect casi nunca necesita sería un pésimo negocio. Es EL sobre EC.

Juntando las dos ramas: Enlace es PA/EL. Disponible bajo partición, rápido el resto del tiempo, consistencia eventual en las dos situaciones. Es la misma clase que Cassandra y DynamoDB, y no por casualidad: Enlace tiene el perfil arquetípico para el que esos sistemas fueron diseñados —lectura pesada, datos tolerantes al desfase, prioridad en responder siempre y rápido—. Esa clasificación no es una etiqueta académica: es una decisión de diseño que dice "no construyas coordinación síncrona en el camino de resolve; sirve de la réplica más cercana". La lección 6 la formaliza como consistencia eventual y mide su ventana de desfase.

Errores comunes

Creer que sin partición "tienes C y A gratis" y no hay nada que decidir (de alcance). Qué pasa: alguien que solo conoce CAP diseña como si el único tradeoff de consistencia ocurriera durante las particiones, e ignora la latencia de coordinación que paga el resto del tiempo. Por qué pasa: CAP, literalmente, no menciona el caso sin partición. Cómo detectarlo: si tu sistema replicado tiene consistencia fuerte y no te preguntaste qué latencia extra cuesta con la red sana, te saltaste el "Else". Cómo corregirlo: usa PACELC —siempre pregunta las dos ramas—; el Else (latencia contra consistencia) suele importar más en la práctica que el if-P, porque ocurre todo el tiempo.

Confundir el tradeoff del Else con el de la partición (de concepto). Qué pasa: alguien dice "elegimos disponibilidad sobre consistencia" para justificar una lectura rápida sin coordinación, cuando lo que realmente eligió fue latencia sobre consistencia (el Else), no disponibilidad. Por qué pasa: las dos ramas comparten la C y se mezclan. Cómo detectarlo: si no hay partición y estás hablando de "disponibilidad", probablemente quieres decir "latencia". Cómo corregirlo: recuerda que bajo partición el tradeoff es A-vs-C, y sin partición es L-vs-C; nómbralos distinto para no confundirte al clasificar.

Tratar la clase como una etiqueta global inamovible (de precisión). Qué pasa: alguien dice "mi base de datos es PA/EL" y asume que toda operación se comporta así, cuando muchos sistemas modernos son ajustables por operación (una escritura con quórum fuerte, una lectura eventual, en el mismo sistema). Por qué pasa: las cuatro clases suenan a categorías fijas. Cómo detectarlo: si tu base de datos te deja elegir el nivel de consistencia por consulta (por ejemplo, LOCAL_QUORUM contra ONE en Cassandra) y tú la clasificas con una sola etiqueta, estás simplificando de más. Cómo corregirlo: clasifica por operación o por camino cuando el sistema lo permita —Enlace puede ser EL en resolve (lectura) y usar una garantía más fuerte en la generación del short_code (escritura), como veremos en la lección 6—.

Ejercicios

Ejercicio 1 — Lee la frase PACELC. Para cada sistema, completa las dos ramas ("if P → ?, Else → ?") y da la clase resultante. (a) Un sistema que bajo partición deja de aceptar escrituras para no divergir, y con red sana espera confirmación de un quórum en cada escritura. (b) Un sistema que bajo partición sigue respondiendo con datos locales, y con red sana lee de la réplica más cercana sin coordinar. (c) El resolve de Enlace.

Ver solución
  • (a) if P → se detiene para ser exacto = PC; Else → coordina en cada escritura = EC. Clase: PC/EC (el RDBMS clásico síncrono: exactitud siempre, a costa de disponibilidad bajo partición y latencia el resto del tiempo).
  • (b) if P → responde con lo local = PA; Else → lee sin coordinar = EL. Clase: PA/EL (Cassandra/DynamoDB: responder siempre y rápido, consistencia eventual).
  • (c) resolve de Enlace: PA/EL, igual que (b). Disponible bajo partición (lección 4), baja latencia el resto del tiempo (este ejemplo trabajado).

Ejercicio 2 — El costo de EC. Enlace recibe ~4,000 lecturas/s. Si eligiera consistencia fuerte en el camino de lectura (EC), cada resolve leería de un quórum a 15 ms en vez de la réplica local a 1 ms. (a) ¿Cuánta latencia extra por lectura? (b) Si un usuario típico hace 5 clics en enlaces cortos en una sesión, ¿cuánto tiempo extra de espera acumula por elegir EC en vez de EL? (c) Explica en una frase por qué, para un redirect, ese costo no compra casi nada.

Ver solución
  • (a) 15 - 1 = 14 ms extra por lectura. Multiplica por 15 la latencia de la operación más frecuente de Enlace.
  • (b) 5 clics × 14 ms = 70 ms extra de espera acumulada por sesión, solo por coordinar cada redirect.
  • (c) Porque un short_code casi siempre apunta a la misma URL toda su vida, así que "la última versión" y "la versión de hace unos segundos" son idénticas el 99.99% del tiempo: EC paga 14 ms por lectura para garantizar una diferencia que casi nunca existe. El costo es real y el beneficio, para un redirect, es prácticamente nulo. Por eso Enlace es EL.

Ejercicio 3 — Clasifica y justifica. Para cada sistema, da su clase PACELC y justifica las dos ramas con una frase cada una. (a) El sistema de saldos de un banco. (b) El feed de publicaciones de una red social. (c) La generación del short_code de Enlace (la escritura, no la lectura).

Ver solución
  • (a) Banco (saldos): PC/EC. if P → se detiene antes que permitir un sobregiro por descoordinación (consistencia sobre disponibilidad). Else → coordina en cada operación (confirma el saldo con exactitud) aunque cueste latencia, porque el dinero no tolera errores. La exactitud manda en las dos ramas.
  • (b) Feed de red social: PA/EL. if P → sigue mostrando el feed con lo que tenga (disponible). Else → lo arma de las réplicas más cercanas sin coordinar (rápido), y no importa que a alguien le falte ver una publicación por unos segundos. Responder siempre y rápido manda.
  • (c) Generación del short_code: tiende a PC/EC en el punto crítico. if P → dos nodos en lados de una partición no deben poder asignar el mismo código a URLs distintas (una colisión corrompe), así que la unicidad exige consistencia. Else → generar un código único puede requerir coordinación (o, mejor, evitarla repartiendo rangos disjuntos por adelantado, como el id_generator del módulo 3). El matiz clave: Enlace es PA/EL en la lectura (resolve) pero necesita una garantía más fuerte en la unicidad de la escritura —por eso se clasifica por operación, no con una sola etiqueta—.

Resumen y siguiente paso

En esta lección completaste el marco que CAP dejó a medias. Con las dos sucursales cuya línea funciona viste el tradeoff que CAP ignora: incluso sin partición, la consistencia fuerte cuesta —porque coordinar (confirmar cada venta por teléfono) toma tiempo—. PACELC lo nombra con dos ramas: if Partition, elige A o C; Else, elige Latencia o Consistencia. Entendiste por qué la consistencia fuerte cuesta latencia siempre que hay réplicas (la replicación síncrona espera confirmaciones; la asíncrona no espera pero deja lag), conociste las cuatro clases (PA/EL, PC/EC, PA/EC, PC/EL) con ejemplos reales, y clasificaste a Enlace como PA/EL: disponible bajo partición y de baja latencia el resto del tiempo, con el número que lo justifica —coordinar costaría 14 ms extra en cada una de 4,000 lecturas/s para garantizar algo que un redirect casi nunca necesita—.

Antes de avanzar deberías poder: leer la frase PACELC y clasificar un sistema en sus dos ramas; explicar por qué la consistencia fuerte cuesta latencia aunque no haya partición; nombrar las cuatro clases con un ejemplo cada una; y justificar por qué Enlace es PA/EL con sus números.

Lo que sigue es bajar todo esto a tierra. Has decidido que Enlace es AP (lección 4) y EL (esta lección), lo que en la práctica se llama con un solo nombre: consistencia eventual. En la lección 6 vas a formalizar la diferencia entre consistencia fuerte y eventual, vas a ver el espectro intermedio (read-your-writes, monotonic reads) que conecta con el replication lag del módulo 5, y vas a medir la decisión de Enlace con los números: cuántos short_code están "en vuelo" en cualquier instante, qué probabilidad hay de que una lectura pegue a uno recién creado, y por qué el único caso incómodo —el creador que lee su propio enlace antes de que propague— se resuelve leyendo del primary. Es donde el tradeoff conceptual se vuelve una decisión con evidencia.

Recursos