CAP, PACELC y los modelos de consistencia
El CAP es el teorema más citado y peor enunciado de la computación distribuida. Este artículo corrige el enunciado y muestra el vocabulario que de verdad usas en el día a día.
"Por el teorema CAP, elegimos AP."
Esa frase aparece en toda discusión de arquitectura, y la mayor parte de las veces quien la dice no sabe lo que está diciendo. No por mala fe, sino porque la versión popularizada del teorema es una simplificación que perdió el sentido por el camino.
El enunciado correcto
La versión popular es "elige dos entre consistencia, disponibilidad y tolerancia a particiones". Sugiere que te sientas en una mesa y eliges.
Pero una partición de red no es una elección. Es algo que te pasa. Cable cortado, switch defectuoso, zona de disponibilidad aislada, regla de firewall equivocada.
El enunciado correcto es:
Cuando existe una partición de red, tienes que elegir entre seguir respondiendo con riesgo de dato divergente, o dejar de responder para preservar la consistencia.
Solo eso. Y fíjate en el "cuando": fuera de la partición, el CAP no dice absolutamente nada.
Sistema CP: durante la partición, el lado minoritario rechaza escrituras. Pierdes disponibilidad y mantienes la corrección. Es el comportamiento de etcd, ZooKeeper, Consul, y de un Postgres con replicación síncrona.
Sistema AP: durante la partición, los dos lados siguen aceptando escrituras, y después alguien reconcilia. Mantienes disponibilidad y aceptas divergencia temporal. Es el comportamiento de Cassandra y DynamoDB en configuraciones permisivas.
Y vale la pena decirlo: la mayoría de los sistemas reales es configurable entre los dos, y la configuración importa más que la etiqueta del producto.
PACELC: la parte que usas todos los días
Daniel Abadi propuso una extensión que debería ser más famosa que el original:
Si hay Partición, elige entre Disponibilidad y Consistencia. Else, elige entre Latencia y Consistencia.
La segunda mitad es la que gobierna tu día a día, porque la partición es rara y el "else" es el resto del tiempo.
- Cada vez que lees de una réplica en vez del primario, cambiaste consistencia por latencia.
- Cada vez que usas replicación asíncrona, ídem.
- Cada vez que pones caché delante, ídem.
Ninguna partición involucrada. Solo decisiones de diseño que casi nunca se registran como decisiones, y que producen los bugs más confusos de depurar.
La escalera de la consistencia
La consistencia no es sí o no. Es una escalera, de la más fuerte a la más débil.
Linealizable. Todo se comporta como si existiera una copia única y las operaciones ocurrieran de una en una, en el orden real del reloj. Es la más intuitiva y la más cara: exige coordinación en cada operación, lo que significa idas y vueltas por la red.
Secuencial. Todo el mundo ve el mismo orden, pero ese orden no tiene que coincidir con el tiempo real.
Causal. Si A causó B, todo el mundo ve A antes que B. Las operaciones sin relación causal pueden aparecer en órdenes distintos para observadores distintos. Es un punto de equilibrio muy bueno, y es lo que los sistemas de mensajería y de feed suelen querer: nunca ves la respuesta antes que la pregunta.
Eventual. Si dejas de escribir, en algún momento todos convergen. No dice cuándo. Es la más barata y la más fácil de vender mal.
- Linealizablemás caraComo si existiera una sola copia, en el orden del reloj. Coordinación en cada operación.
- Secuencialorden únicoTodo el mundo ve el mismo orden, que no tiene que coincidir con el tiempo real.
- Causalbuen equilibrioSi A causó B, todos ven A antes. Nunca la respuesta antes que la pregunta.
- Eventualmás barataSi dejas de escribir, todos convergen. No dice cuándo.
Las garantías de sesión: el atajo práctico
Aquí está el concepto que resuelve la mayor parte de los problemas reales sin pagar el precio de la consistencia fuerte.
En vez de garantizar una propiedad global del sistema, garantizas propiedades para una sesión de usuario:
Lee tu propia escritura. El usuario siempre ve lo que él mismo acaba de grabar.
Lectura monotónica. El usuario nunca ve el reloj andar hacia atrás: si ya vio un dato nuevo, no puede ver el viejo después.
Escritura monotónica. Sus escrituras se aplican en el orden en que las hizo.
Por qué importa: casi todo bug de consistencia que llega a soporte es la violación de una de esas tres. El usuario guarda el perfil, es redirigido, la pantalla lee de la réplica, muestra el dato viejo, y él lo guarda otra vez.
Cómo implementar "lee tu propia escritura"
Tres enfoques, del más simple al más correcto:
- Ventana de primario. Después de una escritura, lee del primario durante unos segundos para ese usuario. Márcalo en la sesión o en una cookie. Resuelve el noventa por ciento de los casos con poquísimo código.
- Posición del log. Guarda en la sesión la posición del log de replicación en el momento de la escritura, y solo acepta leer de una réplica que ya pasó ese punto. Más correcto, más trabajoso, y exige soporte del driver o del proxy.
- Rutas críticas siempre en el primario. Decisión explícita, documentada, para un conjunto pequeño de rutas.
El peor enfoque, que es el más común, es no hacer nada y tratar cada queja como un caso aislado.
Qué preguntar en la próxima reunión
Cambia "¿fuerte o eventual?" por tres preguntas mejores:
¿Qué garantía necesita percibir el usuario? Casi siempre la respuesta es "necesita ver su propio cambio".
¿Cuál es el lag real de nuestra réplica, en el p99, en la última semana? Si nadie lo sabe, ese es el primer trabajo.
¿Qué pasa durante una partición, y ya lo hemos probado? La respuesta honesta en la mayoría de los equipos es "no lo sabemos", y descubrirlo en una prueba controlada es bastante mejor que descubrirlo en un incidente.
Un cierre honesto
La consistencia fuerte no es siempre la respuesta correcta, y la eventual no es siempre suficiente. Lo que separa una buena decisión de una mala no es el nivel elegido, sino haber elegido conscientemente y haber escrito por qué.
Un ADR de media página respondiendo "por qué aceptamos lectura eventual en esta ruta, y cómo mitigamos el efecto en el usuario" vale más que cualquier discusión sobre siglas.
Lee esto después
- IngenieríaPaso 7Los cinco patrones de resiliencia que evitan la caída en cascadaCómo una dependencia secundaria lenta derriba el sistema entero en noventa segundos, y los cinco patrones que lo impiden.Leer artículo
- IngenieríaPaso 6Saga y outbox: transacción sin transacción distribuidaNecesitas debitar en una cuenta y acreditar en otra, y están en bases de datos distintas. Los dos patrones que lo resuelven en la práctica, y qué evitar.Leer artículo
- IngenieríaPaso 4Sharding: una guía de decisiónEl sharding es la decisión más irreversible de un sistema de datos. Esta es la guía para decidir si lo necesitas y, si lo necesitas, cómo elegir la clave.Leer artículo