Break
Systems basics
Nano quiz ↗
press b or esc to carry on
M2

What Is a System?

A system is components chosen to work with each other.

2 figures
what-is-a-system

Pick one from each bin

Pick one from each bin Three bins of parts, then three cars built from them. Engines: E1 a motorbike good for 130 mph at five thousand dollars, E2 a turbo four good for 150 at twelve thousand, E3 a jet good for 400 at two million. Chassis: C1 Lego good for 2 mph at fifty dollars, C2 steel good for 150 at ten thousand, C3 carbon good for 250 at five million. Wheels: W1 road tyres good for 150 at six hundred, W2 sport at 200 for two thousand, W3 slicks at 250 for fifty thousand. The builds: E3 plus C1 plus W3 is mismatched and does 2 mph for two million and fifty thousand dollars; E3 plus C3 plus W3 is oversized and does 250 mph for seven million; E2 plus C2 plus W1 is right-sized and does 150 mph for twenty-two thousand six hundred. Pick one from each bin Every part supports a top speed and costs what it costs. The goal: a car that will do 150 mph on a public road. THE PARTS YOU CAN PICK Engine E1 Motorbike 130 mph $5,000 E2 Turbo four 150 mph $12,000 E3 Jet 400 mph $2M Chassis C1 Lego 2 mph $50 C2 Steel 150 mph $10,000 C3 Carbon 250 mph $5M Wheels W1 Road 150 mph $600 W2 Sport 200 mph $2,000 W3 Slick 250 mph $50,000 WHAT YOU CAN BUILD FROM THEM Mismatched E3 + C1 + W3 2 mph $2.05M · C1 is the bottleneck Oversized E3 + C3 + W3 250 mph $7.05M · 100 mph past the goal Right-sized E2 + C2 + W1 150 mph $22,600 · hits the goal exactly ⊙ Takeaway A build runs at its slowest part, and costs all of them.
The goal is a car that will do 150 mph on a road. Pick one row from each bin; the car then runs at its slowest part and costs every part in it. E3+C1+W3 spends $2.05M and travels at walking pace: C1 is the bottleneck, and no engine above it matters. E3+C3+W3 has no bottleneck at all and is still wrong, because 100 mph of it is past anything the goal asked for, at $7.05M. E2+C2+W1 hits 150 on all three parts at once for $22,600.
Notes ↗
what-is-a-system · The same problem, for data

Same idea, for a data system

Same idea, for a data system The goal is to serve ten thousand requests a second. Three bins of parts. Storage: S1 disk at one thousand requests a second for two hundred dollars a month, S2 SSD at ten thousand for six hundred, S3 memory at a hundred thousand for four thousand. Database: D1 a local database at one thousand for two hundred, D2 a cloud database at ten thousand for two thousand, D3 a NoSQL cluster at a hundred thousand for fifteen thousand. Servers: P1 two nodes at one thousand for four hundred, P2 twenty nodes at ten thousand for three thousand, P3 two hundred nodes at a hundred thousand for thirty thousand. The builds: S3 plus D1 plus P3 puts memory and two hundred nodes behind a single local database, so the local database is the bottleneck and it serves one thousand a second for thirty-four thousand two hundred; S3 plus D3 plus P3 serves a hundred thousand, ten times past the goal, for forty-nine thousand; S2 plus D2 plus P2 hits the goal exactly for five thousand six hundred. Same idea, for a data system Each layer sustains what it sustains, and costs what it costs. The goal: serve 10,000 requests a second. THE PARTS YOU CAN PICK Storage S1 Disk 1,000 r/s $200 S2 SSD 10,000 r/s $600 S3 Memory 100,000 r/s $4,000 Database D1 Local 1,000 r/s $200 D2 Cloud 10,000 r/s $2,000 D3 NoSQL 100,000 r/s $15,000 Servers P1 2 nodes 1,000 r/s $400 P2 20 nodes 10,000 r/s $3,000 P3 200 nodes 100,000 r/s $30,000 WHAT YOU CAN BUILD FROM THEM Mismatched S3 + D1 + P3 1,000 r/s $34,200 · D1 is the bottleneck Oversized S3 + D3 + P3 100,000 r/s $49,000 · 10x past the goal Right-sized S2 + D2 + P2 10,000 r/s $5,600 · hits the goal exactly ⊙ Takeaway The slowest layer decides. You pay for all of them.
Same bins, same arithmetic, and a goal of ten thousand requests a second. S3+D1+P3 puts everything in memory and rents two hundred nodes, then serves it all through one local database: D1 is the bottleneck, so the system does a thousand a second and pays $34,200 a month to miss the goal. S3+D3+P3 has no bottleneck and is still wrong, at ten times the goal for $49,000. S2+D2+P2 hits it exactly for $5,600. Storage, database and servers do the job the engine, chassis and wheels did.
Notes ↗
M2

Data Centers: The Backbone of Modern Databases

Latency in a modern data center spans eight orders of magnitude, from a nanosecond on-chip to a hundred milliseconds across the planet.

1 figure
data-centers · Latency Tiers

One data center: seven orders of magnitude of latency

One data center: seven orders of magnitude of latency Seven latency tiers on one axis, ordered fastest to slowest left to right: L1 cache 1 ns on-chip, RAM 100 ns on-board, SSD 100 microseconds same rack, network within the data center 500 microseconds an aisle away, spinning disk 10 milliseconds same rack, cross-region 50 milliseconds a continent away, cross-continent 100 milliseconds the other side of Earth. The span from end to end is 100 million times. An algorithm that reads from the wrong tier pays up to 10,000,000 times more. Color key: orange is a latency tier, grey is the fast-to-slow axis. One data center: seven orders of magnitude The same byte is 1 nanosecond or 100 milliseconds away, depending on its tier. faster slower L1 cache on-chip 1 ns RAM on-board 100 ns SSD same rack 10 µs Network aisle away 500 µs Disk same rack 10 ms Cross-region continent away 50 ms Cross-continent across Earth 100 ms End to end is 100,000,000×. Read from the wrong tier and one access costs up to 10,000,000× more. Color key  orange = a latency tier  ·  grey = the fast-to-slow axis
Seven tiers in one data center, fastest to slowest, from L1 cache at 1 ns to a cross-continent round trip at 100 ms. End to end is 100,000,000x, eight orders of magnitude, and a read from the wrong tier costs up to 10,000,000x more.
Notes ↗
M2

How Postgres Runs on Linux

Linux runs on any hardware.

4 figures
os-and-databases · The stack

Four layers from hardware up to your database

Four layers from hardware up to your databaseFour stacked layers, numbered from the bottom. Layer one, grey, is the hardware: CPUs, eight or a hundred and twenty-eight; RAM, sixteen gigabytes or two terabytes; SSDs and disks; and a network card reaching storage elsewhere. Layer two, slate, is Linux, which context-switches the CPUs between programs, maps addresses to RAM, and names files and finds their blocks; it is the only layer that touches the hardware. Layer three is the process, a sandbox per program, holding three boxes: a memory virtual RAM, a slice of the real thing counted in pages; a virtual CPU, a slice of a real one taken away and given back; and files, each a stack of same-size pages. RAM and files are both drawn as rows of small page squares, and the CPU slice as a bar with three of its ticks highlighted. Layer four, orange, is your application running a SELECT, which runs inside layer three and never touches layer one. Arrows run down the stack from each layer to the one below. Color key: orange is your application, slate is Linux and what it builds, grey is real hardware.Four layers, and only the bottom one is hardwareLinux builds layers 2 and 3. Postgres only ever talks to layer 3.1 . HARDWARECPUs8, or 128RAM16 GB, or 2 TBSSD and disksone, or twelveNetwork cardstorage elsewhere2 . LINUXevery program gets the same three thingsthe only layer that touches the hardware3 . THE PROCESSvirtual RAMmapped to physical RAM,counted in pagesvirtual CPUscheduled on a real CPU,taken back at a context switchfileseach one a stack ofsame-size OS-pages4 . APPLICATIONPostgresSELECT … FROM listensruns inside layer 3, and never touches layer 1
Linux runs on hardware and exports processes. A process includes a private memory space that Linux maps to physical RAM, a set of open files referenced through handles, and time on a CPU that the kernel schedules via context switches. Postgres runs as a program inside a process and uses only these interfaces.
Notes ↗
os-and-databases · One pool, many workers

