B-Tree vs LSM-Tree: Deep Dive into Database Storage Engines
Explore B-Tree and LSM-Tree storage engines: internals, trade-offs, real-world incidents, and debugging techniques.
20+ years shipping production systems from the metal up. Notes here come from systems that actually shipped.
- ✓Basic understanding of databases and SQL
- ✓Familiarity with data structures (trees, hash tables)
- ✓Knowledge of disk I/O and caching concepts
- B-Trees provide balanced tree structures for efficient point lookups and range scans.
- LSM-Trees use append-only writes and compaction for high write throughput.
- B-Trees are better for read-heavy workloads; LSM-Trees excel in write-heavy scenarios.
- Both have trade-offs in space amplification, write amplification, and read performance.
- Real-world databases like MySQL (InnoDB) use B-Trees; Cassandra and LevelDB use LSM-Trees.
Imagine a library. A B-Tree is like a well-organized card catalog where every book has a fixed spot, making it easy to find a specific book quickly. An LSM-Tree is like a pile of new books on a desk that are periodically sorted and merged into the shelves. Adding a new book is fast (just put it on the pile), but finding an old book might require checking multiple piles.
When you query a database, the storage engine silently works behind the scenes to fetch or write data efficiently. Two dominant data structures power most modern databases: B-Trees and LSM-Trees. Understanding their internals is crucial for optimizing performance, diagnosing production issues, and designing scalable systems. B-Trees, used by MySQL InnoDB and PostgreSQL, offer balanced read performance with predictable latency. LSM-Trees, powering Cassandra, RocksDB, and LevelDB, prioritize write throughput by batching writes and deferring organization. This article dissects both structures, compares their trade-offs, and provides practical debugging guides. You'll learn how to choose the right engine for your workload, diagnose common pitfalls, and avoid production incidents.
B-Tree Internals: Structure and Operations
A B-Tree is a self-balancing tree data structure that maintains sorted data and allows searches, sequential access, insertions, and deletions in logarithmic time. It is optimized for systems that read and write large blocks of data. The tree consists of nodes: internal nodes contain keys and pointers to child nodes, while leaf nodes contain actual data or pointers to data. The height of the tree is kept low by having a high branching factor (often hundreds of children per node). This minimizes disk I/O because each node corresponds to a disk page. Insertions and deletions may cause node splits or merges to maintain balance. B-Trees are the default storage engine for many relational databases (e.g., InnoDB, PostgreSQL). They excel at point queries and range scans due to their sorted structure. However, they suffer from write amplification because updates often require rewriting entire pages.
LSM-Tree Internals: Structure and Operations
An LSM-Tree (Log-Structured Merge-Tree) is designed for high write throughput by converting random writes into sequential writes. Data is first written to an in-memory memtable (often a sorted structure like a red-black tree). When the memtable reaches a threshold, it is flushed to disk as an immutable SSTable (Sorted String Table). Over time, multiple SSTables accumulate, and a background compaction process merges them to maintain order and reclaim space. Reads must check the memtable and all SSTables, so bloom filters are used to quickly skip irrelevant SSTables. LSM-Trees are used in NoSQL databases like Cassandra, HBase, and key-value stores like RocksDB and LevelDB. They excel in write-heavy workloads but can suffer from read amplification and compaction overhead. Write amplification is a key concern: each write may be rewritten multiple times during compaction.
Read and Write Amplification: The Hidden Cost
Amplification factors measure the extra I/O caused by the storage engine. Write amplification (WA) is the ratio of bytes written to disk to bytes written by the application. B-Trees have moderate WA due to page splits and writes, while LSM-Trees can have high WA during compaction. Read amplification (RA) is the number of disk reads per query. B-Trees have low RA for point queries (one or two page reads), but LSM-Trees may need to check multiple SSTables. Bloom filters reduce RA by eliminating unnecessary SSTable reads. Space amplification (SA) is the ratio of disk space used to logical data size. LSM-Trees can have high SA if compaction lags, leaving obsolete data. Understanding these trade-offs helps in selecting the right engine and tuning parameters. For example, in a write-heavy logging system, LSM-Tree with leveled compaction may be optimal, while a user profile database with frequent reads benefits from B-Tree.
Choosing Between B-Tree and LSM-Tree
The choice depends on your workload. B-Trees are ideal for OLTP applications with many point queries, small range scans, and moderate write rates. They provide strong consistency and predictable performance. LSM-Trees excel in write-heavy scenarios like time-series data, logging, and event sourcing. They offer higher write throughput and efficient compression. However, they may have higher read latency for point queries due to multiple SSTable lookups. Consider also the operational complexity: B-Trees require careful tuning of buffer pool size, while LSM-Trees need compaction tuning. Hybrid approaches exist, such as using a B-Tree for hot data and LSM-Tree for cold data. Many modern databases offer configurable storage engines (e.g., MySQL with MyRocks). Evaluate your access patterns: if writes dominate, LSM-Tree is likely better; if reads dominate, B-Tree is safer.
Concurrency and Crash Recovery
B-Trees use techniques like latch-free B-Trees or page-level locking to handle concurrent access. InnoDB uses a combination of row-level locks and MVCC (Multi-Version Concurrency Control) to allow concurrent reads and writes. Crash recovery is handled via a write-ahead log (WAL) that records changes before they are applied to pages. LSM-Trees also use WAL for durability. Compaction can be concurrent, but careful scheduling is needed to avoid conflicts. LSM-Trees typically have simpler concurrency because SSTables are immutable; reads and writes don't conflict. However, compaction may interfere with reads. Both engines rely on checkpoints to limit recovery time. Understanding these mechanisms is vital for tuning performance and ensuring data integrity.
Performance Tuning and Best Practices
For B-Trees, key tuning parameters include buffer pool size, page size, and index fill factor. A larger buffer pool reduces disk reads. Page size affects node fan-out: larger pages reduce tree height but increase write amplification. For LSM-Trees, tune memtable size, compaction strategy, and bloom filter false positive rate. Leveled compaction reduces read amplification at the cost of higher write amplification. Use compression to reduce disk space and I/O. Monitor compaction throughput and adjust number of compaction threads. Both engines benefit from SSD storage due to lower latency. Regularly rebuild indexes to reduce fragmentation. Use database-specific tools like MySQL's pt-online-schema-change for zero-downtime schema changes. For LSM-Trees, schedule compaction during off-peak hours.
The Case of the Exploding Write Amplification
- Monitor compaction metrics (e.g., pending compactions, write amplification factor).
- Set appropriate compaction triggers to avoid cascading merges.
- Use leveled compaction instead of size-tiered for predictable write patterns.
- Provision enough I/O bandwidth for compaction overhead.
- Implement backpressure to throttle writes during compaction storms.
SHOW ENGINE INNODB STATUS;SELECT * FROM cache_stats;| File | Command / Code | Purpose |
|---|---|---|
| b-tree-schema.sql | CREATE TABLE employees ( | B-Tree Internals |
| lsm-tree-schema.cql | CREATE TABLE sensor_data ( | LSM-Tree Internals |
| amplification-metrics.sql | SHOW GLOBAL STATUS LIKE 'Innodb_data_written'; | Read and Write Amplification |
| workload-analysis.sql | SELECT | Choosing Between B-Tree and LSM-Tree |
| crash-recovery.sql | SHOW ENGINE INNODB STATUS\G | Concurrency and Crash Recovery |
| tuning-parameters.sql | SET GLOBAL innodb_buffer_pool_size = 8 * 1024 * 1024 * 1024; -- 8GB | Performance Tuning and Best Practices |
Key takeaways
Common mistakes to avoid
3 patternsUsing default compaction settings for LSM-Tree without tuning.
Setting buffer pool too large for B-Tree, causing swapping.
Ignoring bloom filter false positive rate in LSM-Tree.
Interview Questions on This Topic
Explain how a B-Tree handles insertions and what happens when a node becomes full.
Frequently Asked Questions
20+ years shipping production systems from the metal up. Notes here come from systems that actually shipped.
That's DBMS. Mark it forged?
3 min read · try the examples if you haven't