mailwal

ACID Clearly Explained

Nikki Siapno from Level Up Coding

ACID Clearly Explained

Four guarantees. Four failure modes. Here's how they connect.


Choosing the Right Retrieval Strategy for Production RAG

Presented by Oracle

Vector search isn’t always the best approach for RAG. Keyword search handles exact matches, vector search finds semantic similarities, and SQL retrieves structured facts. Hybrid search combines retrieval methods when needed. Oracle’s technical guide explores how to compare these retrieval strategies, measure answer quality, and test for common production failures.

Read the Guide


ACID Clearly Explained

What does a database actually have to guarantee before it can safely tell you: “Transaction complete.”

Consider a money transfer.

To the user, moving $5,000 from one account to another looks like one action.

But inside the database, several things may need to happen:

  1. Subtract $5,000 from Account A.

  2. Add $5,000 to Account B.

  3. Record the transfer.

That works when everything goes perfectly.

But what if the database crashes after step one?

What if the transfer violates one of the database’s rules?

What if two transactions update the same account at the same time?

Or what if everything succeeds, the database confirms the transfer, and then the machine crashes?

These are four different failure modes. And they map closely to the four guarantees behind ACID:

ACID stands for Atomicity, Consistency, Isolation, and Durability.

Let’s walk through why each one exists.

Atomicity: all or nothing

Start with the most obvious failure.

You’re transferring $5,000 from your checking account to your savings account.

The database needs to:

  1. Subtract $5,000 from checking.

  2. Add $5,000 to savings.

But the system crashes after step one. Without protection, $5,000 could disappear from one account without ever reaching the other.

Atomicity prevents that.

It guarantees that the transaction either completes entirely or has no effect at all. From the application’s perspective, there are only two outcomes:

  • Commit → Every operation succeeds.

  • Rollback → None of the operations take effect.

The database may perform several operations internally, but they are treated as one indivisible transaction.

So atomicity solves the first problem: half-finished work.

But completing every operation is not enough.

The final result also needs to be valid.

Consistency: keep the data valid

Imagine the transaction finishes successfully and every write occurs.

But one of those writes leaves the database in a state that violates its rules.

That is a different kind of failure.

For example, every user in your database must have a unique email address and a transaction tries to create another account with an email that already exists.

The transaction may be technically complete, but the result would violate a constraint.

Consistency is about ensuring transactions move the database from one valid state to another. That might mean preserving rules such as:

  • Unique values → Two users cannot share the same unique identifier.

  • Foreign keys → An order cannot reference a customer that doesn’t exist.

  • Check constraints → A number cannot fall below an allowed value.

  • Application rules → Business logic may require certain conditions to remain true.

So atomicity asks: Did the whole transaction happen?

Consistency asks: Was the resulting state still valid?

There is one important catch: The database can only enforce the rules it knows about.

If an important business rule lives only inside your application, your transaction still needs to be designed to preserve it.

Now we have complete transactions that preserve our rules.

But another problem appears once multiple transactions run at the same time.

Isolation: what happens when transactions collide?

Real databases rarely process one transaction at a time. Hundreds or thousands of users may be reading and updating the same data simultaneously. Most of the time, that concurrency is exactly what we want.

But it creates new failure modes.

imagine there is one item left in stock. Two customers try to buy it at nearly the same moment.

Transaction A reads: inventory = 1

Transaction B also reads: inventory = 1

Both decide the item is available.

Both complete the purchase.

Each transaction may look perfectly reasonable on its own. Together, they have sold the same final item twice.

This is the problem isolation is designed to control.

Isolation determines how much concurrent transactions can see and interfere with each other’s work. The goal is to make simultaneous execution predictable rather than allowing transactions to accidentally depend on incomplete or conflicting changes.

But there’s a tradeoff.

Stronger isolation usually requires more coordination.

That can mean additional locking, retries, or reduced concurrency. For this reason, databases expose different isolation levels. Some provide stronger protection against concurrency anomalies, while others allow more concurrency in exchange for weaker guarantees.

So isolation is not always simply “on” or “off.” The exact behavior depends on the isolation level you choose.

At this point, our transaction can:

  • Complete fully.

  • Preserve database rules.

  • Behave correctly alongside other transactions.

