Topic 05

Page Faults, Swapping and the Page Cache

Operating Systems

The first time Lantern answers a query after the search server starts, it reads postings from its 1.8 GB index file on the solid-state drive. A minute later the same query never touches the drive. Nothing in Lantern's code changed. The kernel kept the file's pages in RAM that nothing else wanted, and the second read became a memory copy.

Follow a page between disk and memory and three things appear: the fault that brings it in, the cache that keeps it, and the collapse when a machine needs more pages than it has. Together they explain "the second run is always faster", and the server that is up, answering pings, and useless.

The Page Fault

When an access finds no valid translation in the page table, the processor traps into the kernel, through the fault door of this chapter's first topic. The kernel then decides what the access was. A minor fault finds the page already in RAM, or needs no read at all: the first touch of freshly allocated memory, a copy-on-write break, or a file page that is cached but not yet mapped into this process. A minor fault costs on the order of a microsecond.

A major fault needs the page from storage, and it costs what the device costs, from the latency ladder in Chapter 4: tens of microseconds up to about a hundred from a solid-state drive, and 5 to 10 milliseconds from a spinning disk. Only an access to an address the process never had a right to is an error, and that one ends the process with a segmentation fault.

One page fault, three outcomes
The page is already in RAM, or is fresh memory→Map it: minor fault, about 1 µs
The page is on storage: a file or swap→Read it: major fault, tens of µs to 10 ms
The address was never valid for this process→Kill the process: the only error

The Page Cache

Every ordinary file read goes through RAM that the kernel manages, and the pages stay there after the read. The next read of the same data, by the same process or any other, is a copy from memory instead of a device read. The cache grows into all the memory that processes are not using and shrinks when they need it back, evicting the pages that have not been used recently. The kernel's eviction is an approximation of the least-recently-used idea that Chapter 5 builds with a linked list.

Writes land in the page cache too. The page is marked dirty, the write returns, and the kernel writes the page to the device later, typically seconds later. That delay is the gap the last topic of this chapter is about.

Memory-Mapped Files

A program can also map a file into its address space, so that the file's pages appear as ordinary memory at some range of addresses. The first touch of each page is a fault that pulls the page from the page cache, or from the disk if it is not cached yet, and maps it. After that the file is read as memory: no system call per access and no second copy inside the process.

Several processes mapping the same file share one set of physical pages, the page-cache frames themselves. Five processes reading a gigabyte file this way hold one gigabyte of it in RAM between them, not five.

Two workers, one mapped file, one copy in memory
worker 1worker 2page cache in RAM: one copyindex page 0index page 0file page 0index page 1index page 1file page 1index page 2index page 2file page 2index page 3index page 3file page 3the 1.8 GB index file on the SSDfirst touch of a page: a fault reads it once

Lantern's Index in the Cache

Lantern's 1.8 GB index file fits comfortably in the page cache of its 8 GB server. Parsed into Python lists and dictionaries it would not fit at all. A record id stored in the file as a 4-byte integer becomes, in Python, a 28-byte integer object plus an 8-byte pointer to it in the list that holds it, as Chapter 5 shows. That is 36 bytes instead of 4, about nine times larger, and nine times 1.8 GB is around 16 GB for the postings alone.

So Lantern maps the file. Every worker process reads postings straight from the one cached copy, and the kernel keeps the hot pages resident because the workers keep touching them. There is one visible side effect. For a while after the 02:00 rebuild, the old index file and the new one are both in the page cache, so the footprint briefly nears 3.6 GB until the old file's pages age out.

The Working Set and Thrashing

A program's working set is the set of pages it touches in a short window. While the working sets of everything running fit in RAM together, major faults are rare, and memory behaves like memory. When they stop fitting, the kernel must evict pages that are needed again moments later, or push process memory out to swap, and nearly every access becomes a major fault.

A machine in that state runs hundreds to thousands of times slower than normal, because RAM answers in about a hundred nanoseconds and a solid-state drive in tens of microseconds up to a hundred. It spends its time moving pages back and forth and does almost no useful work. This is thrashing. From outside, the machine is up, answers pings and shows a busy disk, and every request times out.