A short pool of pages over a big file of them, and two workers on one page

A short pool of pages over a big file of them, and two workers on one pageAt the bottom, the table on disk is drawn as a field of small grey squares, three rows of forty, every one a page of the same size, with a note that a real table has thousands of them. Above it an orange box is the Postgres server. Inside it, four slate worker boxes, one per connection, running SELECT rating, UPDATE rating, SELECT AVG and INSERT. Below the workers, a short row of twenty-four squares is the buffer pool, one shared-memory region every worker is attached to. Three pages then turn orange in scattered places across the file and arrows carry them up into three slots of the pool, labelled read(), three pages in out of thousands. Finally two arrows drop from worker 1 and worker 2 onto the same one of those pages, labelled reading and writing, and it is outlined in orange. A closing panel asks the two questions: worker 1 is halfway through that page when worker 2 changes it, what should worker 1 see; and worker 2's write has already returned, so if the power fails now is the change still there. Color key: orange is a page in play, slate is a worker and the kernel's read, grey is the disk.One pool of pages, many workersThe pool is a small window onto a big file. Everything hard starts when two workers want the same page.THE TABLE ON DISK · THOUSANDS OF DB-PAGES… and onTHE POSTGRES SERVERone worker per connectionITS BUFFER POOLDB-pages, in one shared-memory regionworker 1SELECT ratingworker 2UPDATE ratingworker 3SELECT AVGworker 4INSERTread()readingwritingTWO QUESTIONSIf worker 2 changes the page mid-read, what does worker 1 see?If power fails after write() returns, is the change still there?
A table occupies many DB-pages on disk. The buffer pool holds a small subset of those DB-pages, pulled from scattered locations. Multiple workers can access the same cached DB-page, including one worker that reads it and another that modifies it. Linux treats both workers as ordinary processes that read and write ordinary memory.
Notes ↗
os-and-databases · From one machine to many

A notebook and a paperback: page size against page count

A notebook and a paperback: page size against page countTwo books side by side, drawn the same way. On the left a notebook: a tall orange rectangle labelled one page, 8.5 by 11 inches, and beside it a short stack of ten grey bars seen edge-on, tens of pages. On the right a paperback: a rectangle about half the area, also labelled one page, and beside it a much taller stack of forty bars, hundreds of pages. Both stacks sit on the same baseline so the two contrasts read together: the notebook has the bigger page and fewer of them, the paperback the smaller page and far more. A panel underneath reads: a DB-page varies both ways too, Postgres picks 8 KB, a big-data system picks 64 MB. Color key: orange is one page, the unit; grey is the count.Two ways a book variesPage size and page count are separate choices.one pageNOTEBOOK8.5 by 11 inchestens of pagesone pagePAPERBACKabout half the pagehundreds of pagesA DB-page varies both ways too. Postgres picks 8 KB. A big-data system picks 64 MB.
Page size and page count vary independently. A DB-page also has a fixed size, and a table spans as many DB-pages as its data requires.
Notes ↗
os-and-databases · From one machine to many

One DB-page magnified into the OS-pages it is made of

One DB-page magnified into the OS-pages it is made ofAcross the top, a table on disk is drawn as a long row of eleven grey tiles with an ellipsis between them, labelled: every tile is one DB-page, and a real table has thousands of them. The third tile is outlined in orange and two dashed lines widen from it down to a large panel, the magnified view of that one DB-page. The panel is filled with a grid of much smaller grey squares, the OS-pages, 4 KB each, with an ellipsis cutting each row short and vertical ellipses before the last row, so the drawn squares read as a sample: 16,384 of them make one 64 MB DB-page, and a big-data system reads all 16,384 as one. Finally two adjacent squares are outlined and an arrow rises to them from a panel reading: Postgres picks the small end, one DB-page is 8 KB, two squares. The closing line says Linux always moves one square, and the database decides how many squares count as one read. Color key: grey is the kernel's unit, orange is the database's choice.Zoom in on one DB-pageDB-pages are made of OS-pages.ONE TABLE ON DISKevery tile is one DB-page, and a real table has thousands of themINSIDE ONE DB-PAGE · OS-PAGES, 4 KB EACH, THE UNIT LINUX MOVES16,384 squares make one 64 MB DB-page, read as onePostgres DB-page: 8 KB, two squaresLinux always moves one square. The database decides how many squares count as one read.
A table on disk consists of DB-pages. Each DB-page spans an integer number of 4 KB OS-pages on x86-64. Postgres groups two OS-pages into one 8 KB DB-page. A big-data system groups 16,384 OS-pages into one 64 MB DB-page.
Notes ↗
M2

Paging & Storage: How Does One Machine Process 128 GB with Only 16 GB of RAM?

Disks and SSDs read in fixed-size pages (typically 64 MB in big-data systems).

4 figures
storage-paging · Paging: From CPU to RAM

The CPU works through cache and RAM, never the SSD, and data moves one page at a time

The CPU works through cache and RAM, never the SSD, and data moves one page at a time Three tiers left to right. The CPU on the left works only out of its registers and L1, L2, L3 cache, which the hardware fills from RAM in 64-byte cache lines, in nanoseconds. RAM in the middle holds 16 GB divided by 64 MB, which is 256 fixed-size page slots, drawn as a small grid. The SSD on the right holds 128 GB divided by 64 MB, which is 2048 pages, drawn as a larger grid of the same-size cells; one is highlighted orange as it is paged into a free RAM slot, labelled one page, 64 MB, one IO, transfer-bound at about 5 GB per second so about 13 milliseconds. A page is 8 KB in classic Unix and databases, 64 MB in big-data systems. When RAM fills, a page is evicted and the next is paged in, so all 2048 SSD pages stream through the 256 RAM slots one page at a time. Color key: orange is a page in motion (the unit of one IO), grey is structure. The CPU never touches the SSD Pages move SSD → RAM one at a time (one page = one IO). The CPU works out of cache, filled from RAM. A page is 8 KB in classic Unix and databases, 64 MB in big-data systems (shown here). CPU registers · sub-ns L1 / L2 / L3 cache ~1 to 10 ns works here, never the SSD RAM · 16 GB 16 GB ÷ 64 MB = 256 pages 6 of 256 slots shown SSD · 128 GB 128 GB ÷ 64 MB = 2048 pages 24 of 2048 pages shown load 1 page = 1 IO Mickey's listens · 64 MB SSD ~5 GB/s = ~13 ms (transfer-bound) cache lines · 64 B ~100 ns to RAM When RAM fills, evict a page and load the next: all 2048 SSD pages stream through 256 RAM slots, one at a time. Color key  orange = a page in motion (one IO)  ·  grey = structure
SSD-to-RAM transfers move one page per IO. One 64 MB IO costs the same whether the query needs one row or the whole block, and transfer time dominates, so moving 64 MB at about 5 GB/s takes ~13 ms. RAM-to-CPU transfers move 64-byte cache lines into L1/L2/L3 in nanoseconds, and the core executes out of cache and registers. RAM holds 256 pages (16 GB) and the SSD holds 2048 pages (128 GB), so the dataset passes through RAM one page at a time.
Notes ↗
storage-paging · Why Paging Matters: The Speed Gap

Why paging matters: the speed gap scaled to human time

Why paging matters: the speed gap scaled to human time Four storage tiers left to right, each access time rescaled as if one L1 cache hit took 1 second. L1 cache is 1 second, RAM is 1.5 minutes (100 times slower), SSD is 3 hours (another 100 times), and a hard disk is 4 months (another 1000 times). The point: a disk read feels like a season when a RAM read feels like a short pause, which is why paging keeps the working set in the fast tiers. Color key: orange is a storage tier and its human-scaled wait, grey is the slowdown factor between tiers. Why paging matters: the speed gap, in human time If one L1 cache hit took 1 second, each tier would feel this long. L1 cache ~1 ns 1 second a blink ×100 RAM ~100 ns 1.5 minutes a short pause ×100 SSD ~10 µs 3 hours an afternoon ×1000 Hard disk ~10 ms 4 months a whole season Paging keeps the working set in RAM, so a read is a 1.5-minute pause, not a 4-month wait. Color key  orange = a storage tier and its human-scaled wait  ·  grey = the slowdown between tiers
Rescale latency so an L1 cache hit takes one second. RAM becomes 1.5 minutes, an SSD read becomes 3 hours, and a hard disk read becomes 4 months. Paging keeps the working set in RAM. A query that reads from disk on every page pays the hard disk or SSD cost once per page.
Notes ↗
storage-paging · Storage Hierarchy: Speed, Cost, and Capacity

