StackPractices
intermediate Por Mathias Paulenko

ACID vs BASE — Modelos de Consistencia Explicados

Guía práctica que compara los modelos de consistencia ACID y BASE: cuándo elegir consistencia fuerte, cuándo aceptar la eventual y cómo afecta al diseño.

ACID y BASE son dos respuestas a la misma pregunta: quién impone la corrección cuando los datos viven en más de un lugar — ¿la base de datos o el código de tu aplicación? Esta guía cubre qué garantiza cada modelo, qué cuesta y cómo elegir para un sistema real.

Resumen

ACID garantiza consistencia fuerte mediante transacciones Atómicas, Consistentes, Aisladas y Duraderas — la base de datos rechaza cualquier cosa que dejaría los datos inválidos. BASE (Basically Available, Soft state, Eventually consistent) acepta inconsistencia temporal a cambio de disponibilidad y escala horizontal, trasladando el trabajo de corrección a la aplicación.

Ninguno es “mejor”. ACID paga un coste de coordinación por adelantado a cambio de corrección inmediata; BASE se salta la coordinación y la paga después en forma de código de resolución de conflictos. Equivocarse en cualquier dirección sale caro — ACID en todo se atasca bajo carga, BASE sobre datos financieros pierde dinero.

Como contexto: Eric Brewer — el del teorema CAP — acuñó BASE en un paper de ACM Queue de 2008, cuando almacenes NoSQL como Dynamo y Cassandra escalaban más allá de lo que la coordinación podía costear. El acrónimo es un chiste de química, pero el trade-off es real: relaja las garantías que da un “ácido” y el sistema sigue respondiendo cuando partes de él son inalcanzables.

Propiedades ACID

Atomicidad

Todas las operaciones de una transacción se completan con éxito, o ninguna lo hace. No hay completado parcial:

BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT;  -- Ambas tienen éxito, o ROLLBACK cancela ambas

Si el segundo UPDATE falla, el primero se revierte — la cuenta A no pierde 100 en el vacío.

Consistencia

Las transacciones llevan la base de datos de un estado válido a otro, preservando restricciones y reglas. Una transferencia no puede violar balance >= 0 ni romper una clave foránea, sin importar qué más esté corriendo.

Aislamiento

Las transacciones concurrentes no se interfieren entre sí; el resultado es como si se hubieran ejecutado de forma secuencial. Qué tan estricto es esto depende del nivel de aislamiento — ver la tabla de abajo.

Durabilidad

Una vez confirmados, los cambios sobreviven a los fallos. La escritura llega al WAL (o equivalente) antes de que el cliente reciba la confirmación, así que un reinicio la reejecuta en lugar de perderla.

Esa garantía es también la factura. Cada commit la paga: fsync al write-ahead log, contabilidad del gestor de locks y versiones MVCC mantenidas para los lectores. Ese es el coste de coordinación que BASE evita — pequeño en un solo nodo, doloroso cuando las mismas garantías tienen que abarcar réplicas y regiones.

Niveles de Aislamiento

El aislamiento es donde ACID deja de ser absoluto. Los niveles más bajos cambian corrección por concurrencia:

NivelDirty ReadNon-Repeatable ReadPhantom ReadCaso de Uso
Read UncommittedPosiblePosiblePosibleRaro, solo analítica
Read CommittedNoPosiblePosiblePor defecto en la mayoría de bases
Repeatable ReadNoNoPosibleOperaciones financieras de lectura
SerializableNoNoNoTransacciones financieras críticas

Dos notas prácticas: “por defecto en la mayoría” esconde diferencias reales — PostgreSQL usa Read Committed por defecto, MySQL InnoDB usa Repeatable Read. Y Serializable no es gratis: implica contención de locks o fallos de serialización que tu código tiene que reintentar.

La phantom read es la sutil — se cuela por Repeatable Read porque la anomalía no es una fila modificada sino una fila nueva que coincide con tu predicado. Ejecuta SELECT COUNT(*) FROM orders WHERE status = 'pending' dos veces dentro de una transacción y un insert concurrente puede cambiar la respuesta. Solo Serializable (o predicate locking explícito) lo previene.

