Case Study 4.1: ACID at Stripe

Concept. Every Stripe charge is a transaction that touches several tables (payment, ledger, merchant balance, webhook queue) and must satisfy all four ACID properties, or real money goes missing. Atomicity stops half-charges; Consistency keeps the ledger obeying its rules; Isolation prevents a double-click from charging twice; Durability guarantees a confirmed payment survives a datacenter power loss.

Intuition. When Mickey clicks "Pay $9.99" on a Stripe checkout, a single API call turns into a handful of database writes: charge the card, record the payment in the ledger, update the merchant's balance, and notify the merchant's server. All of those writes must happen together, must not collide with another concurrent charge on the same card, and must stay on disk even if the datacenter loses power the instant after Mickey sees the receipt.

Case Study 4.1 Reading Time: 7 mins


What a Single Charge Touches

Stripe processes roughly $1T of payments a year. Every "Pay" click triggers a handful of writes behind the scenes:

1. Ask Visa to charge the card

External call to the card network. Visa/Mastercard authorize the amount and return an auth code.

2. Record the payment in the ledger

Stripe's books use double-entry: every payment writes two rows, one for the customer side and one for the merchant side. The two rows must always sum to zero.

3. Update the merchant's balance

Running total for the merchant's next payout.

4. Notify the merchant

Send a charge.succeeded webhook to the merchant's server so they can ship the order.

Four writes. If any one fails, the whole charge has to fail together. If two concurrent charges interleave, the ledger breaks. If the server crashes after the customer sees 200 OK, the charge still has to exist tomorrow. That's A, C, I, D. The rest of this case study walks one letter at a time.


1. Atomicity: The Charge That Wasn't

Scenario. Minnie buys a $47 book. Stripe charges her card, records the payment in the ledger, and then the database connection drops before the merchant webhook is queued.

Without atomicity, the result depends on timing: Minnie's card is charged (real money moved), the ledger shows her payment, but the merchant's server never hears about it, so the book never ships. Minnie is out $47, the merchant has money they don't know about, and someone has to reconcile the mess by hand.

ACID Focus: Atomicity

+ Atomicity: All-or-Nothing across the charge

The Problem: Some of the writes succeed and some don't. Card charged + ledger updated, but no webhook → merchant never ships.

Stripe's Approach: Wrap steps 2–4 in a single database transaction. If any step fails, the whole transaction rolls back. Step 1 (the card charge) is then undone with an explicit refund to Visa.

Guarantee: Either Minnie's charge succeeds end-to-end (ledger, balance, webhook) or none of it persists.

Why the Visa call sits outside the database transaction

Once Visa has been told to charge the card, real money has already started moving. You can't undo that by rolling back a database. A database rollback only erases rows in Stripe's database, not anything at Visa. The only way to reverse a card charge is to send Visa a separate refund request.

So Stripe does the card call first, then wraps the rest (ledger, balance, webhook) in one database transaction. If that transaction fails, the code issues a refund to reverse step 1.

Rule of thumb: side effects you can't undo by rolling back (external API calls, emails, SMS) go before the transaction, and you need a compensating action (refund, cancellation) if the transaction fails.


2. Consistency: The Ledger That Balances

Scenario. A platform pays out $1,200 to a seller. The transfer writes two ledger rows: $1,200 debited from the platform, $1,200 credited to the seller. Stripe has one firm rule: every transfer's two rows must sum to zero. If a bug somewhere in the application writes $1,200 on one side and $1,199 on the other, the database refuses to commit it. The transfer fails loudly rather than silently creating a dollar out of nothing.

ACID Focus: Consistency

+ Consistency: The database enforces the rules of the business

The Rule: Every transfer writes two rows, and they must sum to zero. Total money across the platform is conserved.

The Problem: An application bug could try to commit two rows that don't sum to zero. Without a consistency check, the bug would silently break the books.

Stripe's Approach: The schema enforces the rule (via constraints and triggers). A transaction that tries to commit rows violating the rule is rejected by the database itself, not by the application code.

Guarantee: Any transaction that commits leaves the database in a state where every business rule still holds.

Consistency vs. Isolation: the part that trips people up

These two letters often feel identical, especially in examples with money. Here's the clean split:

  • Consistency is about the rules your data must obey. "Debit + credit = 0." "A user's balance is never negative." "A seat is never sold twice." These are invariants. They define what a "valid" database looks like.
  • Isolation is about how concurrent transactions interact. It's the guarantee that running ten transactions at once produces the same result as running them one at a time.

