Storage engines skill

A storage engine is the component of a database that handles how data is written to and read from disk (or memory).

by wondelai·MIT license·★ 2,235 Stars on the repo·GitHub ↗

Use now

Files of Storage engines

wondelai/main1 file
storage-engines.md
Show the full text290 lines

Storage Engines

A storage engine is the component of a database that handles how data is written to and read from disk (or memory). Understanding storage engine internals is essential for predicting performance, choosing appropriate indexes, and avoiding pathological workloads.

Two Families of Storage Engines

All storage engines face a fundamental trade-off: optimizing for write performance or read performance. The two dominant approaches are log-structured engines (optimized for writes) and page-oriented engines (balanced reads and writes).


Log-Structured Engines: LSM Trees and SSTables

How LSM Trees Work

LSM (Log-Structured Merge) trees use a multi-level structure:

  1. Memtable: An in-memory balanced tree (typically a red-black tree or skip list) that receives all writes
  2. Flush to SSTable: When the memtable reaches a size threshold (typically 64MB-256MB), it is written to disk as a Sorted String Table (SSTable) -- a file of key-value pairs sorted by key
  3. Compaction: Background processes merge multiple SSTables into fewer, larger SSTables, removing deleted keys and resolving duplicates
Write Path
Client Write
    |
    v
Write-Ahead Log (WAL) -- sequential append for durability
    |
    v
Memtable (in-memory sorted structure)
    |
    v  (when full)
SSTable on disk (sorted, immutable file)
    |
    v  (background)
Compaction: merge SSTables into larger, consolidated files
Read Path
Client Read
    |
    v
Check Memtable (most recent writes)
    |  (miss)
    v
Check Bloom Filters for each SSTable level
    |  (possible match)
    v
Binary search within SSTable
    |
    v
Return value (or not found)
Compaction Strategies
Strategy How It Works Trade-off
Size-tiered SSTables of similar size are merged together Better write throughput; more space amplification
Leveled SSTables are organized into levels of increasing size; each level is non-overlapping Better read performance and space efficiency; higher write amplification
  • Cassandra defaults to size-tiered compaction
  • RocksDB and LevelDB use leveled compaction
  • Choice depends on read/write ratio and disk space constraints
LSM Tree Strengths
  • Sequential writes: All disk writes are sequential appends, which is much faster than random I/O on both HDDs and SSDs
  • High write throughput: Buffering in memory and batch-flushing to disk minimizes disk operations per write
  • Compression: Sorted SSTables compress well, reducing storage costs
  • No fragmentation: Compaction produces fresh, defragmented files
LSM Tree Weaknesses
  • Read amplification: A point read may need to check the memtable plus multiple SSTables at different levels
  • Write amplification: A single logical write may be written and rewritten multiple times through compaction (typical: 10-30x)
  • Compaction interference: Background compaction consumes CPU and I/O bandwidth, causing latency spikes if not tuned
  • Space amplification: During compaction, both old and new SSTables exist temporarily, requiring 2x space
Databases Using LSM Trees
  • Cassandra, ScyllaDB, HBase (size-tiered)
  • RocksDB, LevelDB (leveled)
  • CockroachDB, TiKV (RocksDB-based)

Page-Oriented Engines: B-Trees

How B-Trees Work

B-trees organize data in fixed-size pages (typically 4KB-16KB) arranged in a balanced tree structure:

  1. Root page: Contains keys and pointers to child pages
  2. Internal pages: Contains keys that split the key space and pointers to child pages
  3. Leaf pages: Contains the actual key-value pairs (or pointers to heap file rows)

A B-tree with a branching factor of 500 and 4 levels can store up to 256TB of data (500^4 pages).

Write Path
Client Write
    |
    v
Write-Ahead Log (WAL) -- for crash recovery
    |
    v
Traverse B-tree from root to leaf
    |
    v
Update the leaf page in place
    |  (if page is full)
    v
Split page: create two half-full pages, update parent pointer
Read Path
Client Read
    |
    v
Start at root page
    |
    v
Binary search within page for correct child pointer
    |
    v
Follow pointer to next level
    |  (repeat log(n) times)
    v
Reach leaf page, binary search for key
    |
    v
Return value
B-Tree Strengths
  • Predictable read latency: Every lookup follows the same number of page accesses (tree depth), typically 3-4 for practical databases
  • Efficient point lookups: O(log n) with small constants due to high branching factor
  • Mature and battle-tested: 40+ years of optimization, well-understood behavior
  • Good range scan performance: Leaf pages are often linked, allowing sequential scanning
