What Is a Storage Engine?#

A storage engine is the component responsible for how data is physically stored, retrieved, and modified. It sits below the SQL layer and above the operating system, composing several subsystems covered in earlier sections.

┌─────────────────────────────────────────────┐
│               SQL Layer                      │
│  (Parser → Planner → Optimiser → Executor)  │
├─────────────────────────────────────────────┤
│             Storage Engine                   │
│  ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│  │ Access   │ │ Buffer   │ │ Transaction │ │
│  │ Methods  │ │ Pool     │ │ Manager     │ │
│  │ (B+tree, │ │          │ │ (WAL, MVCC, │ │
│  │  heap,   │ │          │ │  locks)     │ │
│  │  LSM)    │ │          │ │             │ │
│  └──────────┘ └──────────┘ └─────────────┘ │
├─────────────────────────────────────────────┤
│         File System / Direct I/O             │
├─────────────────────────────────────────────┤
│              Storage Device                  │
└─────────────────────────────────────────────┘

Engine Composition#

Every storage engine combines the same fundamental subsystems — the differences lie in the choices each subsystem makes.

Subsystem Map#

SubsystemResponsibilityChoices
Access methodOn-disk data structure for tables and indexesB+tree, LSM-tree, heap + indexes
Buffer poolPage caching in memoryLRU, clock sweep, direct I/O
WALCrash recovery and durabilityRedo-only, undo/redo (ARIES), WAL-per-table
Concurrency controlTransaction isolationMVCC (append), MVCC (undo log), 2PL
Lock managerWrite–write coordinationRow locks, page locks, implicit (xmax)
Compaction / vacuumReclaiming dead spaceBackground vacuum, compaction, in-place

MySQL: InnoDB vs MyISAM#

MySQL’s pluggable storage engine architecture lets different tables use different engines. The SQL layer is shared; the engine handles storage.

MySQL Architecture:
┌──────────────────────────────────┐
│        MySQL Server Layer         │
│  (Parser, Optimiser, Executor)   │
├────────────┬─────────────────────┤
│  InnoDB    │  MyISAM             │
│  Engine    │  Engine             │
│            │                     │
│  B+tree    │  B+tree indexes    │
│  clustered │  + heap data files  │
│  MVCC      │  Table locks only   │
│  WAL       │  No crash recovery  │
│  Row locks │  No transactions    │
└────────────┴─────────────────────┘

InnoDB#

FeatureInnoDB
Primary structureClustered B+tree (data stored in leaf nodes of primary key index)
Secondary indexesPoint to primary key value (not TID) — requires PK lookup
ConcurrencyMVCC with undo log + row-level locks
DurabilityRedo log (WAL) + doublewrite buffer
Buffer poolOwn buffer pool (innodb_buffer_pool_size), uses O_DIRECT
Crash recoveryRedo log replay + undo rollback
Foreign keysSupported

InnoDB Clustered Index#

Primary key B+tree (clustered):
         ┌─────────────┐
         │  [5 | 20]   │  ← Internal nodes: PK values
         └──┬──────┬───┘
     ┌──────┘      └──────┐
     ▼                    ▼
┌───────────────┐  ┌───────────────┐
│ PK=1: row data│  │ PK=5: row data│  ← Leaf nodes contain the full row
│ PK=2: row data│  │ PK=10: row... │
│ PK=3: row data│  │ PK=15: row... │
└───────────────┘  └───────────────┘

Secondary index:
         ┌───────────────┐
         │  [email vals] │
         └──────┬────────┘
                ▼
  ┌──────────────────────────┐
  │ email='alice' → PK=3    │  ← Points to PK, not page/slot
  │ email='bob'   → PK=1    │
  └──────────────────────────┘

Secondary index lookup = index scan + PK lookup (double traversal)

MyISAM#

FeatureMyISAM
Primary structureHeap file (.MYD) + separate B+tree indexes (.MYI)
ConcurrencyTable-level locks only (no row locking)
DurabilityNo WAL, no crash recovery (relies on REPAIR TABLE)
TransactionsNot supported
Full-text searchYes (legacy; InnoDB now supports it too)
Use caseRead-heavy, non-transactional (largely deprecated)

Why InnoDB Won#

MyISAM’s table-level locking made it unusable for concurrent write workloads. InnoDB became MySQL’s default engine in version 5.5 (2010) and MyISAM is now legacy.


