Page Cache and Zero-Copy Reads in Kafka

Page Cache and Zero-Copy Reads in Kafka

A deep, plain-English guide to the two operating-system tricks that let Kafka move gigabytes per second: letting the kernel cache the log in memory, and sending bytes from that cache to the network card without ever copying them through the application.

Imagine a busy restaurant kitchen. The chef could walk to the warehouse across town every time a customer orders a dish, but that would be painfully slow. Instead, the kitchen keeps the most-used ingredients on the counter, and when a waiter is ready, the plate goes straight from the counter to the table without being poured into another bowl, wrapped, and unwrapped along the way. Apache Kafka works in a surprisingly similar way. It keeps its freshest data on the “counter” (the operating system’s memory), and it hands that data to the network with as few extra “bowls” as possible.

Those two ideas have names. The counter is called the page cache. The straight-from-counter-to-table delivery is called zero-copy. Together they explain why a Kafka broker running on ordinary servers can serve hundreds of consumers while barely touching its disks and using very little CPU. This tutorial explains both ideas from the ground up, then goes deep into how they work, where they break, how to size machines for them, how to tune the operating system, and how to tell when something has gone wrong.

You do not need to be a kernel engineer to follow along. Every new word is explained the first time it appears, every big idea gets an everyday comparison, and the diagrams are explained one by one. By the end you will be able to look at a Kafka cluster and reason about where its speed comes from and what could silently take it away.

1The Big Picture: Why Kafka Feels So Fast

Speed in Kafka is not one magic feature; it is a stack of small decisions that all point in the same direction.

Apache Kafka is a distributed event streaming platform. In simple words, it is a system where programs called producers write records (small messages describing something that happened) and programs called consumers read them, possibly much later, possibly many times. The servers in the middle are called brokers. A broker’s whole job is to accept records, store them safely, and hand them back on request.

Because a broker stores everything on disk, a newcomer often expects it to be slow. Disks are famously slower than memory. Yet in practice a well-run Kafka cluster serves reads at speeds that look more like memory than disk. The reason is that Kafka was designed around how the operating system already behaves, instead of fighting it.

Simple Analogy

Think of a school librarian. Books live in a huge basement archive (the disk). The books everyone is asking for this week sit on a trolley by the front desk (the page cache). When a student asks for a popular book, the librarian grabs it from the trolley in seconds. She does not photocopy it first, retype it, and then hand over the copy; she just hands over the book itself (zero-copy). Kafka is a broker that behaves like that librarian.

There are four design choices that combine to make this work. First, Kafka only ever appends to the end of a file, so disk access is sequential and predictable. Second, Kafka does not build its own memory cache; it lets the operating system’s page cache do that job. Third, records are stored in exactly the same byte format on disk, in memory, and on the network, so nothing has to be converted on the way out. Fourth, when data is ready to leave, Kafka asks the kernel to send it straight from the cache to the network card using a special call, avoiding wasteful copies.

Sequential
Append-only writes keep disk access predictable
Page cache
The OS keeps hot log data in RAM for free
Zero-copy
Bytes go from cache to network card, skipping the app
flowchart LR
    P["Producer"] --> B["Kafka broker process"]
    B --> PC["OS page cache in RAM"]
    PC -. "background flush" .-> D["Disk log segments"]
    PC --> N["Network card"]
    N --> C["Consumer"]
        
FIG 1 — The broker writes into the page cache and reads from it; the disk is filled in the background

Read the figure from left to right. A producer sends records to the broker process. The broker appends them to a file, but the write actually lands in the page cache first, which is just a region of RAM managed by the kernel. The kernel copies dirty (not yet saved) pages to the disk later in the background. When a consumer asks for data, the broker points the kernel at the cached file region, and the kernel pushes it to the network card. The dotted line shows that disk writes happen off to the side, not in the path of a consumer read.

i
Keep This in Mind

Throughout this tutorial, remember the two questions that matter for every Kafka read: “Was the data already in memory?” (page cache) and “How many times was it copied before it left the machine?” (zero-copy). Almost every performance story in Kafka is about one of these two questions.

2Computer Basics You Need First

A few operating-system ideas make everything else in this tutorial easy to understand.

Before we talk about Kafka specifically, we need a shared vocabulary. Do not worry if these words are new; each one is explained with a simple picture in mind. Once you have them, the rest of the tutorial reads like a story.

Term 1

User Space

The area of memory where normal programs run, including the Kafka broker’s Java process. Programs here are not allowed to touch hardware directly.

Term 2

Kernel Space

The protected area where the operating system core runs. It controls disks, network cards and memory on behalf of programs.

Term 3

System Call

A polite request from a program to the kernel, such as “read this file” or “send this data”. Each call crosses the boundary between user space and kernel space.

Term 4

Context Switch

The CPU changing from running program code to running kernel code (or back). It costs time because the CPU must save and restore its working state.

Term 5

DMA

Direct Memory Access. A hardware helper that moves data between a device (disk or network card) and memory without the CPU doing the copying itself.

Term 6

Buffer

A temporary holding area in memory used while data is moving from one place to another.

Simple Analogy

User space is like a customer sitting in a bank lobby. Kernel space is the vault behind the counter. The customer cannot walk into the vault; they fill in a slip (a system call) and wait while a clerk (the kernel) goes in, fetches the money, and passes it across the counter. Every trip between lobby and vault takes a little time. That trip is the context switch, and a bank that lets you finish your business in fewer trips is a faster bank.

Why copies are expensive

Moving data from one memory area to another sounds harmless, but at high volume it is one of the most expensive things a server does. When the CPU copies bytes, it reads them from one memory location and writes them to another. That uses CPU time, uses memory bandwidth (the width of the road between the CPU and RAM), and also pushes useful data out of the small, very fast CPU caches. A server pushing several gigabytes per second can spend a shocking share of its power simply moving the same bytes around.

There are two kinds of copies to know about. A CPU copy is done by the processor itself and burns processor time. A DMA copy is done by a hardware controller on the side, leaving the CPU free to do other work. Zero-copy is not literally zero copies; it is about eliminating the CPU copies and the extra trips through user space, while the unavoidable DMA transfers still happen.

!
Common Misconception

The name “zero-copy” sounds like no data movement at all. That is not true. Data still travels from disk to memory and from memory to the network card. What disappears are the extra copies made by the CPU and the pointless round trip through the application’s own memory.

Disks, memory and the network card

A disk stores data permanently but takes microseconds (SSD) to milliseconds (spinning disk) to respond. Memory, or RAM, forgets everything when power is lost but responds in nanoseconds. The network card sends bytes to other machines. A read request from a consumer means data must, in one way or another, travel from wherever it lives (disk or RAM) to the network card. The whole trick is making that trip short.

ComponentRelative speedSurvives power lossRole in a Kafka read
CPU cacheFastest, tinyNoHolds a few bytes being worked on right now
RAM (page cache)Very fastNoHolds recent log data ready to send
SSD or NVMe diskFastYesPermanent home of the log
Spinning diskSlow for random accessYesPermanent home, good at sequential reads
Network cardLimited by link speedNot applicableDelivers bytes to the consumer

3Kafka Storage Refresher: Logs, Segments and Indexes

To understand what gets cached, we first need to know what Kafka actually puts on disk.

A Kafka topic is a named stream of records, such as “orders” or “payments”. Each topic is split into partitions, and each partition is an ordered, append-only sequence of records. Every record in a partition gets a number called an offset, starting at zero and only ever going up. Consumers remember which offset they have read up to, which is how they resume after a restart.

Simple Analogy

A partition is like a very long roll of receipt paper coming out of a till. New receipts are only ever printed at the end. Nobody goes back to the middle and rewrites a receipt. A reader keeps a finger on the paper to remember how far they have read.

On disk, a partition is a directory. Inside it, the log is not one giant file. It is cut into pieces called segments. Only the newest segment, called the active segment, receives new writes. Once a segment reaches a configured size or age, Kafka closes it and starts a new one. Old segments are deleted or compacted according to retention rules. This makes housekeeping cheap: Kafka can drop a whole old file at once instead of hunting for individual old records.

File 1