Propiedades BASE

Básicamente Disponible

El sistema garantiza una respuesta a cada request — incluso bajo fallos parciales. Esa respuesta puede estar desactualizada o incompleta, pero llega. El trade-off se ve durante un fallo de nodo: donde un almacén ACID serializable agotaría el timeout o rechazaría la lectura, un almacén BASE responde con lo que tenga.

Estado Suave

El estado puede cambiar con el tiempo sin nuevo input, mientras las réplicas se ponen al día y reconcilian. Leer la misma clave dos veces puede devolver valores distintos.

Consistencia Eventual

Si las escrituras paran, todos los nodos convergen al mismo valor. Cuánto dura ese “eventualmente” depende del lag de replicación — normalmente milisegundos, pero sin cota bajo problemas de red:

flowchart diagram: Escritura X = 42

Durante la ventana de replicación, una lectura que cae en la Réplica B devuelve null. Si eso importa o no es toda la cuestión ACID-vs-BASE — un feed social absorbiendo esa ventana es invisible para los usuarios, mientras que un contador de inventario absorbiéndola sobrevende stock.

Comparación ACID vs BASE

AspectoACIDBASE
ConsistenciaFuerte (inmediata)Eventual (demorada)
Disponibilidad bajo particiónSacrificada — la escritura se rechazaPreservada — se aceptan lecturas viejas
CoordinaciónConsenso/locks al confirmarNinguna al confirmar; reconciliación después
Imposición de invariantesBase de datosCódigo de aplicación
Manejo de fallosRollback automáticoTransacciones compensatorias, reintentos
Mejor paraFinanciero, inventario, reservasSocial, analítica, caché, sesiones
Sistemas de ejemploPostgreSQL, MySQL (InnoDB)Cassandra, DynamoDB, Couchbase

Teorema CAP

El teorema CAP dice que un sistema distribuido puede garantizar como máximo dos de Consistencia, Disponibilidad y Tolerancia a Particiones. Como las particiones ocurren te guste o no, la elección real es CP vs AP — qué hace el sistema durante una partición:

  • CP (estilo ACID): rechaza las escrituras que no puede coordinar. Correcto, pero no disponible.
  • AP (estilo BASE): acepta las escrituras igualmente y reconcilia después. Disponible, pero temporalmente inconsistente.

La guía del teorema CAP cubre los casos límite — incluido por qué “2 de 3” es una simplificación (en realidad habla del comportamiento bajo partición, no de un menú de características).

La versión más afilada es PACELC: si hay Partición, elige entre Disponibilidad y Consistencia; si no, elige entre Latencia y Consistencia. Incluso sin partición, la replicación síncrona hace que cada escritura espere a otro nodo — los sistemas BASE cambian esa espera por velocidad. CAP describe el modo de fallo; PACELC también describe el estado normal.

La Pregunta Real: ¿Quién Posee el Invariante?

La dimensión decisiva no es escala ni velocidad — es quién impone los invariantes:

  • Invariantes de la base de datos → ACID. Si la corrección significa “la suma de estas dos cuentas nunca cambia” o “ningún asiento se vende dos veces”, el invariante abarca entidades y debe imponerse atómicamente. Eso es exactamente lo que hacen las transacciones ACID.
  • Lógica de la aplicación → BASE es viable. Si las operaciones son conmutativas o idempotentes (añadir al carrito, incrementar un contador, apendizar a un feed) y las lecturas viejas son tolerables, la aplicación puede reconciliar — y obtienes disponibilidad y throughput de escritura a cambio.

Hay una asimetría de costes que vale la pena interiorizar: el ACID entre particiones paga consenso en cada commit (un round-trip de quorum, o 2PC entre sistemas — dos servicios al 99,9 % de disponibilidad unidos por 2PC caen a ~99,8 % combinado). BASE paga esa misma complejidad después, como código de resolución de conflictos, handlers idempotentes y jobs de reconciliación que tienes que escribir y mantener.

Elegir Entre ACID y BASE