Throughput holds, then falls off a cliff
size of RAMworking sets fit: major faults are rarethrashing: nearly everyaccess waits on the disktotal working set of everything running (schematic)useful work done per second

Cold Caches and Memory Budgets

A benchmark's second run reads from the page cache and its first did not, so the faster number is the misleading one. A database, a search index or a build cache is fast because its hot pages are resident. A restart, a move to a fresh machine, or a large file copy that pushes those pages out makes it slow for minutes, until the cache is warm again, and that is when traffic after a failover arrives.

The same mechanism sets the memory budget. A server needs RAM for its working set, not for its whole data set, plus headroom for everything else that runs. To watch faults, swap and the page cache on a real machine, see Linux Deep Dive, chapters Storage and Disks and Performance and Troubleshooting.

Misconceptions
  • "Low free memory means the server is short of memory." The page cache deliberately fills unused RAM and hands it back on demand. Memory that is only caching files is available, and the real signs of pressure are major faults and swapping, not a small free number.
  • "Reading the whole file into my process is faster than going through the operating system." For data the kernel already caches, it makes a second copy in the process, doubles the memory, loses the sharing between workers and, as Python objects, multiplies the size several times over.
  • "A page fault is an error." Minor faults happen millions of times in normal operation, since every first touch of new memory is one. Only major faults, which wait on storage, are a cost worth watching, and only faults on addresses that were never valid are errors.
  • "Adding swap keeps an overloaded server working." Swap helps when rarely used pages can leave. When the working set itself exceeds RAM, accesses wait on storage at hundreds to thousands of times the latency of RAM, and the machine is up but useless.
  • "The second run of my benchmark is the real number." It measures the page cache. Traffic after a restart, a failover or a scale-out meets a cold cache, and for work that reads a lot of data the difference can be a hundredfold.
Why It Matters
  • Let the page cache hold file data instead of copying it into the process. Map or stream the file, and keep one copy in RAM shared by every worker.
  • Size memory to the working set, not the data set. A 1.8 GB index needs RAM for its hot part, plus headroom for the rest and for the nightly rebuild.
  • Measure cold and warm separately and report both. The cold number is what a restart or a new machine will show.
  • Watch major faults and swap activity, not free memory, as the sign of memory pressure. A full page cache is a healthy machine.
RelatedCPU caches the same keep-what-was-used idea one level up, run by hardware (Chapter 4)Memoization the same trade in your own code, often duplicating the page cache (Chapter 1)A database's buffer pool its own page cache, kept beside the kernel's (PostgreSQL Deep Dive)

Knowledge Check

Lantern's server restarts, and the first query reads postings from the 1.8 GB index file. The same query runs again a minute later. What happens the second time?

  • The pages now come from the page cache in RAM, so no drive read is needed at all
  • The drive reads the pages again, but faster, because they are now sequential
  • The query is served from Lantern's own result cache, which it built on the first run
  • The pages are fetched from swap, which is faster than the index file on the drive

Roughly how do a minor and a major page fault compare in cost on a server with a solid-state drive?

  • Both about a microsecond, since both are handled by the kernel
  • About a microsecond against about a hundred microseconds
  • About a millisecond against about a second, due to the disk
  • About a hundred nanoseconds against about a microsecond

Lantern's index file is 1.8 GB on disk and the server has 8 GB of RAM. Why would parsing the whole index into Python lists fail where mapping the file works?

  • Python cannot hold more than 4 GB in one list on a 64-bit build
  • Parsing reads the file twice, so it needs double the file size in RAM
  • Each 4-byte id becomes about 36 bytes as an object plus its list slot
  • Mapped files are compressed by the kernel, so they take less RAM

What does a thrashing server look like from outside?

  • It crashes and restarts, logging an out-of-memory error each time
  • Its processor sits at 100% busy running user code for every request
  • It rejects new connections at once with a clear error to each client
  • It is up and busy on its disk, yet nearly every request it accepts times out

A benchmark reads a large data set twice. The second run is 50 times faster. Which number should the capacity plan use for traffic after a failover?

  • The first, because a new machine meets a cold page cache
  • The second, because it measures the real speed of the code
  • The average of the two, since traffic will be half cold and half warm
  • Neither, because failover traffic is always served from a CDN

You got correct