The storage hierarchy as a staircase

The storage hierarchy as a staircase Four storage tiers as a descending staircase. Cache and registers at about 1 ns sit at the top, then RAM at 100 ns, 3,500 dollars per TB, 16 GB. One step down is SSD at 10 microseconds, 75 dollars per TB, 512 GB, 100 times slower. One more step is HDD at 10 milliseconds, 25 dollars per TB, 4 TB, another 1,000 times slower. A left-side axis marks faster, costlier, smaller at the top and slower, cheaper, bigger at the bottom. Color key: orange is a storage tier, grey is the speed cost size axis. The storage hierarchy: faster costs more, holds less Each step down trades latency for capacity and price. faster · costlier · smaller slower · cheaper · bigger CPU registers + L1/L2 cache ~1 ns · kilobytes · the hottest data RAM (buffer pool) 100 ns · $3,500 / TB · 16 GB ×100 slower SSD 10 µs · $75 / TB · 512 GB ×1,000 slower HDD 10 ms · $25 / TB · 4 TB Color key  orange = a storage tier  ·  grey = the speed, cost, size axis
Cache, RAM, SSD, and HDD form a latency ladder. Each step down runs roughly 100x to 1,000x slower and costs less per terabyte while providing more capacity: RAM at 100 ns and $3,500/TB, SSD at 10 µs and $75/TB, HDD at 10 ms and $25/TB. An algorithm chooses a working set size that fits within a tier.
Notes ↗
storage-paging · Modern Reality: CPUs/GPUs Can't Escape the Disk Bottleneck

One machine: compute is free, the link is the bottleneck

One machine: compute is free, the link is the bottleneck Two single-machine setups side by side, a laptop and a server, each drawn as a vertical data path: compute (CPU plus GPU) at the top, then RAM, then SSD storage at the bottom, joined by links. Compute is grey because it is effectively free: a GPU does hundreds of trillions of operations per second. RAM has a red strip because it is volatile, lost on power off. The SSD has a green strip because it is durable, it survives. The links are orange because that is the bottleneck: the PCIe bus feeds the GPU at only about 32 GB/s and an SSD reads at about 5 GB/s. The server has more cores, more RAM, and many SSDs, but each link runs at the same speed, so scaling adds capacity, not bandwidth. Color key: orange is a data-movement link, the bottleneck; grey is compute, effectively free; red is RAM, volatile; green is the SSD, durable. One machine: compute is free, the link is the bottleneck Laptop or server, the GPU outruns every link that feeds it. Laptop Compute: 8 CPU cores + GPU ~10 TFLOPS does the math in ~0 time memory bus ~50 GB/s RAM 16 GB · volatile SSD link ~5 GB/s 1 × NVMe SSD 1 TB · durable Server Compute: 64 CPU cores + A100 GPU 312 TFLOPS, GPU memory ~2 TB/s starves waiting for data PCIe bus ~32 GB/s RAM 512 GB · volatile SSD link ~5 GB/s each 8 × NVMe SSD 16 TB · durable Reading 1 TB across the PCIe link to the GPU: ~30 s. The computation on it: ~0. A server adds capacity and devices, never link speed. Same bottleneck, laptop or rack. Color key  orange = a link, the bottleneck  ·  grey = compute, ~free  ·  red = RAM, volatile  ·  green = SSD, durable
Link bandwidth caps end-to-end throughput inside one machine. The GPU does ~3x1014 ops/s, while PCIe moves ~32 GB/s and an SSD supplies ~5 GB/s. Reading a 1 TB dataset across the PCIe link to the GPU takes ~30 s, while computation over it takes ~0. RAM (red) is volatile and the SSD (green) is durable, and both connect through the same links. A server adds cores, RAM, and SSDs as additional devices and capacity, while link speed stays similar.
Notes ↗
M2

Problems: IO Cost Model

2 figures
problems-io-cost

Reference

For the official formulas and notation, check the IO Reference Sheet.

IO Cost Model Reference Card

Notes ↗
problems-io-cost

Setup

System Configuration:

  • Page Size: 64 MB

  • Spotify Songs Table: 500 million rows × 1024 bytes/row

  • Formulas and Device Specs: Refer to the IO Reference Sheet for C_r/C_w formulas

Quick Reference (64MB pages):

Device C_r = C_w Access + Transfer
RAM 0.00064s 100ns + 64MB/100GB/s
SSD 0.01281s 10μs + 64MB/5GB/s
HDD 0.65s 10ms + 64MB/100MB/s
Network 0.00641s 1μs + 64MB/10GB/s
Notes ↗
problems-io-cost

Problem 1: Table Size Calculation

The Spotify Songs table has 500 million rows with an average row size of 1024 bytes. Calculate:

  1. Total size in MB

  2. Number of 64 MB pages needed

Show Solution

Step 1: Calculate Total Size

Total Size = NumRows × RowSize
         = 500,000,000 × 1024 bytes
         = 512,000,000,000 bytes
         = 512,000,000,000 / 1,000,000 MB
         = 512,000 MB = 512 GB

Step 2: Calculate Number of Pages

numPages = Total Size / Page Size
        = 512,000 MB / 64 MB
        = 8,000 pages

Answer

Size: 512,000 MB (512 GB) | numPages: 8,000

Notes ↗
problems-io-cost

Problem 2: Read Cost Comparison

Calculate the time (in seconds) to read 100 pages from different storage devices.

Show Solution

For Each Device:

Cost = numPages × C_r where C_r includes both access time and transfer time: C_r = Access Time + PageSize / Scan Speed

RAM:

Cost = numPages × C_r = 100 × (100×10⁻⁹ + 64MB/100GB/s)
     = 100 × (0.0000001 + 0.00064)
     = 100 × 0.0006401
     = 0.064 seconds

SSD:

Cost = numPages × C_r = 100 × (10×10⁻⁶ + 64MB/5GB/s)
     = 100 × (0.00001 + 0.0128)
     = 100 × 0.01281
     = 1.281 seconds

HDD:

Cost = numPages × C_r = 100 × (10×10⁻³ + 64MB/100MB/s)
     = 100 × (0.01 + 0.64)
     = 100 × 0.65
     = 65 seconds

Answer

Device Time (seconds) Relative Speed
RAM 0.064 1× (fastest)
SSD 1.281 20× slower
HDD 65.0 1,016× slower
Notes ↗
problems-io-cost

Problem 3: Cache Hit Rate Impact

You need to read 200 pages with the following cache hierarchy:

  • Check RAM first for all pages (90% hit rate)

  • For RAM misses (10%): 75% found in SSD, 25% in HDD

Calculate the total read time, including the cost of checking RAM.

Notes ↗
problems-io-cost · Problem 3: Cache Hit Rate Impact

Cache hierarchy: reading 200 pages

Cache hierarchy: reading 200 pages 200 page reads. The RAM check costs 0.128s and serves 180 pages (90% hit, green); 20 miss. 15 misses go to SSD (0.192s, amber) and 5 to HDD (3.25s, red). Total 3.57s. The 5 HDD reads, 2.5% of the requests, are 91% of the time. Color key: green is a RAM hit, amber is SSD, red is the slow HDD tail, grey is structure. Cache hierarchy: reading 200 pages 90% hit RAM; the few that fall to HDD dominate the wall-clock time Request: 200 pages must check RAM first RAM cache check cost: 200 × 0.00064s = 0.128s ✓ 180 pages hit (90%) ✗ 20 pages miss (10%) SSD fetch 15 pages × 0.01281s = 0.192s HDD fetch 5 pages × 0.65s = 3.25s Total time 0.128s + 0.192s + 3.25s = 3.57s Color key  green = RAM hit  ·  amber = SSD  ·  red = slow HDD tail
Of 200 page reads, the RAM check is always paid (200 × 0.00064s = 0.128s) and serves 180 hits at 90% (green). The 20 misses split into 15 SSD fetches (0.192s, amber) and 5 HDD fetches (3.25s, red), for 3.57s total. The 5 HDD reads account for 2.5% of requests and 91% of wall-clock time (3.25 / 3.57).
Notes ↗
problems-io-cost