Log Segment File

Holds the actual record batches, exactly as producers sent them, one after another.

File 2

Offset Index

A sparse lookup table that maps an offset to a byte position inside the segment, so Kafka can jump close to the right place quickly.

File 3

Time Index

Maps timestamps to offsets, so consumers can ask “start from ten minutes ago”.

File 4

Other Helpers

Small files for transaction state and leader information that support advanced features.

Record batches, not single records

Kafka does not store records one by one. Producers group records into batches, and the batch is the unit of storage, replication and fetching. A batch has a small header (with offsets, timestamps, and a checksum) followed by the records, and it may be compressed as a whole. The broker keeps this batch exactly as it arrived. It does not unpack it and repack it later. This one decision is what makes zero-copy possible, because the bytes on disk are already in the exact shape the consumer expects.

flowchart TD
    T["Topic: orders"] --> P0["Partition 0"]
    T --> P1["Partition 1"]
    P0 --> S1["Segment 1 - closed"]
    P0 --> S2["Segment 2 - closed"]
    P0 --> S3["Segment 3 - active"]
    S3 --> L["Log file"]
    S3 --> OI["Offset index"]
    S3 --> TI["Time index"]
        
FIG 2 — A topic is split into partitions, and each partition is a series of segments with their index files

The figure shows how one topic fans out. Each partition owns a stack of segments. The first two are closed and never change again, so they are perfect candidates for staying cached or being read straight from disk. Segment 3 is active, and new records are appended to its log file, while its two index files are updated so lookups keep working. Every one of these files is an ordinary file that the operating system can cache.

i
Why It Matters

Because Kafka files are plain files with a simple append pattern, they behave beautifully with the operating system’s caching and read-ahead logic. Kafka does not need a clever custom storage engine to benefit from the kernel; the simplicity of the layout is the feature.

4The Operating System Page Cache

Every modern operating system quietly keeps recently used file data in spare RAM. Kafka leans on that habit heavily.

The page cache is a part of RAM that the kernel uses to hold copies of file contents. The kernel slices memory into fixed-size blocks called pages, commonly four kilobytes each. When any program reads a file, the kernel first checks whether the needed pages are already in the page cache. If they are, the read is served from RAM. This is called a cache hit. If they are not, the kernel loads them from disk into the cache, and then serves the read. This is a cache miss, and it costs a disk trip.

Simple Analogy

Your kitchen fridge is the page cache and the supermarket is the disk. The first time you need milk, you drive to the supermarket, and you bring back extra so you do not have to go again tomorrow. Next time, you just open the fridge. If the fridge is full, you throw out the food you have not touched for the longest time to make space for something new.

How the page cache behaves

1

A read arrives

A program asks the kernel for some bytes from a file at a given position.

2

The kernel checks the cache

It looks for the matching pages in memory. A hit ends the story quickly, because the data is already there.

3

On a miss, the kernel reads the disk

It loads the pages into free memory, and often reads a little extra ahead of the requested position, betting that you will want it next. This is called read-ahead.

4

Memory pressure trims the cache

When the machine needs memory for something else, the kernel drops the least recently used clean pages. Dropping a clean page is instant because the disk already has the same content.

Writes and dirty pages

The page cache also works for writes. When a program writes to a file, the kernel usually copies the bytes into cache pages and returns immediately, saying “done”. Those pages are now called dirty, meaning they hold newer data than the disk does. A group of kernel background threads later writes dirty pages to disk. This is called write-back. By default, Linux starts writing dirty data in the background after it has been dirty for about thirty seconds, or sooner if too much memory becomes dirty.

!
Do Not Confuse These

A write that has returned successfully is not the same as data safely on disk. Only a special call to force the data out, known as fsync, guarantees the disk has it. Until then, the data lives in RAM and depends on the kernel to flush it later.

Why the page cache is “free”

Every Linux machine already has this cache; no program needs to install or configure it. The kernel automatically uses almost all otherwise-unused memory for it, and instantly gives it back when a program needs that memory. When you look at a server and see very little “free” memory, that is usually a healthy sign that the cache is doing its job, not a sign of a leak.

Idea 1

Shared by everyone

If ten consumers read the same log region, they all benefit from a single cached copy in memory.

Idea 2

Managed by the kernel

Eviction, read-ahead and write-back are handled by the operating system, not by application code.

Idea 3

Survives the process

The cache belongs to the kernel, so if the Kafka process restarts, the cached data is still warm.

Idea 4

Outside the JVM heap

Cached pages are not Java objects, so they add no pressure to Java garbage collection.

5Why Kafka Trusts the Page Cache Instead of Building Its Own

The most surprising decision in Kafka’s design is what it chose not to build.

Most databases build their own buffer pool, a private cache managed by the application. Kafka’s designers went the other way. They decided the broker should not maintain a large in-process cache of log data at all. Instead, the broker writes to files, reads from files, and lets the kernel decide what stays in memory. This is sometimes described as a page-cache-centric design.

DECISION-01 Adopted
Problem

A broker must serve very large volumes of recent data quickly. A private in-process cache of that data would have to live inside the Java heap.

Why the Private Cache Is Harmful

Java objects carry extra memory overhead, so the same data takes more room. A big heap also means longer garbage-collection pauses, which stall the broker. The data would also be cached twice, once in the heap and once by the kernel, wasting memory. And every time the process restarts, the private cache is empty and must be rebuilt slowly.

Chosen Approach

Store data in compact bytes, write it to files, and let the operating system cache those files. Memory is used once, garbage collection stays small, and the cache stays warm across restarts.

The double-caching problem

Whenever a program reads a file in the usual way, the kernel caches the file pages. If the program then also keeps its own copy in a private cache, the same data sits in memory twice. On a machine with, say, thirty-two gigabytes of RAM, a large private cache could waste a big slice of the space that the kernel would otherwise use to cache more log data. By skipping the private cache, Kafka lets nearly all spare memory hold useful data once.

Garbage collection and the heap

Java automatically frees objects that are no longer used, in a process called garbage collection. The more objects live in the heap, the more work collection has to do, and sometimes the whole application must pause while it happens. A broker that pauses is a broker that cannot answer fetch requests or heartbeat to the cluster. Keeping the cache outside the heap means a Kafka broker can run with a fairly small heap (often a few gigabytes) and still cache tens of gigabytes of log data.

Simple Analogy

Imagine a shop that hires a personal assistant to keep a private notebook of every item the landlord’s building storage already tracks. The assistant spends time copying, the notebook takes desk space, and if the assistant goes home sick the notebook is lost. It is smarter to just use the building’s storage system that is always on and shared.

A warm restart

Servers are restarted for upgrades, patches and configuration changes. A private cache disappears with the process and takes a long time to refill. The page cache belongs to the kernel, so if only the Kafka process restarts, the recently used log pages are still in memory. The broker comes back and starts serving from cache almost immediately. That is a real operational benefit during rolling upgrades of a big cluster.

Advantages

  • No duplicate copies of data in memory
  • Small Java heap and short garbage-collection pauses
  • Cache stays warm when the broker process restarts
  • Kernel handles read-ahead and eviction automatically
  • Simple broker code with fewer moving parts

Disadvantages / Trade-offs

  • Kafka cannot decide exactly what stays cached; the kernel decides
  • Other programs on the same machine can steal cache memory
  • Behaviour depends on operating-system settings that must be tuned
  • Harder to see cache health, because the cache is not a Kafka metric
“Kafka does not fight the operating system for memory; it gives the operating system the memory and lets it do what it does best.”
i
Practical Consequence

Because the cache is outside Kafka’s control, a Kafka machine should be treated as a machine whose best feature is spare RAM. Giving the JVM a huge heap does not make Kafka faster. It often makes it slower by shrinking the space the kernel can use for caching.

6The Write Path: From Producer to Page Cache to Disk

Reads get the spotlight, but what happens on the write side decides what is sitting in the cache when a consumer arrives.

When a producer sends a batch of records, the broker’s network threads receive the bytes and hand them to request-handling threads. These threads validate the batch (checking things like the checksum and the format), assign offsets, and append the batch to the end of the active segment file. The append is a normal file write. The kernel copies the bytes into page cache pages and the call returns quickly. From the broker’s point of view the record is now “in the log”.