PostgreSQL: Heap Storage#

PostgreSQL does not have pluggable storage engines in the MySQL sense (though the Table Access Method API introduced in v12 enables alternatives). The default is a heap-based storage engine.

PostgreSQL Storage:
┌───────────────────────────────┐
│  Heap table (unordered pages) │
│  ┌──────┬──────┬──────┐      │
│  │Page 0│Page 1│Page 2│...   │
│  └──────┴──────┴──────┘      │
├───────────────────────────────┤
│  B+tree indexes (separate)   │
│  Index entries → TID (page, slot) │
├───────────────────────────────┤
│  WAL (pg_wal/)               │
├───────────────────────────────┤
│  MVCC via tuple headers      │
│  (xmin, xmax in heap tuples) │
└───────────────────────────────┘

PostgreSQL vs InnoDB Architecture#

FeaturePostgreSQL (heap)MySQL InnoDB (clustered)
Table structureUnordered heapClustered B+tree (ordered by PK)
Primary key lookupIndex scan → heap fetchDirect (data is in the PK index)
Secondary index targetTID (page, slot)Primary key value
Secondary index overheadOne heap fetchOne PK B+tree traversal
Update (non-indexed col)HOT if same pageIn-place (no index update)
Dead tuple handlingVACUUMPurge thread (undo log)
Table orderingNot guaranteedOrdered by PK

Table Access Method API#

PostgreSQL v12 introduced an extensible API allowing alternative storage:

  • zheap — undo-log-based MVCC (avoids bloat from dead tuples)
  • Citus Columnar — columnar storage for analytical workloads
  • orioledb — undo-log MVCC with index-organised tables
-- Use an alternative table access method
CREATE TABLE analytics_data (...) USING columnar;

RocksDB#

RocksDB (developed by Facebook, forked from LevelDB) is an LSM-tree based embedded key-value store used as a storage engine in many distributed databases.

RocksDB Architecture:
┌──────────────────────────────────────────┐
│  Application (CockroachDB, TiKV, etc.)  │
├──────────────────────────────────────────┤
│  RocksDB API                             │
│  Put(key, value) / Get(key) / Delete(key)│
├──────────────────────────────────────────┤
│  Memtable (skip list or hash-skip list)  │
├──────────────────────────────────────────┤
│  WAL (write-ahead log per column family) │
├──────────────────────────────────────────┤
│  SST files (sorted string tables)        │
│  Level 0: overlapping                    │
│  Level 1+: non-overlapping (leveled)     │
├──────────────────────────────────────────┤
│  Bloom filters (per-SST or per-block)    │
│  Block cache (LRU)                       │
│  Compression (Snappy, LZ4, Zstd)        │
└──────────────────────────────────────────┘

RocksDB as a Building Block#

SystemUses RocksDB for
CockroachDBDistributed SQL storage layer (migrating to Pebble, a Go rewrite)
TiKV (TiDB)Distributed key-value storage
YugabyteDBDocDB storage engine
Kafka (KRaft)Metadata storage
MyRocksMySQL storage engine alternative to InnoDB

Embedded Databases#

Embedded databases run inside the application process — no separate server, no network protocol.

SQLite#

The world’s most deployed database (billions of installations in phones, browsers, apps).

SQLite Architecture:
┌──────────────────────────────┐
│  Application Process         │
│  ┌──────────────────────┐   │
│  │  SQLite Library       │   │
│  │  ┌──────────────────┐│   │
│  │  │ B-tree (tables   ││   │
│  │  │ and indexes)     ││   │
│  │  ├──────────────────┤│   │
│  │  │ Pager (page      ││   │
│  │  │ cache + WAL)     ││   │
│  │  ├──────────────────┤│   │
│  │  │ OS Interface     ││   │
│  │  └──────────────────┘│   │
│  └──────────────────────┘   │
│              │               │
│         Single File          │
│        (database.db)         │
└──────────────────────────────┘
FeatureSQLite
StorageSingle file (B-tree pages)
ConcurrencyMultiple readers, single writer (file-level lock)
WAL modeMultiple readers + single writer with WAL file
Page size4 KB default (configurable)
TransactionFull ACID (serialised writes)
Use caseMobile apps, desktop apps, edge computing, testing

BoltDB#