Problem 3: Cache Hit Rate Impact

Figure 1. Of 200 page reads, the RAM check is always paid (200 × 0.00064s = 0.128s) and serves 180 hits at 90% (green). The 20 misses split into 15 SSD fetches (0.192s, amber) and 5 HDD fetches (3.25s, red), for 3.57s total. The 5 HDD reads account for 2.5% of requests and 91% of wall-clock time (3.25 / 3.57).

Show Solution

Step 1: Check RAM for ALL pages

Must check RAM for all 200 pages to determine hits/misses
RAM Check Cost = numPages × C_r = 200 × (100ns + 64MB/100GB/s)
               = 200 × 0.00064 = 0.128 seconds

Results: 180 pages found (90% hit), 20 pages miss (10%)

Step 2: Fetch misses from lower levels

Of the 20 misses:
  → 15 pages (75%) found in SSD
  → 5 pages (25%) found in HDD

SSD Fetch Cost = numPages × C_r = 15 × (10μs + 64MB/5GB/s)
               = 15 × 0.01281 = 0.192 seconds

HDD Fetch Cost = numPages × C_r = 5 × (10ms + 64MB/100MB/s)
               = 5 × 0.65 = 3.25 seconds

Step 3: Total Cost

Total = RAM Check + SSD Fetch + HDD Fetch
      = 0.128 + 0.192 + 3.25 = 3.57 seconds

Answer

Total time: 3.57 seconds

Key Insights:

  1. You ALWAYS pay the cost to check RAM for all pages - this is how caching works!

  2. Even with 90% cache hit rate, the 2.5% that goes to HDD (5 out of 200 pages) dominates the total cost!

Notes ↗
problems-io-cost

Problem 4: Mixed Read/Write Operations

You need to:

  • Read 30 pages

  • Write 10 pages

Calculate total cost for each storage device.

Show Solution

Cost Formula

For mixed operations, calculate each separately:

Total = Read Cost + Write Cost
      = numPages(read) × C_r + numPages(write) × C_w
      = 30 × C_r + 10 × C_w

Since read and write costs are equal for these devices: C_r = C_w

Total = (30 + 10) × C_r = 40 × C_r

RAM:

Cost = 40 × C_r = 40 × 0.00064s = 0.026 seconds

SSD:

Cost = 40 × C_r = 40 × 0.01281s = 0.512 seconds

HDD:

Cost = 40 × C_r = 40 × 0.65s = 26 seconds
Notes ↗
problems-io-cost

Problem 5: Network vs Local Storage

Compare reading 1 page from:

  1. Local RAM

  2. Local SSD

  3. Local HDD

  4. Network RAM (RAM on another machine)

Notes ↗
problems-io-cost · Problem 5: Network vs Local Storage

Reading one 64 MB page: local vs network

Reading one 64 MB page: local vs network Local RAM 0.00064s (green), local SSD 0.0128s (amber), local HDD 0.65s (red). A network RAM path, remote read 0.00064s plus 0.0064s transfer plus 0.00064s local write, totals 0.0077s (orange, the path in focus). Network RAM beats local SSD because transfer cost dominates locality. Color key: green fastest, amber SSD, red slow HDD, orange the network path in focus, grey structure. Reading one 64 MB page: local vs network Transfer cost matters more than the local-vs-remote intuition Local machine Local RAM 100 ns + transfer = 0.00064s Local SSD 10 µs + transfer = 0.0128s Local HDD 10 ms + transfer = 0.65s Network RAM path 1. remote RAM read 0.00064s 2. network transfer 0.0064s 3. local RAM write 0.00064s → 0.0077s Remote machine Remote RAM 64 MB page read: 0.00064s 10 GB/s network Color key  green = fastest  ·  amber = SSD  ·  red = slow HDD  ·  orange = network path
Reading one 64 MB page costs about 0.00064s from local RAM (green), 0.0128s from local SSD (amber), and 0.65s from local HDD (red). Network RAM includes a remote read (0.00064s), a network transfer (0.0064s), and a local write (0.00064s), for 0.0077s total (orange).
Notes ↗
problems-io-cost

Problem 5: Network vs Local Storage

Figure 2. Reading one 64 MB page costs about 0.00064s from local RAM (green), 0.0128s from local SSD (amber), and 0.65s from local HDD (red). Network RAM includes a remote read (0.00064s), a network transfer (0.0064s), and a local write (0.00064s), for 0.0077s total (orange).

Show Solution

Local Storage Costs

Local RAM:

Cost = numPages × C_r = 1 × (100ns + 64MB/100GB/s)
     = 1 × (0.0000001s + 0.00064s)
     = 0.0006401 seconds

Local SSD:

Cost = numPages × C_r = 1 × (10μs + 64MB/5GB/s)
     = 1 × (0.00001s + 0.0128s)
     = 0.01281 seconds

Local HDD:

Cost = numPages × C_r = 1 × (10ms + 64MB/100MB/s)
     = 1 × (0.01s + 0.64s)
     = 0.65 seconds

Network RAM Cost

Three steps: Read from remote RAM → Network transfer → Write to local RAM

Remote RAM read:  numPages × C_r = 1 × 0.0006401 = 0.0006401 seconds
Network transfer: numPages × C_network = 1 × (1μs + 64MB/10GB/s)
                = 1 × (0.000001 + 0.0064) = 0.006401 seconds
Local RAM write:  numPages × C_w = 1 × 0.0006401 = 0.0006401 seconds
Total:           0.0006401 + 0.006401 + 0.0006401 = 0.0076812 seconds

Answer

Storage Type Time (seconds) Relative to Local RAM
Local RAM 0.00064 1× (baseline)
Network RAM 0.0077 12× slower
Local SSD 0.0128 20× slower
Local HDD 0.65 1,016× slower

Key Insight: With modern 10GB/s networks, Network RAM is faster than Local SSD! This is why distributed caching works so well.

Notes ↗
problems-io-cost

Key Takeaways

  1. HDDs and SSDs are much slower than RAM for random access

  2. Cache hit rates matter enormously - even 5% HDD access can dominate

  3. Network RAM ≈ Local SSD in performance

  4. Page size affects random access penalty - larger pages amortize seek cost

  5. Moving forward: We'll count IOs (1 page = 1 IO) to analyze algorithm complexity

Notes ↗
M2

Hashing: From Data to Algorithms

A hash function maps a key to a fixed-size bucket id, so you can find or insert in expected O(1) by reading the page where the bucket lives.

1 figure
basic-hashing · Integer Hashing

Integer hashing with h(x) = x % 10

Integer hashing with h(x) = x % 10 Inputs 42, 157, 1042, 89 are hashed by x mod 10 into ten buckets. 42 and 1042 both land in bucket 2, a collision (shown in orange); 157 lands in bucket 7 and 89 in bucket 9. Color key: orange is the colliding bucket in focus, grey is the other buckets. Integer hashing: h(x) = x % 10 Same modulo, different inputs, same bucket = a collision Input → h(x) Buckets [0–9] 0 1 2 3 4 5 6 7 8 9 89 % 10 = 9 89 42 % 10 = 2 42 157 % 10 = 7 157 1042 % 10 = 2 2 1042 Color key  orange = the colliding bucket  ·  grey = other buckets
h(x) = x % 10 maps four integers into ten buckets numbered 0 through 9. 42 and 1042 both land in bucket 2 (orange) because both end in 2, while 157 and 89 fall in distinct grey buckets. The collision is the point: an index must handle two keys reaching the same bucket. Chaining stores a list per bucket. Open addressing probes for an empty slot.
Notes ↗
M2

Case Study 2.1: How Uber and Google Maps use Geo-hashing

Geo-hashing converts a 2-D (lat, lng) coordinate into a 1-D grid key.

4 figures
case-study-geohashing · Why hexagons, not squares

Why hexagons, not squares: a hexagon's neighbours are all the same distance

