백엔드 데이터 인프라 84편 — Kafka Design: Persistence (디스크가 빠르다)

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 84편. Kafka Design Persistence — *Don't fear the filesystem!*. Sequential I/O 가 random memory access 보다 빠를 수 있는 이유, page cache 활용, BTree O(log N) 대신 append-only log O(1) 의 의미, 디스크 1급 설계 철학을 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 84편 — Kafka Design: Persistence (디스크가 빠르다)

이 글은 백엔드 데이터 인프라 시리즈 130편 중 84편이에요. 83편 에서 Kafka 의 설계 동인 5가지를 잡았다면, 이번 84편은 그 중 high throughput + large backlog 의 핵심인 Persistence 입니다. Kafka 가 디스크를 1급으로 쓰는 결정이 어째서 빠른가, 그 비밀을 풀어봅니다.

Persistence가 어렵게 느껴지는 이유

처음 듣는 사람의 직관은 "디스크는 메모리보다 1,000배 느리다" 입니다. 그러니 Kafka 가 디스크를 1급으로 쓴다고 하면 "느린 거 아닌가?" 하는 의문이 따라오죠.

답은 두 가지 사실에 있습니다.

첫째, 디스크 성능은 sequential 인가 random 인가에 따라 6,000배 차이가 납니다. random 은 ms 단위지만 sequential수백 MB/sec 가 나오고, 이건 메모리 random access 와 비슷한 수준이에요.

둘째, modern OS 가 page cache (OS 가 디스크 데이터를 메모리에 자동 캐싱) 로 free 메모리를 자동 활용합니다. 그래서 디스크 + page cache 조합이 사실상 memory-mapped 처럼 동작하죠.

이 두 사실 위에 Kafka 가 어떻게 설계됐는지를 따라가면 "Don't fear the filesystem!" 의 의미가 보입니다.

디스크 성능의 진실

Sequential vs Random — 6,000배 차이

공식 문서의 인용 데이터:

  • JBOD (여러 디스크를 묶지 않고 그대로 노출) with 7200rpm SATA (PC·서버용 표준 디스크 인터페이스) RAID-5 (패리티 분산 저장 RAID 방식)
  • Sequential write — ~600 MB/sec
  • Random write — ~100 KB/sec

6,000배 차이. 즉 sequential 패턴만 잘 따르면 디스크가 놀랄 만큼 빠르다는 얘기예요.

Modern OS Page Cache

OS 는 free 메모리를 모두 page cache 로 활용합니다. 디스크 읽기·쓰기는 다 page cache 를 경유하죠.

  • 메모리에 이미 있는 데이터 = 메모리 속도
  • 메모리에 없는 데이터 = 디스크 + 메모리에 캐시
  • Read-ahead·write-behind연속 패턴 자동 최적화

결과적으로 32GB 머신이라면 free 메모리 28~30GB 가 모두 디스크 cache 가 됩니다. 서비스를 재시작해도 warm 캐시가 유지된다는 게 큰 장점이에요.

JVM 의 memory overhead 함정

여기서 시험 함정 하나. 왜 Kafka 는 자체 in-process 캐시를 안 만들고 OS pagecache 에 의존할까요?

JVM (Java Virtual Machine, 자바 실행 환경) 의 두 가지 문제 때문입니다.

  1. 객체 메모리 overhead — Java 객체는 원본 데이터의 2배 이상 메모리를 씁니다 (object header·padding 등)
  2. GC 부담 — heap 안 데이터가 늘면 GC stop-the-world 가 길어져서, 32GB heap 이면 수 초의 GC pause 가 나올 수 있어요

따라서:

  • in-process cache 10GB = JVM 메모리 20GB+ + GC 부담 큼
  • OS pagecache = byte-level 저장, GC 없음
  • 같은 32GB 머신 = OS pagecache 가 압도적

Kafka 의 설계 — 즉시 디스크

Kafka 의 결정은 이렇습니다.

"All data is immediately written to a persistent log on the filesystem without necessarily flushing to disk. In effect this just means that it is transferred into the kernel's pagecache."

핵심:

  • 메시지 도착 → 즉시 디스크에 write (정확히는 pagecache 에 write)
  • fsync (메모리 버퍼를 디스크에 강제 flush) 강제 X (옵션) — pagecache 가 나중에 디스크에 flush
  • consumer 가 읽을 때도 page cache 활용 → 메모리 속도

이게 바로 page cache-centric design 입니다. 애플리케이션이 자체 캐시를 만들지 않고 OS pagecache 를 그대로 사용하죠.

한 줄 정리 — Kafka 의 속도 비밀 = sequential 디스크 I/O + OS page cache + JVM heap 회피.

O(1) 자료구조 — Append-Only Log

전통 메시지 큐의 BTree

전통적 메시지 큐 (RabbitMQ·ActiveMQ 등) 는 per-consumer queue 에 BTree (정렬 키 기반 균형 트리 인덱스) 또는 random access 자료구조를 씁니다.

문제는 다음과 같아요.

  • BTree 연산 = O(log N)
  • 일반적 O(log N) 은 "사실상 constant" 라고 하지만 디스크에서는 다릅니다
  • 디스크 seek = 10 ms, parallelism 도 제한적
  • data 가 늘수록 cache miss 비율이 커져 성능이 super-linear 로 떨어집니다

Kafka 의 단순한 선택

"a persistent queue could be built on simple reads and appends to files"

핵심 결정은 모든 연산이 O(1) 이라는 점. reads do not block writes.

Topic Partition = 한 개의 파일 (또는 segment 들의 sequence)

Producer:  → append (O(1))
Consumer:  → 자기 offset 부터 sequential read (O(1) + sequential)