How to tell them apart in an example: ask "is this failure possible with only one transaction running at a time?" If yes, it's a consistency issue (the rule itself was broken). If it only shows up because two transactions interleaved, it's an isolation issue.

They're related. Bad isolation can break consistency (two concurrent transfers sneaking past each other's checks). But the concepts are distinct: consistency is what is valid, isolation is how concurrent work stays valid.


3. Isolation: The Double-Submit

Scenario. Mickey clicks "Pay" on a slow phone connection. Nothing visibly happens, so two seconds later he clicks again. Both clicks reach Stripe within 200 ms of each other. Without isolation, both requests read Mickey's account, both see enough funds, both charge the card, and Mickey is charged twice for one order.

ACID Focus: Isolation

+ Isolation: Concurrent charges don't step on each other

The Problem: Two POST /charges requests arrive at nearly the same moment. Without isolation, both read the pre-state, both succeed, and Mickey is double-charged.

Stripe's Approach: The client attaches a unique key (commonly called an idempotency key) to each purchase. It's a random string generated once, on the customer's device, for this specific order. The first request with that key acquires a row-level lock on (customer_id, idempotency_key). The second request either waits and gets back the first one's result, or is rejected as a duplicate.

Guarantee: Two requests carrying the same idempotency key produce at most one charge, no matter how they interleave, how many times the client retries, or how fast Mickey taps.

An idempotency key per purchase turns isolation into a simple promise

Under the hood, that key becomes a row-level lock. But to the developer writing the integration, it's just a rule: "generate one random string per order, send it along with the charge, and Stripe will never charge twice." The database's isolation guarantee is repackaged as an API-level guarantee.


4. Durability: The 200 OK That Must Survive

Scenario. The Stripe API returns 200 OK to Mickey's browser. The merchant's server gets the webhook. Mickey is looking at the receipt. 5 ms later, a power event takes down the primary database server.

Without durability, the charge could evaporate: if the transaction was only in RAM when the server lost power, the restart loses it. Mickey has a receipt for a charge that's no longer in the database.

ACID Focus: Durability

+ Durability: Committed charges survive crashes

The Problem: A transaction that committed in memory but didn't reach disk before a crash is gone on restart. The customer has a receipt; the database has nothing.

Stripe's Approach: Before responding 200 OK, the database writes the transaction to a log on disk. On restart, the log is enough to rebuild the charge. Stripe also replicates the log to multiple datacenters, so even a whole-datacenter failure can't lose a committed charge. (We'll see how the log works in Module 4B.)

Guarantee: If the client saw 200 OK, the charge will still be there after any crash.

Durability is a contract between the API and the disk

The rule: the 200 OK is only sent after the log hits durable storage. This is why financial APIs feel slightly slower than you'd expect. They're paying a real round-trip to disk before every acknowledgment. The latency is the price of the guarantee.


5. ACID Together: What Each Letter Buys You

Drop any one letter and something breaks:

Drop What breaks Real consequence
Atomicity Partial charges Customer charged, merchant never notified
Consistency Ledger drifts Money appears or disappears silently
Isolation Double-charges under concurrency One click, two charges, two support tickets
Durability Lost commits after crash Customer has receipt, database has nothing

Each failure mode costs money directly, and each one gets more likely as traffic grows, not less. ACID is the minimum contract that lets a payments system scale from $1/day to $1T/year without the failure rate scaling with it.


Key Takeaways

  1. A single "charge" is a multi-row transaction. Card auth, ledger entries, balance update, and webhook queue all have to commit together or none of them.

  2. Unreversible side effects go outside the transaction. Charge the card first (reversible via refund), wrap only Stripe's own writes in the transaction, issue a refund if the transaction fails.

  3. ACID guarantees show up in the API. An idempotency key per purchase is isolation. 200 OK only after the log hits disk is durability. A ledger that always balances is consistency.

  4. "Is this failure possible with one transaction at a time?" is the fastest way to tell consistency from isolation. If yes, it's consistency; if it only happens under concurrency, it's isolation.


Next

Sample Code: Running Transactions → Now that we've seen what ACID buys, the next page shows how to actually write transactions in SQL: four worked BEGIN ... COMMIT examples (ticket purchase, bank transfer, inventory, viral counter) on top of the grab-bag of scenarios that make ACID concrete.