1

Network thread receives the batch

Bytes arrive from the producer’s connection and are gathered into a full request.

2

Request handler validates and assigns offsets

The leader broker checks the batch and stamps the next offsets, keeping the partition strictly ordered.

3

Append to the active segment

The write goes into the page cache. The file grows, and the index files receive occasional new entries.

4

Followers fetch the new data

Replica brokers read the freshly written batch, usually straight out of the leader’s page cache, and append it to their own logs.

5

Acknowledgement is sent

Depending on the producer’s settings, the leader replies once its own write is done or once enough replicas have the data.

6

The kernel flushes dirty pages later

In the background, and without the broker asking, dirty pages are written to disk.

sequenceDiagram
    participant Prod as Producer
    participant Brk as Leader broker
    participant PC as Page cache
    participant Fol as Follower broker
    participant Dsk as Disk
    Prod->>Brk: Send record batch
    Brk->>PC: Append to active segment
    PC-->>Brk: Write returns immediately
    Fol->>Brk: Fetch new data
    Brk->>PC: Read newly written pages
    PC-->>Fol: Data served from memory
    Brk-->>Prod: Acknowledge
    PC->>Dsk: Background write-back later
        
FIG 3 — A produced batch is written to the page cache, replicated from it, and only later flushed to disk

Look at the middle of the diagram. The follower fetches data that the leader has only just written. Because that data is still fresh in the page cache, the follower is served from memory and the disk is not involved at all. This is why replication in Kafka is so cheap on the leader: the same cached pages serve the followers and the consumers.

Durability without forcing the disk every time

A natural worry appears here. If a write only reaches RAM before the broker acknowledges it, what happens if the machine loses power? Kafka’s answer is replication. A record is normally copied to several brokers, each in a different failure zone. With the producer setting that waits for all in-sync replicas, and a cluster setting that requires a minimum number of them, a record is only acknowledged once several independent machines have it in their page caches. It is very unlikely that all of them lose power at the same instant.

Simple Analogy

Suppose you write an important note and, instead of locking it in a safe, you hand copies to three friends who live in different towns. One friend may lose the note in a flood, but the chance that all three lose it at exactly the same moment is tiny. Replication is safety through many friends instead of one heavy safe.

What Kafka does flush

Kafka does have settings that force data to disk after a number of messages or a period of time, but by default they are not used, so the operating system decides when to flush. Kafka does flush when a segment is closed and rolled, and on a clean shutdown. It also records a recovery point, a marker saying “everything before here is known to be on disk”. After a crash, Kafka only needs to check and repair the log after that marker, which keeps restarts fast.

!
A Subtle Point

If only the Kafka process dies (for example, it is killed or crashes), data already written to the page cache is safe, because the cache belongs to the kernel, not to the process. Data in the cache is lost only when the whole machine or the kernel goes down before write-back happens. That is exactly the failure that replication is designed to cover.

Advantages

  • Produce latency stays low because the disk is out of the path
  • Followers and consumers read fresh data from memory
  • The kernel batches many small writes into large efficient ones
  • Throughput scales with memory and network, not disk latency

Disadvantages / Trade-offs

  • Single-machine durability is weaker than forced flushing
  • Correct safety depends on replication settings being right
  • Large bursts of dirty pages can cause sudden flush storms

Segment rolling and the boundary flush

Every segment eventually reaches its size or age limit, and at that moment Kafka rolls to a new one. Rolling is an important moment for the page cache, because the closing segment is flushed so that its contents are known to be on disk before it becomes a read-only file. On a busy partition this happens regularly, and on a broker with thousands of partitions many rolls occur every minute. Each one triggers a small burst of write-back that competes briefly with reads for disk bandwidth.

Two consequences follow. First, very small segments mean many rolls, many open files, and many small flushes, which is wasteful. Second, very large segments mean fewer rolls but bigger deletions later, because retention removes whole segments at a time. Most clusters keep the default large segment size and change it only after measuring, since the kernel is already good at absorbing the flushing work in the background.

i
Practical Habit

If disk latency spikes appear at regular intervals, check whether they line up with segment rolls or with retention deletions. Regular spikes with a clear rhythm usually point to housekeeping work, not to a problem with consumers.

7Sequential I/O and Read-Ahead: Why the Access Pattern Matters

The page cache works best when the data it fetches from disk is read in a straight line. Kafka’s append-only log gives it exactly that.

Disk speed depends heavily on how you use the disk. A sequential access reads or writes neighbouring bytes one after the other. A random access jumps to unrelated locations. On a spinning disk, random access forces a mechanical arm to physically move, which takes milliseconds each time. Sequential access lets the arm stay put while the platter spins past, so throughput is thousands of times higher. Even on SSDs, sequential access is friendlier, because it lets the drive and the operating system read big chunks efficiently and prefetch what comes next.

Simple Analogy

Reading a novel from page one to the end is sequential. Picking a random sentence from a random page thousands of times is random. You would finish the novel long before you finished the random jumping, even though the total words are the same.

Kafka’s log is naturally sequential for writes, since everything is appended at the end, and naturally sequential for most reads, since consumers walk forward through offsets. When a consumer reads and the data is not yet cached, the kernel notices the forward pattern and starts read-ahead: it loads the following blocks before the consumer has asked for them. By the time the consumer’s next request arrives, the data is often already waiting in the page cache.

PatternTypical behaviourKafka relevance
Sequential writeVery high throughput, kernel merges writesEvery produce request appends to the log tail
Sequential readHigh throughput, read-ahead helpsConsumers and followers read forward through offsets
Random readSlow on spinning disks, moderate on SSDRare; happens when a consumer seeks to an arbitrary offset
Random writeSlowest pattern of allKafka avoids it by never updating in place

Why the index is sparse

The offset index does not list every record. It lists one entry roughly every few kilobytes of log data. When a consumer wants offset 8,000,000, Kafka finds the nearest earlier index entry and then scans forward a short distance in the log. That small scan is sequential and cheap. Keeping the index small also means it fits comfortably in memory, and Kafka maps its index files into memory so lookups feel instant.

i
Memory-Mapped Indexes

Index files are accessed by mapping them into the process’s address space, which is another way of using the page cache: the program reads the file as though it were ordinary memory, and the kernel quietly backs it with cached pages. Many partitions mean many mapped files, so operating systems with a low limit on mapped areas can run into trouble on busy brokers. We revisit this in the tuning chapter.

Tail reads: the common case

Most consumers are “caught up”, reading records that were written a few milliseconds ago. Those pages were just written and are still in the page cache, so no disk read is needed at all.

Catch-up reads: the harder case

A consumer that has been offline or slow needs older data. The pages may have been evicted, so the kernel reads them from disk. Sequential access and read-ahead keep this reasonably fast, but it does use disk bandwidth and cache space.

8The Traditional Read Path: Four Copies and Four Context Switches

To appreciate zero-copy, first see how wasteful the ordinary way of sending a file over a network is.

Suppose a normal program wants to send a chunk of a file to a client over the network. In most programming languages, the natural way is to read the file into a buffer, then write that buffer to a network socket. It looks like two simple lines. Underneath, the machine does a surprising amount of work.

1

Read call enters the kernel

The program asks for file data. This is the first context switch, from user space into kernel space.

2

Copy 1: disk to page cache

If the data is not cached, the disk controller uses DMA to place it into the page cache. No CPU copy is needed here.

3

Copy 2: page cache to the application buffer

The CPU copies the bytes from the kernel’s cache into the program’s own memory. The read call returns, which is the second context switch back to user space.

4

Write call enters the kernel

The program now hands the same bytes back to the kernel to send. This is the third context switch.

5

Copy 3: application buffer to the socket buffer

The CPU copies the bytes yet again, this time into a kernel buffer that belongs to the network connection. The write call returns, the fourth and last context switch.

6

Copy 4: socket buffer to the network card

DMA moves the bytes from the socket buffer to the network card, which sends them out on the wire.

