DDIA Chapter 3: Storage and Retrieval

Part I: Foundations of Data Systems

Under every database is a bet about how to lay bytes on disk (or in memory) so writes and reads stay feasible. The simplest efficient write is append to a file; indexes are deliberate trade-offs that speed reads and cost writes.

Log-structured storage

Hash indexes map keys to byte offsets in an append-only log. Fast point lookups; table must fit memory; no efficient range scans. Segments compact in the background.

LSM-trees keep a sorted memtable, flush to sorted segments, and compact. Excellent write throughput and compression; reads may check several levels (bloom filters help negatives). Compaction can steal disk bandwidth.

Page-oriented storage

B-trees split data into fixed-size pages (often ~4KB), stay balanced at O(log n), and dominate OLTP relational engines. WAL + latches provide crash safety and concurrency. Optimizations include copy-on-write pages, key abbreviations, and sibling pointers.

Roughly: LSM favors write-heavy workloads; B-trees favor predictable reads and update-in-place patterns. Both support secondary indexes.

Other index flavors

Clustered indexes store row data in the index. Multi-column / specialized indexes help geo and similar queries. Fuzzy indexes support similarity search. In-memory databases win latency but need a durability story.

OLTP vs analytics

Operational databases (OLTP) optimize point lookups and small transactions. Warehouses (OLAP) optimize scans of few columns over many rows, hence column storage, compression, vectorized execution, and materialized aggregates. ETL/ELT keeps analytics from crushing production.

flowchart TD
  Write[Write] --> Mem[Memtable]
  Mem -->|flush| SS[Sorted segments]
  SS -->|compaction| SS
  Read[Read] --> Mem
  Read --> Newer[Newer SSTables]
  Newer --> Older[Older SSTables]

Snippet

Snippets below use Java, SQL, or plain text as labeled.

Append-only log + hash index (sketch):

public final class HashKvStore {
  private final Map<String, Long> index = new HashMap<>();
  private final RandomAccessFile log;

  public void put(String key, byte[] value) throws IOException {
    long offset = log.length();
    log.seek(offset);
    log.writeUTF(key);
    log.writeInt(value.length);
    log.write(value);
    index.put(key, offset);
  }

  public byte[] get(String key) throws IOException {
    Long offset = index.get(key);
    if (offset == null) return null;
    log.seek(offset);
    log.readUTF();
    int len = log.readInt();
    byte[] value = new byte[len];
    log.readFully(value);
    return value;
  }
}

Column vs row mental model:

Row:    [user=1,age=30,city=Berlin] [user=2,age=41,city=Lisbon]
Column: age -> [30, 41...]   # scan only what the query needs

So what

Indexes speed reads and cost writes, so add them on purpose. LSM trees lean write-heavy; B-trees are the usual OLTP default. Keep analytics off the OLTP box when you can.