Skip to main content

Command Palette

Search for a command to run...

El teorema CAP

Por qué no puedes tenerlo todo en un sistema distribuido

Updated
View as Markdown
El teorema CAP
C
Desarrollador apasionado por convertir bugs en funcionalidades (y café en productividad) Amante del código limpio, aunque mi historial de commits podría decir otra cosa. Siempre aprendiendo, siempre depurando, y ocasionalmente negociando con mi teclado para que coopere.

Si trabajás con bases de datos distribuidas, tarde o temprano te vas a topar con las siglas CAP. Se repiten en charlas, documentación de MongoDB, Cassandra, DynamoDB... pero muchas veces se usan mal, como si fueran una ley universal que explica cualquier limitación de un sistema. Vamos a desarmar el concepto con calma, con ejemplos concretos y sin quedarnos en la versión de manual.

Por qué el teorema apareció justo cuando apareció

La conjetura la planteó Eric Brewer en una charla del año 2000, y dos investigadores del MIT la formalizaron matemáticamente en 2002 para el caso de un registro replicado de lectura/escritura. Hasta ahí, es historia conocida.

Lo interesante es por qué ese teorema encontró tanto eco unos años después. Mi lectura del contexto (una interpretación, no un dato que pueda citar con una fuente puntual) es la siguiente: a mediados de la década de 2000, empresas como Amazon y Google empezaban a operar a una escala que ninguna base de datos relacional tradicional podía sostener cómodamente: miles de servidores, centros de datos en distintos continentes, picos de tráfico impredecibles. Los motores relacionales clásicos estaban pensados para un servidor (o un puñado de ellos con replicación estrecha), no para clústeres masivos y geográficamente dispersos.

Ahí nace el movimiento NoSQL, y necesitaba una justificación técnica sólida para explicar por qué renunciaba a garantías que el mundo relacional daba por sentadas. El teorema CAP, con su aura de resultado matemático incuestionable, encajó perfecto para eso: le dio a toda una generación de bases de datos un argumento de autoridad para decir "no es que no podamos ofrecer consistencia fuerte, es que las matemáticas lo prohíben si querés escalar". Como vamos a ver más adelante, esa lectura mezcla dos cosas que el teorema en realidad no mezcla.

Las tres letras, con ejemplos de la vida cotidiana

Consistencia (C): todos los nodos ven los mismos datos al mismo tiempo. Pensalo como una planilla compartida por varias personas: si vos actualizás una celda, esperás que la persona que la mira un segundo después vea el valor nuevo, no uno viejo.

Disponibilidad (A): todo nodo que recibe una solicitud responde, pase lo que pase con el resto del sistema. Es como un cajero de banco que atiende igual aunque en ese momento no pueda comunicarse con la casa central: te da una respuesta ya, aunque sea con información parcial.

Tolerancia a particiones (P): el sistema sigue funcionando aunque se corte la comunicación entre nodos. Es el equivalente a que dos sucursales de una empresa sigan operando aunque se corte internet entre ellas: cada una sigue atendiendo clientes, aunque no puedan sincronizar información en tiempo real.

Por qué la partición no es una opción, es un hecho

Acá está el punto que mucha gente pasa por alto: el teorema CAP no dice "elegí dos de tres, las que más te gusten". Dice algo más específico y más incómodo: en cualquier sistema distribuido real, las particiones de red van a ocurrir, y no hay forma de evitarlo por diseño.

Pensalo así: un cable de fibra óptica que cruza el océano se puede cortar por un ancla de barco (pasó, y más de una vez). Un switch de datacenter se puede quemar. Un proveedor de nube puede tener una falla regional que aísla una zona de disponibilidad del resto. Ninguna de estas cosas depende de qué tan bien programado esté tu sistema — son fallas de infraestructura física, y cuando pasan, dos nodos que antes se hablaban dejan de poder hacerlo.

Ahí es donde aparece la decisión real. Cuando el switch se quema o el cable se corta, cada nodo aislado tiene que decidir, sin poder consultar al resto: ¿respondo con lo que tengo, aunque pueda estar desactualizado? ¿O me quedo callado hasta que la comunicación se restablezca? La primera opción sacrifica consistencia. La segunda sacrifica disponibilidad. No hay una tercera puerta.

Un caso de uso contado como historia

Imaginate una plataforma de reservas de vuelos con servidores en dos regiones: uno en Buenos Aires y otro en Madrid, para dar buena latencia a usuarios de ambos lados del Atlántico. Un día se corta el enlace submarino entre ambas regiones durante un rato (los minutos exactos no importan para el ejemplo, es solo para ilustrar la mecánica).