Elige ACID Cuando

  • Transacciones financieras (banca, pagos, trading) — el invariante es el dinero
  • Gestión de inventario — la sobreventa es un bug de corrección, no de UX
  • Sistemas de reserva — la doble reserva no se puede reconciliar después
  • Requisitos regulatorios que exigen registros exactos y auditables
  • El coste de la inconsistencia supera al de rechazar una request

Elige BASE Cuando

  • Feeds sociales y streams de actividad — un feed algo viejo está bien
  • Analítica, métricas, recomendaciones — aproximado es suficiente
  • Carritos y sesiones — estado fusionable y recuperable
  • Distribución de contenido y caché — la obsolescencia es el diseño, no un bug
  • Leaderboards, contadores de vistas, conteos de reacciones — los números aproximados son el producto
  • Alto throughput de escritura donde perder escrituras es peor que demorar consistencia

Consistencia Ajustable

ACID vs BASE no es estrictamente binario. Cassandra, DynamoDB y Cosmos DB permiten elegir consistencia por operación:

  • ONE — lee/escribe un solo nodo. Lo más rápido, la garantía más débil.
  • QUORUM — debe responder una mayoría. Con factor de replicación N=3 y QUORUM en ambos lados obtienes R + W > N — lecturas y escrituras se solapan, así que una lectura ve la última escritura confirmada.
  • ALL — deben responder todas las réplicas. Máxima consistencia, disponibilidad del nodo más lento.

La regla práctica: R + W > N te da consistencia fuerte para esa operación al coste de un quorum; R + W <= N es más rápido pero puede devolver datos viejos. Usa QUORUM para las operaciones que alimentan decisiones (checkout, consultas de saldo) y ONE para el resto (contadores de vistas, impresiones).

Cosmos DB expone el espectro de forma más explícita con cinco niveles: Strong (linealizable), Bounded Staleness (viejo como máximo K versiones o T tiempo), Session (read-your-writes dentro de una sesión), Consistent Prefix (ordenado pero posiblemente atrasado) y Eventual. Ese punto medio — Bounded Staleness y Session — suele ser el punto dulce práctico: lo bastante estricto para la UX, lo bastante barato para escalar.

Anomalías de Lectura en BASE y Cómo Manejarlas

La consistencia eventual tiene modos de fallo con nombre — manéjalos explícitamente en lugar de descubrirlos en producción:

  • Violación de read-your-writes — un usuario actualiza su perfil, recarga y ve el valor viejo porque la lectura cayó en una réplica atrasada. Mitiga con sticky sessions (enruta lecturas al nodo que tomó la escritura), read repair, o guardando un token de versión y verificándolo al leer.
  • Lecturas no monótonas — los valores parecen retroceder cuando las lecturas rebotan entre réplicas con distinto lag. Mitiga con garantías de lectura monótona (session tokens en Cosmos DB, patrones readConcern: "majority") o fijando la sesión a una réplica.
  • Actualizaciones perdidas en escrituras concurrentes — dos escrituras corren en paralelo y last-write-wins descarta una en silencio. Mitiga con relojes vectoriales o merge a nivel de aplicación (un carrito que une items en lugar de reemplazarlos).

Las escrituras tienen su propio modo de fallo durante una partición: split-brain. Dos nodos aceptan escrituras conflictivas para la misma clave y ambas “ganan” localmente hasta que la partición se cura. Todo lo de abajo — inventarios, feature flags, precios — queda bifurcado, y resolverlo exige una regla de merge determinista o intervención humana. Si tu dominio no puede expresar “fusionar dos valores conflictivos” de forma segura, esos datos no pertenecen a BASE.

Si tu caso de uso no tolera ninguna de estas ni siquiera bajo partición, esa es la señal de ir a ACID para esos datos.

Enfoques Híbridos

La mayoría de los sistemas reales dividen por criticidad del dato — ACID en el núcleo, BASE en los bordes:

