← All posts
2026-06-19·11 min read·system design

Why your database is choking (and how modern architectures fix it)

Every database is a bundle of hyper-specific trade-offs. If you migrate without understanding the data structures underneath, you don't fix your bottleneck — you just move it somewhere else.

It always starts the same way. The app is a success, traffic is surging, everyone's celebrating — until the alerts start firing. The database, the beating heart of the system, is suddenly buckling. P95 latency is through the roof, the working set has overflowed memory, and basic reads are forced to hit disk, dragging everything to a crawl.

Our first instinct is usually to panic-migrate to the newest, shiniest NoSQL database that promises "infinite horizontal scalability." But here's the hard truth: at scale, there is no free lunch. Every database is a collection of specific trade-offs. To actually fix a scaling problem, you have to look under the hood — from the friction of data safety to the specialized trees that power PostgreSQL, Redis, and Cassandra.


1. The heavy friction of safety: the ACID tax

Before a database can be fast, it must be reliable. We demand it never lose our data, even if someone pulls the power plug mid-query. That safety net is ACID (Atomicity, Consistency, Isolation, Durability), and enforcing it is the main reason databases are historically slow.

Picture a banking app moving $100 from Alice to Bob — two operations: subtract from Alice, add to Bob.

But the real execution cost is Isolation. When thousands of users transfer money at once, how do you stop them overwriting each other? The strictest level, Serializable, forces transactions into a single-file line as if each owned the whole database. Because that destroys throughput, databases offer weaker levels (Read Committed, Repeatable Read) — faster, but they open the door to concurrency anomalies:

Takeaway: traditional databases are structurally bound by the locks, concurrency checks, and synchronous disk logging required to keep your data safe.

2. Bypassing the disk: the single-threaded magic of Redis

If writing to disk and managing locks is the bottleneck, what if we remove the disk entirely? That's Redis — an in-memory database orders of magnitude faster than on-disk counterparts. But its speed isn't only RAM; it's a counterintuitive choice: Redis is primarily single-threaded.

Why would a high-performance database use one core? Think of multi-threading as a kitchen with 20 chefs — to stop them colliding (race conditions) or grabbing the same pan, you need locks. Under high concurrency, threads burn cycles waiting on locks (contention) and switching context. With one thread, Redis has no locks, no race conditions, no context switching. So how does one thread serve 100,000 concurrent requests without blocking?

3. The great disk divide: B-Trees vs LSM Trees

Eventually your data outgrows RAM (or your wallet outgrows the cost of keeping terabytes in memory) and you must write to disk. How a database lays data out on disk decides whether it's optimized for reads or writes. This is the fork in the road.

The read-optimized standard: the B-Tree (relational)

PostgreSQL, MySQL, and Oracle rely on the B-Tree (and its B+ Tree variant) — think of a massive, perfectly balanced filing cabinet. Data lives in fixed-size pages; internal nodes hold routing keys, leaves hold the data. Because the tree stays balanced, finding a record takes a small, predictable number of page lookups.

The flaw: a firehose of writes (millions of IoT logs, app clicks) is brutal. To insert, the DB must find the right page, load it, modify it, write it back — and split it when it fills. That's heavy random I/O, the disk head jumping everywhere. Under write-heavy load, B-Trees choke.

The write-optimized challenger: the LSM Tree (NoSQL)

To kill the write bottleneck, systems like Cassandra, RocksDB, and LevelDB adopted the Log-Structured Merge-Tree (LSM Tree). It never updates in place — it treats the disk as an append-only log.

B-Tree in-place random writes (slow) versus LSM Tree MemTable, immutable SSTables and sequential writes (fast)
Left: B-Tree in-place updates → random I/O, the choke point. Right: the LSM path — buffer in the MemTable, flush immutable SSTables sequentially, with Bloom filters and compaction making reads fast.

4. Taming LSM reads: compaction and Bloom filters

LSM Trees conquer writes but create a read problem: if a record was updated five times in an hour, it's now scattered across five SSTables. A single read may have to scan many files. Two background optimizations fix this.

Background compaction

An asynchronous process constantly tidies up: it reads multiple sorted SSTables sequentially, merges them (much like the merge phase of merge sort), keeps only the newest values, and discards stale data and tombstoned keys.

O(N squared) simple sorting versus O(N log N) efficient sorting — as data grows, better algorithms win
Why compaction leans on merge-style algorithms: at small N the constant factors barely matter, but as data grows, an O(N log N) merge crushes an O(N²) approach. The structure you pick is an algorithmic bet.

Bloom filters

To avoid reading disk blocks for keys that don't exist, every LSM level keeps a Bloom filter in memory — a space-efficient probabilistic structure. Ask it whether a key is in an SSTable and you get one of two answers:

5. The specialists: indexes for non-standard queries

Once you see a database as a wrapper around data structures, it's clear some problems can't be solved by B-Trees or LSM Trees alone:

Conclusion: stop chasing the shiny

When production is breaking, don't blindly migrate to a fancier system off a marketing page. Infinite horizontal scalability always hides a cost — usually weaker transactional guarantees or severe limits on how you can model data. Before you jump ship, read the manual; check the "Limits" page. Ask:

Database engineering isn't about finding the "best" tool — it's about choosing which structural limitations you're willing to live with. So: which bottleneck is your system actually facing right now — write locking, slow range queries, or memory exhaustion?

Written up from my notes on database internals. Diagrams are my own.