sequenceDiagram
    participant App as Application (user space)
    participant Ker as Kernel
    participant Dsk as Disk
    participant Nic as Network card
    App->>Ker: read (switch 1)
    Dsk->>Ker: DMA copy into page cache
    Ker->>App: CPU copy into app buffer (switch 2)
    App->>Ker: write (switch 3)
    Ker->>Ker: CPU copy into socket buffer
    Ker->>Nic: DMA copy to network card
    Ker-->>App: return (switch 4)
        
FIG 4 — The traditional path: four copies and four context switches for data the application never even changed

Count the waste. Two of the four copies are done by the CPU, and the application only acted as a middleman that never modified the data. It read the bytes in and immediately wrote the same bytes out. The bytes made a round trip into user space and back, purely because the ordinary programming interface demands it.

Simple Analogy

A courier collects a parcel from a warehouse and drives it to the head office. The head office opens the parcel, copies the contents into a fresh box, and hands it to a second courier to deliver to the customer. Nothing was inspected or changed. The extra box and the extra courier only added time and cost.

!
Why It Hurts at Scale

One copy of a few kilobytes is trivial. A broker moving several gigabytes each second, multiplied by dozens of consumers, spends huge amounts of CPU and memory bandwidth on copies that add nothing. It also fills the Java heap with short-lived buffers, feeding garbage collection.

9Zero-Copy Explained: The sendfile Idea

Zero-copy removes the middleman: the kernel itself moves bytes from the file cache to the network connection.

Operating systems offer a special system call for exactly the “send this file region over a socket” job. On Linux it is called sendfile. Instead of asking the application to read and then write, the application makes one request: “Kernel, please send bytes from this file to that connection.” The data never enters the application’s memory at all.

1

One call enters the kernel

The application makes a single sendfile request naming a file, a position, a length and a network connection. That is the first context switch.

2

Copy 1: disk to page cache

If the data is not already cached, the disk controller uses DMA to load it. For a caught-up consumer this step is skipped, since the pages are already there.

3

Copy 2: page cache to the network card

On hardware that supports it, the kernel gives the network card a list saying where the pages live, and the card gathers the bytes by DMA. Only tiny descriptors are added to the socket buffer, not the data itself.

4

The call returns

Control comes back to the application. That is the second and final context switch.

sequenceDiagram
    participant App as Application (user space)
    participant Ker as Kernel
    participant Dsk as Disk
    participant Nic as Network card
    App->>Ker: sendfile (switch 1)
    Dsk->>Ker: DMA copy into page cache (only on a miss)
    Ker->>Nic: DMA gather copy straight from page cache
    Ker-->>App: return (switch 2)
        
FIG 5 — With sendfile the application never touches the bytes; the CPU performs no data copy at all

Compare this figure with the previous one. Two context switches instead of four. Two transfers, both done by DMA hardware, instead of four transfers of which two used the CPU. The Java process never sees the data, so nothing lands in the heap, and garbage collection has nothing to clean up.

A note on older hardware

On network cards that cannot gather scattered pages, the kernel has to make one CPU copy from the page cache into the socket buffer before the card sends it. That is three copies rather than two, and still better than four. Modern network cards commonly support the gather feature, so the two-copy version is what you usually get on a current server.

FeatureTraditional read and writesendfile (zero-copy)
System callsTwoOne
Context switchesFourTwo
CPU data copiesTwoNone on modern hardware
DMA copiesTwoTwo
Data visible to the applicationYesNo
Java heap pressureHighNone for the data itself
Simple Analogy

Now the courier is given a direct instruction: “Pick up from shelf twelve and deliver to this address.” The parcel goes from shelf to customer without visiting the head office. That is sendfile. The catch is obvious too: if the head office needed to stamp, seal or change something on the parcel, this shortcut could not be used.

i
The Golden Rule

Zero-copy only works when the bytes leaving the machine are exactly the bytes sitting in the file. The moment the application needs to look at, change, compress, or encrypt those bytes, the shortcut is impossible, because the data must enter user space to be processed.

10How Kafka Uses Zero-Copy Inside the Broker

Kafka is written for the Java virtual machine, and Java exposes sendfile through a single channel method.

Java’s file channel offers a method named transferTo. On Linux, when the destination is a plain network socket, the Java runtime translates this method into a sendfile system call. Kafka’s log code represents a slice of a segment file as a “file records” object. When the broker builds a fetch response for a consumer, it does not read these bytes into the heap. Instead, it remembers the file, the start position and the length, and later asks the channel to transfer those bytes straight to the socket.

Step 1

Fetch request arrives

A consumer or follower says which partition and offset it wants, and how many bytes at most.

Step 2

Index lookup

The broker uses the offset index to find the byte position in the right segment file.

Step 3

Slice, do not read

The broker creates a lightweight view of that file range, with no data copied into memory.

Step 4

Transfer to socket

The network layer asks the file channel to transfer the range directly to the connection, and the kernel does the rest.

Why the on-disk format matters

Kafka keeps record batches on disk in the same wire format that clients use on the network. The producer builds a batch, possibly compresses it, and sends it. The broker stores those very bytes. A consumer fetching the batch receives those very bytes and does the decompression on its own side. Because the broker never has to unpack and repack the data, the bytes in the file are already a valid response, and sendfile can ship them untouched.

flowchart LR
    A["Producer builds and compresses batch"] --> B["Broker appends same bytes to log"]
    B --> C["Page cache holds same bytes"]
    C --> D["sendfile ships same bytes"]
    D --> E["Consumer decompresses and reads"]
        
FIG 6 — One byte format end to end means the broker never needs to rewrite the data

The figure shows a single unbroken chain of identical bytes. This is a subtle design triumph. Compression happens once at the producer, the network carries the smaller data, storage holds the smaller data, the page cache holds the smaller data (so more history fits in memory), and only the final consumer pays the cost of decompressing. The broker in the middle is a very efficient courier that never opens the parcel.

Consumer fetches

Every regular consumer read benefits. Data is served from cache and shipped by sendfile without touching the Java heap.

Follower replication

Replica brokers fetch from the leader exactly as consumers do, so replication traffic is also served through the same efficient path.

Fan-out to many consumers

When ten consumer groups read the same topic, the page cache holds one copy of the data and sendfile serves it ten times, so extra readers cost mostly network bandwidth, not extra memory or heavy CPU work.

i
Where Zero-Copy Does Not Apply

The produce path is not zero-copy. Incoming bytes have to be read from the network into buffers so the broker can inspect them and assign offsets before appending. The big win of zero-copy is on the read side, which usually carries much more traffic than the write side because each record is written once but read many times.

Batch size, fetch size and how much each sendfile call moves

A fetch request carries limits that decide how many bytes the broker may return: a maximum for the whole response and a maximum for each partition. The broker slices up to those limits from the file and hands the whole slice to the kernel. If limits are tiny, the broker makes many small transfers and pays the fixed cost of a system call each time. If limits are generous, one transfer moves a large, contiguous region, which suits both read-ahead and the network card. This is why consumers that fetch a lot of data per request tend to be gentler on a broker than consumers that ask for a few kilobytes at a time.

There is also a waiting rule. A consumer can tell the broker to wait until a minimum amount of data is ready, or until a short time passes. That rule reduces the number of nearly empty responses, which saves CPU on both sides and lets more data accumulate in the cache so each transfer is worth the trip.

Why the follower path matters as much as the consumer path

Replication multiplies traffic. With three copies of every partition, the leader serves two followers in addition to all consumer groups. Because followers use the same fetch mechanism, they gain from the same cached pages and the same sendfile shortcut. A leader with a warm cache can feed its followers almost for free, while a leader whose cache has been polluted has to read from disk for the followers too, which slows replication, which in turn can shrink the set of in-sync replicas. In other words, a cache problem can start as a latency issue and grow into an availability issue.

11When Zero-Copy Does Not Apply

The shortcut is fragile. Several perfectly reasonable configuration choices quietly switch it off.

Because zero-copy needs the bytes on disk to be the bytes on the wire, anything that forces the broker to touch the data can break it. When that happens, the broker falls back to the traditional path: read the data into memory, process it, then write it to the socket. The system still works, so no error appears, but CPU use rises, garbage collection increases, and throughput per broker drops. This is why these situations are dangerous: they are silent.

