055 — CAP, PACELC y lo que realmente se elige
Parte 10 — Distribución, réplica y consistencia · Avanzado ·
3 horas estimadas · motores cassandra, spanner, postgresql · laboratorio
labs/05-nosql-workloads · 4 fuentes.
Conceptos centrales: partición de red · disponibilidad · latencia frente a consistencia
En este caso se comparan 7 motores: 6 lo resuelven (0 con el resultado comprobado por máquina) y 1 no, con el motivo escrito.
De qué trata esta clase
CAP dicho con precisión y despojado de la versión de póster. La disponibilidad del teorema es mucho más estricta que el «99,9 % de tiempo activo» del lenguaje operativo, y confundirlas es el origen de casi todas las lecturas erróneas. PACELC añade lo que CAP calla: el compromiso entre latencia y consistencia existe también los días en que no hay avería.
flowchart LR
C["🗄️ Clase 055"]
C --> K1["partición de red"]
C --> K2["disponibilidad"]
C --> K3["latencia frente a consistencia"]
classDef raiz fill:#0b3d2e,stroke:#3fb950,color:#fff
class C raiz
Antes de empezar
Esta clase supone que ya trabajaste lo siguiente. Si algo de la última columna no te suena, vuelve a esa clase antes de seguir: aquí se usa sin volver a explicarlo.
| # | Clase previa | Lo que se da por sabido |
|---|---|---|
| 053 | Réplica: líder único, multilíder y sin líder | replicación sincrónica · retraso de réplica · quórum · lectura de tu propia escritura |
Vocabulario de la clase
Los términos que siguen se usan más adelante con este significado exacto. La definición completa, con sus términos relacionados, está en el glosario del programa.
Propósito
Enunciar CAP con precisión, entender por qué su lectura popular es engañosa y usar PACELC, que describe mejor el compromiso que se toma todos los días.
Resultados de aprendizaje
Al terminar podrás:
- Enunciar el teorema CAP con las definiciones exactas de sus términos.
- Explicar por qué «elegir dos de tres» es una lectura incorrecta.
- Aplicar PACELC y situar motores reales en su clasificación.
- Determinar qué garantías sobreviven a una partición.
- Decidir el comportamiento de tu sistema durante una partición, por operación.
Fundamentos
El enunciado preciso
Gilbert y Lynch (2002) demostraron formalmente la conjetura de Brewer. Los términos tienen definiciones estrictas que casi nunca se citan:
- C (consistencia): linealizabilidad. Existe un orden total de las operaciones compatible con el tiempo real; toda lectura devuelve la última escritura confirmada.
- A (disponibilidad): toda petición a un nodo no caído recibe respuesta correcta, en tiempo finito.
- P (tolerancia a particiones): el sistema sigue funcionando aunque la red pierda arbitrariamente mensajes entre nodos.
Teorema: ningún sistema distribuido puede garantizar las tres simultáneamente.
Por qué «dos de tres» está mal
La red se particiona. No es una opción de diseño: es un hecho del mundo. Renunciar a P significaría suponer que la red nunca falla, lo cual no es un sistema distribuido sino una apuesta.
Por tanto la elección real es binaria y solo durante la partición:
- CP: rechazar peticiones que no puedan garantizar consistencia. Se pierde disponibilidad.
- AP: responder con datos posiblemente obsoletos. Se pierde consistencia.
Brewer lo aclaró él mismo en 2012: el teorema se aplica en el instante de la partición y las tres propiedades son continuas, no binarias. Además, la mayor parte del tiempo no hay partición, y ahí CAP no dice nada. Ese vacío es lo que PACELC llena.
PACELC
Abadi (2012):
if (P) then (A or C) -- durante una partición: disponibilidad o consistencia
else (L or C) -- en operación normal: latencia o consistencia
La segunda mitad es la que gobierna el 99,9 % del tiempo. Cada confirmación sincrónica a otra región cuesta una ida y vuelta: entre Santiago y Fráncfort, unos 200 ms. Ese costo se paga siempre, no solo cuando algo falla.
| Sistema | Con partición | Sin partición | Clasificación |
|---|---|---|---|
| PostgreSQL (líder único, síncrono) | C | C | PC/EC |
| PostgreSQL (líder único, asíncrono) | C en el líder | L | PC/EL |
Cassandra (ONE) |
A | L | PA/EL |
Cassandra (QUORUM) |
Configurable | C | PC/EC |
| DynamoDB (eventual) | A | L | PA/EL |
| DynamoDB (fuerte) | C | C | PC/EC |
MongoDB (majority) |
C | C | PC/EC |
| Spanner | C | C | PC/EC |
Dos observaciones que cambian la conversación:
- La clasificación es por operación, no por producto. Cassandra es AP o CP según el nivel de consistencia de cada consulta.
- Spanner es PC/EC y aun así ofrece alta disponibilidad, porque usa redes privadas con particiones extremadamente raras y consenso rápido (clase 047). No viola CAP: elige C y su disponibilidad práctica es alta porque P casi nunca ocurre.
Qué sobrevive a una partición
Bailis et al. clasifican qué garantías son alcanzables manteniendo la disponibilidad total:
| Garantía | ¿Disponible bajo partición? |
|---|---|
| Consistencia eventual | Sí |
| Lectura de tus escrituras | Sí (con sesión fijada) |
| Lectura monótona | Sí |
| Consistencia causal | Sí |
| Aislamiento de instantánea | No |
| Serializabilidad | No |
| Linealizabilidad | No |
La frontera es nítida: todo lo que exige un orden total acordado necesita coordinación, y la coordinación es lo primero que una partición rompe. Todo lo demás —incluida la consistencia causal, que es bastante fuerte— se puede sostener sin coordinar.
flowchart TD
P{"¿Hay partición<br/>de red?"}
P -- "Sí" --> A{"¿Responder con datos<br/>posiblemente obsoletos?"}
A -- "Sí" --> AP["AP: disponible,<br/>inconsistente"]
A -- "No" --> CP["CP: rechaza o espera,<br/>consistente"]
P -- "No (99,9 % del tiempo)" --> L{"¿Coordinar en cada<br/>operación?"}
L -- "Sí" --> EC["EC: consistente,<br/>+latencia por ida y vuelta"]
L -- "No" --> EL["EL: rápido,<br/>consistencia más débil"]
Ejemplo trabajado
Plataforma educativa con nodos en Santiago y Fráncfort. La red entre regiones se corta 4 minutos.
Operación 1 — leer el catálogo de cursos.
Datos que cambian una vez al día. Servir la versión de hace unos minutos no daña a nadie.
Decisión: AP. Se sirve desde la réplica local aunque esté desconectada.
Consecuencia: un curso creado hace 3 minutos en Santiago no se ve en Fráncfort.
Aceptable: sí, y se documenta.
Operación 2 — inscribir en un curso con cupo limitado.
Si ambas regiones aceptan inscripciones sin coordinarse, el cupo se excede.
Decisión: CP. Durante la partición, la región sin quórum rechaza.
Consecuencia: 4 minutos sin inscripciones en Fráncfort.
Aceptable: sí. Peor sería vender 50 plazas de un curso de 40.
Operación 3 — registrar una nota.
Escritura de un solo autor por par (estudiante, curso). No hay conflicto posible.
Decisión: AP con consistencia causal. Se acepta localmente y se reconcilia después.
Consecuencia: la nota tarda en verse en la otra región.
Aceptable: sí, porque no hay dos escritores compitiendo por el mismo dato.
La tabla que resulta, y que es el entregable de esta clase:
| Operación | Con partición | Justificación | Coste declarado |
|---|---|---|---|
| Leer catálogo | A | Datos casi estáticos | Hasta 5 min de desfase |
| Inscribir con cupo | C | Recurso finito compartido | Indisponible en la minoría |
| Registrar nota | A | Un solo escritor por clave | Visibilidad diferida |
| Autenticar | A | Credenciales replicadas, cambios raros | Un cambio de contraseña puede tardar |
| Cambiar contraseña | C | Seguridad: no puede quedar la antigua | Indisponible en la minoría |
Y el lado «else», que se paga siempre. Sin partición, la inscripción con QUORUM entre regiones cuesta:
ida y vuelta Santiago-Fráncfort ≈ 200 ms
inscripción con quórum global ≈ 200 ms añadidos por operación
Ese es el EC de PACELC, y es la razón por la que casi todos los sistemas globales acaban con datos regionales: la inscripción se coordina solo dentro de la región del curso, no globalmente. Eso convierte una decisión de consistencia global en una local, y es el diseño que de verdad se usa.
Comparación
| Situación | Clasificación adecuada |
|---|---|
| Sesiones y preferencias | PA/EL |
| Catálogo de productos | PA/EL |
| Inventario con reserva | PC/EC |
| Movimientos contables | PC/EC |
| Métricas y telemetría | PA/EL |
| Autenticación (lectura) | PA/EL |
| Cambio de credenciales | PC/EC |
Errores frecuentes
- «Elegimos AP» como decisión de producto. Es por operación, no por sistema.
- Renunciar a P. La red se particiona; no es opcional.
- Usar «consistencia» de CAP y de ACID como sinónimos. Son cosas distintas: linealizabilidad frente a restricciones de integridad.
- Ignorar el lado «else». El costo de latencia se paga siempre, la partición casi nunca.
- Suponer que un producto tiene una clasificación fija. Depende del nivel de consistencia elegido.
- Coordinar globalmente lo que podría coordinarse por región.
De la clase a la operación
La pregunta útil en una revisión de arquitectura no es «¿somos CP o AP?», sino «¿qué hace exactamente cada operación durante una partición, y quién decidió que eso era aceptable?». Esa tabla, escrita, vale más que cualquier etiqueta.
Reto de transferencia
- Enumera las operaciones críticas de tu sistema.
- Decide para cada una A o C durante la partición, con justificación de negocio.
- Calcula el costo de latencia del lado «else» en tu topología real.
- Identifica una operación que hoy se coordina globalmente y podría hacerlo por región.
Preguntas de evaluación
- Enuncia CAP con las definiciones exactas de C, A y P.
- ¿Por qué Spanner puede ser PC/EC y tener alta disponibilidad práctica?
- Da una operación de tu sistema que hoy es AP sin que nadie lo haya decidido.
- ¿Qué garantías transaccionales son inalcanzables manteniendo disponibilidad total?
🌐 El mismo problema en cada motor
Caso: Qué se elige de verdad cuando se dice «elegimos AP» o «elegimos CP»
El teorema CAP dice algo muy concreto y muy limitado: cuando la red se parte, un sistema no puede ser a la vez consistente —en el sentido de linealizable— y disponible. Nada más. No dice que haya que elegir dos de tres, ni describe el comportamiento normal del sistema, que es el 99,9 % del tiempo.
Por eso Abadi propuso PACELC, que completa la frase: si hay partición (P), se elige entre disponibilidad (A) y consistencia (C); y si no (E, else), se elige entre latencia (L) y consistencia (C). La segunda mitad es la que manda casi siempre, y es la que las etiquetas «AP» y «CP» esconden.
Aquí se compara qué elige cada motor en las dos mitades, y —lo más útil— qué se puede ajustar por operación en vez de por sistema.
Esta comparación es conceptual: la decisión no se reduce a una consulta con resultado, así que aquí no hay sello de máquina. Lo que se compara es lo que cada motor ofrece y a qué precio, con la página oficial al lado de cada afirmación.
| Motor | ¿Resuelve el caso? | Nivel de prueba | Código | Fuente |
|---|---|---|---|---|
| Apache Cassandra | sí | conceptual | — | doc oficial |
| MongoDB | sí | conceptual | — | doc oficial |
| PostgreSQL | sí | conceptual | — | doc oficial |
| Google Cloud Spanner | sí | conceptual | — | doc oficial |
| CockroachDB | sí | conceptual | — | doc oficial |
| Redis | sí | conceptual | — | doc oficial |
| SQLite | no | — | — | doc oficial |
Los que resuelven el caso
Apache Cassandra
- Cómo se hace aquí: PA/EL en la clasificación de Abadi: ante partición prefiere disponibilidad, y sin partición prefiere latencia. Pero eso es el valor por omisión, no una propiedad fija: el nivel de consistencia se elige por consulta, y con
QUORUMen lectura y escritura se obtiene consistencia fuerte a cambio de esperar a la mayoría. - Por qué sí: Poder decidir por operación es lo más honesto que ofrece esta lista: la escritura del pago con
QUORUM, la del registro de actividad conONE, en el mismo clúster y en la misma aplicación. - Por qué no: Esa decisión hay que tomarla en cada consulta, y equivocarse no da ningún error: da datos viejos. Y la fórmula «R + W > RF» hay que llevarla en la cabeza al escribir cada línea de código.
- 📄 Documentación oficial: https://cassandra.apache.org/doc/latest/cassandra/developing/cql/dml.html
MongoDB
- Cómo se hace aquí: PC/EC: ante partición, la minoría deja de aceptar escrituras —el primario se degrada— y sin partición prioriza consistencia con
readConcernywriteConcern. También se ajusta por operación. - Por qué sí: El valor por omisión es el seguro y el arriesgado hay que pedirlo, que es el orden correcto:
w: "majority"yreadConcern: "majority"dan lecturas coherentes sin cambiar de motor. - Por qué no: Leer de secundarios para repartir carga rompe esa garantía sin avisar, y es lo primero que se hace cuando el primario va justo: la optimización más tentadora es justo la que cambia el modelo de consistencia.
- 📄 Documentación oficial: https://www.mongodb.com/docs/manual/reference/read-concern/
PostgreSQL
- Cómo se hace aquí: Con un solo nodo, la pregunta no se plantea: es CA en el sentido trivial de que no hay partición posible. Con réplica, el líder es consistente y las réplicas van por detrás;
synchronous_commit = remote_applyda lecturas coherentes en la réplica a cambio de latencia en cada confirmación. - Por qué sí: Es el sistema más fácil de razonar: mientras se lea del primario, no hay modelo de consistencia que estudiar.
- Por qué no: En cuanto se reparte la lectura entre réplicas, el sistema deja de ser trivial y nadie lo declara: la aplicación empieza a leer datos viejos sin que nada en el código lo indique. Es el «lo dirigimos a la réplica» que rompe el registro después de crearlo.
- 📄 Documentación oficial: https://www.postgresql.org/docs/current/warm-standby.html
Google Cloud Spanner
- Cómo se hace aquí: CP con disponibilidad muy alta en la práctica: consistencia externa —equivalente a linealizabilidad— mediante Paxos y relojes con incertidumbre acotada (TrueTime). En vez de fingir que los relojes están sincronizados, mide el error y espera a que la incertidumbre pase.
- Por qué sí: Ofrece transacciones distribuidas con serializabilidad estricta a escala global, algo que se consideraba imposible antes de 2012, y con SQL estándar encima.
- Por qué no: Ese «esperar a que la incertidumbre pase» es latencia real en cada confirmación, y depende de una infraestructura de relojes —GPS y relojes atómicos— que solo existe dentro de un proveedor. La disponibilidad es altísima; el teorema no se ha roto, se ha comprado.
- 📄 Documentación oficial: https://cloud.google.com/spanner/docs/true-time-external-consistency
CockroachDB
- Cómo se hace aquí: CP también, con Raft por rango y serializabilidad como único nivel de aislamiento. Sin relojes atómicos: usa un intervalo de incertidumbre y reintenta las transacciones que caen dentro de él.
- Por qué sí: Da la misma garantía fuerte sobre infraestructura corriente y con protocolo de PostgreSQL, así que buena parte de las herramientas ya existentes funcionan.
- Por qué no: Los reintentos por incertidumbre son visibles para la aplicación: hay que escribir el ciclo de reintento sí o sí. Y con réplicas repartidas entre regiones, cada escritura paga la latencia de llegar a la mayoría.
- 📄 Documentación oficial: https://www.cockroachlabs.com/docs/stable/architecture/transaction-layer
Redis
- Cómo se hace aquí: Ni AP ni CP en sentido estricto: la réplica es asíncrona y la conmutación puede perder escrituras confirmadas. Su propia documentación lo dice y no promete consistencia fuerte.
- Por qué sí: Para su trabajo —caché, colas, contadores, sesiones— esa elección es la correcta: la latencia es el requisito y perder el último segundo de una caché no cuesta nada.
- Por qué no: El problema aparece cuando alguien empieza a guardar ahí lo que sí cuesta perder. Redis no engaña a nadie; la arquitectura que lo usa como fuente de verdad, sí.
- 📄 Documentación oficial: https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
Los que no resuelven este caso — y qué se hace en su lugar
Descartar un motor con un argumento es tan formativo como usarlo. Ninguna de estas filas dice que el motor sea peor: dice que este problema no es el suyo.
| Motor | Por qué no | Qué se hace en su lugar | Fuente |
|---|---|---|---|
| SQLite | Un solo proceso y un solo archivo: no hay red que se pueda partir, así que el teorema no dice nada sobre él. Incluirlo aquí sería usar el vocabulario sin el problema. | La pregunta análoga en un sistema de un solo nodo es otra —qué pasa si se corta la luz— y se estudia en la clase de registro anticipado y recuperación. | doc |
Laboratorio
python scripts/validate_repository.py
python labs/05-nosql-workloads/run_nosql_lab.py
Guarda como evidencia la salida completa, la versión del motor y la semilla o los parámetros usados. Una captura sin comando no es evidencia: no se puede repetir.
Evaluación
| Criterio | Peso | Qué se comprueba |
|---|---|---|
| Comprensión conceptual | 25 % | Explica el mecanismo, no solo el resultado |
| Ejecución reproducible | 25 % | Otra persona obtiene lo mismo con las instrucciones dadas |
| Interpretación basada en evidencia | 25 % | Cada conclusión se apoya en una salida o una medición |
| Límites y riesgos declarados | 25 % | Dice qué no demuestra el ejercicio y qué faltaría en producción |
La clase se da por superada cuando la respuesta explica el mecanismo, muestra la salida que la respalda y declara al menos un límite del ejercicio.
Fuentes de esta clase
Todo lo afirmado arriba procede de estas obras. Los identificadores viven en
catalog/sources.json y el estado de los
enlaces se comprueba con python scripts/check_external_links.py.
- Seth Gilbert, Nancy Lynch (2002). Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services. ACM SIGACT News 33(2). DOI 10.1145/564585.564601.
Demostración formal del teorema CAP y de su enunciado exacto. - Eric Brewer (2012). CAP Twelve Years Later: How the Rules Have Changed. IEEE Computer 45(2). DOI 10.1109/MC.2012.37.
El propio autor corrige la lectura simplista de elegir dos de tres. - Daniel J. Abadi (2012). Consistency Tradeoffs in Modern Distributed Database System Design. IEEE Computer 45(2). DOI 10.1109/MC.2012.33.
PACELC: el compromiso latencia-consistencia existe también sin particiones. - Peter Bailis, Aaron Davidson, Alan Fekete, Ali Ghodsi, Joseph M. Hellerstein, Ion Stoica (2014). Highly Available Transactions: Virtues and Limitations. PVLDB 7(3).
Qué garantías transaccionales sobreviven a una partición y cuáles no.