Data size 와 성능 분리 — 1GB partition 과 1TB partition 의 append/read 속도가 똑같음. 거대 데이터 보존이 비용 없이 가능.

결과적 이점 — 메시지 큐 + 영구 보존

이 설계가 가져오는 직접적인 결과는 다음과 같습니다.

(1) 매우 긴 retention 가능

"in Kafka, instead of attempting to delete messages as soon as they are consumed, we can retain messages for a relatively long period (say a week)."

전통 큐는 consume 되는 즉시 delete 합니다 (성능 유지 때문). Kafka 는 consume 후에도 그대로 보존하고, 일주일·한 달·infinite 까지 잡을 수 있어요.

(2) Consumer 가 여러 명 + 독립

각 consumer 는 자기 offset 만 관리하면 됩니다. 다른 consumer 에 영향을 주지 않으니까 real-time, batch, ML 학습용 과거 데이터 처리를 모두 같은 데이터 위에서 돌릴 수 있죠.

(3) 재처리 자연스러움

Consumer 가 offset 을 되감기만 하면 과거 메시지를 다시 읽을 수 있습니다. 버그 수정 후 재처리, 새 분석 시스템 도입 후 과거 데이터 학습 같은 시나리오가 자연스럽게 풀려요.

(4) 매우 큰 데이터 보존

SATA 7200rpm 1+TB 디스크는 seek 이 느려도 sequential 은 빠릅니다. 그러니 값싼 하드웨어로 거대 데이터를 보존할 수 있어요.

Segment 파일 모델

여기까지 따라오셨다면 한 가지 의문이 들 거예요. "한 partition 이 1TB 면 단일 파일인가?" 답은 segment 로 분할한다는 것입니다.

Topic "payments", Partition 0
├─ 00000000000000000000.log       (첫 segment, 1GB 까지)
├─ 00000000000000050000.log       (다음 segment, offset 50000부터)
├─ 00000000000000100000.log       (...)
└─ 00000000000000150000.log       (현재 active segment)
  • 각 segment = 기본 1GB (log.segment.bytes)
  • Active segment (가장 최신) 만 append 받음
  • 오래된 segment 는 retention 만료 시 통째로 삭제됩니다 (한 메시지씩이 아니라 segment 단위라 매우 효율적)

각 segment 옆에는 index 파일 (.index·.timeindex) 이 붙어서 offset → 파일 위치 lookup 을 빠르게 잡아줍니다.

00000000000000050000.log
00000000000000050000.index
00000000000000050000.timeindex

그래서 정말 빠르나? — 벤치마크

LinkedIn 의 Kafka 발표 데이터 (대략):

  • 단일 broker = 초당 수십~수백 MB 처리
  • 3 broker cluster = 초당 GB 단위
  • 지연 시간 = 수 ms (producer ack 옵션에 따라)

전통 메시지 큐 대비 10~100배 처리량이 나오고, 데이터베이스와는 비교가 어렵습니다 (DB 가 그만큼의 처리량을 노리지 않으니까요).

한계 — 디스크 1급의 비용

여기서 중요한 자리. 디스크 1급이 만능은 아닙니다.

  • 디스크 비용 — 거대 retention 은 거대 디스크를 요구하고, SSD 면 비용이 확 뜁니다
  • 디스크 장애 모니터링 — broker 마다 디스크 health 를 추적해야 합니다
  • Compaction 비용 (107편) — log compaction 은 디스크 I/O 를 추가로 씁니다
  • Tiered Storage (106편) — 오래된 segment 를 S3 로 옮기는 옵션 (Kafka 3.6+)

시험 직전 한 번 더 — Kafka Persistence 함정 압축 노트

  • Don't fear the filesystem! — Kafka 의 핵심 철학
  • Sequential vs Random — 6,000배 차이 (600 MB/sec vs 100 KB/sec)
  • Sequential 패턴 = 디스크 수백 MB/sec (메모리 random access 와 비슷)
  • Modern OS Page Cache — free 메모리 자동 활용, 32GB 머신의 28~30GB
  • Read-ahead·write-behind 로 sequential 자동 최적화
  • 서비스 재시작 후 = warm 캐시 유지 (in-process cache 와 다름)
  • JVM 회피 이유 = 객체 overhead (데이터 2배+) + GC stop-the-world
  • in-process 10GB cache = JVM 20GB+ + 수 초 GC pause
  • 즉시 디스크 (pagecache) — fsync 강제 X
  • 애플리케이션 자체 캐시 만들지 않고 OS pagecache 그대로 활용
  • 전통 메시지 큐 = BTree O(log N) — 디스크 seek 때문에 사실상 더 큼
  • Kafka = append-only log O(1) — data 크기 무관 동일 속도
  • 결과 1 = 매우 긴 retention (consume 후도 보존, 며칠~infinite)
  • 결과 2 = 여러 consumer 독립 (각자 offset 만)
  • 결과 3 = 재처리 자연스러움 (offset 되감기)
  • 결과 4 = 거대 데이터 보존 (SATA 디스크로도 OK)
  • Segment 파일 — partition 을 1GB 단위 segment 로 분할
  • Active segment 만 append, 오래된 segment 통째 삭제
  • 각 segment 옆 = .index·.timeindex 빠른 lookup
  • 벤치마크 = 단일 broker 수십~수백 MB/sec, 3 broker GB/sec
  • 한계 = 디스크 비용·디스크 장애 모니터링·compaction I/O
  • Tiered Storage (Kafka 3.6+) = 오래된 segment 를 S3 로

공식 문서: Kafka Design — Persistence 에서 자세한 설계를 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!