Teorema CAP y Trade-offs de Bases de Datos
Teorema CAP explicado para practicantes: trade-offs CP vs AP, PACELC, consistencia tunable por base de datos y cómo evitar split-brain.
Introducción
El teorema CAP establece que un almacén de datos distribuido puede garantizar como máximo dos de tres propiedades durante una partición de red: Consistencia, Disponibilidad y Tolerancia a Particiones. Dado que las particiones son inevitables en cualquier sistema que abarca más de una máquina, en realidad eliges entre comportamiento CP (Consistencia + Tolerancia a Particiones) y AP (Disponibilidad + Tolerancia a Particiones) cuando la red falla — no eliges dos letras de un menú para siempre.
Esta guía cubre qué garantiza realmente cada propiedad, cómo la extensión PACELC completa los huecos que CAP deja, cómo las bases de datos reales permiten ajustar el trade-off por consulta, y un marco de decisión aplicable por operación en lugar de por producto.
Las Tres Propiedades
Consistencia (C)
Cada lectura recibe la escritura más reciente o un error. Todos los nodos ven los mismos datos al mismo tiempo, lo que la literatura llama linearizability.
Cliente escribe X=10 en Nodo A
Cliente lee X desde Nodo B → debe obtener 10 (o un error)
Ejemplos: PostgreSQL en un primario único, MongoDB con majority write concern + read concern linearizable, etcd, ZooKeeper.
Disponibilidad (A)
Cada solicitud recibe una respuesta sin error, sin garantía de que contenga la escritura más reciente.
Cliente escribe X=10 en Nodo A (particionado de Nodo B)
Cliente lee X desde Nodo B → obtiene un valor desactualizado (ej: X=5)
Ejemplos: Cassandra, DynamoDB, Riak, Couchbase.
Tolerancia a Particiones (P)
El sistema sigue operando a pesar de particiones de red: períodos en los que los nodos no pueden intercambiar mensajes.
El Nodo A y el Nodo B no pueden comunicarse
El sistema sigue respondiendo a solicitudes en ambos lados
Realidad: la tolerancia a particiones no es opcional en un sistema distribuido. Las redes pierden paquetes, los switches fallan, las zonas de disponibilidad se aíslan. No eliges P — eliges qué sacrificar cuando P ocurre: C (AP) o A (CP).
CP vs AP en la Práctica
La elección solo importa durante una partición. La pregunta que la guía: cuando dos nodos no pueden comunicarse, ¿qué es peor: rechazar la operación o devolver datos posiblemente desactualizados?
Sistemas CP (Eligen Consistencia)
| Cuándo Elegir | Ejemplos |
|---|---|
| Transacciones financieras | Saldos bancarios, trading de acciones |
| Gestión de inventario | Conteos de stock de e-commerce |
| Almacenes de configuración | Service discovery, feature flags |
| Elección de líder | Locks distribuidos, coordinación de clúster |
Trade-off: durante una partición, el sistema puede rechazar escrituras (sacrificando disponibilidad) para evitar estado divergente. Un primario de PostgreSQL que pierde sus standbys síncronos puede configurarse para bloquear commits hasta que uno vuelva.
Sistemas AP (Eligen Disponibilidad)
| Cuándo Elegir | Ejemplos |
|---|---|
| Feeds de redes sociales | Timeline, feed de actividad |
| Analítica y métricas | Datos de series temporales, tracking de clicks |
| Almacenes de sesión | Caché de sesiones de usuarios |
| Entrega de contenido | Cachés de CDN, réplicas de lectura |
Trade-off: durante una partición, ambos lados aceptan escrituras, creando estado divergente que debe reconciliarse después — last-write-wins, CRDTs, vector clocks o resolución manual de conflictos.
Lo que el Teorema no Dice
CAP se cita constantemente y se lee con cuidado mucho menos. Cuatro aclaraciones evitan la mayoría de errores:
- “Elige dos” no es una etiqueta permanente del producto. El trade-off solo aplica mientras existe una partición. Fuera de ella, un sistema puede ser consistente y disponible — por eso existe PACELC.
- La consistencia de CAP es linearizability, no la C de ACID. La consistencia ACID significa que la base de datos mantiene invariantes (constraints, claves foráneas). La consistencia CAP significa que cada lectura ve la última escritura confirmada. Son propiedades distintas que comparten nombre.
- La elección es por operación, no por producto. Cassandra no es “una base AP”; es una base que por defecto se comporta AP y permite pedir comportamiento CP con
QUORUM/ALL. Mismos datos, garantías distintas por consulta. - La disponibilidad en CAP es total, no “cinco nueves”. El teorema significa que toda solicitud a un nodo no fallido obtiene respuesta. Un SLA de 99.999 % de uptime es otro concepto — un sistema CP puede tener excelente disponibilidad en el sentido de SLA.
PACELC: Extendiendo CAP
CAP solo describe el comportamiento durante una partición. PACELC (Abadi, 2012) añade la segunda pregunta: else, cuando el sistema opera normal, ¿optimizas para latencia o para consistencia?
| Tipo de Sistema | Si hay Partición | Si no (Else) |
|---|---|---|
| PA/EL | Disponible | Optimizado para latencia (consistencia eventual) |
| PA/EC | Disponible | Optimizado para consistencia |
| PC/EL | Consistente | Optimizado para latencia |
| PC/EC | Consistente | Optimizado para consistencia |
Mapeos reales:
- DynamoDB — PA/EL: sigue disponible durante particiones y por defecto ofrece lecturas eventualmente consistentes en operación normal (las lecturas fuertemente consistentes son opt-in y cuestan el doble de unidades de lectura).
- Cassandra — PA/EL por defecto, pero los niveles de consistencia por consulta permiten que un mismo clúster se comporte como PA/EC o incluso PC/EC para operaciones concretas.
- etcd / ZooKeeper — PC/EC: linearizable por defecto; un nodo del lado minoritario rechaza servir durante una partición.
- Replica set de MongoDB — PC/EC en la configuración por defecto (escrituras
majority, lecturas del primario); mover lecturas a secundarios cambia consistencia por latencia, acercándolo a PC/EL.
Modelos de Consistencia
La consistencia no es binaria; es un espectro. Los modelos útiles, de más fuerte a más débil:
| Modelo | Garantía | Ejemplo |
|---|---|---|
| Linearizable (fuerte) | Todas las lecturas ven la última escritura confirmada | Primario PostgreSQL, etcd, ZooKeeper |
| Secuencial | Todos los nodos ven las operaciones en el mismo orden (no necesariamente la última) | Lecturas por defecto de ZooKeeper |
| Causal | Las lecturas respetan el orden causa-efecto | COPS, sesiones causales de MongoDB |
| Read-your-writes / sesión | Dentro de una sesión, las lecturas ven tus propias escrituras | Nivel session de Cosmos DB, lecturas consistentes de DynamoDB |
| Staleness acotada | Las lecturas se atrasan como máximo una ventana definida | Bounded staleness de Cosmos DB |
| Eventual | Las réplicas convergen si no hay nuevas escrituras | Cassandra por defecto, S3, DNS |
# Cassandra: ajusta el trade-off por consulta
from cassandra import ConsistencyLevel
# Fuerte para esta lectura: espera mayoría de réplicas
session.execute(
"SELECT * FROM users WHERE id = %s",
(user_id,),
consistency_level=ConsistencyLevel.QUORUM,
)
# Rápida, puede estar desactualizada: vale para contadores y analítica
session.execute(
"SELECT count(*) FROM events",
consistency_level=ConsistencyLevel.ONE,
)
La regla práctica: W + R > N te da consistencia para esa operación (W réplicas de escritura, R de lectura, N factor de replicación). Con N=3, QUORUM + QUORUM se solapa. ONE + ONE es el extremo rápido y laxo.
Ajustando la Consistencia en Bases de Datos Reales
La elección CAP suele hacerse por consulta, no por clúster. Cómo expone el control cada sistema:
MongoDB — writeConcern controla durabilidad/confirmación; readConcern controla qué puede ver una consulta:
// Tendencia CP: lectura linearizable confirmada por mayoría
db.accounts.find({ _id: id })
.readConcern("linearizable")
.writeConcern("majority", { wtimeout: 5000 });
// Tendencia AP: leer de un secundario, aceptar datos desactualizados
db.events.find({})
.readConcern("local")
.readPreference("secondaryPreferred");
DynamoDB — eventual por defecto; ConsistentRead=true cuesta aproximadamente el doble y solo es fuerte por partición.
Cosmos DB — el menú público más granular: strong, bounded staleness, session, consistent prefix, eventual. Session es el default y cubre la mayoría de aplicaciones.
etcd / ZooKeeper — lecturas linearizables por defecto; etcd ofrece lecturas serializable que se saltan la ronda de consenso para resultados desactualizados pero rápidos.
Réplicas de lectura de PostgreSQL — la replicación asíncrona hace que las lecturas de réplicas sean eventualmente consistentes. synchronous_commit y hot-standby feedback estrechan la ventana pero nunca la cierran; para read-your-writes, fija la sesión al primario o sigue el LSN de replay.
Las Particiones de Red en la Práctica
Una partición no siempre es un cable cortado. En producción se ve así:
- Aislamiento de AZ o región — una zona de disponibilidad cloud pierde conectividad; los nodos de cada lado siguen sirviendo a sus clientes locales.
- Particiones asimétricas — A alcanza a B pero B no alcanza a A. La peor clase: los health checks discrepan y cada lado cree que el otro está caído (escenario clásico de split-brain).
- Pausas largas de GC o CPU saturada — una pausa stop-the-world de JVM o una CPU saturada hace que un nodo pierda heartbeats; el clúster lo trata como particionado aunque los paquetes fluyan bien.
- Micro-particiones — enlaces inestables que caen por segundos, se recuperan y vuelven a caer. Cada caída puede disparar una elección de líder y una ráfaga de escrituras rechazadas en sistemas CP.
Todo sistema distribuido detecta particiones de la misma forma tosca: un timeout. No hay modo de distinguir “nodo muerto” de “red muerta” — esa ambigüedad es exactamente por qué el trade-off CAP no se puede esquivar. Ajusta los intervalos de heartbeat y los tiempos de lease a tu red real, y alerta sobre tormentas de elecciones, no solo sobre fallos duros.
Checklist de Decisión
Para cada operación (no para cada sistema), recorre estas preguntas:
- Si esta lectura lleva 5 minutos de retraso, ¿se rompe algo? Si no, AP/eventual es el default correcto.
- Si esta escritura es rechazada durante una partición, ¿cuál es el fallo visible al usuario? Un pago fallido es peor que una notificación demorada.
- Si ambos lados de la partición escriben la misma clave, ¿puedes reconciliar? Los contadores y los sets reconcilian bien (CRDTs); los saldos financieros no.
- ¿La operación necesita read-your-writes? La consistencia de sesión suele bastar; no necesitas linearizability global para que un usuario vea su propia actualización.
- ¿Cuál es el costo real de coordinar? Una lectura quorum en Cassandra o un commit síncrono en PostgreSQL añade milisegundos reales por operación; mídelo antes de elegir fuerte.
Ejemplos del Mundo Real
Checkout de E-Commerce
| Operación | Consistencia Requerida | Elección |
|---|---|---|
| Verificar stock | Fuerte (no vender de más) | CP — consulta al nodo primario |
| Agregar al carrito | Sesión | AP — caché con afinidad de sesión |
| Ver recomendaciones | Eventual | AP — lectura desde caché |
| Procesar pago | Fuerte | CP — transacción ACID |
Feed de Redes Sociales
| Operación | Consistencia Requerida | Elección |
|---|---|---|
| Publicar un post | Eventual | AP — aceptar escritura, propagar asíncrono |
| Ver feed | Eventual | AP — cacheado, puede tener segundos de retraso |
| Dar like | Eventual | AP — incrementar contador, reconciliar después |
| Eliminar cuenta | Fuerte | CP — asegurar que todas las réplicas eliminen |
El patrón en ambas tablas: los caminos críticos reciben tratamiento CP y el 90 % de operaciones alrededor se queda en AP. Los sistemas que fuerzan todo por un único nivel de consistencia lo pagan en latencia o en caídas.
Cuándo el Razonamiento CAP no Aplica
No todo componente merece un análisis CP/AP:
- Despliegues de un solo nodo. No hay partición de red posible, así que un PostgreSQL de un nodo es trivialmente CA. En el momento en que añades un standby para failover, el comportamiento en partición se vuelve una pregunta real.
- Cachés efímeros. Si un miss o un hit desactualizado no cuesta nada (una recomputación, un re-render), no gastes presupuesto de consistencia ahí.
- Estado del lado cliente. LocalStorage, cachés en la app y colas offline-first siguen semántica de sincronización, no de CAP — los conflictos se resuelven al fusionar, no al leer.
- Réplicas de analítica. Una réplica de reporting minutos atrás no necesita pensamiento CAP; necesita un SLA de lag.
Escenario Detallado: Elección de Base de Datos para una App FinTech
Una plataforma de pagos dividida en microservicios, donde cada servicio elige su propio punto del espectro:
Sistema: App de pagos FinTech (microservicios)
Servicios: Cuentas, Transferencias, Notificaciones, Analítica
Matriz de requisitos por servicio:
| Servicio | Consistencia | Disponibilidad | Latencia | Elección |
|----------|--------------|----------------|----------|----------|
| Cuentas (saldos) | Fuerte (CP) | 99.99% | < 10ms | PostgreSQL (primario + réplica sync) |
| Transferencias | Fuerte (CP) | 99.99% | < 50ms | PostgreSQL + patrón saga |
| Notificaciones | Eventual (AP) | 99.9% | < 500ms | Cassandra (write-heavy) |
| Analítica | Eventual (AP) | 99.9% | < 5s | ClickHouse (OLAP columnar) |
| Caché de sesión | Eventual (AP) | 99.95% | < 2ms | Redis (en memoria) |
| Feature flags | Fuerte (CP) | 99.9% | < 50ms | etcd (consenso Raft) |
Configuración PostgreSQL para el servicio de Cuentas:
- Primario: 1 instancia (escrituras)
- Réplicas síncronas: 2 (lecturas + failover)
- Synchronous commit: ON (esperar confirmación de al menos 1 réplica)
- Failover: Patroni con etcd para consenso
postgresql.conf:
synchronous_commit = on
synchronous_standby_names = "FIRST 1 (replica1, replica2)"
wal_level = replica
max_wal_senders = 10
Configuración Cassandra para Notificaciones:
- 5 nodos en 3 datacenters
- Replication factor: 3 por datacenter
- Consistency level: LOCAL_QUORUM para escrituras
- Compaction: Size-tiered (STCS) para datos de notificación
CREATE KEYSPACE notifications WITH replication = {
"class": "NetworkTopologyStrategy",
"dc1": 3, "dc2": 3, "dc3": 3
};
CREATE TABLE notifications (
user_id UUID,
notification_id TIMEUUID,
type TEXT,
payload JSON,
read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP,
PRIMARY KEY (user_id, notification_id)
) WITH CLUSTERING ORDER BY (notification_id DESC);
Manejo de particiones de red:
- Cuentas (CP): Si el primario pierde contacto con las réplicas,
rechaza escrituras. Los usuarios no pueden transferir dinero
pero los saldos se mantienen consistentes.
- Notificaciones (AP): Si un datacenter se aísla,
las notificaciones se aceptan en ambos lados.
Hinted handoff resuelve la consistencia al reconectar.
Monitoreo:
- Replication lag de PostgreSQL: < 100ms (alerta si > 500ms)
- Cassandra repair: ejecutar semanalmente para anti-entropy
- Cambios de líder de etcd: alertar si > 1 por hora
- Transferencias fallidas por partición: dashboard en tiempo real
Lecciones aprendidas:
- No todos los servicios necesitan la misma base de datos
- CP para el dinero, AP para todo lo demás
- El costo de la consistencia fuerte es latencia y menor disponibilidad
- Vigilar el replication lag detecta el daño de particiones temprano
Troubleshooting
Fallos que verás de verdad cuando el trade-off CAP está mal configurado:
- Lecturas desactualizadas tras un failover: el nuevo primario fue elegido antes de que se replicaran las últimas escrituras del anterior. Endurece
synchronous_commit/majoritywrite concern, o aísla el primario viejo con fencing tipo STONITH. - Escrituras en split-brain: ambos lados de la partición aceptan escrituras para la misma clave; al sanar tienes valores divergentes. Mitiga con lecturas/escrituras de quorum, o acéptalo y reconcilia con CRDTs o last-write-wins donde perder una escritura es inofensivo.
- Quorum perdido, clúster de solo lectura: un sistema CP (etcd, MongoDB) pierde su mayoría y rechaza escrituras: el trade-off CP funcionando como está diseñado. Restaura miembros rápido; nunca fuerces a una minoría a servir escrituras.
- El replication lag crece sin parar: vigila
pg_stat_replication,replSetGetStatusde MongoDB onodetoolde Cassandra. Divide escrituras grandes, revisa el I/O de disco en réplicas y verifica la red entre AZs. - Backlog de hinted handoff tras sanar una partición (Cassandra): las escrituras se acumularon como hints mientras un nodo estuvo caído; ejecuta repair y monitorea métricas de
hinted_handoffantes de que expire la ventana (default 3 h).
Lo que Funciona
- No uses consistencia fuerte en todas partes — cuesta latencia y disponibilidad
- Identifica los requisitos de consistencia por operación — no todos los datos necesitan las mismas garantías
- Usa patrones saga para transacciones distribuidas — no fuerces ACID entre servicios
- Diseña para idempotencia — la consistencia eventual implica reintentos, y los reintentos implican duplicados
- Monitorea el replication lag — el lag es la distancia entre “escrito” y “visible en todas partes”
- Registra los eventos de partición como incidentes — cuando un almacén CP rechaza escrituras durante una partición, es un incidente de producción con runbook, no una sorpresa
Errores Comunes
- Tratar todos los datos como si necesitaran consistencia fuerte — la mayoría de datos de aplicación van bien con eventual
- Creer que “distribuido” significa “más consistente” — la replicación añade modos de fallo, no garantías
- Usar una base CP para una carga AP (o al revés) — empareja la herramienta con el requisito; consulta selección NoSQL
- Ignorar el replication lag en escenarios read-after-write — los usuarios pueden no ver sus propias escrituras de inmediato; fija lecturas al primario o usa consistencia de sesión
- Asumir que el nivel de consistencia es fijo por producto — Cassandra, MongoDB, Cosmos DB y DynamoDB permiten elegir por consulta; el default es un punto de partida, no la respuesta
- Probar solo el camino feliz — tumba un nodo o corta una ruta de red en staging antes de confiar tu elección CP/AP en producción
Lectura Adicional
- Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services — la demostración formal de Gilbert & Lynch (SIGACT News, 2002)
- Problems with CAP, and Yahoo’s little known NoSQL system — Daniel Abadi introduciendo la extensión PACELC
- Cassandra tunable consistency — documentación oficial sobre
ONE,QUORUM,LOCAL_QUORUM - Cosmos DB consistency levels — el menú de cinco niveles, de strong a eventual
- Análisis de Jepsen — pruebas reales de fallos de partición en bases distribuidas; el mejor lugar para ver cómo se rompen los trade-offs CAP en la práctica
- Lab complementario — clúster etcd de 3 nodos con script de partición más los snippets de configuración CP/AP de esta guía
Puntos Clave
- Las particiones son inevitables, así que la elección real es CP vs AP, hecha por operación en lugar de por producto.
- Fuera de una partición, consistencia y disponibilidad coexisten; PACELC captura qué sacrifica el sistema entonces (latencia vs consistencia).
- La consistencia tunable (
ONE/QUORUM/ALL, read/write concerns, niveles de Cosmos DB) permite que un clúster sirva cargas CP y AP a la vez. - El dinero va por CP; feeds, sesiones y analítica se quedan en AP. Verifica bajo una partición inducida en staging antes de confiarlo a producción.
Preguntas frecuentes
¿Es posible tener las tres propiedades CAP?
No. El teorema es una demostración: durante una partición de red debes elegir entre consistencia y disponibilidad. Ningún sistema distribuido garantiza las tres a la vez — solo puedes elegir qué garantía ceder mientras dure la partición.
¿CAP significa que no puedo tener consistencia y disponibilidad?
No. Cuando no hay partición, puedes tener ambas, que es justamente el punto de la extensión PACELC. Muchos sistemas son efectivamente CA en operación normal y revelan su comportamiento CP o AP solo durante fallos.
¿Cómo elijo entre CP y AP?
Pregúntate: "¿Qué duele más: una escritura fallida o datos desactualizados?" Si las escrituras fallidas son inaceptables (pagos, inventario, configuración), elige CP. Si los datos desactualizados son aceptables (feeds, analítica, sesiones), elige AP. La mayoría de sistemas reales mezclan ambos: CP en el camino crítico, AP en el resto.
¿Cuál es la diferencia entre CAP y PACELC?
CAP solo cubre el tiempo de partición. PACELC añade el trade-off de operación normal: cuando no hay partición (else), ¿optimizas para latencia o para consistencia? DynamoDB es PA/EL (disponible bajo partición, optimizado para latencia en caso contrario); etcd es PC/EC.
¿Qué es la consistencia tunable?
Sistemas como Cassandra, MongoDB y Cosmos DB permiten fijar el nivel de consistencia por operación — ONE (rápido, eventual), QUORUM (mayoría, consistente), ALL (máxima consistencia, menor disponibilidad). Eliges el trade-off por consulta, no por clúster.
¿Una base de datos de un solo nodo se ve afectada por CAP?
Sin particiones no hay trade-off CAP: un PostgreSQL de un nodo es trivialmente consistente y disponible. Las preguntas CAP empiezan en el momento en que añades una réplica — para failover, escalar lecturas o distribución geográfica.
Recursos Relacionados
Guía de Selección NoSQL — MongoDB, DynamoDB, Cassandra
Guía práctica para elegir la base de datos NoSQL correcta. Compara documentos, clave-valor, columnas anchas y grafos con criterios de selección.
GuideGuía Completa de Sharding de Bases de Datos
Domina el sharding de bases de datos: estrategias por rango, hash, lista y directorio, consistent hashing, shard keys, consultas cross-shard, resharding, Vitess y Citus.
GuideRéplicas de Lectura: Escala Lecturas Sin Tocar tu App
Guía de producción sobre réplicas de lectura: configuración de replicación streaming, enrutamiento de lectura/escritura (app, ORM y proxy), manejo del lag, monitoreo, failover y ajuste fino en PostgreSQL, MySQL y réplicas gestionadas en la nube.
RecipePatrones de Comunicación entre Microservicios
Elige entre patrones de comunicación síncronos y asíncronos para arquitecturas de microservicios resilientes.
RecipeRetry Backoff: Budgets, Deadlines y Config de Librerías
Implementa estrategias de retry resilientes con exponential backoff, jitter e integración de circuit breaker para recuperación de fallas transitorias.
RecipeReplicación de Bases de Datos
Configura y gestiona replicación de bases de datos para alta disponibilidad, escalado de lecturas y disaster recovery con arquitecturas primaria-réplica.