flowchart diagram: subgraph App[Capa de Aplicación

La división sigue la regla del invariante: lo que la base de datos debe imponer se queda en ACID; lo que la aplicación puede reconciliar va a BASE. La guía de replicación de bases de datos cubre cómo el lag de réplicas crea la ventana BASE en primer lugar.

Implementar BASE con Sagas

Cuando necesitas escala BASE pero confiabilidad tipo ACID, las sagas dividen una transacción distribuida en transacciones locales con acciones compensatorias ante fallos:

class OrderSaga {
  async execute(order: Order): Promise<void> {
    try {
      await this.inventoryService.reserve(order.items);
      await this.paymentService.charge(order.total);
      await this.shippingService.schedule(order);
    } catch (error) {
      await this.compensate(order);
      throw error;
    }
  }

  private async compensate(order: Order): Promise<void> {
    await this.inventoryService.release(order.items);
    await this.paymentService.refund(order.total);
  }
}

Dos requisitos hacen que las sagas funcionen: cada paso necesita una compensación idempotente (reintentar release dos veces debe ser seguro) y necesitas el patrón outbox para publicar eventos — escribir el pedido y su evento en la misma transacción local y luego publicar de forma asíncrona. Las claves de idempotencia importan en cada salto: un charge reintentado sin clave de deduplicación es un cobro doble, exactamente el tipo de invariante que moviste al código de aplicación.

Una regla de diseño para el orden de los pasos: deja las acciones no compensables para el final. Un paso “enviar email de confirmación” no se puede des-enviar, así que corre solo después de que todo lo que podría disparar una compensación haya tenido éxito — o se encola tras el commit final en lugar de dentro de la saga.

El patrón de lock distribuido es la herramienta adyacente cuando necesitas acceso exclusivo entre servicios en lugar de compensación.

Escenario de E-commerce Híbrido

Una división concreta para un sistema con 500K pedidos/día:

ServicioModeloAlmacénConsistencia
PagosACIDPostgreSQLSerializable
InventarioACIDPostgreSQLRepeatable Read
PedidosACIDPostgreSQLRead Committed
CatálogoBASEMongoDBEventual
BúsquedaBASEElasticsearchCasi tiempo real
AnalíticaBASEClickHouseEventual

El flujo de pedido usa una saga: reservar inventario (ACID) → cobrar el pago (ACID) → crear el pedido (ACID) → publicar OrderCreated vía outbox (BASE, Kafka). Si el pago falla se compensa liberando la reserva; si el pedido falla se reembolsa y se libera.

La sincronización entre almacenes es deliberadamente demorada — los cambios de catálogo llegan a Elasticsearch por change streams (1-5 s), los pedidos llegan a analítica por consumers de Kafka (30-60 s). La tabla de mitigaciones es la parte que los equipos olvidan: catálogo viejo → caché TTL más refresh; review sin indexar → reindex programado; analítica atrasada → aceptar NRT; fallo de compensación de la saga → alerta más un job diario de reconciliación que cruza los almacenes.

Las lecciones que sobreviven al contacto con producción: ACID para el dinero, BASE para todo lo demás; el outbox resuelve el dual-write; las compensaciones deben ser idempotentes; y la reconciliación periódica es lo que detecta las inconsistencias silenciosas en las que nadie alertó.

Checklist de Decisión

Recorre esta lista por conjunto de datos — no por sistema:

PreguntaApunta a
¿Una respuesta incorrecta corrompe dinero, stock o reservas?ACID
¿Pueden dos réplicas ver valores distintos durante una partición?BASE solo si el sí es aceptable
¿Las escrituras conflictivas se pueden fusionar de forma determinista?BASE viable
¿Los usuarios actúan sobre el valor que acaban de escribir?BASE necesita manejo de read-your-writes
¿El dato es append-only (logs, eventos, métricas)?BASE encaja naturalmente
¿Un regulador exige registros exactos?ACID
¿Una operación abarca varias entidades?Transacción ACID

Si tres o más respuestas apuntan a ACID para un conjunto de datos, no lo forces — mantén esos datos en PostgreSQL y deja que los bordes vayan a BASE.

Errores Comunes

  • Usar ACID para todo — transacciones serializables sobre datos no críticos compran latencia y contención que no necesitabas.
  • Usar BASE para datos financieros — la consistencia eventual sobre saldos es como ocurren los dobles gastos y las sobreventas.
  • Ignorar la elección CAP — “nuestro sistema es consistente Y disponible” solo es cierto hasta la primera partición; decide qué pasa durante una.
  • No manejar las anomalías de lectura de BASE — lecturas viejas que alimentan escrituras (cobrar según un saldo leído con lag) es el incidente de producción clásico.
  • Tratar las sagas como ACID gratis — el código de compensación es corrección a cargo de la aplicación; si lo omites, tienes BASE con pasos extra y nada de la seguridad.
  • Un solo nivel de consistencia para todas las operaciones — con consistencia ajustable, ONE en una lectura de saldo y ALL en un contador de vistas están mal los dos; mapea el nivel a la tolerancia de cada operación.
  • Sin job de reconciliación — la consistencia eventual converge réplicas, no invariantes de negocio; algo tiene que verificar periódicamente que los pedidos cuadran con los pagos.
  • Asumir que “eventual” significa segundos — el lag de replicación no tiene cota durante particiones o sobrecarga. Si nadie alerta sobre el lag, “eventualmente” se convierte en “mañana” sin ruido.
  • No medir el lag de replicación — en un sistema BASE, el lag es una métrica de primera clase. No puedes razonar sobre datos viejos que no estás midiendo.

Ver También

Preguntas frecuentes

¿Una base de datos puede soportar ACID y BASE?

Sí — y muchas lo hacen. PostgreSQL da ACID en el primario mientras las réplicas de lectura sirven resultados efectivamente BASE. Cosmos DB expone cinco niveles de consistencia que eliges por operación, y Cassandra permite fijar ONE/QUORUM/ALL por query. El modelo suele ser por carga de trabajo, no por base de datos.

¿Cómo manejo los conflictos en sistemas BASE?

Tres estrategias comunes: last-write-wins con timestamps (la más simple, puede perder datos), relojes vectoriales (detecta escrituras concurrentes y la app resuelve), o diseñar las operaciones para que sean conmutativas — un carrito que fusiona items nunca tiene conflicto. La mejor opción suele ser hacer el conflicto imposible en lugar de resolverlo.

¿Es BASE más rápido que ACID?

Para escrituras bajo carga y entre regiones, sí — no hay round-trip de coordinación al confirmar. Pero BASE traslada el coste a la aplicación: handlers idempotentes, resolución de conflictos y jobs de reconciliación. Cambias tiempo de base de datos por tiempo de ingeniería.

¿Qué me da realmente la consistencia ajustable?

La capacidad de pagar por consistencia solo donde importa. Con factor de replicación 3, lecturas y escrituras QUORUM se solapan (R + W > N), así que una lectura de quorum ve la última escritura de quorum. ONE es rápido pero puede estar viejo. El error es usar un solo nivel para todo.

¿Cómo evito que los usuarios vean sus propias escrituras viejas?

Usa garantías de read-your-writes: sticky sessions que enrutan lecturas a la réplica que tomó la escritura, session tokens (Cosmos DB lo soporta de forma nativa), o escribir y releer con una lectura de quorum hasta converger. Una alternativa barata es UI optimista — mostrar el valor nuevo del lado del cliente mientras la replicación se pone al día.

¿Qué modelo elijo para un sistema de pagos?

ACID, sin dudarlo — los invariantes de pago (un cobro exactamente una vez, saldo nunca negativo) deben mantenerse incluso durante particiones. Las piezas alrededor del pago (notificaciones, analítica, visualización de recibos) pueden ser BASE. Traza el límite en el invariante, no en el servicio.

¿Puedo usar event sourcing en lugar de elegir?

Event sourcing es complementario, no un sustituto — registra un log de eventos append-only pero sigue necesitando un modelo de consistencia para proyecciones y lecturas. Suele combinarse con BASE: los eventos escriben rápido y las proyecciones convergen eventualmente. La guía de CQRS + event sourcing cubre la combinación.