Case 1

TLS encryption in the broker

When a listener uses TLS, the Java process itself must encrypt each byte before sending. The data has to enter user space, so sendfile cannot be used.

Case 2

Format down-conversion

If a very old client asks for data in an older record format than the one stored, the broker must convert the batches in memory, which breaks the byte-for-byte match.

Case 3

Recompression on the broker

If the broker or topic is set to a different compression codec than the producer used, the broker has to unpack and repack batches on the write path, adding CPU cost even though later reads still ship the stored bytes.

Case 4

Data not in the cache

This does not disable sendfile, but the kernel must first read the pages from disk, so the first stage of the transfer is a disk read rather than a memory hit.

The TLS trade-off in detail

TLS (Transport Layer Security) protects data from being read or changed while it travels over the network. Many organisations require it, and rightly so. The cost is that the encrypting work must happen inside the Java process, which means the bytes must be read into memory, encrypted, and then written out. A broker serving TLS traffic therefore loses the biggest advantage of zero-copy for those connections and needs noticeably more CPU for the same data rate.

Simple Analogy

A courier normally delivers a sealed parcel without opening it. If the customer demands that every parcel be locked in a personal safe with a unique key before leaving the depot, someone at the depot must handle each parcel, use the key, and close the lock. Delivery is far safer, but it is no longer a straight shelf-to-door hand-off.

flowchart TD
    Q["Fetch request arrives"] --> T{"Is the listener using TLS?"}
    T -- "Yes" --> S["Read into memory, encrypt, then write to socket"]
    T -- "No" --> V{"Client needs an older record format?"}
    V -- "Yes" --> C["Convert batches in memory, then write to socket"]
    V -- "No" --> Z["sendfile: page cache straight to network card"]
        
FIG 7 — A fetch is only zero-copy when both questions are answered “No”

Follow the arrows in the figure. The first decision asks about encryption; the second asks about format conversion. Only the bottom-right path is the fast one. Two of the three outcomes send data through the Java process. A platform team should be able to say, for each cluster, which of these three paths its traffic mostly takes.

ANTI-PATTERN-01 Avoid
Problem

A team enables encryption on every internal listener and later observes that brokers need far more CPU than capacity plans predicted, with no obvious cause.

Why It’s Harmful

The capacity plan assumed zero-copy reads. With TLS, every fetched byte passes through the Java process, so CPU, heap churn and garbage collection all grow, and brokers saturate long before their network or disk limits.

Correct Approach

Decide deliberately where encryption is required, size the cluster for the encrypted path, and load-test with realistic TLS traffic. If some links are already protected by the network, weigh that honestly against your security policy rather than assuming one setting fits everything.

!
Do Not Weaken Security for Speed

The lesson is not “turn TLS off”. The lesson is to know the cost, plan hardware for it, and never test performance with security disabled and then deploy with it enabled.

12Caught-Up Consumers versus Lagging Consumers

Two consumers reading the same topic can have wildly different effects on a broker, depending on how far behind they are.

A consumer is caught up when it reads records almost as fast as they are produced. It sits at the tail of the log, and the data it wants was written seconds ago. That data is almost certainly still in the page cache, so its reads are served from RAM and shipped by sendfile. On a cluster where consumers are mostly caught up, the disks can be almost silent on reads, even while gigabytes flow out through the network.

A consumer is lagging when it is far behind, perhaps because it was down for maintenance, is slow, or is a new application replaying history. Its wanted data is old. It may have already been pushed out of the cache, so the kernel has to read it from disk.

flowchart LR
    subgraph Hot["Caught-up consumer"]
    H1["Requests recent offsets"] --> H2["Page cache hit"] --> H3["sendfile to network"]
    end
    subgraph Cold["Lagging consumer"]
    C1["Requests old offsets"] --> C2["Page cache miss"] --> C3["Disk read plus read-ahead"] --> C4["Pages enter cache"] --> C5["sendfile to network"]
    end
        
FIG 8 — Both paths end with sendfile, but the lagging path pays for a disk read first

The figure shows that both consumers get zero-copy delivery. The difference is the first stage. The lagging path involves the disk, and disk reads are much slower and much more limited than memory hits.

The cache pollution problem

The cost of a lagging consumer goes beyond its own slowness. When the kernel loads old pages for it, those pages need room in memory. The kernel makes room by evicting other pages, and those may be the very tail-of-log pages that hundreds of caught-up consumers and follower replicas depend on. Suddenly the healthy consumers start seeing cache misses too. Disk reads rise, latency climbs, and the broker can appear to be “slow for everyone” because of one heavy reader.

Simple Analogy

A library has a small “popular books” trolley. One visitor arrives and requests fifty ancient volumes from the basement archive. The librarian, to make room on the trolley, moves the popular books back to the archive. Now the next dozen visitors, who wanted the popular books, must wait for a basement trip as well.

Reprocessing jobs

A batch job or new analytics application reads days of history from the start. It is sequential and efficient, but it can churn a large part of the cache if the machine’s memory is small compared with the amount read.

Recovering consumers

After an outage, a consumer group resumes with a large backlog. Many partitions are read at the same time, producing a burst of disk reads exactly when the system is already under stress.

Follower catch-up

A broker rejoining the cluster after being down must fetch a lot of missed data from leaders. This can heavily load the leaders’ disks and cache while they are also serving normal traffic.

i
Protecting the Hot Path

Cluster operators use several levers here: client quotas to cap how fast a heavy consumer can read, throttles on replication during recovery, running heavy reprocessing during quiet hours, and, where budget allows, giving replay workloads their own cluster or mirrored topic so they never share cache with real-time consumers.

When read-ahead helps and when it hurts

Read-ahead is a bet that you will keep reading forward. For a lagging consumer scanning a partition in order, the bet pays off: the next blocks arrive before they are requested, and the disk stays busy doing large, efficient reads. For a reader that jumps around, the bet is lost, because the extra blocks loaded are never used and they occupy cache space that something else could have used. Kafka mostly benefits from read-ahead because its consumers overwhelmingly read forward.

The lesson for operators is to be cautious with very large read-ahead values. They help sequential replays on spinning disks, but on a broker with many partitions read at once, each partition’s read-ahead multiplies, and the total amount of speculative data can crowd the cache. Moderate values that suit the storage device are usually the safest starting point.

13Memory Sizing and Capacity Planning

The page cache is only as good as the memory you leave for it, so sizing a broker is really a question of how RAM is split.

On a Kafka broker, machine memory is divided into a few regions. The JVM heap holds Java objects for the broker’s internal work, such as request handling, metadata, and buffers for producer requests. Native memory holds things outside the heap, such as thread stacks and network buffers. Everything left over is free for the operating system, and the kernel fills nearly all of it with page cache.

flowchart TD
    R["Total machine RAM"] --> J["JVM heap: small, often a few gigabytes"]
    R --> N["Native and OS overhead: modest"]
    R --> P["Page cache: everything else"]
    P --> H["Hot tail of every partition"]
    P --> I["Mapped index files"]
    P --> O["Recently read older segments"]
        
FIG 9 — On a healthy broker the largest slice of RAM belongs to the page cache, not the Java heap

The figure emphasises how lopsided the split should be. A common starting point for a busy broker is a heap of roughly six gigabytes, on a machine that has far more RAM in total. The exact number depends on partition count and workload, but the principle is stable: give the heap enough to run comfortably without frequent pauses, and let the rest be cache.

How much cache do you actually need?

A useful way to think about it: the cache needs to hold the data that active readers will want. For caught-up consumers, that is the most recent stretch of each partition’s log. Estimate it like this. Take the write rate per second for the whole broker, multiply by the number of seconds of history you want to keep cached (for example, thirty seconds to a few minutes for real-time readers), and that is the minimum useful cache. If some consumers regularly lag by an hour, the cache needed to protect them grows accordingly.