Quedan dos asientos disponibles en un vuelo. Un usuario en Argentina y otro en España intentan reservar el último asiento casi al mismo tiempo, justo durante el corte.

  • Si el sistema prioriza consistencia (CP), el servidor de Madrid, al no poder confirmar con Buenos Aires cuántos asientos quedan realmente disponibles, prefiere rechazar la operación o bloquearla hasta que la comunicación vuelva. Resultado: un usuario se queda sin poder reservar durante el corte, aunque el asiento sí exista. Molesto, pero seguro: nunca se vende un asiento dos veces.

  • Si el sistema prioriza disponibilidad (AP), ambos servidores aceptan la reserva de manera independiente, cada uno con la información que tiene localmente. Cuando el enlace se restablece, el sistema descubre que vendió el mismo asiento dos veces y tiene que reconciliar: cancelar una de las dos reservas, ofrecer una compensación, reasignar a otro vuelo. Nadie se quedó sin poder comprar durante el corte, pero ahora hay un problema de negocio que resolver después.

Ninguna de las dos decisiones es "la correcta" en abstracto. Una aerolínea podría preferir la primera opción (nunca vender de más, aunque a veces rechace una venta válida); una plataforma de streaming, en cambio, casi seguro preferiría la segunda para un caso como "cuántas veces se reprodujo un video", donde un conteo levemente desactualizado no le importa a nadie.

Ejemplos de bases de datos reales (con la letra chica)

Es común ver tablas que clasifican bases de datos como "CP" o "AP" de forma tajante, pero conviene tomarlas con pinzas: la mayoría de los sistemas modernos permiten ajustar el comportamiento con parámetros de configuración, y la clasificación describe el comportamiento por defecto, no una propiedad fija e inmutable.

  • MongoDB: por defecto tiende hacia CP, gracias a su arquitectura de réplica con un nodo primario que concentra las escrituras. Pero esto se puede ajustar con los llamados write concern y read preference: si configurás confirmaciones de escritura más laxas o permitís leer de nodos secundarios, el sistema se corre hacia el lado de la disponibilidad, a costa de consistencia. Bajo el marco más completo de PACELC (que menciono abajo), suele describirse como PC+EC.

  • Cassandra y DynamoDB (en su modo por defecto): nacen del diseño del paper Dynamo de Amazon, orientado a disponibilidad. Cualquier réplica puede aceptar lecturas y escrituras, incluso durante una partición, y los conflictos se resuelven después con mecanismos como vector clocks o "gana la escritura más reciente". Pero ambos sistemas también permiten subir el nivel de consistencia exigido por operación, sacrificando disponibilidad y aumentando la latencia.

En resumen: pensá estas clasificaciones como el comportamiento de fábrica de cada motor, no como un destino inevitable. La configuración real de tu clúster es la que termina definiendo si estás más cerca de C o de A en el día a día.

El mito de "CAP contra la escalabilidad"

Durante años se repitió que el teorema CAP demostraba que era imposible tener, a la vez, consistencia transaccional fuerte (tipo ACID) y un sistema que escale horizontalmente. Pero mirado de cerca, el teorema habla estrictamente de tres cosas: consistencia, disponibilidad y tolerancia a particiones. La escalabilidad no aparece en ningún lado de la formulación original.

¿Por qué entonces la gente asoció una cosa con la otra? Porque, en la práctica, lograr consistencia fuerte en un sistema que escala horizontalmente sí es difícil — pero por razones de ingeniería concretas, no por un teorema. Cuando agregás más nodos para escalar, cada escritura que exige consistencia fuerte necesita coordinación entre más participantes: más mensajes de confirmación, más rondas de consenso (por ejemplo, protocolos tipo Paxos o Raft), más puntos donde algo puede demorar o fallar. La coordinación tiene un costo que crece con el número de nodos involucrados, y ese costo compite directamente contra la latencia que buscás reducir al escalar. Ese es un problema real de ingeniería de sistemas — de diseño de protocolos de consenso, de topología de red, de cuántos nodos necesitás que confirmen una escritura — no una imposibilidad matemática dictada por CAP. De hecho, hoy existen bases de datos distribuidas (algunas del mundo NewSQL) que logran consistencia fuerte con escalabilidad horizontal razonable, a costa de una ingeniería de consenso más sofisticada, no gracias a haber "esquivado" el teorema.

PACELC: la parte de la historia que CAP no cuenta

Una crítica frecuente al teorema CAP es que solo describe qué pasa durante una partición, pero las particiones son, con suerte, un evento poco frecuente. La mayor parte del tiempo, tu sistema está funcionando con la red intacta — y ahí CAP no tiene nada que decir.

