TL;DR — The Bank vs the Social Feed
- The two fundamental philosophies for managing data: correctness-first (ACID) and availability-first (BASE)
- Why you can't fully have both — and why that's not a problem in practice
- How real systems like banks and social media pick the right philosophy for each piece of data
- The chemistry metaphor hiding in the names and what it tells you about the spectrum between them
ACID guarantees your data is always correct. BASE guarantees your system is always available. You can't fully have both — so you pick the one that matters more for each piece of data.
Imagine you're transferring $500 from your savings account to your checking account. This is not a casual operation. The bank has to do two things: subtract $500 from savings and add $500 to checking. If the system crashes after subtracting but before adding — your money has vanished into thin air. That's not "a minor glitch." That's a lawsuit.
This is why banks use a philosophy called ACIDStands for Atomicity, Consistency, Isolation, Durability — four guarantees that a database transaction either fully completes or fully rolls back. Named to evoke chemistry: strong, precise, no room for ambiguity.. ACID guarantees that either both accounts update or neither does. The money never vanishes, never duplicates, never exists in both places at once. It's all-or-nothing — and the database will move heaven and earth to keep that promise, even if the server catches fire mid-transaction.
Now think about something very different: the "like" count on a social media post. Your photo has 4,023 likes. Someone in Tokyo taps the heart button. For the next 200 milliseconds, your phone still shows 4,023 while someone in London already sees 4,024. Is that a problem? Nobody notices. Nobody cares. The numbers will match up in a fraction of a second, and the world keeps spinning.
That's the philosophy called BASEStands for Basically Available, Soft state, Eventually consistent. The deliberate opposite of ACID — named as a chemistry pun. Prioritizes availability and speed over immediate correctness.. Instead of guaranteeing that every user sees the exact same data at the exact same instant, BASE says: "The system will always respond to you (it won't throw errors or go down), and the data will catch up and become correct eventually — usually within milliseconds." It prioritizes being available and fast over being perfectly synchronized at every moment.
The naming is intentional and clever. In chemistry, an acid and a base are opposites on the pH scale. The database community borrowed this metaphor on purpose: ACID is at one extreme (strong guarantees, precise, no room for error) and BASE is at the other extreme (relaxed guarantees, flexible, tolerates temporary imprecision). And just like in chemistry, most real systems live somewhere in the middle — using ACID for the data that absolutely must be correct (money, medical records, inventory counts) and BASE for the data where speed and availability matter more than perfect synchronization (likes, views, recommendations, analytics).
Here's the key insight: it's not ACID or BASE for your whole system. It's ACID for this piece of data and BASE for that piece of data. The art of system design is knowing where to draw that line.
What: ACID and BASE are two philosophies for handling data. ACID says "data must always be correct, even if the system has to slow down or reject requests." BASE says "the system must always respond, even if the data is temporarily stale."
When: Use ACID for data where mistakes cost real money or cause real harm — financial transactions, medical records, inventory. Use BASE for data where brief staleness is harmless — social media feeds, analytics, recommendations, caches.
Key Principle: It's a spectrum, not a binary choice. Most real systems use ACID for their critical paths and BASE for everything else. The skill is knowing which data needs which guarantee.
The Scenario — One Transaction, Two Philosophies
Let's make this concrete with a story you'll encounter in every system design interview and every real codebase: placing an order on an e-commerce platform.
A customer finds a pair of headphones for $50, clicks "Buy Now," and expects the magic to happen. But behind that single button click, the system has to do five things:
- Charge $50 to their payment method
- Reduce inventory by 1 (so someone else doesn't buy the last pair)
- Create the order record in the database
- Send a confirmation email
- Update analytics (total revenue, popular products, etc.)
All five need to happen. But here's the critical question: do they all need to happen at the same instant, inside the same transaction? Or can some happen a few seconds later? The answer to that question determines whether you reach for ACID or BASE — and the answer is different for different steps.
Look at what happened: the ACID approach wraps the three critical steps (payment, inventory, order) into a single transactionA group of database operations that are treated as a single unit. Either all of them succeed (commit) or all of them are undone (rollback). Think of it like a contract — everyone signs, or the deal is off.. If inventory can't be reduced (the item is out of stock), the payment is automatically rolled back. You never end up in a state where money was taken but the item wasn't available. The email and analytics? Those can happen separately — they're not part of the critical "must be correct" path.
The BASE approach, on the other hand, breaks every step into its own independent service. The payment service charges the card and publishes an event saying "payment succeeded." The inventory service hears that event and tries to reduce stock. If it can't (out of stock), it publishes its own event — "refund needed" — and the payment service hears that and issues a refund. Everything eventually gets to the right state, but there's a brief window (maybe a few hundred milliseconds) where the payment is taken and the inventory hasn't been checked yet.
You might be wondering: "Why would anyone choose BASE? The ACID approach sounds safer." And you're right — for a single database on a single server, ACID is almost always the answer. But the moment your system grows to multiple servers in multiple data centers, ACID becomes extremely expensive. Coordinating a transaction across servers in New York and Tokyo means they have to talk to each other, agree, and lock data while they wait. That coordination adds latency (hundreds of milliseconds of waiting) and creates fragility (if one server is unreachable, the whole transaction fails). BASE says: "let each server handle its own piece, and we'll reconcile afterward." That's why every large-scale system — Amazon, Google, Netflix, Facebook — uses BASE for the majority of their data.
ACID Explained — The Four Guarantees
ACID isn't one thing — it's four separate promises that a database makes about every transaction. Each letter is a different guarantee, and each guarantee costs something in terms of performance. Let's walk through them one by one, in plain English first, then with the formal definition, then with the "why it matters" reasoning.
A — Atomicity: "All or Nothing"
Think of a light switch. It's either on or off. There's no "half-on" state where the light is sort of flickering in a quantum superposition of brightness. A transaction works the same way: it either completes fully (every single operation inside it succeeds) or it completes not at all (every operation is undone, as if the transaction never happened).
Why does this matter? Imagine that $500 bank transfer again. The database has to do two things: subtract $500 from savings, add $500 to checking. If the system crashes after the subtraction but before the addition, without atomicity your $500 has literally vanished. It left savings but never arrived in checking. Atomicity prevents this nightmare: if anything goes wrong mid-transaction, the database undoes everything and puts you back to exactly where you started.
How does it actually work under the hood? Databases use something called a write-ahead logA file where the database writes "what I'm about to do" BEFORE actually doing it. If the system crashes, it reads this log on restart and either completes or undoes the half-finished work. Think of it like writing your shopping list before going to the store — if you get interrupted, you know exactly where you left off. (WAL). Before making any change to the actual data, the database first writes down "here's what I'm about to do" in a separate log file. If the system crashes mid-transaction, the database reads the WAL on restart and says: "Ah, this transaction never finished — let me undo the parts that did happen." The WAL is like a flight recorder for your data.
C — Consistency: "Data Always Follows the Rules"
Every database has rules. You might say "an account balance can never go below zero." Or "every order must reference a customer that exists." Or "email addresses must be unique." These rules are called constraintsRules enforced by the database itself — not by your application code. Examples: NOT NULL (field can't be empty), UNIQUE (no duplicates), CHECK (value must meet a condition), FOREIGN KEY (must reference an existing row). If a transaction violates any constraint, the database rejects it., and the "C" in ACID guarantees that every transaction moves the database from one valid state to another valid state. No transaction is allowed to leave the data in a state that breaks any rule.
If you try to withdraw $600 from an account with $500, and you have a constraint that says "balance >= 0," the database rejects the entire transaction. Not "it sets the balance to -$100 and hopes someone notices." It flat-out refuses. The data stays clean.
I — Isolation: "Transactions Don't Interfere"
This one is the trickiest to understand — and the most expensive to implement. Imagine two people are trying to book the last available concert ticket at the exact same moment. Both of them check: "Is the ticket available?" Both see "yes." Both click "Book." Without isolation, both bookings succeed, and you've just sold one ticket to two people. Congratulations, you've got an angry customer and a refund to process.
Isolation guarantees that concurrent transactionsTransactions happening at the same time — like two users checking out simultaneously. Without isolation, they can read each other's half-finished work and make decisions based on data that's about to change. don't interfere with each other. Even though they're running at the same time on the same database, each transaction behaves as if it's the only one. The first person to book gets the ticket. The second person's transaction sees "sold out" and is rejected.
Here's the expensive part: perfect isolation (called serializable isolationThe strictest isolation level. Transactions execute as if they ran one-at-a-time in sequence, even though they actually run concurrently. Guarantees no anomalies at all — but can be 10-100x slower than lower isolation levels because transactions often have to wait for each other.) is slow because transactions effectively have to wait in line. So most databases offer a menu of isolation levels — from "barely any isolation, very fast" to "perfect isolation, quite slow." You pick the one that matches your needs. We'll explore these levels in detail on the Isolation Levels page, but for now, just know that isolation is a dial, not a switch.
D — Durability: "Once Committed, It's Permanent"
This one is the simplest to understand. When the database says "transaction complete" — that data is safe forever. Not "safe until the next power outage." Not "safe unless the disk fails." Actually, permanently, written-to-physical-storage safe. Even if the server catches fire one millisecond after the commit message, that data survives.
How? The database writes committed data to non-volatile storageStorage that keeps data even when the power is off — like SSDs, hard drives, or NVMe. The opposite of volatile storage (RAM), which loses everything on power loss. "Non-volatile" literally means "doesn't evaporate." (the disk). The WAL we mentioned earlier is part of this: it's written to disk before the commit is acknowledged. Many systems also replicate the data to another server in a different physical location, so even a total data center fire doesn't lose the data.
Without durability, a database is just an expensive notepad. You write something down, the power flickers, and it's gone. Durability is what turns a database from a temporary scratchpad into a permanent record — and it's why databases are slower than in-memory caches like RedisAn in-memory data store — lightning fast because it reads and writes to RAM instead of disk. But by default, data in Redis is volatile: if the server restarts, data is lost (though Redis can be configured with persistence). Great for caches, not for your primary data store.. Writing to disk is slower than writing to RAM, but disk doesn't forget.
BASE Explained — The Three Trade-offs
BASE was coined as the deliberate, tongue-in-cheek opposite of ACID. Where ACID says "I will guarantee correctness no matter the cost," BASE says "I will guarantee that your system stays up and responds fast — and the data will sort itself out shortly." It's what you get when you decide that availability and speed matter more than instant perfection.
Let's break down each letter. Fair warning: the acronym is a bit forced (the researchers clearly picked the name for the chemistry pun first and backfilled the letters). But the underlying concepts are genuinely important.
B.A. — Basically Available
"Basically available" means the system always gives you a response — even if the response isn't perfectly up-to-date. Compare this to ACID, where if the database can't guarantee correctness, it returns an error and refuses to answer.
Think about browsing Amazon. You search for "wireless headphones" and see "In Stock" on a product page. That stock status is probably based on cached dataData that was copied from the main database and stored closer to the user for faster access. It might be a few seconds (or minutes) behind the real data. Like a printed menu at a restaurant — it's fast to read, but the kitchen might have run out of the fish since it was printed. that's a few seconds old. The real inventory might have changed since the cache was last updated. But you see something — a useful, mostly-accurate answer — instead of a spinning wheel or an error page. If the system had to check the actual inventory database in real-time and that database was slow or temporarily unreachable, you'd get nothing. Basically available says: a good-enough answer now is better than a perfect answer in 5 seconds (or never).
During extreme load or a partial outage, a BASE system might degrade gracefully: search results might be slightly stale, recommendations might not be personalized, or a feature might be temporarily simplified. But the core experience stays alive. You can still browse, still add to cart, still check out. An ACID system in the same situation might just refuse connections entirely — "Sorry, database is overloaded, try again later."
S — Soft State
This one is subtle. In an ACID system, the database's state is always "hard" — meaning it's definitive, authoritative, and the same everywhere. If you query the database, you get THE answer. In a BASE system, the state is "soft" — meaning it can change over time even without new input, because data is still propagating between nodes.
Here's a concrete example: you post a photo on a social media platform that has servers in five data centers around the world. The moment you click "Post," your photo is written to the nearest data center (say, US-East). For the next few seconds, the other four data centers don't have it yet — the data is still being replicated across the network. During those seconds, your friend in London opens the app and doesn't see your photo. Your friend in Tokyo doesn't see it either. But you, in New York, can see it just fine.
The system's state is "soft" because there's no single, globally consistent answer to "has this photo been posted?" The answer is "yes in US-East, not yet everywhere else." The state is in flux, converging toward consistency but not there yet. This is completely fine for a photo — nobody is harmed by a 2-second delay. It would be completely unacceptable for a bank balance.
E — Eventually Consistent
This is the most important concept in BASE, and the one you'll hear most often in system design discussions. Eventual consistencyA consistency model where, if no new updates are made to a piece of data, all nodes will eventually return the same value. "Eventually" is usually milliseconds to seconds, but there's no hard guarantee on the exact timing. means: if you stop writing new data and just wait, all copies of the data across all nodes will eventually converge to the same value. There's no guarantee on exactly how long "eventually" takes, but in practice it's usually milliseconds to a few seconds.
The best real-world example is DNSThe Domain Name System — the internet's phone book. It translates domain names (like google.com) into IP addresses (like 142.250.80.46). DNS is eventually consistent: when you change a domain's IP, the update propagates across thousands of DNS servers over minutes to hours, depending on TTL settings. — the internet's address book. When a company changes the IP address for their website, that change doesn't instantly appear everywhere. DNS servers around the world have cached the old address with a TTLTime To Live — a timer on cached data that says "throw this away and fetch a fresh copy after X seconds/minutes/hours." DNS records commonly have TTLs of 300 seconds (5 minutes) to 86400 seconds (24 hours). (time-to-live). Over the next minutes or hours, each DNS server's cache expires, and they fetch the new address. During this window, some users reach the old server and some reach the new one. Eventually, every DNS server has the new address, and everyone agrees. That's eventual consistency in action — at global scale, happening billions of times a day.
Here's why BASE exists in the first place: ACID requires coordination between nodes. If you have a database spread across 100 servers in 5 countries, an ACID transaction means those servers have to talk to each other, agree on the order of operations, and lock data while they negotiate. This coordination uses protocols like two-phase commitA protocol where a coordinator asks all participants "are you ready to commit?" (phase 1). If ALL say yes, the coordinator says "commit" (phase 2). If ANY say no, everyone rolls back. It guarantees consistency but is slow — every participant must wait for every other participant, and if the coordinator crashes, everyone is stuck waiting., which are slow (round-trips across the ocean take time) and fragile (if one server is unreachable, the whole transaction fails). BASE says: "skip the coordination. Let each server handle its piece independently, and we'll reconcile the differences afterward." That's how you get systems that serve billions of users across the globe without falling over.
ACID vs BASE — Side by Side
You've now seen both philosophies up close. ACID says "I will never give you wrong data, even if I have to make you wait." BASE says "I will always give you an answer, even if it's slightly stale." Time to put them head to head and see exactly where each one wins, loses, and makes trade-offs.
Here's the thing nobody tells you upfront: these aren't competing standards — they're opposite ends of the same dial. Every database sits somewhere on a spectrum between "perfectly correct but potentially slow" and "blazing fast but temporarily imprecise." Understanding the six dimensions below is how you turn that dial for each part of your system.
Let's walk through each dimension so the trade-offs are crystal clear.
Data correctness. With ACID, every read returns the latest committed data. If you deposit $500 and immediately check your balance, you see it — guaranteed. With BASE, you might see the old balance for a few milliseconds (or seconds, depending on the system). For a banking app, that's terrifying. For a social media like counter, nobody cares.
Availability. ACID databases sometimes say "no" to protect correctness. If two transactions collide, one gets aborted. If the primary server is down, writes stop until it recovers (unless you have a failover setup). BASE databases almost never say no. Even if some nodes are down, the remaining nodes keep serving requests. That's why Cassandra and DynamoDB are favorites for systems that must be up 24/7.
Performance. Every ACID transaction pays a tax: writing to the WAL, acquiring locks, coordinating across indexes. That overhead might be 1-5ms per transaction — trivial for a banking app doing 1,000 transactions/second, but a dealbreaker for a system doing 1,000,000 writes/second like Discord's message pipeline. BASE skips most of that overhead, so writes are faster.
Horizontal scaling. This is where BASE really pulls ahead. To scale an ACID database across multiple servers, you need distributed transactionsA transaction that spans multiple database servers. All servers must agree to commit or all must roll back. This requires coordination protocols like 2PC (Two-Phase Commit), which are slow and fragile. If one server is unreachable, the entire transaction stalls. — which are slow, fragile, and complex. BASE databases were designed for multiple servers from day one. Cassandra, DynamoDB, and MongoDB all spread data across nodes with minimal coordination.
Application complexity. This is the hidden cost of BASE. With ACID, you write BEGIN; UPDATE ...; UPDATE ...; COMMIT; and the database handles all the hard stuff — rollbacks, retries, isolation. With BASE, your application code must handle retries when writes fail, conflict resolution when two users edit the same data, idempotency keys to avoid duplicate operations, and compensation logic to "undo" partial failures. That's a LOT more code to write, test, and maintain.
Comprehensive Comparison Table
| Dimension | ACID | BASE |
|---|---|---|
| Data guarantee | Always correct after commit | Eventually correct (convergence) |
| Availability | May reject requests to protect correctness | Always responds, even during partitions |
| Read freshness | Latest committed value, always | Might be stale for milliseconds to seconds |
| Write speed | Slower (WAL + lock overhead) | Faster (write locally, replicate async) |
| Horizontal scaling | Hard — distributed transactions are expensive | Natural — designed for multi-node from day one |
| Application code | Simple — DB handles consistency | Complex — app handles retries, conflicts, idempotency |
| Failure handling | Automatic rollback | Compensating transactions (you write them) |
| Best for | Money, inventory, auth, compliance | Social feeds, analytics, caching, search |
| Example databases | PostgreSQL, MySQL, Oracle, SQL Server | Cassandra, DynamoDB, Redis, CouchDB |
| Cost of being wrong | Catastrophic (lost money, security breach) | Tolerable (stale feed, missed metric) |
When to Choose ACID vs BASE
Don't pick ACID or BASE based on the database name. Pick based on the data. What kind of data is it? What happens if it's wrong? What happens if it's slow? Those three questions give you the answer every single time.
Here's the mental shortcut that senior engineers use: ask yourself, "What's the cost of a wrong answer vs. a slow answer?" If a wrong answer could cause legal trouble, lose money, or create a security hole — ACID. If a wrong answer just means someone sees a slightly stale like count for 200 milliseconds — BASE.
Use ACID When...
Use BASE When...
Real-World Database Spectrum
Here's something that trips up a lot of people: databases aren't strictly "ACID" or "BASE." They sit on a spectrum, and many let you tune where you land on that spectrum per query. Cassandra lets you choose between fast-but-stale and slow-but-consistent on every single read. MongoDB added ACID transactions in version 4.0 after years of being purely BASE. The lines are blurring fast.
Let's walk through the most popular databases and see exactly where each one sits, what guarantees it offers out of the box, and what you can tune.
Guarantees: Full ACID. Every transaction is atomic, consistent, isolated, and durable by default.
Default isolation: Read CommittedThe default isolation level in PostgreSQL. Each statement in a transaction sees only data committed before that statement began. It prevents dirty reads (seeing uncommitted changes) but allows non-repeatable reads (same query returns different results if another transaction commits between reads). — good enough for most applications. You can bump it up to SERIALIZABLE for maximum safety (Stripe does this for payments).
Best for: Financial systems, inventory management, user accounts, anything where correctness is non-negotiable. If you're building a startup and aren't sure what database to use, PostgreSQL is almost always the right default.
Scaling story: Traditionally single-node, but extensions like CitusA PostgreSQL extension that turns it into a distributed database. It shards your tables across multiple nodes while keeping ACID guarantees. Used by companies like Microsoft (Azure Cosmos DB for PostgreSQL) and Algolia. add horizontal scaling. Read replicas handle read-heavy workloads.
Guarantees: Full ACID (with the InnoDB engine — the default since MySQL 5.5). The older MyISAM engine is NOT ACID, so always check your engine.
Default isolation: Repeatable ReadMySQL's default is one step stricter than PostgreSQL's. Within a transaction, the same SELECT always returns the same data, even if other transactions commit changes. MySQL achieves this using MVCC (Multi-Version Concurrency Control) — it keeps snapshots of the data as it was when your transaction started. — one level stricter than PostgreSQL's default. Same SELECT within a transaction always returns the same rows.
Best for: Web applications, e-commerce, content management. Powers WordPress, Facebook (partially), GitHub, and most of the internet's LAMP stack applications.
Scaling story: Similar to PostgreSQL — primary + read replicas. MySQL Group Replication and tools like Vitess (used by YouTube) add horizontal write scaling.
Guarantees: Single-document operations have always been atomic (and since most applications model related data in one document, this covers 80%+ of use cases). Multi-document ACID transactions were added in version 4.0 (2018) — a game-changer that moved MongoDB firmly toward the center of the spectrum.
Default behavior: Single-document atomicity. Multi-document transactions are opt-in and add overhead (about 10-20% slower than without), so use them only when you actually need cross-document consistency.
Best for: Applications with document-shaped data (user profiles, product catalogs, content management). Great when your data doesn't fit neatly into rows and columns.
The catch: Multi-document transactions across sharded clusters are possible but expensive in terms of latency. If you find yourself needing lots of multi-document transactions, consider whether a relational database would be a better fit.
Guarantees: Eventually consistent readsWhen you read from DynamoDB, you might get data that's a fraction of a second old. The write went to one replica, and it hasn't propagated to all replicas yet. For most reads this is fine, and it's faster and cheaper than consistent reads. by default. Strongly consistent reads cost 2x and are available per-request. Transactions (added in 2018) support up to 100 items per transaction but are limited and expensive.
Default behavior: Eventually consistent. You explicitly opt into strong consistency on a per-read basis. Most applications leave the default because the staleness window is typically under 100ms.
Best for: Massive-scale applications that need single-digit millisecond latency at any scale. Shopping carts, user sessions, gaming leaderboards, IoT data. AWS-native architectures where you want zero database administration.
The catch: Transactions are limited to 25 items for writes, 100 items for reads. No cross-table transactions. If your transaction patterns are complex, DynamoDB will fight you.
Guarantees: This is the most interesting one. Cassandra lets you choose your consistency level on every single read and write. It's like having a dial that goes from "fast but possibly stale" to "slow but guaranteed correct."
How tuning works: You set how many replica nodesCassandra stores copies of your data on multiple servers (replicas). The replication factor (RF) controls how many copies exist. With RF=3, every piece of data lives on 3 different servers. When you write, you can choose how many of those 3 must confirm the write before Cassandra tells you "success." must acknowledge a read/write. With a replication factor of 3:
ONE— fastest. Write to one node, return immediately. Other nodes catch up asynchronously. Maximum speed, weakest consistency.QUORUM— balanced. Majority of nodes (2 out of 3) must acknowledge. Good balance of speed and consistency. Most production systems use this.ALL— strictest. Every replica must acknowledge. Strong consistency, but if any single node is down, the operation fails. Rarely used.
Best for: Write-heavy, globally distributed systems. Discord (messages), Netflix (viewing history), Apple (iCloud). Systems where you need to write fast in multiple data centers simultaneously.
Guarantees: None by default — Redis is an in-memory data store. If the server crashes, data in RAM is gone. You can add persistenceRedis offers two persistence mechanisms: RDB (periodic snapshots saved to disk) and AOF (Append-Only File that logs every write command). RDB is faster but you lose data since the last snapshot. AOF is more durable but slower. Many production setups use both: AOF for safety, RDB for fast restarts. with RDB snapshots or AOF logging, but these add latency and still aren't true ACID.
Default behavior: Pure in-memory. Reads and writes in microseconds. No transactions in the traditional sense (though Redis does support MULTI/EXEC for atomic command batches).
Best for: Caching (the #1 use case), session storage, rate limiting, real-time leaderboards, pub/sub messaging. Anything where speed is the absolute priority and data loss is tolerable.
The catch: Redis is typically a complement to a database, not a replacement. Use PostgreSQL for your source of truth, Redis for the fast copy. If Redis crashes, your app should degrade gracefully (slower reads from the database) rather than fail.
Guarantees: Full ACID transactions across globally distributed nodes. These are the "have your cake and eat it too" databases — strong consistency AND horizontal scaling. Sounds like magic, and the engineering behind it kind of is.
How they work: Google Spanner uses atomic clocksGoogle's TrueTime API uses GPS receivers and atomic clocks in every data center to give every transaction a globally-accurate timestamp. This lets Spanner order transactions across the entire planet without nodes needing to communicate with each other first. It's the most expensive clock system ever built for a database. (yes, actual atomic clocks in data centers) to timestamp transactions globally. CockroachDB achieves similar results with hybrid logical clocksA cheaper alternative to atomic clocks that combines physical timestamps with logical counters. Less precise than TrueTime but good enough for most applications. The trade-off: CockroachDB may need to wait a few extra milliseconds to ensure ordering, while Spanner's atomic clocks minimize that wait. — a software-only approach that doesn't require specialized hardware.
Best for: Global applications that need ACID guarantees across regions — global banking, multi-region e-commerce, any system where you need strong consistency AND geographic distribution.
The catch: Expensive and complex. Spanner is only available on Google Cloud. CockroachDB is open-source but running a distributed SQL database still requires significant operational expertise. For most applications, a single-region PostgreSQL is simpler and sufficient.
The Hybrid Approach — Mixing ACID and BASE in One System
Here's the reality that no "ACID vs BASE" tutorial tells you: every serious production system uses both. Not either/or. Both. In the same application. At the same time. The question isn't "should I use ACID or BASE?" — it's "which parts of my system need which guarantee?"
Think about Amazon. When you buy something, the payment processing hits a PostgreSQL-like ACID database — because losing a payment is catastrophic. But the product recommendations you see? That's a BASE systemAmazon's recommendation engine uses distributed data stores that prioritize availability and speed over perfect consistency. If the recommendation engine shows you a slightly stale suggestion, nobody notices. If the payment system loses your money, it's on the front page of Hacker News. that updates asynchronously — because showing you a slightly stale recommendation is totally fine.
Notice the pattern? The ACID zone is small — just three core tables (orders, inventory, user accounts). Everything else lives in the BASE zone, optimized for speed and availability. This is how real architectures work. You don't pick one philosophy for the whole system. You assign the right guarantee to each piece of data.
The Saga Pattern: Transactions Across Services
Here's where it gets interesting. What happens when a single "transaction" spans multiple services, each with its own database? For example, placing an order involves: (1) charge the credit card, (2) reserve inventory, (3) create shipping label. Three different services, three different databases. You can't wrap all three in a single BEGIN; ... COMMIT; because they're separate systems.
This is where the Saga patternA design pattern for managing transactions that span multiple services. Instead of one big atomic transaction, each service executes its own local transaction and publishes an event. If any step fails, previously completed steps are "undone" by compensating transactions. It's ACID at the service level, BASE at the system level. comes in. Each service runs its own local ACID transaction. If everything succeeds, great — the order is complete. If step 3 (shipping) fails, you run compensating transactionsThe "undo" operations for each step. If you charged the credit card in step 1 and reserved inventory in step 2, but step 3 fails, you issue a refund (compensating for step 1) and release the inventory (compensating for step 2). Writing these compensating transactions is your responsibility — the database won't do it for you. to undo steps 1 and 2 — refund the card, release the inventory. It's like ACID at the micro level and BASE at the macro level.
Why Not Just Use 2PC?
You might be thinking: "Why not use Two-Phase Commit (2PC)A protocol for distributed transactions. A coordinator asks all participants "Can you commit?" (Phase 1: Prepare). If all say yes, it says "Commit" (Phase 2: Commit). If any say no, it says "Rollback." Sounds great in theory, but if the coordinator crashes between Phase 1 and Phase 2, all participants are stuck holding locks, waiting for a decision that may never come. This is called the "blocking problem." to make the whole thing ACID?" You can — and some systems do. A coordinator asks all three services "can you commit?" If all say yes, it says "go." If any say no, everyone rolls back.
The problem: if the coordinator crashes after asking "can you commit?" but before saying "go," all three services are stuck. They're holding locks, waiting for a decision that never comes. In microservice architectures, where network partitions happen regularly, this is a real and frequent problem. That's why most modern distributed systems prefer sagas over 2PC — a saga might leave the system in a temporarily inconsistent state, but at least nothing is stuck.
Quick Reference: What Goes Where
| Component | Database | Guarantee | Why |
|---|---|---|---|
| Orders & payments | PostgreSQL | ACID | Losing money is unacceptable |
| Inventory | PostgreSQL | ACID | Double-selling is catastrophic |
| User accounts | PostgreSQL | ACID | Wrong auth state = security breach |
| Session cache | Redis | BASE | Speed matters, stale session is fine for 1s |
| Product search | Elasticsearch | BASE | 2s indexing delay is invisible to users |
| Event pipeline | Kafka | BASE | Async processing, 0.01% loss tolerable |
| Shopping cart | DynamoDB | BASE | Conflict merge keeps items from both devices |
| Rate limiting | Redis | BASE | Approximate counts are fine for rate limits |
Isolation Levels — The ACID Consistency Dial
We've talked about the "A" (Atomicity), the "C" (Consistency), and the "D" (Durability) in ACID. Now we hit the hard one: the "I" — Isolation. This is where databases get interesting, because isolation isn't a simple on/off switch. It's a dial. You can turn it up for maximum correctness (but slower), or turn it down for speed (but riskier). Every database lets you choose where to set this dial, and the setting you pick has real consequences for your application.
To understand why this dial exists, you first need to understand what goes wrong when multiple transactions run at the same time. Imagine a busy restaurant kitchen: if two chefs both reach for the same ingredient at the same time, things get messy. Databases have the same problem. Three specific messes can happen, and each isolation level prevents different ones.
The Three Problems Isolation Prevents
Problem 1: Dirty Read — You read someone else's unfinished work. Transaction A starts updating a bank balance from $1,000 to $500 (deducting $500), but hasn't committed yet. Transaction B reads the balance and sees $500. Then Transaction A fails and rolls back — the balance was always $1,000. But Transaction B already acted on $500, a value that never actually existed in the database. It's like reading a text message that someone typed but decided to delete before sending. You saw a draft that was never meant to be real.
Problem 2: Non-Repeatable Read — You read the same thing twice and get different answers. You're running a report that reads the balance on row #42. It says $1,000. While your report is still running, another transaction updates row #42 to $800 and commits. Your report reads row #42 again (maybe for a different calculation) and now it says $800. Same row, same transaction, two different answers. It's like checking your bank balance, going to get coffee, checking again three minutes later, and seeing it changed because a bill was auto-paid between your two checks. Your "view" of the data wasn't stable.
Problem 3: Phantom Read — New rows appear out of nowhere during your transaction. You run SELECT COUNT(*) FROM orders WHERE status = 'pending' and get 42. While your transaction continues, another transaction inserts 3 new pending orders and commits. You run the same query again and get 45. The individual rows you read before are unchanged (that's not a non-repeatable read) — but new rows materialized that match your query. It's like counting people in a room, turning your back for a moment, counting again — and three more people snuck in through the door.
The Four Isolation Levels
Now here's the dial. SQL databases define four isolation levels, from weakest (fast but risky) to strongest (slow but bulletproof). Think of it like locking a door: you can leave it wide open (fast to enter, no security), use a basic lock (decent security), a deadbolt (strong security), or a bank vault door (maximum security, takes 30 seconds to open).
Read Uncommitted is the "no locks" level. Your transaction can see changes from other transactions even before they commit. This means you might read data that gets rolled back — data that never "really" existed. Almost no production system uses this because the risk is too high. It exists mostly for rare, performance-critical read-only analytics where approximate numbers are acceptable.
Read Committed is the sensible default, and it's what PostgreSQLPostgreSQL defaults to Read Committed isolation. You can check yours right now: SHOW default_transaction_isolation; You can change it per-transaction with SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; or globally in postgresql.conf. uses out of the box. Your transaction only sees data that has been committed by other transactions. No more reading drafts. But if another transaction commits a change while yours is running, your next read will see that new value. For 90% of web applications — product pages, user profiles, dashboards — this is perfectly fine.
Repeatable Read is MySQL InnoDB'sMySQL's default storage engine, InnoDB, uses Repeatable Read as its default isolation level. Interestingly, MySQL's implementation of Repeatable Read actually prevents phantom reads too (using gap locks), making it stronger than the SQL standard requires. Check yours: SELECT @@transaction_isolation; default. Once your transaction reads a row, that row's value is "frozen" for the duration of your transaction — even if another transaction modifies and commits it. Your report that reads the same balance twice will get the same answer both times. The downside: the database has to keep track of which version of each row your transaction should see, which uses more memory and CPU.
Serializable is the strongest guarantee. The database behaves as if every transaction ran one at a time, in sequence, with no overlap at all. Every problem — dirty reads, non-repeatable reads, phantom reads — is prevented. This is what banks and financial systems use for money transfers. The cost? The database must detect and abort conflicting transactions, which means some transactions will fail and need to be retried. Throughput drops significantly under high concurrency.
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
You're building a ticket booking system. Two users try to book the last seat on the same flight at the exact same time. What isolation level do you need, and why would Read Committed fail here?
With Read Committed, both users could read "1 seat available," both proceed to book, and you've oversold the flight. You need at least Repeatable Read (so the row is locked once read) or Serializable (so one transaction blocks until the other finishes). In practice, most booking systems use SELECT ... FOR UPDATE to explicitly lock the row, which works at Read Committed level and avoids the overhead of Serializable for the entire transaction.Common Misconceptions — What People Get Wrong
ACID and BASE are surrounded by outdated myths, oversimplified blog posts, and interview answers that were true in 2010 but aren't anymore. These six misconceptions trip up engineers at every level — from juniors who read one Medium article to seniors who learned databases before distributed systems became mainstream. Let's clear each one up.
The myth: ACID guarantees require coordination between servers, coordination is slow, therefore ACID databases can't handle large-scale workloads. You need NoSQL (BASE) if you want to scale.
The reality: This was a reasonable take in 2010. It's flat wrong in 2025. Google's SpannerGoogle Cloud Spanner is a globally distributed relational database that provides full ACID transactions across continents. It uses synchronized atomic clocks (TrueTime) to order transactions without the typical coordination overhead. Google built it because even they got tired of dealing with eventual consistency bugs. provides full ACID compliance across data centers spanning the entire planet — it processes millions of transactions per second for Google Ads, one of the highest-traffic systems on Earth. CockroachDBAn open-source distributed SQL database inspired by Google Spanner. Provides serializable isolation with automatic sharding across nodes. Used by companies like DoorDash, Netflix (for some services), and Bose. The name comes from its cockroach-like survivability: it keeps running even when nodes die. and Amazon Aurora are ACID-compliant and horizontally scalable. The old binary of "ACID = single server, BASE = distributed" no longer holds.
What actually happened: Database engineers spent the last 15 years figuring out how to make ACID work in distributed systems. Techniques like multi-version concurrency controlMVCC (Multi-Version Concurrency Control): instead of locking rows, the database keeps multiple versions of each row. Readers see the version that was current when their transaction started. Writers create a new version. This means readers never block writers and writers never block readers — a huge performance win over traditional locking. (MVCC), distributed consensus (Raft/Paxos), and synchronized clocks (TrueTime) solved the coordination problem. The trade-off isn't "ACID or scale" — it's "ACID at scale costs more money and engineering effort."
The myth: If a database is "NoSQL" (not a traditional relational database), it must use BASE semantics. NoSQL = eventually consistent.
The reality: "NoSQL" describes the data model (document, key-value, graph, wide-column) — not the consistency model. MongoDBA document database that stores data as JSON-like documents. Since version 4.0 (2018), MongoDB supports multi-document ACID transactions. You can do BEGIN, read/write across multiple collections, and COMMIT or ROLLBACK — just like a relational database. has supported multi-document ACID transactions since version 4.0. DynamoDB supports TransactWriteItemsA DynamoDB API that lets you write to up to 100 items across multiple tables in a single atomic, all-or-nothing transaction. If any write fails, they all roll back. This gives you ACID guarantees inside a NoSQL database. for atomic writes across multiple items. FaunaDB was designed from the ground up as a NoSQL database with serializable ACID transactions.
The better mental model: Data model and consistency model are two independent choices. You can have SQL + BASE (like CockroachDB with relaxed consistency settings) or NoSQL + ACID (like MongoDB with transactions). The four quadrants all exist in production.
The myth: BASE systems are the Wild West — your data could be lost, corrupted, or disappear at any time. They trade ALL safety for performance.
The reality: BASE systems provide different guarantees, not fewer guarantees. DynamoDB guarantees that once a write is acknowledged, the data is durably stored across multiple availability zones — it won't be lost. Cassandra's tunable consistencyCassandra lets you choose the consistency level per query. QUORUM means a majority of replicas must agree. ONE means just one replica is enough. ALL means every replica must respond. You can even mix: write with QUORUM (durable) but read with ONE (fast). This per-query tuning is why "BASE" isn't a single thing but a spectrum. lets you pick QUORUM reads (majority of replicas must agree) for important queries while using ONE (any single replica is fine) for less critical reads.
What BASE actually relaxes: Only immediate consistency across all nodes. The data is still durable (won't be lost), still available (you can always read/write), and still consistent — just not instantly consistent everywhere. The "eventual" in eventual consistency has a concrete meaning: the system guarantees convergence, typically within 5-50 milliseconds.
The myth: The "C" in ACID and the "C" in CAP theorem mean the same thing. They're both about consistency, right?
The reality: These are completely different concepts that happen to share a name. It's like "bank" (financial institution) and "bank" (river edge) — same word, unrelated meanings.
- ACID Consistency: The data satisfies all integrity constraints — foreign keys are valid, unique constraints hold, check constraints pass. If your schema says
balance >= 0, then no transaction can make the balance negative. This is about the rules your data must follow. - CAP Consistency: Every read returns the most recent write. All nodes see the same data at the same time. If you write "balance = $500" on Node A, an immediate read on Node B returns $500. This is about all copies of the data being in sync.
You can have ACID consistency without CAP consistency (a single PostgreSQL server satisfies all integrity constraints but there's only one node, so CAP doesn't apply). You can have CAP consistency without ACID consistency (all nodes show the same data, but that data violates a foreign key constraint because you didn't enforce it).
The myth: When designing a system, you pick one philosophy and apply it everywhere. Either you're an ACID shop or a BASE shop.
The reality: No. The correct approach is per-feature, per-table decisions. Within a single application:
- Payments → ACID (PostgreSQL with Serializable isolation). Losing a cent is unacceptable.
- Activity feeds → BASE (Cassandra or Redis). A 200ms delay in showing a new post is invisible to users.
- User profiles → ACID (but Read Committed is fine). Profile data must be consistent but doesn't need the strongest isolation.
- Analytics events → BASE (Kafka + ClickHouse). Approximate real-time counts are perfectly acceptable.
- Shopping cart → BASE (DynamoDB). Amazon proved this — a temporarily stale cart is better than a cart that's unavailable during a sale.
Amazon, Uber, Stripe, Netflix — every one of them uses both ACID and BASE within the same system. The engineering skill isn't choosing a side. It's knowing which data needs which guarantee.
The myth: "Eventually consistent" sounds scary and vague. "Eventually" could mean hours, days, never. How can you trust a system that's "eventually" correct?
The reality: In practice, "eventually" is almost always 5 to 50 milliseconds. That's faster than a human blink (300ms). Users literally cannot perceive the inconsistency window. And "eventually consistent" doesn't mean "maybe consistent" — the system guarantees convergence. All replicas will reach the same state. The only question is how many milliseconds it takes.
Here's the ultimate proof: DNS — the system that translates domain names to IP addresses — is eventually consistent. When you update a DNS record, it takes minutes to hours to propagate worldwide. And DNS runs the entire internet. Every website you've ever visited relied on an eventually consistent system to find the server's address. It works because the convergence guarantee is strong enough and the staleness window is short enough that it doesn't matter.
Interview Playbook — Nail ACID vs BASE Questions
ACID vs BASE is one of the most common system design interview topics because it tests whether you think in trade-offs or absolutes. Interviewers aren't looking for textbook definitions — they want to see if you can apply the concepts to a real scenario and reason through the decision. Here's the framework that works every time.
The Five-Step Framework
When you get any ACID vs BASE question, run through these five steps in order. The framework works whether the question is "Explain ACID vs BASE" or "Design the data layer for an e-commerce platform."
Common Questions — How to Answer
What they're testing: Can you go beyond the acronym expansion? Do you understand the WHY behind each philosophy?
The strong answer: "ACID guarantees that every transaction is correct — either all changes happen or none do, and the database never violates its own rules. Think of a bank transfer: the money leaves one account and arrives in another, guaranteed. BASE accepts that in a distributed system, perfect consistency everywhere at all times is expensive, so it relaxes that. A social media like count might be 1,847 on your screen and 1,849 on your friend's screen for 50 milliseconds — and nobody cares. The key difference is the cost of being wrong. If being wrong means someone loses money, use ACID. If being wrong means a number is slightly stale for a moment, BASE is a better engineering trade-off."
The weak answer: "ACID stands for Atomicity, Consistency, Isolation, Durability. BASE stands for Basically Available, Soft state, Eventually consistent." (This is just expanding acronyms. No insight, no trade-off analysis.)
What they're testing: Can you decompose a system into features with different consistency needs?
The strong answer: "A chat application has at least three data types with different requirements:
- Messages → ACID. Message ordering matters — if Alice sends "Are you coming?" and then "Never mind, I'll go alone," those must appear in order. Duplicates are confusing. Lost messages are unacceptable. I'd use PostgreSQL or a system with strong ordering guarantees.
- Read receipts → BASE. If your "seen" indicator updates 200ms late, nobody notices. This is high-volume, low-stakes data. Eventually consistent storage like Redis or Cassandra works perfectly.
- Presence (online/offline status) → BASE. Showing someone as "online" when they disconnected 3 seconds ago is fine. This data changes constantly and is inherently approximate. Eventual consistency with a short TTL is the right call."
What they're testing: Do you know that distributed transactions are hard, and do you know the modern solution?
The strong answer: "You can't just wrap a BEGIN/COMMIT around two microservices — they have separate databases. The traditional approach is Two-Phase Commit2PC (Two-Phase Commit) is a protocol where a coordinator asks all participants "Can you commit?" (Phase 1), waits for all to say yes, then tells them all to commit (Phase 2). The problem: if the coordinator crashes between phases, all participants are stuck waiting. It's a blocking protocol with a single point of failure. Most modern systems avoid it. (2PC), but it's a blocking protocol with a single point of failure — if the coordinator dies, all participants freeze. The modern approach is the Saga patternA sequence of local transactions where each step has a compensating transaction that undoes it. Order Service creates an order, Payment Service charges the card, Inventory Service reserves stock. If inventory fails, you run compensating transactions: refund the card, cancel the order. No central coordinator needed. Each service owns its own rollback logic.: each service runs its own local ACID transaction and publishes an event. If a downstream service fails, you run compensating transactions to undo previous steps. For example: Order Service creates an order (ACID), Payment Service charges the card (ACID), Inventory Service tries to reserve stock. If inventory fails, you run a compensating transaction to refund the payment and cancel the order. No global lock, no coordinator, no blocking."
What they're testing: Do you understand that isolation is a spectrum, not a single setting?
The strong answer: "Even within a banking system, different operations need different isolation levels:
- Money transfers between accounts → Serializable. Two concurrent transfers touching the same account must not interfere. I'd rather retry a failed transaction than risk $1 being lost or double-spent.
- Balance inquiries (checking your balance on the app) → Read Committed. You're just reading — seeing a balance that's 100ms old is fine. Using Serializable here would slow down every balance check for no benefit.
- Monthly statement generation → Repeatable Read. The report needs a consistent snapshot of all your transactions for the month. If new transactions are being added while the report generates, I don't want them appearing halfway through the report."
Think-Aloud Practice
Interviews reward candidates who think out loud. Here's what a strong think-aloud sounds like for a classic scenario question:
Step 1 — Identify the data types: "A ride-sharing app has several distinct data types: rider/driver profiles, ride requests, real-time location updates, payment transactions, ride history, and ratings. Each of these has different consistency requirements, so I wouldn't pick one approach for all of them."
Step 2 — Classify by correctness cost: "What happens if each data type is briefly inconsistent? Payments: someone gets charged twice or not at all — catastrophic, must be ACID. Location updates: a driver's dot on the map is 500ms behind their actual position — nobody notices, BASE is fine. Ride matching: a ride gets assigned to two drivers simultaneously — terrible user experience, needs ACID-level guarantees for the assignment itself."
Step 3 — Pick technologies: "Payments in PostgreSQL with Serializable isolation. Location updates in Redis with geospatial commands — eventually consistent, extremely fast writes. Ride matching uses PostgreSQL with SELECT ... FOR UPDATE on the ride row to prevent double-assignment. Ride history and ratings go in a document store like DynamoDB — high read volume, rarely updated, eventual consistency is fine."
Step 4 — Address cross-service transactions: "When a ride completes, I need to charge the rider, pay the driver, and update the ride status. These span multiple services. I'd use the Saga pattern: Ride Service marks ride as complete, Payment Service charges the rider, Payment Service credits the driver. If the charge fails, a compensating transaction reverses the ride completion."
Step 5 — Summarize the hybrid: "So the data layer is a hybrid: PostgreSQL for payments and ride assignment (ACID), Redis for real-time locations (BASE), DynamoDB for ride history and ratings (BASE). The system uses both ACID and BASE, choosing per-feature based on the cost of inconsistency."
Practice Exercises — Build Your ACID vs BASE Intuition
Reading about ACID and BASE is one thing. Knowing when to use which is another. These exercises start with classification (easy) and work up to debugging real isolation anomalies (hard). Try each one yourself before peeking at the hints.
You're designing the data layer for a large e-commerce platform. For each of the following data types, decide whether it needs ACID guarantees or whether BASE (eventual consistency) is good enough. Justify each choice in one sentence.
- Order records — the main order table linking customer, items, and payment.
- Product search index — the data behind the search bar.
- User sessions — who's logged in, what's in their cart right now.
- Payment transactions — actual money movement between accounts.
- Recommendation engine data — "customers who bought X also bought Y."
- Inventory count — how many units of each product are in stock.
- Page view analytics — tracking which pages users visit.
(a) ACID — Orders must be created atomically with payment and inventory deduction. A half-created order means a customer paid but got nothing, or got something without paying.
(b) BASE — If the search index is a few seconds behind the product catalog, nobody gets hurt. A newly added product showing up 5 seconds late is invisible to users.
(c) BASE — Sessions live in Redis with eventual consistency. If a session is briefly stale across replicas, the worst case is a user sees their old cart for a moment. No money is lost.
(d) ACID — Money must be exactly right. A payment that debits your card but doesn't credit the merchant is a lawsuit. This is the textbook ACID use case.
(e) BASE — Recommendations being slightly stale is perfectly fine. "Customers also bought" doesn't need to update in real time — a few hours of lag is invisible.
(f) ACID — Inventory must be exact. If your count says 3 units but there are really 0, you oversell and have to cancel orders — angry customers and refund costs.
(g) BASE — Missing a handful of page views out of millions doesn't affect any business decision. Analytics is the classic "eventual consistency is fine" workload.
Pattern: If wrong data costs money or trust, use ACID. If wrong data causes mild inconvenience, use BASE.
A bank transaction transfers $200 from Account A to Account B. Your task:
- Describe what each ACID property does during this specific transfer.
- Now imagine the server crashes after debiting Account A but before crediting Account B. What happens on restart?
Atomicity: The debit from A and the credit to B are wrapped in one transaction. Either both happen, or neither happens. There's no state where A lost $200 but B didn't gain it.
Consistency: The database checks constraints on every commit — for example, A.balance >= 0. If deducting $200 would make A's balance negative, the entire transaction is rejected before anything changes.
Isolation: While this transfer is in progress, no other transaction can see the "half-done" state where A lost $200 but B hasn't gained it yet. Other queries see either the before-state or the after-state, never the middle.
Durability: Once the database says "committed," the transfer survives any crash — power failure, disk error, kernel panic. The data is on disk.
Crash scenario: The WALWrite-Ahead Log — a file where every change is recorded BEFORE it's applied to the actual data. On crash recovery, the database replays committed entries and rolls back uncommitted ones. recorded the debit but the transaction was never committed. On restart, PostgreSQL reads the WAL, sees an incomplete (uncommitted) transaction, and rolls back the debit. Account A gets its $200 back. The transfer never happened — which is exactly what "all or nothing" means.
You're using CassandraA distributed NoSQL database that replicates data across multiple nodes. It uses eventual consistency by default, meaning different replicas may briefly disagree. Great for high write throughput across data centers. (a BASE system) for a shopping cart. Two users on a joint account add items simultaneously from different devices:
- User A (on a laptop) adds a laptop to the cart.
- User B (on a phone) adds headphones to the cart.
Both writes hit different Cassandra replicas at the same time. With the default last-writer-wins strategy, one item gets overwritten — the cart shows only one item instead of two.
Design a conflict resolution strategy that preserves both items.
The key insight is that a shopping cart is an add-only set (at least for adding items). Instead of replacing the entire cart on each write, treat each "add item" as a set union operation.
Solution: Use a CRDTConflict-Free Replicated Data Type — a data structure designed so that concurrent updates on different replicas can always be merged automatically without conflicts. Examples: G-Counter (grow-only counter), G-Set (grow-only set), OR-Set (observed-remove set). set (G-Set or OR-Set).
- Each replica stores the cart as a set of items, not a single value.
- When User A adds "laptop," Replica 1's set becomes
{laptop}. - When User B adds "headphones," Replica 2's set becomes
{headphones}. - When the replicas sync, they merge via set union:
{laptop} ∪ {headphones} = {laptop, headphones}. - Both items preserved. No conflict. No data loss.
For item removal, use an OR-Set (Observed-Remove Set), which tracks add and remove operations separately so that "remove headphones" doesn't accidentally remove a headphones that was re-added after the remove.
Alternative approach: Application-level merge — on read, fetch all replica versions, take the union of all cart items, and write the merged result back. Simpler to implement but adds a read-before-write round trip.
You're building a flight booking system that spans three services:
- Seat Service — reserves a seat on the plane.
- Payment Service — charges the customer's credit card.
- Notification Service — sends a confirmation email.
You can't use a distributed ACID transaction (too slow, services are independent). Instead, design a sagaA saga is a sequence of local transactions where each step has a compensating transaction that undoes it. If step 3 fails, you run compensations for steps 2 and 1 in reverse order. It's the BASE world's answer to distributed transactions.:
- Define the compensating transaction (undo) for each step.
- Trace what happens if step 2 (payment) fails.
Step 1: Reserve seat
- Action: Mark seat 14A as "reserved" in the seat database.
- Compensating transaction: Release seat 14A — mark it as "available" again.
Step 2: Charge credit card
- Action: Call the payment gateway to charge $450.
- Compensating transaction: Issue a refund of $450 through the payment gateway.
Step 3: Send confirmation email
- Action: Send "Your flight is booked!" email.
- Compensating transaction: Send "Your booking has been cancelled" email.
If step 2 fails (card declined, payment gateway down, insufficient funds):
- Step 3 never ran, so no compensation needed for it — no email was sent.
- Step 1's compensating transaction fires: release the reserved seat.
- The customer sees an error: "Payment failed. No seat was reserved."
The system ends up in a consistent state even though no single ACID transaction wrapped all three services. That's the power of the saga pattern — eventual consistency with clear rollback semantics.
Your PostgreSQL database uses Read Committed isolation (the default). Two transactions run at the same time:
- T1 reads all orders where
status = 'pending'and counts them. Result: 10 orders. - T2 changes one order from
'pending'to'shipped'and commits. - T1 runs the same query again. Result: 9 orders.
Answer these four questions:
- What isolation anomaly is this?
- Which isolation level prevents it?
- What's the performance cost of using that level?
- Is there a way to get correct results without going to Serializable?
(a) Phantom read. T1's query returned a different set of rows the second time — not because a row's value changed, but because a row disappeared from the result set. That's what makes it a phantom (the row "vanished" between two identical queries in the same transaction).
(b) Serializable prevents phantom reads. It's the strictest isolation level, guaranteeing that concurrent transactions produce the same result as if they ran one at a time.
(c) Performance cost: PostgreSQL implements Serializable via SSISerializable Snapshot Isolation — PostgreSQL's approach to Serializable that doesn't use traditional locking. Instead, it tracks read/write dependencies between transactions and aborts one if a cycle is detected. Faster than lock-based approaches but still causes retries under high contention. (Serializable Snapshot Isolation). It tracks dependencies between transactions and aborts one if it detects a conflict cycle. Under high contention, you'll see 10–30% throughput reduction due to transaction retries. Each retried transaction wastes the work it already did.
(d) Yes — you don't always need Serializable. Several lighter alternatives:
SELECT ... FOR UPDATE— locks the matching rows so T2 can't modify them until T1 finishes. Prevents phantoms for that specific query without Serializable overhead on the whole database.- Explicit table lock —
LOCK TABLE orders IN SHARE MODEprevents writes while T1 reads. Heavy-handed but simple. - Redesign the query — if T1 just needs an accurate count, use a materialized counter updated atomically (e.g., a
pending_countcolumn in a summary table). Avoids the concurrent read pattern entirely.
Rule of thumb: Use the lightest tool that solves your specific problem. Serializable is a sledgehammer — sometimes a scalpel (FOR UPDATE) is all you need.
Cheat Sheet — ACID vs BASE at a Glance
Connected Topics — Where to Go Next
ACID and BASE don't exist in isolation (pun intended). Every concept on this page connects to a deeper topic. Pick the ones that matter most for your next interview or project — each link takes you to a full System Guide deep-dive.