QuestionWhy it mattersEffect on RAM plan
How fast is data written per broker?Fast writes fill the cache and age data out soonerHigher rate means more RAM for the same time window
How far behind are typical consumers?Lag decides how far back the cache must reachLarger lag means more RAM or acceptance of disk reads
How many consumer groups read the same data?Fan-out shares one cached copySmall effect on RAM, large effect on network
How many partitions per broker?Each has its own hot tail and mapped indexesMore partitions spread the cache thinner
Is compression used?Compressed batches occupy less cacheBetter compression means more history fits
Small heap
Enough to avoid long pauses, no more
Big cache
All remaining RAM works for you automatically
Fast network
With zero-copy the link often becomes the limit
!
Common Mistake

Raising the heap “to give Kafka more memory” is one of the most frequent tuning errors. Since Kafka’s speed comes from the cache outside the heap, a larger heap steals from the cache and lengthens garbage-collection pauses, giving you the opposite of what you wanted.

Other programs on the same machine

Anything else running on a broker host competes for memory. A monitoring agent, a log shipper, or an unrelated application that uses lots of RAM shrinks the cache. That is why Kafka brokers are best run on dedicated hosts, or on hosts where the neighbours have small, well-understood memory needs.

A worked example in plain words

Imagine a broker that receives data at a steady rate of one hundred megabytes each second. Real-time consumers are usually within a minute of the tail. Sixty seconds of writes is about six thousand megabytes, or roughly six gigabytes. That is the amount of recent data that should stay cached for most reads to be memory hits. Add some room for index files, for other partitions’ tails, and for a few consumers that lag by several minutes, and a working target of twenty to thirty gigabytes of page cache is sensible.

Now add the heap, native memory and the operating system. A machine with sixty-four gigabytes of RAM comfortably covers everything: a heap of a few gigabytes, some native and system memory, and a page cache of well over forty gigabytes. A machine with only sixteen gigabytes would leave perhaps ten for cache, which is thin for this workload, and the operator would see disk reads climb whenever any consumer fell behind. The example is deliberately simple, but it shows the method: start from the write rate, choose how much history you want in memory, and work backwards to a machine size.

i
Remember the Replicas

A broker stores data for partitions it leads and partitions it follows. Its write rate includes both, so estimate the total incoming bytes per second, not only what producers send to the partitions it currently leads.

14Tuning Linux and Kafka for the Page Cache

A few operating-system knobs decide whether the kernel behaves like a friend or a nuisance for a Kafka broker.

Kafka works out of the box on a default Linux system, but a handful of settings are commonly adjusted on production brokers. The names below are kernel parameters that operators set in system configuration. They are described here in words, since the point is to understand what each one controls and why.

Setting 1

Swappiness

Controls how eagerly the kernel moves program memory to disk. A very low value stops the Kafka process from being swapped out, since a swapped-out broker stalls badly.

Setting 2

Dirty background ratio

The share of memory that may be dirty before the kernel begins quiet background flushing. A lower value means smoother, smaller, earlier flushes.

Setting 3

Dirty ratio

The hard limit at which writers are forced to wait for flushing. Setting it higher gives bursts of produce traffic more room before that pause occurs.

Setting 4

Maximum memory maps

The number of mapped regions a process may hold. Brokers with many partitions have many mapped index files and may need a much higher limit than the default.

Setting 5

Open file limit

Every segment and index is an open file. High partition counts need a generous file-descriptor allowance.

Setting 6

Read-ahead size

How much extra the kernel reads on a miss. Larger values help sequential consumers on spinning disks, but waste cache if reads are scattered.

Choosing the file system and mount options

Kafka is commonly run on XFS or ext4, and XFS is often preferred for its good behaviour with large files and heavy parallel writes. It is also common to mount the data volumes so that the kernel does not update a “last accessed” time on every read, because that extra tiny write per read is wasted work. Keeping Kafka data on its own volumes, separate from the operating system, also stops noisy neighbours from interfering with flushes.

Why smoother flushing helps

If dirty data piles up for a long time and is flushed all at once, the disk suddenly receives a big wave of writes. During that wave, reads that miss the cache queue behind the writes, and produce requests can be delayed if the hard dirty limit is reached. Lowering the background threshold makes the kernel start flushing sooner and in smaller waves, so the disk load is steady rather than spiky. Raising the hard limit keeps normal bursts from ever hitting the wall.

Simple Analogy

Picture a restaurant dishwasher. If dirty plates are washed steadily as they arrive, the pile stays small and the kitchen never stops. If nobody washes until the sink overflows, the whole kitchen freezes while one giant washing session happens. Lowering the background ratio is like washing continuously; raising the hard ratio is like buying a bigger sink.

1

Measure first

Record cache hit behaviour, disk read rates, and flush latency under normal load before changing anything.

2

Change one setting at a time

Adjusting several kernel parameters together makes it impossible to know which change helped or hurt.

3

Test under realistic load

Include lagging consumers, replication catch-up and TLS if you use it. Quiet-hour tests hide the problems that matter.

4

Roll out gradually

Apply to one broker, watch it, and only then extend across the cluster.

i
Kafka-Side Settings Worth Knowing

The broker’s segment size and index interval shape how many files exist and how large each cached region is. The broker’s own flush settings exist but are usually left at their defaults so that the kernel and replication handle durability. Network buffer sizes affect how well sendfile keeps a fast link full. Treat all of these as second-order to the RAM split and the operating-system settings above.

15Monitoring and Diagnosing Cache and Zero-Copy Health

Because the cache lives in the kernel, you must look outside Kafka’s own metrics to see whether it is healthy.

Kafka does not report a “page cache hit ratio” of its own. Operators infer cache health from indirect signs: how much the disks are being read, how long fetches take, how idle the broker’s threads are, and what the operating system says about memory. A good habit is to build one dashboard that shows both sides together.

OS Signal

Disk read throughput

On a healthy cluster of mostly caught-up consumers, disk reads should be low compared with network output. A sudden climb means cache misses.

OS Signal

I/O wait

The share of time the CPU waits for the disk. Rising I/O wait during reads is a classic sign the cache is being missed.

OS Signal

Memory breakdown

Shows how much is “cached” versus used by programs. A shrinking cache on a broker host warns that something else is eating memory.

OS Signal

Dirty and write-back memory

Large, growing dirty totals suggest the disk cannot keep up with incoming writes.

Kafka Metric

Fetch request time

Broken down by stage, it shows whether time is spent waiting, in local processing, or sending to the client. Growth in local time often means disk reads.

Kafka Metric

Thread idle percentages

Low idle time for request handlers or network threads means the broker is busy, which may be due to extra CPU work from lost zero-copy.

Kafka Metric

Log flush time

How long it takes to flush a log to disk. Spikes indicate disk trouble that may spill into latency.

Kafka Metric

Under-replicated partitions

When followers fall behind, they read older data, which puts pressure on the cache and the disks of leaders.

Special tools for looking into the cache

Several small utilities can show which files are cached and how well the cache is working. Some report how much of a given file is currently in memory. Others trace kernel cache accesses and report hits and misses per second. Standard system tools also show per-disk throughput and CPU wait. Learning one tool from each group is enough for most investigations.

1

Confirm the symptom

Are fetch times up, are consumers lagging, or is a broker running hot on CPU? Pin down which one before digging.

2

Check the disks

Compare disk read rate with network output. High disk reads mean the cache is being missed.

3

Find the reader

Look for consumer groups or replicas with large lag. One heavy lagging reader is a frequent culprit.

4

Check for lost zero-copy

If disks are quiet but CPU is high, suspect TLS, format conversion, or recompression forcing data through the heap.

5

Check for memory thieves

Confirm nothing else on the host, or a recent heap increase, has shrunk the cache.

flowchart TD
    S["Slow fetches or high broker load"] --> D{"Are disk reads high?"}
    D -- "Yes" --> L["Find lagging readers and check cache size"]
    D -- "No" --> C{"Is CPU high?"}
    C -- "Yes" --> Z["Suspect TLS, conversion or recompression"]
    C -- "No" --> W["Check network limits and client behaviour"]
        
FIG 10 — A simple decision path that separates cache problems from lost zero-copy and from network limits