B-Tree Weaknesses
  • Write amplification: Even a small update requires rewriting an entire page (typically 4KB-16KB)
  • Page splits: When a page is full, it splits into two, requiring parent page updates (can cascade)
  • Fragmentation: Over time, pages become partially full, wasting space
  • Concurrency control: In-place updates require careful locking (latches) to prevent torn reads
Databases Using B-Trees
  • PostgreSQL, MySQL/InnoDB, Oracle, SQL Server
  • SQLite
  • Most traditional relational databases

LSM Trees vs. B-Trees: Decision Guide

Factor LSM Trees B-Trees
Write throughput Higher (sequential writes) Lower (random in-place updates)
Read latency Less predictable (multiple levels) More predictable (fixed tree depth)
Space efficiency Better (compaction removes dead entries) Worse (fragmentation, partial pages)
Write amplification Higher (compaction rewrites) Lower per write, but each write is a full page
Compression Better (sorted data compresses well) Moderate
Concurrency No in-place updates, simpler Requires page-level latching
Maturity Newer, less predictable edge cases Decades of production hardening
Best for Write-heavy, append-heavy workloads Mixed read/write OLTP
Rules of Thumb
  • Write-heavy with few reads: LSM tree (Cassandra, RocksDB)
  • Read-heavy with indexed lookups: B-tree (PostgreSQL, MySQL)
  • Mixed OLTP: B-tree, unless write throughput is the bottleneck
  • Time-series ingestion: LSM tree (high sequential write rate)
  • When in doubt: Start with B-tree (PostgreSQL); it handles most workloads well

Column-Oriented Storage

The Problem with Row Storage for Analytics

Analytical queries typically access a few columns across millions or billions of rows:

SELECT product_category, SUM(revenue), COUNT(*)
FROM sales
WHERE sale_date BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY product_category;

In a row-oriented store, this query reads entire rows (all columns) even though it only needs three columns. With 100 columns and 1 billion rows, you read 100x more data than necessary.

How Column Storage Works

Column-oriented storage stores each column separately:

Row store:          Column store:
[id, name, age]     ids:   [1, 2, 3, ...]
[1, Alice, 30]      names: [Alice, Bob, Carol, ...]
[2, Bob, 25]        ages:  [30, 25, 28, ...]
[3, Carol, 28]
Column Storage Benefits
  • I/O reduction: Only read the columns your query needs
  • Compression: Values in a single column have similar data types and distributions, enabling excellent compression (often 10:1)
  • Vectorized processing: Modern CPUs process arrays of same-typed values much faster than individual rows (SIMD instructions)
  • Bitmap indexes: Column values can be efficiently indexed with bitmaps for fast filtering
Column Storage Implementations
Database Type Key Feature
ClickHouse Column OLAP MergeTree engine, real-time aggregation
Apache Parquet File format Columnar storage for Hadoop/Spark/data lakes
Apache ORC File format Optimized for Hive, predicate pushdown
BigQuery Cloud OLAP Serverless, automatic optimization
Redshift Cloud OLAP Zone maps, sort keys for pruning
DuckDB Embedded OLAP In-process, Parquet-native

In-Memory Databases

Why In-Memory is Fast

The performance advantage of in-memory databases is not simply "RAM is faster than disk." RAM-based systems are fast because they avoid the overhead of encoding data into disk-friendly formats. In-memory data structures (hash tables, skip lists, trees) can be used directly without serialization.

In-Memory Database Types
Database Model Persistence Use Case
Redis Key-value, data structures Optional (RDB snapshots, AOF log) Caching, sessions, rate limiting, queues
Memcached Key-value None Simple caching
VoltDB Relational Durable (WAL + replication) High-throughput OLTP with serializability
SAP HANA Relational + column Durable Mixed OLTP/OLAP
Anti-Heap: Persistence for In-Memory Databases

In-memory databases that need durability use several techniques:

  • Write-ahead log (WAL): Append every write to disk sequentially; on crash, replay the log
  • Periodic snapshots: Write the entire in-memory state to disk at intervals
  • Replication: Keep copies on other machines; if one crashes, another has the data
  • Battery-backed RAM: Hardware guarantee that RAM contents survive power loss

The WAL approach means writes are actually written to disk, but reads never touch disk. The disk serves only as a durability mechanism, not as a primary data structure.


Choosing a Storage Engine: Decision Framework