There is still one final problem.

What happens after the database commits changes successfully?

Durability: committed means committed

Let’s go back to the example where a customer places an order.

The transaction succeeds. The database responds: Commit successful.

One second later, the machine crashes. When the database comes back online, should that order still exist?

It should.

That is what durability guarantees.

Once a transaction has committed, its changes should survive failures that happen afterward.

Databases usually accomplish this by making sure enough information reaches persistent storage before confirming the commit. One common mechanism is a write-ahead log, or WAL. Instead of relying only on changes sitting in memory, the database records transaction information in a log that can be used during recovery.

If the machine crashes, the database can use that log to restore committed changes.

Durability is what makes the word committed meaningful.

The database is not just saying: “The operation worked.”

It’s saying: “The operation worked, and I can recover it even if something goes wrong next.”

Four different problems, one transaction

This is the easiest way to think about ACID.

Imagine a transaction moving through four questions:

  • Did every step happen? → Atomicity

  • Did the result preserve the rules? → Consistency

  • Did concurrent transactions interfere with it? → Isolation

  • Will the result survive after commit? → Durability

Each property protects the transaction from a different kind of failure. Together, they make database updates much more predictable.

But ACID describes the guarantees.

It doesn’t tell the database exactly how to implement them.

How databases provide ACID

Databases combine several mechanisms to provide these guarantees.

  • Transaction managers → Group operations together and coordinate commits or rollbacks.

  • Write-ahead logs → Record changes so committed transactions can survive crashes.

  • Locks → Prevent certain conflicting operations from modifying the same data simultaneously.

  • MVCC → Multi-Version Concurrency Control keeps multiple versions of data so readers and writers can operate with less blocking.

  • Constraints → Enforce rules such as uniqueness, valid references, and permitted values.

Different databases combine these techniques differently.

PostgreSQL, for example, uses MVCC for concurrency control and a write-ahead log for crash recovery.

Distributed databases make the problem harder because one transaction may span multiple machines. Now several nodes may need to coordinate before the system can safely decide whether a transaction committed.

The implementation changes.

The four questions remain largely the same.

When ACID matters most

Not every database operation needs a complicated multi-step transaction.

But ACID becomes especially important when several related changes must behave like one logical operation.

For example:

  • Payments → A debit and credit need to stay synchronized.

  • Orders → Payment, inventory, and order state may need coordinated updates.

  • Inventory → Concurrent purchases shouldn’t sell the same limited item twice.

  • Account management → Related records need to remain internally consistent.

  • Booking systems → Two users shouldn’t reserve the same limited resource.

A useful way to think about it is: How expensive would it be if this operation were left half-finished, invalid, conflicting, or lost?

The higher that cost, the more important strong transactional guarantees become.

But stronger guarantees have a cost

It might sound like every operation should simply use the strongest transactional guarantees available.

But that introduces another tradeoff: Coordination costs time.

Transactions that lock large amounts of data, span several services, or require multiple machines to agree can increase latency and reduce throughput.

So stronger guarantees should be used where correctness requires them.

For other workloads, temporary inconsistency may be an acceptable tradeoff for lower latency, higher throughput, or greater availability.

The real goal is to identify which operations cannot safely tolerate partial completion, invalid state, concurrency conflicts, or lost writes?

That tells you where strong transactional guarantees matter most.

Putting it together

ACID is easier to remember when you stop thinking of it as four definitions and start thinking of it as four failure modes.

A transaction can fail because:

  • It only finishes halfway. Atomicity protects against that.

  • It leaves the database in an invalid state. Consistency protects against that.

  • It collides with another transaction. Isolation controls that.

  • Its committed result disappears after a failure. Durability protects against that.

Together, these guarantees give applications a safer way to change shared state.

Because production systems are messy. Servers crash. Requests overlap. Processes restart. Users perform actions at the same time.

ACID doesn’t prevent those things from happening.

It makes sure they don’t leave your data in a state you can’t trust.


👋 If you found this useful → Like + Restack to help others learn system design.


Subscribe to get high-signal and clear system design deep dives, case studies, and breakdowns straight to your inbox:

© 2026 Level Up Coding
548 Market Street PMB 72296, San Francisco, CA 94104