Why hexagons, not squares: a hexagon's neighbours are all the same distance Two grids compared. On the left a square cell has eight neighbours at two different distances: its four edge neighbours are one cell away, its four corner neighbours are farther by a factor of root two, so the ring a radius query must read is uneven. On the right a hexagon has six neighbours, every one exactly the same distance from the centre, so the ring is even in all directions. That uniform neighbour distance is why map systems grid the world into hexagons. Color key: orange is the query cell and, for the hexagon, its single even neighbour ring; grey is the grid; dashed circles are neighbour distances. Why hexagons, not squares? A radius query reads a cell plus its neighbour ring. The ring is only even if the neighbours are. edge: d corner: 1.4d Squares 8 neighbours, TWO distances the ring is lopsided d Hexagons 6 neighbours, ONE distance the ring is even all around Color key  orange = the query cell and the even ring  ·  grey = the grid  ·  dashed = neighbour distance
Cell shape determines the neighbor ring. A square has eight neighbors at two distances: four edge-neighbors one cell away, four corner-neighbors farther by a factor of root two. A hexagon has six neighbors, each at the same distance from the center, which yields a symmetric ring for a radius query.
Notes ↗
case-study-geohashing · Uber H3: hexagons in practice

Nearby points share a cell, far points do not

Nearby points share a cell, far points do not The three Stanford campus buildings versus SF Mission, computed with H3. At resolution 8 (zoomed out) the three campus buildings get the identical code 8828347417fffff, one shared cell, while SF Mission, 29 miles away, gets a different code. At resolution 12 (zoomed in) the three campus codes split into three cells, differing in the fine digits at the end (orange), and SF stays different. Color key: orange is where the campus codes differ at resolution 12, black is shared. Nearby points share a cell, far points do not Three Stanford buildings vs SF Mission, computed with H3. Campus shares a cell at res 8 and splits at res 12. Resolution 8 · zoomed out NVidia8828347417fffff Packard8828347417fffff Gates8828347417fffff campus: one cell, one code SF Mission88283082a1fffff 29 mi away: a different cell Resolution 12 · zoomed in NVidia8c28347416257ff Packard8c2834741602bff Gates8c28347416021ff campus: three cells now SF Mission8c283082a06bbff still a different cell Color key  orange = where the campus codes differ at res 12  ·  black = shared
H3 codes for three Stanford campus buildings and SF Mission. At resolution 8 the three campus buildings map to 8828347417fffff and SF Mission, 29 miles away, maps to a different cell. At resolution 12 the three campus buildings split into three cells, differing in the trailing digits (orange), and SF Mission remains in a different cell.
Notes ↗
case-study-geohashing · The hash and the lookup

Geo-hashing: a location becomes a cell code

Geo-hashing: a location becomes a cell code Order pins start as raw latitude and longitude. Lay H3's hexagons over the map and every pin falls in one hexagon, which has a cell code. Pins in the same hexagon, three Stanford buildings here, share one code; a far pin in San Francisco gets a different code. The hash is just which hexagon a point lands in. Color key: orange is a shared cell and the pins in it, grey is the hex grid, dark dots are orders. Step 1, the hash: a location becomes a cell code Every order takes the code of the hexagon it falls in. Nearby orders land in the same hexagon. 3 Stanford orders SF order, far away 8828347417fffff 88283082a1fffff Same hexagon, same code: nearby orders share a key. The hash is the cell. Color key  orange = a shared cell and its orders  ·  grey = the hex grid  ·  dark dots = orders
Hashing maps each (lat, lng) to the H3 cell that contains it. Ten million orders start as coordinates. The system computes the cell ID per order and stores orders by that ID. Nearby orders share a cell ID. Distant orders map to different IDs.
Notes ↗
case-study-geohashing · The hash and the lookup

Geo-hashing lookup: a radius query is a bounded handful of cell reads

Geo-hashing lookup: a radius query is a bounded handful of cell reads A within-2km query hashes the driver's location to one cell, then because the radius crosses cell borders it also reads the six neighbour cells. That is up to seven cell lookups, which may sit on seven different pages, so up to seven reads. Seven is constant: it does not grow with the ten million orders, unlike a full scan. Color key: orange is the query cell and its six neighbours and the up-to-seven page reads they cost, grey is the rest of the grid, the dashed circle is the 2km radius. Step 2, the lookup: a radius query is a few cell reads The query cell plus its six neighbours: up to seven reads, constant, never the ten million. driver, within 2 km 1 cell the radius crosses borders: + 6 neighbours up to 7 reads 7 cell IDs, maybe 7 different pages. 7, not 10,000,000. Constant for any N. Color key  orange = the 7 cells and their reads  ·  grey = the rest of the grid  ·  dashed = the 2km radius
A radius query hashes the driver location to one cell, then reads the six adjacent cells that the radius can cross. The query performs up to seven cell lookups. Those seven cell IDs can map to seven different pages, which yields up to seven reads. The read count stays constant as the table grows from ten thousand orders to ten million.
Notes ↗
M2

Compression Basics: Making Data Smaller

Columnar compression (run-length, dictionary, delta) exploits repetition inside one column, typically shrinking on-disk size 10× and cutting IO by the same factor.

2 figures
compression-basics · When to Use What?

Four columnar compression techniques at a glance

Four columnar compression techniques at a glance Four panels side by side, one per technique. RLE turns a run like Mickey times three, Daffy times four into one value plus a count, 10 to 100 times smaller, very fast, best for sorted repeated values. Dictionary replaces low-cardinality strings with small integer codes, 5 to 20 times, fast, best for low-cardinality strings. Delta stores the difference between sorted numbers, 5 to 10 times, fast, best for sorted numbers and timestamps. Bit packing stores small integers in the minimum bits, 4 to 8 times, very fast, best for small integers and flags. In each panel the raw input is grey and the encoded result is orange. Color key: orange is the encoded result, grey is the raw input. Four ways to shrink a column Same idea: a column repeats, so stop repeating it. Pick by the data. RLE RAW Mickey,Mickey,Mickey, Daffy,Daffy,Daffy,Daffy ENCODED (Mickey, 3) (Daffy, 4) Ratio 10–100× Speed very fast BEST FOR Sorted, repeated values Dictionary RAW rock, pop, rock, jazz ENCODED 0, 1, 0, 2 + dict {rock:0, pop:1} Ratio 5–20× Speed fast BEST FOR Low-cardinality strings Delta RAW 1700000000, …001, …003, …004 ENCODED base, +1, +2, +1 Ratio 5–10× Speed fast BEST FOR Sorted numbers, timestamps Bit packing RAW 5 3 4 1 (32-bit ints) ENCODED 101 011 100 001 3 bits each Ratio 4–8× Speed very fast BEST FOR Small integers, flags Color key  orange = the encoded result  ·  grey = the raw input
Four columnar techniques compress a column by removing repeated structure. RLE stores each run as a value plus a count. Dictionary encoding maps each distinct value to a small integer code. Delta encoding stores differences between adjacent sorted numbers. Bit packing stores small integers in the minimum number of bits. The raw input is grey, the encoded result orange. Ratios run from about 4x (bit packing) to 100x (RLE on sorted data); choose based on the column's value distribution.
Notes ↗
compression-basics · Combining Techniques

Stacking compression: 4 MB down to 50 KB

Stacking compression: 4 MB down to 50 KB One user_id column of 1 million values, 4 MB raw, sorted by user_id with about 25,000 active users, shrunk in three stacked steps, drawn as a descending staircase of orange boxes whose height tracks the size. RLE collapses the sorted runs to 200 KB. Dictionary shrinks each value because 25,000 ids need only a 2-byte code, not a 4-byte int, to 100 KB. zstd is a general-purpose codec that entropy-codes the leftover byte patterns, to 50 KB. The end result is 98.75 percent smaller. Color key: orange is the column shrinking, grey is the step applied. Stack the techniques and the column collapses 1M user_id values, sorted by user_id, ~25K users. Box height tracks size, not to scale. Raw user_id 4 MB 1M × 4 bytes RLE After RLE 200 KB sorted → long runs dictionary After dict 100 KB 25K ids → 2-byte codes zstd After zstd 50 KB 4 MB → 50 KB 98.75% smaller Color key  orange = the column, shrinking  ·  grey = the step applied  ·  ink = the result
A 1M-row user_id column sorted by user_id with ~25,000 active users compresses by chaining encodings. RLE collapses the sorted runs (4 MB to 200 KB). Dictionary encoding then replaces ids with codes; 25,000 ids fit in a 2-byte code rather than a 4-byte int (to 100 KB). zstd entropy-codes the remaining byte patterns (to 50 KB), a 98.75% reduction. Unsorted data or high cardinality leaves little for these encoders to remove.
Notes ↗
M2

