Module F-2·22 min read

Cache lines, the memory hierarchy, RAM vs NVMe vs HDD, IOPS — and why a sequential disk write beats a random RAM access, the one fact that explains Kafka, LSM-trees, and UUID keys later.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module F-2 — Mechanical Sympathy: CPU Caches, Memory, and Disks

What this module covers: Everything above this layer is built on how long the machine takes to do things, and most engineers carry numbers that are wrong by three orders of magnitude. This module fixes that. It covers the memory hierarchy with real latencies, cache lines and why pointer-chasing is slow, the difference between latency and throughput, IOPS and queue depth on NVMe vs SATA vs spinning disks, the page cache and what fsync actually costs — and one specific counterintuitive fact, stated precisely: measured in useful bytes per second, a sequential write to an SSD beats a random access pattern in RAM. That single fact explains Kafka's design, LSM-trees, and why a random UUID primary key quietly wrecks a large table.


Why an Architect Cares About Silicon

You can design systems for years without knowing what an L3 cache is. You cannot, however, do any of the following without it:

  • Estimate whether a proposed design is 10× or 1000× off before you build it (Module F-3).
  • Explain why Cassandra and RocksDB chose a completely different storage structure from Postgres (Module P-2).
  • Explain why switching a primary key from an auto-incrementing integer to a random UUID degraded insert throughput on a 400GB table (Module P-3).
  • Explain how Kafka sustains gigabytes per second while writing every message to disk (Module P-15).

All four have the same answer, and it lives at this layer. "Mechanical sympathy" is Martin Thompson's borrowed term for it — a driver who understands the engine gets more out of the car, without being a mechanic.

Analogy: the memory hierarchy is a kitchen. What's on the cutting board in front of you is a register. The knife block within arm's reach is L1. The fridge two steps away is RAM. The supermarket across town is disk. Ordering from another city is the network. Every layer down is dramatically slower and dramatically bigger, and a good cook's entire technique is mise en place — arranging things so the next ingredient you need is already on the board. That's what a cache line, a prefetcher, and a sequential scan all are: mise en place for data.


The Numbers, and the Same Numbers in Human Time

Approximate latencies on a modern server. These vary by hardware generation, so treat them as orders of magnitude — which is exactly how they should be used.

OperationLatencyScaled: if 1 ns = 1 second
L1 cache reference~1 ns1 second
L2 cache reference~4 ns4 seconds
L3 cache reference~15 ns15 seconds
Main memory (DRAM) reference~100 ns1.5 minutes
NVMe SSD random read (4 KB)~50 µs~14 hours
SATA SSD random read (4 KB)~150 µs~1.7 days
Round trip within a datacentre~0.5 ms~6 days
HDD seek~8 ms~3 months
Round trip Mumbai → Virginia~180 ms~6 years

The right-hand column is the one worth memorising, because it converts abstract nanoseconds into intuitions you already have. A cache hit is picking up the knife next to you. A main-memory read is walking to the fridge. An SSD read is a full working day. A cross-continent round trip is six years — which is why Module A-9 treats a multi-region design as a fundamentally different animal rather than "the same system, further apart."

Two things follow immediately:

Distance dominates everything. The gap between L1 and DRAM (100×) is smaller than the gap between DRAM and NVMe (500×), which is smaller than the gap between NVMe and a cross-continent round trip (3,600×). This is why the single most reliable performance intervention in any system is moving the data closer to the computation — and it's why "just add a cache" works so often it's almost boring.

Nothing in your code will fix a bad layer choice. An algorithm improvement that halves CPU work is worth nothing if the request makes four sequential cross-region calls. The layer you're operating at sets the ceiling; code quality decides where you land under it.


Cache Lines: Memory Doesn't Arrive One Byte at a Time

When the CPU reads a single byte from memory, it does not fetch one byte. It fetches an entire cache line — 64 bytes on essentially every x86-64 and ARM64 server you'll deploy on — and puts it in L1. The consequence is enormous: the next 63 bytes are now effectively free, and reading a byte 1 MB away costs the full DRAM round trip again.

On top of that, the hardware prefetcher watches your access pattern. If you're walking memory with a predictable stride, it starts fetching lines before you ask for them, hiding the latency entirely. If your pattern looks random, it can't help, and you eat ~100 ns per access.

That's the whole mechanism. Now the consequence, which shows up in JavaScript just as much as in C:

typescript

Both loops are O(n). They differ by a large constant factor — often 3–10× on this kind of scan — and Big-O deliberately hides exactly that constant. This is where "O(n) is O(n)" stops being the whole story: two linear scans can differ by an order of magnitude purely from memory layout, and no amount of complexity analysis will show it to you.

The same principle explains why a linked list is slower to traverse than an array with identical complexity — every next pointer is a potential cache miss, and the prefetcher can't guess where the next node lives. It also explains why columnar storage formats (Module P-22) are so much faster for analytics: if a query only needs amount, a columnar layout hands you cache lines containing nothing but amounts, while a row store drags along every other field in each row.

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.