Step 1: Classify Your Workload
Question Answer Determines
What is your read:write ratio? LSM (write-heavy) vs. B-tree (balanced/read-heavy)
Do you need point lookups or range scans? Hash index (point) vs. B-tree/LSM (range)
Is your data mostly queried by row or by column? Row store (OLTP) vs. column store (OLAP)
Does your data fit in memory? In-memory store for sub-millisecond latency
What latency percentile matters (p50 vs. p99)? B-tree for predictable p99; LSM for better p50
Step 2: Consider Operational Factors
  • Team expertise: Use what your team knows unless there is a compelling reason to switch
  • Ecosystem: Consider drivers, ORMs, monitoring tools, and backup solutions
  • Managed services: Cloud-managed databases reduce operational burden significantly
  • Vendor lock-in: Open-source engines provide more flexibility
Step 3: Test With Your Actual Workload

Benchmarks from the internet are misleading. They test different hardware, different data sizes, different access patterns, and different configurations. The only benchmark that matters is one that uses your data, your queries, and your expected concurrency.

Tools for benchmarking:

  • YCSB (Yahoo Cloud Serving Benchmark): Standard workload generator for key-value stores
  • TPC-C: Standard OLTP benchmark
  • TPC-H: Standard OLAP benchmark
  • pgbench: PostgreSQL-specific benchmark
  • sysbench: MySQL-specific benchmark
