ACID and BASE
What each ACID property guarantees in practice, what BASE really describes, and why the two are ends of a spectrum rather than opposing camps.
ACID
Atomicity. A transaction either applies completely or not at all. Implemented with a write-ahead log or undo log: if the process dies mid-transaction, recovery rolls the partial work back.
Consistency. The database moves from one valid state to another, where "valid" means your declared constraints hold — foreign keys, check constraints, unique indexes. Note that this is the database enforcing your rules; it is unrelated to the C in CAP.
Isolation. Concurrent transactions do not observe each other's partial work. How much isolation you actually get depends on the isolation level, which is almost never the strictest one by default. See Transactions and Isolation Levels.
Durability. Once a commit is acknowledged, it survives a crash. This is where the guarantee is most often weaker than assumed — durability holds against process failure only if the write reached stable storage, and against node loss only if it reached another node.
BASE
BASE describes systems that relax ACID to stay available:
- Basically Available — the system responds to requests, possibly with stale or partial data.
- Soft state — state may change over time without new input, as replicas converge.
- Eventual consistency — given no new writes, all replicas eventually agree.
BASE is not a protocol or a standard. It is a label for the design point that prioritises availability and latency over immediate agreement, and it was coined largely as a rhetorical counterpart to ACID.
The spectrum in practice
Real engines are not in one camp:
The practical question is never "is this ACID". It is: what is the unit of atomicity, and does my data model fit inside it?
If your invariants fit inside a single document, MongoDB gives you atomicity for free. If they fit inside one Cassandra partition, batched writes to that partition are atomic. If they span entities, you need real transactions — or a design that tolerates temporary inconsistency and repairs it.
Working without multi-object transactions
When the engine cannot span your invariant, the application must:
- Model to avoid it. Put data that must change together in the same document, row or partition. This is the cheapest fix and the reason document and wide-column modelling is denormalised.
- Use idempotent operations. If a step can be safely retried, a partial failure becomes a retry rather than corruption.
- Use an outbox. Write the state change and an event row in one local transaction, then publish the event asynchronously. This is how you get exactly-once-looking behaviour across systems without distributed transactions. See Change Data Capture.
- Compensate explicitly. If step two fails, run a defined compensating action for step one. Sagas are this pattern with a name.