Series: Kafka 為什麼這麼快 (1/2)

Why Is Kafka Fast? Part 2: Partitions, Replication, and Consumer Groups →
Table of Contents

Kafka deliberately writes data to disk, yet it’s one of the fastest message queues in production. This seems contradictory—isn’t disk far slower than memory?

Not necessarily. Fast or slow depends on how you access disk. Kafka’s performance story is fundamentally about access patterns.

TL;DR

  • Sequential I/O: Disk’s weakness is seek time. Kafka is append-only by design, eliminating seek entirely
  • Page Cache: Linux automatically caches disk data in memory; consumers usually read from cache, not physical disk
  • Zero-Copy: sendfile() moves data from disk directly to NIC, bypassing CPU and eliminating 2 memory copies and 2 context switches
  • Batching: All of the above multiplied by batch processing = compounding throughput gains

Disk Isn’t Slow—Random Access Is

A traditional HDD has a mechanical arm. Seeking (moving the read head to the right track) costs 5–10ms. In that time, a modern CPU can execute tens of millions of instructions.

But with sequential access—each read/write continuing where the last one left off—seek time is near zero. Sequential disk throughput can reach 500+ MB/s on HDD, far higher on SSD.

More importantly, sequential reads enable the OS read-ahead mechanism: the kernel predicts what you’ll need next and pre-loads it into Page Cache. This makes disk access feel like memory access.

A Kafka topic partition is an append-only log file. Producers append to the end; consumers read sequentially from an offset. The read head always moves in one direction.

Page Cache: OS Manages Memory So Kafka Doesn’t Have To

The Linux kernel maintains a Page Cache layer. When you read from disk, the kernel puts the data in memory. The next request for the same data comes from memory, not disk.

Kafka aggressively relies on this instead of managing its own in-memory buffer (as many systems do). Benefits:

  1. Low JVM GC pressure: Kafka broker heap stays small; memory management is delegated to OS
  2. Cache survives broker restarts: JVM heap clears on restart; OS Page Cache persists after the Kafka process restarts—consumers keep hitting cache
  3. Near-free consumption when consumers keep up: A message just written by a producer is already in Page Cache; the consumer reads it without touching physical disk

This is why Kafka recommends giving most broker RAM to OS (not JVM heap): you want the OS to use it for Page Cache, not your application.

Zero-Copy: Eliminating Unnecessary Data Movement

The traditional path for “read from disk, send over network” looks like:

Disk → kernel buffer (Page Cache) → user space buffer → socket buffer → NIC

Data is copied 4 times, with 4 context switches (user space ↔ kernel space).

Kafka uses the sendfile() syscall (Linux) or transferTo() (Java NIO):

Disk → kernel buffer (Page Cache) → NIC buffer → NIC

2 copies, 2 context switches. More importantly, the CPU doesn’t touch the data—transfer is handled by the DMA (Direct Memory Access) controller.

At high throughput—several GB/s—the eliminated copies and CPU cycles translate directly into measurable throughput and latency improvements.

Batching Multiplies Everything

Every optimization above compounds with batching:

  • Producer packs multiple messages into one batch before sending—one syscall for many messages
  • Consumer pulls one batch at a time—fewer network round trips
  • Compression works better on batches (similar-format messages compress far better together than individually)

Kafka supports gzip, snappy, lz4, and zstd. When network bandwidth is the bottleneck, compression can be the deciding factor in hitting throughput targets.

Why This Works: The Kafka Design Bet

Kafka’s model—append-only log with consumer offsets—is what makes all this possible. Because nothing is ever deleted or modified, read patterns are always sequential. Because consumers track their own position (offset), the broker doesn’t need to maintain per-message delivery state.

This simplicity is what allows the sequential I/O assumption to hold, which is what makes Zero-Copy and Page Cache effective.

Part 2 covers Kafka’s partition model, replication, and how Consumer Groups achieve horizontal scalability.

References

Ask this article

Answers come from this article only. Click any prompt below or open the chat at the bottom right.

🇺🇸 English

Isn't disk supposed to be slow? Way slower than memory? So why is it that Kafka — one of the fastest message queues running in production anywhere — deliberately writes everything to disk? That sounds like a contradiction. But here's the thing: it isn't. Whether disk is fast or slow doesn't depend on the disk. It depends on *how* you talk to it. And Kafka's entire performance story comes down to access patterns.