Bloom Filters

A Bloom filter stores a set in a bit array and uses k hash functions to map each item to k bit positions.

1 figure
bloom-filters · Visual: How Bloom Filters Work

Bloom filter: a bit array plus k hash functions

Bloom filter: a bit array plus k hash functions A 16-bit array; bits 9 and 11 are set (orange) from earlier inserts. INSERT "alice" hashes to bits 3, 7, 14 and sets them. LOOKUP "bob" hashes to bits 3, 9, 2; bit 2 is 0, so "bob" is definitely not in the set. Color key: orange is a set bit, grey is an unset bit, green is a guaranteed answer. Bloom filter: a bit array + k hash functions 16-bit array (real ones use millions of bits) 0 [0] 0 [1] 0 [2] 0 [3] 0 [4] 0 [5] 0 [6] 0 [7] 0 [8] 1 [9] 0 [10] 1 [11] 0 [12] 0 [13] 0 [14] 0 [15] INSERT "alice" hash1("alice") % 16 = 3 → bit[3] = 1 hash2("alice") % 16 = 7 → bit[7] = 1 hash3("alice") % 16 = 14 → bit[14] = 1 Bits 3, 7, 14 set now; bits 9 and 11 came from prior inserts. 1 1 1 LOOKUP "bob" hash1("bob") % 16 = 3 → bit[3] = 1 hash2("bob") % 16 = 9 → bit[9] = 1 hash3("bob") % 16 = 2 → bit[2] = 0 bit[2] = 0 ⇒ definitely NOT in the set Color key  orange = a set bit  ·  grey = an unset bit  ·  green = a guaranteed answer
A Bloom filter stores a 16-bit array and k = 3 hash functions. INSERT "alice" hashes to bits 3, 7, and 14 and sets them (orange). Earlier inserts also set bits 9 and 11. LOOKUP "bob" hashes to bits 3, 9, and 2. bit[2] = 0 guarantees "bob" is not in the set (green). Any 0 bit implies "definitely not in the set." All 1s imply "probably in the set," so false positives occur and false negatives do not.
Notes ↗
M2

Case Study 2.2: How does Google Chrome protect 3 billion users using Bloom Filters?

Chrome ships a Bloom filter of known-malicious URL hashes to every device (the classic Safe Browsing design).

1 figure
case-study-bloomfilters

Chrome Safe Browsing: a local Bloom filter, a prefix to Google only on a maybe

Chrome Safe Browsing: a local Bloom filter, a prefix to Google only on a maybe A dashed privacy boundary splits your device (left) from Google (right). Your device holds a 125 MB Bloom filter, Google's 8 GB blocklist shrunk 64 times. A safe URL is answered locally and nothing is sent (green). A maybe URL sends only a 32-bit hash prefix across the boundary; Google returns the hashes sharing that prefix and the device finishes the check, blocking the threat (red). Color key: orange is your device and the filter, green is a guaranteed-safe local answer, red is a confirmed threat, grey is Google. Safe Browsing: check on-device, ask Google almost never A 125 MB Bloom filter on every device. Only a maybe sends a hash prefix. privacy boundary YOUR DEVICE — Chrome GOOGLE SAFE BROWSING Bloom filter · 125 MB Google's 8 GB list, 64× smaller Full blocklist 100M URLs · ~8 GB google.com definitely SAFE ✓ Answered on-device. Nothing sent to Google. phish-bank.ru/login MAYBE 32-bit hash prefix only hashes sharing that prefix BLOCKED ✗ Google only saw the prefix, never your URL. Color key  orange = your device + filter  ·  green = safe, answered locally  ·  red = a confirmed threat  ·  grey = Google
A device carries a 125 MB Bloom filter, derived from Google's 8 GB blocklist and shrunk 64 times. A safe URL gets a local answer and sends nothing. A maybe URL sends a 32-bit hash prefix to Google; Google returns the hashes with that prefix, and the device completes the match locally. Google receives a prefix and does not receive the URL, so 100M URLs and 3 billion users leak almost nothing.
Notes ↗
M2

Locality-Sensitive Hashing and Vector Embeddings

An ordinary hash spreads similar inputs across different buckets.

4 figures
lsh-vector-hashing · Locality Sensitive Hashing (LSH)

An ordinary hash scatters similar items

An ordinary hash scatters similar items Two near-identical cat vectors feed an ordinary hash, which is built to spread inputs out, so they land in unrelated buckets, bucket 7 and bucket 3, far apart in the bucket row. A red bar states the consequence: similar inputs land in unrelated buckets, so finding similar items would have to scan every bucket. This is the problem locality-sensitive hashing fixes. Color key: orange marks the two similar items, grey the hash and buckets, red the two scattered destinations and the verdict. An ordinary hash scatters similar items A normal hash is built to spread inputs out, so two near-identical vectors land in unrelated buckets. cat A [0.81, 0.79, ...] cat B [0.80, 0.79, ...] near-identical hash( ) buckets 0 1 2 3 4 5 6 7 8 9 cat A → 7,  cat B → 3 Similar items, unrelated buckets: "find similar" would have to scan every bucket. orange = the two similar items  ·  grey = the hash and buckets  ·  red = where they scatter
An ordinary hash spreads inputs across buckets. Two near-identical vectors (cat A and cat B) land in unrelated buckets, 7 and 3. The bucket index carries no information about distance, so a similarity query still scans every bucket. Locality-sensitive hashing groups near vectors instead.
Notes ↗
lsh-vector-hashing · Locality Sensitive Hashing (LSH)

LSH: similar items hash to the same bucket

LSH: similar items hash to the same bucket Four items become feature vectors, then k random hyperplanes turn each vector into a sign-bit code, then a bucket. Cat 1 and Cat 2 are similar so both get code 1011 and bucket 11 (orange, the focus pair); Dog gets bucket 9 and Car bucket 4 (grey). A query for items similar to Cat scans only bucket 11. Color key: orange is the similar pair in focus, grey is the other items. LSH: similar items land in the same bucket k random hyperplanes turn each vector into a sign-bit code, then a bucket Item Cat 1 Cat 2 Dog Car Feature vector [0.8, 0.7, 0.9, ...] [0.7, 0.8, 0.8, ...] [0.3, 0.9, 0.2, ...] [0.1, 0.0, 0.5, ...] k-bit LSH code 1011 1011 1001 0100 Bucket Bucket 11 Bucket 11 Bucket 9 Bucket 4 Query “find items similar to Cat”:  scan only bucket 11 → find Cat 2; never compare against Dog or Car Color key  orange = the similar pair in focus  ·  grey = the other items
Each item becomes a feature vector, an embedding, then a short LSH code, then a bucket. The two similar cats receive code 1011 and share a bucket, while the dog and car map to different buckets. A query for items like the cat reads that one bucket instead of comparing against every stored vector. Code bits come from random cuts of the space.
Notes ↗
lsh-vector-hashing · Locality Sensitive Hashing (LSH)

Each random cut adds one bit to the code

Each random cut adds one bit to the code A space crossed by three dashed cuts. Above a cut reads 0 and below reads 1, so three cuts give each point a three-bit code. The two cat photos read 111 because no cut falls between them, and share a bucket. The truck photo reads 011, differing on the first cut. A table on the left fills in above or below for each point as every cut lands. Computing the code: one cut, one bit Above a cut reads 0, below reads 1. Three cuts give every item three bits. Cat 1 Cat 2 Dog Car Which side of each cut? cut 1 cut 2 cut 3 Cat 1 Cat 2 Dog Car above = 0, below = 1 cut 1 below 1·· below 1·· above 0·· below 1·· cut 2 below 11· below 11· below 01· below 11· cut 3 below 111 below 111 below 011 above 110 The two cats match on all three cuts. The dog differs on cut 1, the car on cut 3. Equal codes go in the same bucket. No distance is ever computed.
Each cut asks one yes-or-no question: above the line is 0, below is 1, and the code assembles one bit at a time. The same four items as Figure 2. The two cats end on 111 because no cut fell between them, so they share a bucket; the dog differs on cut 1 and the car on cut 3, so each lands elsewhere. Nothing measures a distance, which is what turns a near-duplicate lookup into a hash-table lookup: cost O(N) becomes about O(N / 2k). Misses happen when a cut does fall between a close pair.
Notes ↗
lsh-vector-hashing · Locality Sensitive Hashing (LSH)