1# Storage Engines
2 
3A storage engine is the component of a database that handles how data is written to and read from disk (or memory). Understanding storage engine internals is essential for predicting performance, choosing appropriate indexes, and avoiding pathological workloads.
4 
5## Two Families of Storage Engines
6 
7All storage engines face a fundamental trade-off: optimizing for write performance or read performance. The two dominant approaches are log-structured engines (optimized for writes) and page-oriented engines (balanced reads and writes).
8 
9---
10 
11## Log-Structured Engines: LSM Trees and SSTables
12 
13### How LSM Trees Work
14 
15LSM (Log-Structured Merge) trees use a multi-level structure:
16 
171. **Memtable:** An in-memory balanced tree (typically a red-black tree or skip list) that receives all writes
182. **Flush to SSTable:** When the memtable reaches a size threshold (typically 64MB-256MB), it is written to disk as a Sorted String Table (SSTable) -- a file of key-value pairs sorted by key
193. **Compaction:** Background processes merge multiple SSTables into fewer, larger SSTables, removing deleted keys and resolving duplicates
20 
21### Write Path
22 
23```
24Client Write
25 |
26 v
27Write-Ahead Log (WAL) -- sequential append for durability
28 |
29 v
30Memtable (in-memory sorted structure)
31 |
32 v (when full)
33SSTable on disk (sorted, immutable file)
34 |
35 v (background)
36Compaction: merge SSTables into larger, consolidated files
37```
38 
39### Read Path
40 
41```
42Client Read
43 |
44 v
45Check Memtable (most recent writes)
46 | (miss)
47 v
48Check Bloom Filters for each SSTable level
49 | (possible match)
50 v
51Binary search within SSTable
52 |
53 v
54Return value (or not found)
55```
56 
57### Compaction Strategies
58 
59| Strategy | How It Works | Trade-off |
60|----------|-------------|-----------|
61| **Size-tiered** | SSTables of similar size are merged together | Better write throughput; more space amplification |
62| **Leveled** | SSTables are organized into levels of increasing size; each level is non-overlapping | Better read performance and space efficiency; higher write amplification |
63 
64- **Cassandra** defaults to size-tiered compaction
65- **RocksDB** and **LevelDB** use leveled compaction
66- Choice depends on read/write ratio and disk space constraints
67 
68### LSM Tree Strengths
69 
70- **Sequential writes:** All disk writes are sequential appends, which is much faster than random I/O on both HDDs and SSDs
71- **High write throughput:** Buffering in memory and batch-flushing to disk minimizes disk operations per write
72- **Compression:** Sorted SSTables compress well, reducing storage costs
73- **No fragmentation:** Compaction produces fresh, defragmented files
74 
75### LSM Tree Weaknesses
76 
77- **Read amplification:** A point read may need to check the memtable plus multiple SSTables at different levels
78- **Write amplification:** A single logical write may be written and rewritten multiple times through compaction (typical: 10-30x)
79- **Compaction interference:** Background compaction consumes CPU and I/O bandwidth, causing latency spikes if not tuned
80- **Space amplification:** During compaction, both old and new SSTables exist temporarily, requiring 2x space
81 
82### Databases Using LSM Trees
83 
84- Cassandra, ScyllaDB, HBase (size-tiered)
85- RocksDB, LevelDB (leveled)
86- CockroachDB, TiKV (RocksDB-based)
87 
88---
89 
90## Page-Oriented Engines: B-Trees
91 
92### How B-Trees Work
93 
94B-trees organize data in fixed-size pages (typically 4KB-16KB) arranged in a balanced tree structure:
95 
961. **Root page:** Contains keys and pointers to child pages
972. **Internal pages:** Contains keys that split the key space and pointers to child pages
983. **Leaf pages:** Contains the actual key-value pairs (or pointers to heap file rows)
99 
100A B-tree with a branching factor of 500 and 4 levels can store up to 256TB of data (500^4 pages).
101 
102### Write Path
103 
104```
105Client Write
106 |
107 v
108Write-Ahead Log (WAL) -- for crash recovery
109 |
110 v
111Traverse B-tree from root to leaf
112 |
113 v
114Update the leaf page in place
115 | (if page is full)
116 v
117Split page: create two half-full pages, update parent pointer
118```
119 
120### Read Path
121 
122```
123Client Read
124 |
125 v
126Start at root page
127 |
128 v
129Binary search within page for correct child pointer
130 |
131 v
132Follow pointer to next level
133 | (repeat log(n) times)
134 v
135Reach leaf page, binary search for key
136 |
137 v
138Return value
139```
140 
141### B-Tree Strengths
142 
143- **Predictable read latency:** Every lookup follows the same number of page accesses (tree depth), typically 3-4 for practical databases
144- **Efficient point lookups:** O(log n) with small constants due to high branching factor
145- **Mature and battle-tested:** 40+ years of optimization, well-understood behavior
146- **Good range scan performance:** Leaf pages are often linked, allowing sequential scanning
147 
148### B-Tree Weaknesses
149 
150- **Write amplification:** Even a small update requires rewriting an entire page (typically 4KB-16KB)
151- **Page splits:** When a page is full, it splits into two, requiring parent page updates (can cascade)
152- **Fragmentation:** Over time, pages become partially full, wasting space
153- **Concurrency control:** In-place updates require careful locking (latches) to prevent torn reads
154 
155### Databases Using B-Trees
156 
157- PostgreSQL, MySQL/InnoDB, Oracle, SQL Server
158- SQLite
159- Most traditional relational databases
160 
161---
162 
163## LSM Trees vs. B-Trees: Decision Guide
164 
165| Factor | LSM Trees | B-Trees |
166|--------|-----------|---------|
167| **Write throughput** | Higher (sequential writes) | Lower (random in-place updates) |
168| **Read latency** | Less predictable (multiple levels) | More predictable (fixed tree depth) |
169| **Space efficiency** | Better (compaction removes dead entries) | Worse (fragmentation, partial pages) |
170| **Write amplification** | Higher (compaction rewrites) | Lower per write, but each write is a full page |
171| **Compression** | Better (sorted data compresses well) | Moderate |
172| **Concurrency** | No in-place updates, simpler | Requires page-level latching |
173| **Maturity** | Newer, less predictable edge cases | Decades of production hardening |
174| **Best for** | Write-heavy, append-heavy workloads | Mixed read/write OLTP |
175 
176### Rules of Thumb
177 
178- **Write-heavy with few reads:** LSM tree (Cassandra, RocksDB)
179- **Read-heavy with indexed lookups:** B-tree (PostgreSQL, MySQL)
180- **Mixed OLTP:** B-tree, unless write throughput is the bottleneck
181- **Time-series ingestion:** LSM tree (high sequential write rate)
182- **When in doubt:** Start with B-tree (PostgreSQL); it handles most workloads well
183 
184---
185 
186## Column-Oriented Storage
187 
188### The Problem with Row Storage for Analytics
189 
190Analytical queries typically access a few columns across millions or billions of rows:
191 
192```sql
193SELECT product_category, SUM(revenue), COUNT(*)
194FROM sales
195WHERE sale_date BETWEEN '2024-01-01' AND '2024-12-31'
196GROUP BY product_category;
197```
198 
199In a row-oriented store, this query reads entire rows (all columns) even though it only needs three columns. With 100 columns and 1 billion rows, you read 100x more data than necessary.
200 
201### How Column Storage Works
202 
203Column-oriented storage stores each column separately:
204 
205```
206Row store: Column store:
207[id, name, age] ids: [1, 2, 3, ...]
208[1, Alice, 30] names: [Alice, Bob, Carol, ...]
209[2, Bob, 25] ages: [30, 25, 28, ...]
210[3, Carol, 28]
211```
212 
213### Column Storage Benefits
214 
215- **I/O reduction:** Only read the columns your query needs
216- **Compression:** Values in a single column have similar data types and distributions, enabling excellent compression (often 10:1)
217- **Vectorized processing:** Modern CPUs process arrays of same-typed values much faster than individual rows (SIMD instructions)
218- **Bitmap indexes:** Column values can be efficiently indexed with bitmaps for fast filtering
219 
220### Column Storage Implementations
221 
222| Database | Type | Key Feature |
223|----------|------|-------------|
224| **ClickHouse** | Column OLAP | MergeTree engine, real-time aggregation |
225| **Apache Parquet** | File format | Columnar storage for Hadoop/Spark/data lakes |
226| **Apache ORC** | File format | Optimized for Hive, predicate pushdown |
227| **BigQuery** | Cloud OLAP | Serverless, automatic optimization |
228| **Redshift** | Cloud OLAP | Zone maps, sort keys for pruning |
229| **DuckDB** | Embedded OLAP | In-process, Parquet-native |
230 
231---
232 
233## In-Memory Databases
234 
235### Why In-Memory is Fast
236 
237The performance advantage of in-memory databases is not simply "RAM is faster than disk." RAM-based systems are fast because they avoid the overhead of encoding data into disk-friendly formats. In-memory data structures (hash tables, skip lists, trees) can be used directly without serialization.
238 
239### In-Memory Database Types
240 
241| Database | Model | Persistence | Use Case |
242|----------|-------|-------------|----------|
243| **Redis** | Key-value, data structures | Optional (RDB snapshots, AOF log) | Caching, sessions, rate limiting, queues |
244| **Memcached** | Key-value | None | Simple caching |
245| **VoltDB** | Relational | Durable (WAL + replication) | High-throughput OLTP with serializability |
246| **SAP HANA** | Relational + column | Durable | Mixed OLTP/OLAP |
247 
248### Anti-Heap: Persistence for In-Memory Databases
249 
250In-memory databases that need durability use several techniques:
251 
252- **Write-ahead log (WAL):** Append every write to disk sequentially; on crash, replay the log
253- **Periodic snapshots:** Write the entire in-memory state to disk at intervals
254- **Replication:** Keep copies on other machines; if one crashes, another has the data
255- **Battery-backed RAM:** Hardware guarantee that RAM contents survive power loss
256 
257The WAL approach means writes are actually written to disk, but reads never touch disk. The disk serves only as a durability mechanism, not as a primary data structure.
258 
259---
260 
261## Choosing a Storage Engine: Decision Framework
262 
263### Step 1: Classify Your Workload
264 
265| Question | Answer Determines |
266|----------|-------------------|
267| What is your read:write ratio? | LSM (write-heavy) vs. B-tree (balanced/read-heavy) |
268| Do you need point lookups or range scans? | Hash index (point) vs. B-tree/LSM (range) |
269| Is your data mostly queried by row or by column? | Row store (OLTP) vs. column store (OLAP) |
270| Does your data fit in memory? | In-memory store for sub-millisecond latency |
271| What latency percentile matters (p50 vs. p99)? | B-tree for predictable p99; LSM for better p50 |
272 
273### Step 2: Consider Operational Factors
274 
275- **Team expertise:** Use what your team knows unless there is a compelling reason to switch
276- **Ecosystem:** Consider drivers, ORMs, monitoring tools, and backup solutions
277- **Managed services:** Cloud-managed databases reduce operational burden significantly
278- **Vendor lock-in:** Open-source engines provide more flexibility
279 
280### Step 3: Test With Your Actual Workload
281 
282Benchmarks from the internet are misleading. They test different hardware, different data sizes, different access patterns, and different configurations. The only benchmark that matters is one that uses your data, your queries, and your expected concurrency.
283 
284Tools for benchmarking:
285- **YCSB (Yahoo Cloud Serving Benchmark):** Standard workload generator for key-value stores
286- **TPC-C:** Standard OLTP benchmark
287- **TPC-H:** Standard OLAP benchmark
288- **pgbench:** PostgreSQL-specific benchmark
289- **sysbench:** MySQL-specific benchmark
290 

Discussion