Let's start with the myth that disk is slow. On a traditional spinning hard drive, there's a physical mechanical arm. Every time it has to jump to a different spot on the platter to find your data, that seek takes something like five to ten milliseconds. Now, five milliseconds sounds tiny to us, but in that same window a modern CPU can rip through tens of millions of instructions. So the arm is the bottleneck. That's the real villain — not the disk itself, but *random* access, all that jumping around.

Now flip it. What if you never jump? What if every read and every write just continues right where the last one left off? That's sequential access, and suddenly seek time basically disappears. A plain hard drive can push five hundred megabytes a second or more when it's going sequentially, and an SSD blows way past that.

And there's a bonus. When the operating system notices you're reading in order, it starts guessing ahead — it's called read-ahead. The kernel pre-loads the next chunk you're probably going to want, before you even ask for it. So disk starts to *feel* like memory.

Here's where Kafka's design clicks into place. A Kafka topic partition is just an append-only log file. Producers only ever add to the end. Consumers only ever read forward from a given position. The read head marches in one direction, always. That's the whole trick — Kafka structured its data so that the disk's one weakness, seeking, just never comes up.

Which brings us to the second pillar: the page cache. The Linux kernel keeps this layer in memory called the page cache. When you read something off disk, the kernel quietly stashes a copy in RAM. Ask for that same data again, and it comes straight from memory — the disk never gets touched.

Now, a lot of systems fight the OS here. They build their own in-memory buffer, manage their own cache, try to be clever. Kafka does the opposite. It leans hard on the page cache and lets the operating system do the work. And that pays off in three big ways.

First, the JVM stays lean. The Kafka broker's heap can be small, because it's not hoarding data in memory — the OS is handling that. Small heap means way less garbage collection pressure.

Second — and this one's beautiful — the cache survives a restart. If Kafka's memory lived in the JVM heap, restarting the process would wipe it clean. But the page cache belongs to the OS, not to Kafka. So you can bounce the Kafka process and the cache is still sitting there warm. Consumers keep getting hits.

Third, when consumers are keeping up with producers, consumption is almost free. Think about it: a message a producer just wrote is *already* in the page cache. So when the consumer comes along a moment later to read it, it never has to reach down to the physical disk at all. It's already in memory.

This is exactly why the recommendation for Kafka is to give most of your broker's RAM to the operating system, not to the JVM heap. You *want* the OS to spend that memory on page cache. That's where your speed lives.

Okay, third pillar, and this is my favorite: zero-copy. Let's trace what normally happens when a server reads a file off disk and sends it over the network. The data goes from disk into a kernel buffer — that's the page cache. Then it gets copied up into a user-space buffer, into your application. Then copied into a socket buffer. Then finally out to the network card. Count them: that's four copies of the same bytes, and four context switches back and forth between user space and kernel space. All of that just to shovel data from disk to network — data your application never even needed to look at.

Kafka says, why are we dragging this up into user space at all? So it uses a system call — `sendfile` on Linux, or `transferTo` in Java's NIO — that tells the kernel: take this data from the page cache and send it straight to the network card. Now the path is disk, to kernel buffer, to network card. Two copies instead of four. Two context switches instead of four. And the best part — the CPU doesn't touch the data at all. The actual transfer is handled by the DMA controller, Direct Memory Access, dedicated hardware whose entire job is moving bytes so the CPU doesn't have to. When you're running at several gigabytes a second, cutting out those copies and freeing up those CPU cycles turns directly into real, measurable throughput and lower latency.

And then there's the multiplier on top of all of it: batching. Every optimization we've talked about compounds when you batch. The producer packs a bunch of messages into one batch before sending — one system call carries many messages instead of one at a time. The consumer pulls a whole batch per request — fewer network round trips. And compression works dramatically better on a batch, because a pile of similar-looking messages squeezes down far more than each one would alone. Kafka gives you gzip, snappy, lz4, and zstd to choose from. When your bottleneck is network bandwidth, compression can be the exact thing that gets you over the line to your throughput target.

So step back and look at the bet Kafka made. An append-only log, where consumers track their own position — their own offset. Because nothing ever gets deleted or modified, reads are *always* sequential. And because each consumer remembers where it is, the broker doesn't have to babysit per-message delivery state for everybody. That simplicity is the foundation. It's what lets the sequential assumption hold, and *that's* what makes page cache and zero-copy actually work. It's not three unrelated tricks — it's one design decision paying off three times.