The cut is always one dimension less than the space

The cut is always one dimension less than the space A cloud of 200 unit vectors in three dimensions, holding the two near-duplicate cats and the unrelated dog. Planes through the origin arrive one at a time and orange marks every vector still on Cat 1’s side of all of them. After the first plane the dog is still in Cat 1’s bucket, because one cut only halves the space; the second plane separates it, and Cat 2 stays throughout. The candidate set falls from 200 to 94, then 43, then 19. A ladder along the bottom names the same object in each dimension: in two dimensions the cut is a line, in three a plane, and in 256 a hyperplane, always one dimension less than the space it cuts. One cut is always a flat one dimension below the space Drawn here at d = 3, where that flat is an ordinary plane. 200 vectors, no cuts yet d = 2 the cut is a line 1-dimensional d = 3 the cut is a plane 2-dimensional ← you are here d = 256 the cut is a hyperplane 255-dimensional One cut is always a flat one dimension below the space Drawn here at d = 3, where that flat is an ordinary plane. one plane halves the space, and the dog lands on Cat 1's side by luck d = 2 the cut is a line 1-dimensional d = 3 the cut is a plane 2-dimensional ← you are here d = 256 the cut is a hyperplane 255-dimensional One cut is always a flat one dimension below the space Drawn here at d = 3, where that flat is an ordinary plane. the second plane ejects the dog d = 2 the cut is a line 1-dimensional d = 3 the cut is a plane 2-dimensional ← you are here d = 256 the cut is a hyperplane 255-dimensional One cut is always a flat one dimension below the space Drawn here at d = 3, where that flat is an ordinary plane. three planes: 19 of 200 still share Cat 1's bucket, Cat 2 among them d = 2 the cut is a line 1-dimensional d = 3 the cut is a plane 2-dimensional ← you are here d = 256 the cut is a hyperplane 255-dimensional
A cut is always a flat one dimension below the space it cuts. In two dimensions that is a line, which is what Figure 3 drew; in three it is an ordinary plane, drawn here; in an embedding’s few hundred dimensions it is a hyperplane (harder to picture). Nothing else changes: above or below is still the only question. Watch what one cut is worth. After the first plane the dog is still in the cat’s bucket, because one cut halves the space and a coin flip put it on that side. The second ejects it, and the near-duplicate survives all three. Each plane roughly halves the candidates: 200, then 94, then 43, then 19.
Notes ↗
M2

Case Study 2.3: How OpenAI and Facebook Speed Up Queries with Vector Databases

You built LSH, an approximate-nearest-neighbour (ANN) index.

4 figures
case-study-vectordb · The problem: a dot product against every row

The exact scan: one query against every vector

The exact scan: one query against every vector A nearest-neighbour query done exactly compares the query vector to all N stored vectors, one 1,536-dimensional dot product each. The left card is the query; it fans out to a stack of a million stored vectors; a cost panel tallies one million dot products, about 1.5 billion multiply-adds and 6 GB scanned per query, times billions of queries a day. A red bar states the verdict: O(N) per query does not scale. Color key: orange marks the query, grey the stored vectors and the fan-out, red the cost verdict. The exact scan: one query against every vector Done exactly, a nearest-neighbour query compares the query to all N stored vectors, one dot product each. query a 1,536-D vector N = 1,000,000 stored vectors every one, to the last one 1,536-D dot product each Cost of one query 1,000,000 dot products ≈ 1.5 billion multiply-adds ≈ 6 GB scanned and then, at OpenAI scale × billions of queries a day O(N) per query: an exact scan cannot serve billions. orange = the query  ·  grey = the N stored vectors and the scan  ·  red = the O(N) cost
Exact scan against all N vectors. At a million vectors: a million 1,536-D L2 distances, about 1.5 billion multiply-adds and ~6 GB scanned per query. In FAISS this exact index is IndexFlatL2.
Notes ↗
case-study-vectordb · The fix: an approximate nearest-neighbour index

HNSW is built in layers

HNSW is built in layers A hierarchical near-neighbour graph. Layer 0 holds every point, each linked to its nearest neighbours with short local links. A few points are promoted to layer 1, fewer to layer 2, with progressively longer links, the highways. Dashed verticals mark the same point appearing across layers. Color key: orange is points and their near-neighbour links, grey dashed is the same point across layers. HNSW is built in layers Every point links to near neighbours. A few are promoted up, with longer links. Layer 0 Layer 0: every point, linked to its nearest neighbours (short local links). Layer 1 Layer 2 Fewer points up top, longer links: the express lanes. Color key  orange = points + near-neighbour links  ·  grey dashed = the same point across layers
How the graph is built. Layer 0 holds every vector, each linked to its nearest neighbours with short local links. Layer 0 is the local streets. A few vectors are promoted to layer 1, where they link much further: the expressways. Fewer still reach layer 2, with the longest links of all: the freeways. The dashed verticals are the same vector appearing on more than one layer. This is built once, when vectors are added, never per query.
Notes ↗
case-study-vectordb · The fix: an approximate nearest-neighbour index

Brute-force scan vs an ANN index

Brute-force scan vs an ANN index Two panels. Left, grey: exact brute force compares the query against all 1 million 1,536-dimensional vectors, about 1.5 billion multiply-adds and ~6 GB scanned per query. Right, orange: an ANN index (IVF clusters, or an HNSW proximity graph) looks at only the few clusters or graph neighbours near the query, a small fraction of the vectors, in well under a millisecond, at the cost of an occasional missed neighbour. Same recall target, roughly 100 to 1000 times less work. Color key: grey is the exact baseline, orange is the ANN index. One query over a million 1,536-D embeddings Same nearest-neighbour answer, ~100 to 1000x less work. Exact: IndexFlatL2 (the baseline) Compare the query to every vector 1M × 1,536-D L2 distances ~1.5 billion multiply-adds / query ~6 GB of vectors scanned Exact answer, but O(N) per query does not scale to billions index ANN index (IVF or HNSW) Cluster (IVF): probe a few cells look at a small fraction, skip the rest Or a proximity graph (HNSW) greedy-descend to the neighbourhood Sub-millisecond, occasional miss approximate, but it scales Color key  grey = the exact brute-force baseline  ·  orange = the ANN index
Exact scan compares the query against all million 1,536-D vectors: ~1.5 billion multiply-adds and ~6 GB scanned per query. An ANN index probes only nearby vectors, either the IVF cells it selects or the HNSW neighbours it visits, so a query touches a small fraction of the data in well under a millisecond and can miss a neighbour. Same recall target, roughly 100 to 1000x less work.
Notes ↗
M2

Case Study 2.4: Compressing a Billion Embeddings

A float32 embedding index stores more precision than search needs.

4 figures
case-study-vector-compression · The demo worked. Production did not.

The demo fit in RAM; production does not

The demo fit in RAM; production does not Two identical RAM boxes, the machine's memory. Left: the raw float32 index is a red bar that rises past the top of the box and overflows, about 60 GB, more than fits. Right: the same index quantized to a few bits per number is a green bar that sits well inside the box, with room to spare. Color key: the grey box is the machine's RAM, the red bar is the float32 index that overflows it, the green bar is the quantized index that fits. The demo fit in RAM; production does not The index has to sit in RAM to search fast, and raw float32 embeddings outgrow the box. raw float32 index the same index, quantized overflows the box ~60 GB 10 M × 1,536-D × 4 B ≈ 6 KB per vector quantize 8–32× fits, with room to spare quantized index a few bits per number grey = the machine's RAM  ·  red = float32, overflows  ·  green = quantized, fits
Float32 storage size. Embeddings often have about a thousand dimensions or more. OpenAI's smaller embedding model is 1,536-D and the large one is 3,072-D. At 1,536-D float32 that is about 6 KB per vector, so 10 million vectors take ~60 GB in RAM for fast search. The large model uses double. Quantizing each coordinate to a few bits shrinks the index 8 to 32 times and brings it under the RAM limit.
Notes ↗
case-study-vector-compression · Most of the information hides in a few directions

