Interview Prep Zoneby CuriouserLabs

Question 5 of 5 · staff level

What is zero-copy I/O? Explain sendfile and why Kafka is fast.

🎤 Say this first

The naive file→socket path (read then write) costs four copies and four mode switches: disk→page cache (DMA), page cache→user buffer, user buffer→socket buffer, socket buffer→NIC (DMA). The user-space round trip is pure waste when you don't transform the bytes. sendfile() (FileChannel.transferTo) tells the kernel 'move file→socket yourself': with scatter-gather DMA the NIC reads straight from page-cache pages — CPU copies drop to zero, switches to two. Kafka's serving path is exactly this: sequential log reads (readahead-friendly) + page cache as the cache + sendfile to consumers — brokers push GB/s with modest CPUs. mmap is the other zero-copy trick (map instead of copy), used for Kafka's index files.

The full picture

The copy ledger
// Naive proxy loop — 4 copies, 4 user/kernel switches per chunk:
while ((n = fileIn.read(buf)) > 0)      // DMA->pageCache, pageCache->buf
    socketOut.write(buf, 0, n);          // buf->socketBuf, socketBuf->NIC

// Zero-copy — CPU copies: 0 (with SG-DMA), switches: 2:
FileChannel file = FileChannel.open(path);
long sent = file.transferTo(pos, count, socketChannel);   // sendfile(2)

// The catch that defines its use-case: the kernel moves bytes it never
// shows you — no transformation possible. TLS breaks it (must encrypt)
// unless the NIC does kTLS offload; Kafka with TLS loses sendfile.
  • Kafka's full recipe (worth reciting as one breath): append-only sequential writes → OS writeback batches them; consumers read recent data → served from page cache; transport = sendfile → no JVM heap involvement, no GC pressure from payload bytes; ordering by offset → readahead wins. 'Kafka is fast because it cooperates with the OS instead of fighting it.'
  • mmap flavor: map the file, write to memory, kernel handles paging — removes copies for random access patterns (index files, MappedByteBuffer) but page faults become your I/O and error handling gets weird (SIGBUS on truncation) — the trade vs transferTo.
  • Limits: any byte transformation (compression, encryption, protocol framing) forces data through the CPU anyway — then the game becomes doing it once (kTLS, NIC offloads) or not at all (Kafka consumers decompress, broker doesn't).
  • The pattern generalizes: splice/vmsplice, NGINX serving static files, gRPC's slice buffers, NVMe-oF — 'don't copy what you don't touch' is a design principle, not a syscall.

🔄 Likely follow-up questions

  • Why does enabling TLS change Kafka's zero-copy story, and what is kTLS?
  • When does mmap beat transferTo and vice versa?
  • Where else in a typical stack are bytes copied that needn't be? (serialization!)