So the three things to walk away with. One: disk isn't slow, random access is slow — and Kafka's append-only log makes every access sequential, so the disk's weakness never shows up. Two: Kafka hands memory management to the OS page cache instead of hoarding it in the JVM, which keeps it lean and keeps the cache warm even across restarts. And three: zero-copy sends data straight from page cache to the network card, no CPU, no wasted copies. Underneath all of it is that one elegant bet — the append-only log — and everything else is just that bet cashing in. Next time, we'll get into partitions, replication, and how consumer groups let Kafka scale out sideways.

🇹🇼 中文

每次聽到有人說「Kafka 很快」,我第一個想問的其實是——快,到底是指什麼?

「快」這個字本身很模糊。是延遲低嗎?還是吞吐高?又是跟誰比之下的快?這件事講不清楚,就沒辦法討論 Kafka 的設計為什麼有效。

先給答案:Kafka 優化的目標是高吞吐,不是低延遲。

我很喜歡一個比喻——水管。管徑越粗,單位時間能通過的水就越多。所以當有人說 Kafka 快,通常指的不是單筆訊息跑得多急,而是它能有效率地搬動「大量」資料。這點要先釐清,因為接下來每一個設計決策,目的都是「把管子做粗」,而不是「讓單趟跑得更快」。

Kafka 的高效能來自很多決策,但影響最大的有兩個。今天先聊最核心的第一個:循序 I/O。

這裡要先打破一個常見迷思——「磁碟一定比記憶體慢」。其實這很大程度取決於你怎麼存取資料。

磁碟存取分兩種:隨機,跟循序。拿傳統機械硬碟來說,資料存在旋轉的碟片上,靠一支機械臂移動到不同位置去讀寫。如果你的存取是隨機的,機械臂就得不停地在碟片上跳來跳去、實體移動——這正是隨機存取慢的根本原因。但如果是循序存取,機械臂不用亂跳,一塊接著一塊往下走,速度就快得多。

Kafka 就是抓住了這個特性。它的做法,是把 append-only log 當成主要的資料結構。

append-only log 的規則超級單純:新資料一律加到檔案的尾端。既然只往後追加,不回頭插入、也不改寫,這個存取模式天生就是循序的——機械臂永遠朝同一個方向走。換句話說,Kafka 不是靠更快的硬體贏,而是靠選對資料結構,把磁碟存取「導向」它最擅長的循序模式。

那循序跟隨機的差距到底多大?在一組現代硬碟陣列上,循序寫入可以做到每秒好幾百 MB;而隨機寫入,只有每秒幾百 KB 的等級。差了好幾個數量級。這也是為什麼「磁碟一定比記憶體慢」這句話,在正確的存取模式下,根本不成立。

用 HDD 還有第二個好處,是成本。跟 SSD 比,機械硬碟大概只要三分之一的價格,卻能給你大約三倍的容量。這等於給了 Kafka 一大片便宜的磁碟空間,而且因為存取是循序的,用起來還不用付效能代價。結果就是——Kafka 可以用很低的成本,把訊息長期保留,放個好幾天甚至更久。

這在 Kafka 出現之前,其實是訊息系統很少見的能力。多數傳統訊息佇列都假設:訊息被消費完就該刪掉。而 Kafka 反過來,把「保留」當成一等公民。這份設計上的自由,正是便宜的循序儲存換來的。

所以總結一下,today 的三個重點。

第一,先定義快。Kafka 優化的是吞吐,不是延遲,它是一根粗水管,不是一支快箭。

第二,循序 I/O。磁碟慢的是隨機存取,循序存取可以快上好幾個數量級,而 append-only log 讓存取模式天生就是循序的。

第三,便宜的 HDD。低成本、大容量、又沒有效能懲罰,讓「長期保留訊息」這件事變得划算。

至於 Kafka 高效能的第二個關鍵設計,我們留到這個系列的下一集再好好拆解。

Tags

Related Articles

RAG's Five Stages: From Pipeline to Reasoning Retrieval, and the Naive RAG on My Own Site

Over the past two years RAG evolved from a 'linear pipeline' to 'loop-based reasoning'. It maps cleanly to five stages: Naive, Advanced, Modular, Graph, Agentic. The real inflection point is control moving from pipeline to agent — a System 1 → System 2 shift. Looking back at engineer-news's own RAG stack, it's stuck at the Naive edge — so this post also lays out what to fix next.

Building a Real RAG: 5 Infra Lessons from InfiniFlow's 2024 Year-in-Review

The previous post zoomed out for a five-stage panorama of RAG. This one zooms in on the five infra lessons any real RAG has to face: document ingestion, contextualized chunking, three-lane hybrid search, tensor reranker, and GraphRAG's semantic gap. Each lesson is checked against engineer-news's current stack, ending with a priority list for a personal site.