This figure gives a quick first cut. High disk reads point to the cache; high CPU with quiet disks points to data flowing through the heap; neither points to the network or to the clients. Real incidents can combine causes, but this three-way split resolves a surprising number of cases in minutes.

Three dashboard patterns and what they usually mean

Pattern A: Disk reads climb, network output is flat

Consumers are asking for about the same amount of data as before, but more of it now comes from disk. This usually means the cache has shrunk or a lagging reader has started polluting it. Look for a new heavy consumer, a recently added neighbour process, or a heap increase.

Pattern B: CPU climbs, disk reads stay quiet

The data is still served from memory, but the broker is working harder per byte. This often follows a configuration change such as enabling encryption on a listener, adding an old client that needs conversion, or changing compression settings. The fast path has been replaced by the slower one.

Pattern C: Produce latency rises while disk write activity spikes

Dirty pages are piling up faster than the disk can absorb them, and writers may be waiting. Look at dirty memory totals, flush times, and whether disks are also serving heavy reads at that moment.

These three patterns are not the only ones, but they teach the right habit. Never look at one graph alone. Pair each Kafka symptom with an operating-system graph, and ask which of the two questions from the start of this tutorial is being answered badly: was the data in memory, and how many times was it copied?

16Security and Multi-Tenant Considerations

Speed features touch security in a few important ways, and a good architect keeps both in view.

The page cache holds plain, unencrypted copies of whatever is in the log files. That is normal for any file cache, but it means that protection has to come from the machine boundary: who can log into the host, who can read the data directory, and how disks are encrypted at rest. If the underlying volume uses disk-level encryption, the kernel decrypts as it loads pages, and the page cache still holds clear data in RAM.

Risk 1

Data in RAM is in the clear

Anyone who can read the machine’s memory or the data directory sees the records. Restrict host access tightly.

Risk 2

Encryption costs zero-copy

Encrypting client connections at the broker moves data through the Java process and needs extra CPU.

Risk 3

Shared cache, shared fate

In a multi-tenant cluster, one tenant’s heavy replay can evict another tenant’s hot data from the cache.

Risk 4

Denial by resource use

A misbehaving client that reads huge backlogs can degrade the whole cluster unless quotas are set.

Quotas as a security and fairness tool

Client quotas let an operator cap how many bytes per second a user or client application may read or write. In the light of this tutorial, quotas are more than a fairness feature; they are how you protect the shared page cache. A quota on fetch rate bounds how much cold data a single tenant can drag into memory in a given time.

i
Design Advice

Treat the page cache as a shared resource, like network bandwidth. If tenants have very different access patterns, separate the ones that replay history from the ones that need very low latency, either with quotas or by giving them separate clusters.

17Design Patterns and Anti-Patterns

Good architects turn the ideas of this tutorial into repeatable decisions, and learn to spot the decisions that quietly undo them.

PATTERN-01 Recommended
Problem

Real-time consumers need consistently low latency, but the cluster also has to support occasional replays and backfills.

Why It Matters

Replays evict the hot tail from the page cache, harming everyone else. Trying to fix this by adding more memory alone is expensive and unreliable.

Recommended Approach

Isolate replay workloads: cap them with quotas, run them off-peak, or serve them from a mirrored copy on a separate cluster. Keep the primary cluster’s cache for the readers that need it.

PATTERN-02 Recommended
Problem

Brokers are provisioned with lots of CPU and small memory because “Kafka is a Java application”.

Why It Matters

Kafka’s read speed depends on spare RAM and network, not raw CPU. Small memory means a thin cache and constant disk reads.

Recommended Approach

Choose machines that are memory-rich, with fast networking, moderate CPU and disks matched to the sustained write rate. Add CPU headroom mainly when TLS or compression on the broker is in use.

PATTERN-03 Recommended
Problem

Producers use different compression settings, and the broker recompresses to a single standard.

Why It Matters

Recompression burns broker CPU on every write and adds latency, when the whole design is meant to keep the broker a light courier.

Recommended Approach

Compress at the producer, let the broker keep the producer’s choice, and let consumers decompress. The same bytes then flow from producer to consumer untouched.

ANTI-PATTERN-02 Avoid
Problem

An operator forces frequent disk flushes on every message “to be safe”.

Why It’s Harmful

Forcing the disk on each message removes the benefit of batching writes in the cache, sharply reduces throughput, and adds latency, while replication already offers stronger protection against machine loss.

Correct Approach

Rely on replication with sensible acknowledgement and minimum in-sync replica settings, and reserve forced flushing for unusual environments with a specific need for it.

ANTI-PATTERN-03 Avoid
Problem

A team co-locates Kafka with other memory-hungry services to “save on servers”.

Why It’s Harmful

The neighbours take memory the kernel would use for cache. Kafka’s reads slip from memory to disk, and the cause is invisible from inside Kafka.

Correct Approach

Give brokers dedicated hosts or strict resource boundaries, and watch the host’s cached-memory level as a first-class metric.

ANTI-PATTERN-04 Avoid
Problem

Running with a mix of old client versions that require the broker to convert record formats.

Why It’s Harmful

Every fetch from an old client forces the broker to open, convert and rewrite batches in memory. This uses the heap, burns CPU, and disables zero-copy for that traffic.

Correct Approach

Upgrade clients so that they understand the stored format, and retire legacy applications on a schedule.

18Containers, Cloud Disks and Tiered Storage

Modern deployment environments add new twists to a design that was born on bare-metal servers.

Kafka in containers and Kubernetes

When a broker runs in a container, the container has a memory limit enforced by the kernel through a mechanism called a control group. Page cache pages used by files that the container reads and writes are usually counted against that container’s memory. This surprises many people. A dashboard might show container memory use climbing to the limit and look alarming, when much of it is reclaimable cache that the kernel will happily discard under pressure.

Simple Analogy

A hotel room has a fixed number of shelves. Your suitcase is the JVM heap. Your souvenirs, which you can throw away at any time, are the page cache. If a guard counts every item on the shelves and says the room is “full”, he is right, but the room is not in trouble; you can drop the souvenirs whenever you need space for a real suitcase.

The right way to read container memory is to look at the “working set”, which subtracts cache the kernel can easily drop, and to remember that too tight a limit leaves no room for the cache. If you set the container’s memory limit only slightly above the heap size, the page cache will be tiny and reads will drift towards disk. A memory limit for a Kafka broker container should deliberately include generous space for cache, and the memory request should reflect that too.

!
Watch Out

Scheduling many brokers onto shared nodes can make them compete for the same node-level cache. Prefer dedicated nodes or clear anti-affinity so one busy broker does not evict another’s hot data.

Cloud block storage and network disks

In the cloud, log data usually sits on network-attached volumes instead of local disks. These volumes have per-volume limits on throughput and operations per second, and their latency is higher than local flash. The page cache is even more valuable here, because every miss now travels across a network to the storage service. On the write side, write-back to a network volume can lag, so watching dirty memory and flush time matters more.

Tiered storage

Newer Kafka versions support tiered storage, where older closed segments are moved from broker disks to a cheaper remote object store, while recent data stays local. This changes the picture for old reads. Recent data still lives in local files and benefits from the page cache and sendfile exactly as described. Older data that has been moved off the broker is fetched from the remote store, so those reads do not enjoy the same local, zero-copy path. In practice this makes the hot local tail smaller and more focused, but it also means historical reads have a different performance profile that should be tested separately.

EnvironmentCache behaviourWhat to watch
Bare metal with local disksCache is large and predictableNeighbouring processes, dirty memory
ContainersCache is counted against the memory limitWorking set versus limit, node sharing
Cloud network volumesMisses are expensive because of network latencyVolume throughput limits, flush time
Tiered storageHot data cached locally, old data fetched remotelyLatency of historical reads, remote fetch load

Choosing between local disks and network volumes

Local disks attached to the machine give low and steady latency, and they make cache misses cheaper. The trade-off is that data on them must be rebuilt from replicas if the machine is lost, which shifts more work to replication after failures. Network volumes survive machine loss and are easy to resize, but every miss pays a network round trip and every volume has throughput ceilings. Teams that choose network volumes generally compensate with more memory for a bigger cache and with careful monitoring of volume limits, while teams with local disks compensate with more brokers and faster recovery plans.