Para llenar ese vacío, en 2010 el investigador Daniel Abadi propuso una extensión llamada PACELC: si hay una Partición, hay que elegir entre Availability y Consistency (esto es CAP tal cual); pero Else (en operación normal, sin particiones), hay que elegir entre Latency y Consistency. Es decir, incluso sin ninguna falla de red, pedirle a un sistema distribuido consistencia fuerte tiene un costo: más tiempo de espera por cada operación, porque hay que coordinar entre nodos antes de confirmar una escritura o antes de servir una lectura.

Bajo este marco, MongoDB se describe como PC+EC (prioriza consistencia tanto en partición como en operación normal, cuando está en su configuración más estricta), mientras que Cassandra en su modo por defecto se acerca más a PA+EL (prioriza disponibilidad en partición y baja latencia en operación normal). PACELC es, en cierto sentido, una versión más honesta del trade-off: te obliga a pensar no solo en el caso extremo de una partición, sino en el costo que pagás todos los días por la consistencia que elegiste.

CP vs. AP, de un vistazo

CP (prioriza consistencia) AP (prioriza disponibilidad)
Durante una partición Los nodos aislados dejan de responder o bloquean operaciones Todos los nodos siguen respondiendo con lo que tengan disponible
Riesgo principal Indisponibilidad parcial Datos desactualizados o conflictos a reconciliar
Ejemplos típicos* HBase, MongoDB (config. por defecto) Cassandra, DynamoDB (modo por defecto)
Buen encaje para Saldos bancarios, inventario crítico, reservas únicas Contadores de vistas, carritos de compra, redes sociales
Costo aceptado El usuario espera o recibe un error temporal El negocio asume reconciliación posterior

*Estas clasificaciones describen el comportamiento de fábrica de cada motor, no una propiedad fija: como vimos arriba, MongoDB puede correrse hacia AP y Cassandra o DynamoDB pueden correrse hacia CP según cómo configures write concern, read preference o el nivel de consistencia por operación.

Preguntas para hacerte antes de elegir

A la hora de elegir (o configurar) una base de datos distribuida, en vez de preguntarte "¿qué letra de CAP sacrifico?", conviene hacerte preguntas más concretas sobre tu propio sistema:

  1. ¿Qué tan grave es una lectura desactualizada? No es lo mismo mostrar un "me gusta" con un segundo de retraso que mostrarle a un usuario un saldo bancario que ya no existe.

  2. ¿Qué tan grave es que el sistema no responda por unos segundos? Un sistema de checkout de e-commerce que se cae dos minutos puede significar ventas perdidas; un sistema de logs internos que se demora un rato no le importa a nadie afuera de tu equipo.

  3. ¿Con qué frecuencia esperás particiones reales? Un sistema que corre en un único datacenter con redundancia interna sufre particiones muy distintas (en frecuencia y en duración) que uno distribuido entre continentes.

  4. ¿Quién paga el costo de la reconciliación? Si elegís disponibilidad, alguien —humano o automatizado— va a tener que resolver los conflictos que aparezcan después. ¿Tenés ese mecanismo diseñado, o vas a improvisarlo el día que pase?

  5. ¿La decisión puede variar por tipo de operación? No hace falta que todo tu sistema sea CP o todo AP: muchas bases de datos modernas te dejan pedir consistencia fuerte solo para las operaciones críticas (por ejemplo, confirmar un pago) y relajarla para el resto (por ejemplo, contar visitas a un perfil).

Las respuestas a esas preguntas, pensadas para tu propio caso de uso, son las que deberían guiar la elección — no una fórmula genérica ni el nombre de moda del motor de base de datos que usa la empresa que más admirás.

Para cerrar

El teorema CAP sigue siendo una herramienta conceptual valiosa, pero como toda simplificación, se presta a malentendidos si se repite sin entender su alcance real. No se trata de elegir "dos de tres" como si fuera un menú, ni de usarlo como excusa para explicar cualquier decisión de arquitectura. Se trata de entender que las particiones de red son inevitables, que la decisión real ocurre en el momento en que pasan, y que herramientas complementarias como PACELC ayudan a completar el cuadro para el resto del tiempo, cuando todo funciona bien.

More from this blog

E

El Blog de CRAFFED – Software sin Fronteras

16 posts

CRAFFED es un espacio para explorar, aprender y compartir ideas que dan forma al mundo digital. Aquí convergen experiencias, conocimientos y perspectivas sobre la creación y evolución de la tecnología. Más que un conjunto de artículos, es un punto de encuentro para mentes curiosas que buscan comprender, construir y transformar el software desde cualquier ángulo posible.