A random spin turns the grid, not the points

A random spin turns the grid, not the points One panel. San Francisco, Berkeley and Stanford stay at the same coordinates in every frame while the grid behind them rotates. SF and Berkeley begin in one cell and end in different cells after a spin of 58 degrees; Stanford, much further away, is unaffected. A read-out lists the cell each city falls in. SF Berkeley Stanford SF (-1, -1) Berkeley (-1, -1) Stanford (0, 0) SF and Berkeley share a cell rotation 0° The points never move. Only the grid turns. SF Berkeley Stanford SF (-1, -1) Berkeley (-1, -1) Stanford (0, 0) SF and Berkeley share a cell rotation 14° The points never move. Only the grid turns. SF Berkeley Stanford SF (-1, -1) Berkeley (-1, -1) Stanford (0, 0) SF and Berkeley share a cell rotation 29° The points never move. Only the grid turns. SF Berkeley Stanford SF (-1, -1) Berkeley (-1, -1) Stanford (0, 0) SF and Berkeley share a cell rotation 44° The points never move. Only the grid turns. SF Berkeley Stanford SF (-1, 0) Berkeley (-1, -1) Stanford (0, 0) SF and Berkeley are now in different cells rotation 58° The points never move. Only the grid turns.
Eight frames from 0° to 58°. The three cities sit at identical coordinates in every frame, so you can check for yourself that only the grid moved. San Francisco and Berkeley begin in one cell and end in different ones. Stanford, much further off, is untouched.
Notes ↗
end of lecture 1~69 min of figures
case-study-vector-compression · Most of the information hides in a few directions

Four bits buys 16 levels. Before the rotation, 5 get used.

Four bits buys 16 levels. Before the rotation, 5 get used. 256 embedding coordinates drawn as bars, beside a histogram of how many fall into each of the 16 levels a 4-bit quantiser offers. Before the rotation the coordinates are dominated by a few large ones, so only 5 of the 16 levels are ever used and one level holds most of them. After a random rotation the coordinates are evenly spread and 15 of the 16 levels are in use. Storage is 4 bits per coordinate in both cases. 256 COORDINATES THE 16 LEVELS THEY ROUND INTO one shared axis: read straight across from a bar to its slot empty empty empty empty empty empty empty empty empty empty empty levels holding any data 5 of 16 levels sitting empty 11 error at the same 4 bits 38% Every coordinate costs 4 bits to store either way. What changes is how many of the 16 levels do any work. 256 COORDINATES THE 16 LEVELS THEY ROUND INTO one shared axis: read straight across from a bar to its slot empty empty empty empty empty empty empty empty empty levels holding any data 7 of 16 levels sitting empty 9 error at the same 4 bits 33% Every coordinate costs 4 bits to store either way. What changes is how many of the 16 levels do any work. 256 COORDINATES THE 16 LEVELS THEY ROUND INTO one shared axis: read straight across from a bar to its slot empty empty levels holding any data 14 of 16 levels sitting empty 2 error at the same 4 bits 14% Every coordinate costs 4 bits to store either way. What changes is how many of the 16 levels do any work. 256 COORDINATES THE 16 LEVELS THEY ROUND INTO one shared axis: read straight across from a bar to its slot empty levels holding any data 15 of 16 levels sitting empty 1 error at the same 4 bits 11% Every coordinate costs 4 bits to store either way. What changes is how many of the 16 levels do any work.
The same idea at 256 dimensions, where it earns its keep. A 4-bit budget is 16 levels. Before the rotation the coordinates are so uneven that only 5 levels are ever used, which is 2.3 bits of information in the 4 bits you are paying to store. After it, 15 levels are in play at 3.9 bits. Storage is 4 bits per coordinate either way; only the useful fraction changes.
Notes ↗
case-study-vector-compression · Then just drop the precision

Drop the precision, keep the distance

Drop the precision, keep the distance One coordinate, stored two ways. On the left, in orange, the exact value 0.4327 held as a 32-bit float. An arrow labelled keep fewer bits leads to the right, where the same coordinate is held as a 2-bit bucket number, written 1 0, which is bucket 2 of 4 and decodes back to about 0.4. What is stored is the bucket number, not the decimal. A strip below states that the distance between two vectors barely moves, because after the rotation no single coordinate carries the answer, and that this is sixteen times smaller with no codebook to train. Color key: orange is the coordinate before and after, green is the distance being preserved. Drop the precision, keep the distance You do not need thirty two bits to tell two vectors apart. AS STORED TODAY 0.4327 32 bits per number the exact coordinate keep fewer bits AFTER QUANTIZING 10 2 bits · bucket 2 of 4 decodes back to about 0.4 The distance between two vectors barely moves, because after the spin no single coordinate carries the answer. Sixteen times smaller, and no codebook to train.
Scalar quantization of one coordinate. 0.4327 stored in 32 bits maps to a 2-bit bucket 10 and decodes to about 0.4. One coordinate contributes little signal, but a distance aggregates hundreds of coordinates. In high dimensions, quantization errors across coordinates behave close to independent and tend to cancel. The rotation enforces a shared distribution across coordinates, so the same quantizer works across coordinates and across datasets without training. Two bits gives 16× compression, and one bit gives 32×.
Notes ↗
M2

Practice Problems for Basic Systems Design

1 figure
data-structures-problem-solving · Putting it together

Two levers for a too-big problem: partition the lookup, or shrink each item

Two levers for a too-big problem: partition the lookup, or shrink each item One too-big problem; scanning all N per query is too slow and storing all N bytes too costly. Two composable levers. Lever 1, don't scan all N: partition the lookup so a query touches one slice. Exact, hash to one bucket (hashing). Approximate, bucket by similarity or prune what is definitely absent, then check only the candidates near the query (Bloom, LSH, IVF, HNSW). Lever 2, don't store all N bytes: shrink each item. Exact and lossless, drop the redundancy the data already has (RLE, dictionary, delta, bit-packing), which only works if the column has structure. Approximate and lossy, drop the precision but keep the rank order (PQ, TurboQuant), reframing with a random rotation so one coarse quantizer fits any data. Real systems compose both, for example IVF plus PQ, the vector database. Color key: orange is the lever and the approach, grey is the scaffolding. Every structure here turns a big problem into a small one Too slow to scan all N, too costly to store all N. Two levers, used together. 1 · Don't scan all N Partition the lookup, so a query touches one slice EXACT Hash to one bucket Partition by key; read one bucket, not N. Hashing Basic Hashing APPROXIMATE Bucket by similarity, or prune early Bucket similar items, or prune what is definitely absent; check only the candidates. Bloom · LSH · IVF · HNSW Bloom Filters · LSH & Vector Hashing · Case Study 2.3 2 · Don't store all N bytes Shrink each item, so the index fits EXACT · LOSSLESS Drop the redundancy Exploit the structure the data already has. RLE · dictionary · delta · bit-pack Compression Basics · only if there is structure APPROXIMATE · LOSSY Drop the precision, keep the order Reframe with a random rotation so one coarse quantizer fits any data. PQ · TurboQuant Case Study 2.4 Stack both, and you get, for example, IVF-PQ, the vector database Color key  orange = the lever and the approach  ·  grey = the exact/approx tag and scaffolding
Two levers for a too-big problem. Lever 1 partitions the lookup so a query examines one slice instead of all N: exactly, by hashing to a bucket; approximately, by pruning what is definitely absent (Bloom) or bucketing by similarity (LSH, IVF, HNSW) and checking only the candidates near the query. Lever 2 shrinks each item: losslessly by dropping the redundancy the data already has (only if it has structure), or lossily by dropping precision while keeping the rank order (a random rotation reframes the data so one coarse quantizer fits any of it). The exact-vs-approximate split inside each lever is the module's Basic-vs-Approximation track. Production composes both, IVF-PQ is partition plus compress, the vector database.
Notes ↗
end of lecture 2~6 min of figures
M2 Systems basics 1 of 35
Notes ↗ Video ▶ Why ◎ Schedule ↗ □ keys

M2 Systems basics

Disks, hashing, bloom filters, compression, vectors · 35 figures · ~75 min · 2 lectures at this pace