A pure-Go embedded key-value store (used by etcd, Consul).

FeatureBoltDB
StorageSingle file, B+tree
ConcurrencyMultiple readers, single writer (MVCC via copy-on-write)
TransactionFull ACID
Use caseEmbedded metadata storage for Go applications

LevelDB#

Google’s embedded key-value store (inspiration for RocksDB).

FeatureLevelDB
StorageLSM-tree (memtable + sorted files)
ConcurrencySingle process (no multi-process access)
CompactionLeveled
Use caseChrome’s IndexedDB backend, embedded applications

Comparison: Engine Families#

Engine TypeData StructureBest ForWeakness
Heap + B+tree (PostgreSQL)Unordered heap + separate indexesGeneral purpose, flexibleTuple bloat (MVCC in heap)
Clustered B+tree (InnoDB)Data in PK index leavesPK lookups, range scans on PKSecondary index double-lookup
LSM-tree (RocksDB, LevelDB)Memtable + SSTablesWrite-heavy, time-seriesRead amplification, compaction overhead
B-tree single-file (SQLite, BoltDB)B-tree pages in one fileEmbedded, edge, testingLimited concurrency

How Engines Compose: Summary Diagram#

┌────────────────────────────────────────────────┐
│              Query Layer (SQL)                  │
│  Parse → Analyse → Rewrite → Plan → Execute    │
├─────────────┬──────────────┬───────────────────┤
│ Access      │ Buffer Pool  │ Transaction       │
│ Methods     │              │ Manager           │
│             │              │                   │
│ ┌─────────┐ │ ┌──────────┐ │ ┌───────────────┐ │
│ │ Heap    │ │ │ Page     │ │ │ WAL           │ │
│ │ B+tree  │ │ │ cache    │ │ │ (redo/undo)   │ │
│ │ GiST    │ │ │ Eviction │ │ ├───────────────┤ │
│ │ GIN     │ │ │ Dirty    │ │ │ MVCC          │ │
│ │ BRIN    │ │ │ flush    │ │ │ (visibility)  │ │
│ │ Hash    │ │ │ Prefetch │ │ ├───────────────┤ │
│ └─────────┘ │ └──────────┘ │ │ Lock Manager  │ │
│             │              │ │ (deadlock det.)│ │
│             │              │ └───────────────┘ │
├─────────────┴──────────────┴───────────────────┤
│           File System / Direct I/O              │
├────────────────────────────────────────────────┤
│              Storage (SSD/HDD)                  │
└────────────────────────────────────────────────┘

Each subsystem depends on the others:

  • The access method requests pages from the buffer pool
  • The buffer pool respects the WAL protocol before flushing dirty pages
  • The transaction manager uses the lock manager for write coordination and the WAL for durability
  • The access method checks MVCC visibility before returning tuples

Choosing a Storage Engine#

WorkloadRecommended EngineWhy
General OLTPPostgreSQL (heap) or InnoDBMature MVCC, row-level locking, full SQL
PK-heavy lookupsInnoDB (clustered)Data is in the PK index — zero heap fetch
Write-heavy (logs, events)LSM-based (RocksDB, Cassandra)Sequential writes, high throughput
Embedded / edgeSQLiteZero-config, single file, ACID
Analytics (columnar)ClickHouse, DuckDB, Citus ColumnarColumn store, compression, vectorised execution
Distributed SQLCockroachDB (Pebble), TiDB (TiKV)LSM + Raft consensus

Key Takeaways#

  • A storage engine composes access methods (B+tree, LSM, heap), a buffer pool, a WAL, MVCC, and a lock manager — each making trade-offs that define the engine’s personality.
  • MySQL’s pluggable architecture lets different tables use different engines; InnoDB’s clustered B+tree with MVCC and row locking made it the universal default over MyISAM.
  • PostgreSQL uses a heap + separate B+tree indexes with MVCC in tuple headers. The Table Access Method API (v12+) opens the door to alternative engines.
  • RocksDB is the dominant LSM-tree engine for distributed databases — it provides a key-value API that CockroachDB, TiDB, and others build SQL layers on top of.
  • Embedded databases (SQLite, BoltDB, LevelDB) eliminate the server process, running entirely within the application — suitable for edge computing, mobile, and metadata storage.
  • No single engine is best for all workloads. Understanding how the subsystems compose lets you match the engine to the access pattern.