19Real-World and Industry Examples

The same two ideas appear again and again in the way large organisations run streaming platforms.

Kafka began at LinkedIn, where engineers needed a way to move huge streams of activity data between systems. The page-cache-centric and sendfile-based design was part of the original thinking and has remained a distinguishing feature as the technology spread to many industries. Large technology and financial companies now operate clusters that carry very large volumes of events, and many of them describe similar operational habits: memory-rich brokers, small heaps, careful control of lagging readers, and a strong preference for keeping most traffic on the fast path.

Real-time analytics pipelines

A streaming application reads events within milliseconds of their arrival. Nearly every read is a cache hit, so the cluster handles high volume with little disk activity and modest CPU.

Event-driven microservices

Dozens of services subscribe to the same topics. The page cache holds one copy of the data and sendfile serves each subscriber, so adding another consumer costs network capacity rather than memory copies.

Change data capture and data lakes

Database changes stream through Kafka to storage systems. Some sinks batch their reads and run periodically, meaning they occasionally read older data. Operators watch these sinks, since they are the most likely lagging readers on the cluster.

Financial and payment platforms

These often require encryption in transit. Their architects plan more CPU per broker knowing that zero-copy is unavailable on the encrypted path, and they compensate with more brokers or newer hardware that handles cryptography efficiently.

Log and metrics aggregation

Very high write volumes with modest read fan-out. The main concern is keeping write-back smooth, so kernel dirty-page settings and fast disks matter more than sheer memory.

flowchart LR
    W["Producers write once"] --> PC["Page cache holds one copy"]
    PC --> R1["Consumer group A"]
    PC --> R2["Consumer group B"]
    PC --> R3["Consumer group C"]
    PC --> F["Follower replicas"]
        
FIG 11 — Write once, read many: a single cached copy serves every reader through zero-copy transfers

This last figure captures the whole economic idea of Kafka’s read path. A record is written once. It sits in memory once. It may be sent out many times, to many consumer groups and replicas, each time by a handoff that costs the CPU almost nothing. That is why adding readers to a healthy Kafka cluster tends to hit the network limit long before the CPU or memory limit.

20Best Practices and Common Mistakes

A short checklist of habits that keep the fast path fast.

Best Practices

  • Keep the JVM heap modest and give the rest of RAM to the kernel
  • Run brokers on dedicated or well-isolated hosts
  • Set kernel parameters for low swapping and smooth write-back
  • Compress at the producer and keep the broker’s compression as the producer chose
  • Keep clients current so no format conversion is needed
  • Use quotas to protect the cache from heavy or lagging readers
  • Rely on replication for durability instead of forced flushes
  • Monitor OS-level cache and disk signals next to Kafka metrics

Common Mistakes

  • Inflating the heap to “give Kafka more memory”
  • Assuming free memory near zero means a leak
  • Load-testing without TLS, then deploying with TLS
  • Letting a single replay job read from a production cluster during peak hours
  • Sharing broker hosts with memory-heavy applications
  • Ignoring the memory-map and file-descriptor limits when partition counts grow
  • Sizing memory limits in containers with no room for cache
Habit 1

Think in paths

For any workload, ask which path its bytes take: cache hit with sendfile, cache miss with sendfile, or through the heap.

Habit 2

Plan for the slowest path

Capacity plans should cover the most expensive path that will realistically occur, not only the ideal one.

Habit 3

Protect the tail

The hot tail of every partition is the most valuable memory on the machine. Guard it from anything that would evict it.

Habit 4

Change little, measure often

Kernel tuning gives strong gains only when guided by measurements, not by copying numbers from another company’s setup.

21Frequently Asked Questions

Q1Does Kafka read from disk at all, if it uses the page cache?

Yes, when the needed data is not in the cache. Caught-up consumers are usually served from memory, but lagging consumers, restarted replicas, and reprocessing jobs cause real disk reads. The design makes the common case fast and the uncommon case acceptable.

Q2Is data lost if the broker process crashes before it is flushed to disk?

Not if only the process crashes. The written data is already in the kernel’s page cache, which continues to exist and will be flushed by the kernel. Data can be lost only if the whole machine or kernel fails before write-back, and replication to other brokers is the protection against that.

Q3What exactly is “zero” in zero-copy?

It refers to the number of copies made by the CPU in user space on the way from the file to the network. The data still moves by DMA between the disk, memory and the network card, but the processor does not shuffle it through the application’s own buffers.

Q4Does zero-copy help producers writing to Kafka?

Not directly. The write path reads incoming bytes into buffers for validation and offset assignment. Zero-copy mainly benefits reads, which include consumers and follower replicas, and reads usually dominate traffic.

Q5Why does enabling TLS make brokers use more CPU?

Encryption is done inside the Java process, so the data must be brought into user space, encrypted, and sent. That means sendfile cannot be used for those connections, and the broker has to do real cryptographic work for every byte.

Q6Should I give the broker a bigger heap to improve performance?

Almost never. Kafka’s data is cached outside the heap, so extra heap takes memory away from the cache and can make garbage-collection pauses longer. Increase the heap only if monitoring shows real heap pressure.

Q7Why does my server show almost no free memory even though Kafka uses a small heap?

The kernel fills unused memory with page cache on purpose. It is reclaimable immediately when a program needs it. Look at the “available” figure rather than the “free” figure to judge real pressure.

Q8How can one slow consumer hurt others?

A consumer reading old data forces the kernel to load old pages, which can evict the recent pages other consumers rely on. Those consumers then suffer cache misses, and the disk becomes busier for everyone.

Q9Does compression help or hurt the page cache design?

It helps. Compressed batches take less space in the cache and on the network, so more history fits in memory. The broker stores what the producer sent, and consumers do the decompression.

Q10Are the index files also cached?

Yes. Kafka maps its index files into memory, so they are served by the page cache as well. They are small, and they are very valuable to keep resident because every fetch begins with a lookup in them.

Q11Does tiered storage remove the need to think about the page cache?

No. Recent data is still local and benefits from the cache and sendfile. Tiered storage changes how older data is served, and it can shrink the amount of local data, but the hot tail remains the most important part of the memory plan.

Q12Can I test whether my brokers are using zero-copy?

You can infer it. With plaintext listeners and up-to-date clients, high network output with low broker CPU and quiet heap activity indicates the fast path. If CPU and garbage collection climb in proportion to traffic, some traffic is likely going through the heap, and you should check for TLS or format conversion.

22Summary and Key Takeaways

Kafka’s read speed is not the result of a single trick but of a coherent design that cooperates with the operating system. Producers write batches that are appended sequentially. The kernel keeps recent data in the page cache, so most reads never touch the disk. The bytes in the file are the bytes on the wire, so the kernel can send them straight from cache to the network card with sendfile, skipping the application’s memory and the wasteful copies that come with it. Replication provides durability, so the broker does not need to force every write onto the disk. The price of this design is that it depends on plenty of spare memory, sensible operating-system settings, and vigilance about anything that breaks the fast path, such as TLS, format conversion, and heavy lagging readers.

Key Takeaways

  • The page cache is Kafka’s cache — the broker deliberately builds no large private cache and lets the kernel keep hot log data in RAM, avoiding double caching and heap pressure.
  • Zero-copy removes the CPU copies — sendfile moves bytes from the cache to the network card with two context switches and no CPU copies, instead of four switches and two CPU copies.
  • Same bytes everywhere — the producer’s batch format is what is stored, cached and sent, which is what makes zero-copy possible and lets consumers do the decompression.
  • Durability comes from replication — data reaches the cache first and the disk later, so acknowledgement settings and minimum in-sync replicas are the real safety mechanism.
  • Protect the hot tail — lagging consumers, replays and noisy neighbours evict the pages that real-time readers need, so use quotas, isolation and dedicated hosts.
  • Size for cache, not for heap — a small heap and a large remainder of RAM beat a large heap, and container limits must leave room for the cache.
  • Know which path your traffic takes — TLS, format conversion and recompression silently move data through the heap, so plan capacity and monitoring for the slowest realistic path.