Kafka / MyKafka 라이프사이클 흐름도

레코드의 일생 · 세그먼트의 일생 · 오프셋의 일생

Producer 단계 Broker 단계 Consumer 단계 실 Kafka / MyKafka 차이 표시

① 레코드(메시지)의 일생

한 메시지가 produce되어 디스크에 영속되고, fetch·소비·커밋되기까지의 전체 흐름.

Producer
1 메시지 생성 & batch 적재
key로 partition 결정 후, 같은 partition끼리 batch로 모음.
key → murmur2 % NcontentHashCode % N
linger.ms / batch.size 동안 모음 → 압축 → CRC 계산(Kafka)
2 네트워크 전송 (length-prefix frame)
[4 len][1 apiKey][payload] — TCP 바이트 스트림에 메시지 경계 부여.
leader broker 도착 (Kafka는 partition leader)
Broker
3 offset 부여 (broker가 결정)
클라이언트가 못 정함. nextOffset → 같은 partition 동시 쓰기 충돌 방지.
4 인코딩 + CRC 부착
RecordCodec.encode → 끝에 무결성 체크섬.
batch별 CRC32C (offset 제외)record별 CRC32
partition당 락 (lock.withLock)
5 Segment에 append-only write
N record를 메모리에서 합쳐 1 syscall로 순차 쓰기 → 61× speedup의 정체.
6 Sparse 인덱스 갱신
4KB 간격으로만 (relOffset, filePos) 기록. mmap.
⑥' ISR 복제 + ack Kafka
follower가 leader에서 fetch해 복제. acks=all이면 모든 ISR 수신 후 ack. MyKafka는 단일 노드 → 없음
💾 디스크에 영속 — OS page cache 상주
consumer가 자기 offset부터 요청
Consumer
7 FETCH (offset, maxBytes)
데이터 없을 때 —
long-poll로 대기즉시 빈 응답(busy-poll)
8 위치 탐색 → 읽기
파일명(baseOffset) 이진 탐색 → sparse index lookup → 순차 scan으로 정확한 offset. cross-segment 자동, 첫 record는 maxBytes 초과해도 무조건 포함(진행 보장).
전송 단계 — 가장 큰 갈림길
9 전송
zero-copy sendfile (page cache → socket 직송)MyKafka: 재직렬화 byte copy
10 CRC 검증 → 처리
batch/record CRC 재계산·비교 → 일치하면 application 처리. 불일치면 corruption.
11 Offset commit
"여기까지 읽음"을 __consumer_offsets에 기록.
50 partition + compacted1 partition
다음 poll은 commit된 offset+1부터 → 1번으로 루프백. 커밋 전에 consumer가 죽으면 다음 consumer가 같은 메시지를 다시 받음(at-least-once) → idempotent 처리 필요.

② 세그먼트(.log + .index)의 일생

파티션은 여러 segment로 구성된다. 각 segment의 생성 → 활성 → 읽기전용 → 삭제 상태 전이.

생성
파일명 = %020d(baseOffset). .log + .index 한 쌍.
append 가능
ACTIVE
유일하게 쓰기 가능한 segment. append마다 sizeBytes↑, sparse index 갱신.
sizeBytes ≥
maxSegment
(1MB)?
READ-ONLY
maybeRoll로 새 ACTIVE 생성 → 이전 것은 읽기전용. mmap 최적화 유리.
retention 만료
DELETE
파일 통째 unlink → 삭제 O(1). (compaction은 별도 정리)

※ diamond에서 아니오면 ACTIVE 유지(계속 append), 면 roll.

⟲ 재시작 시 복구 (꼬리 자르기 recovery)
  1. loadSegments() — 디렉토리의 *.log를 baseOffset 순 정렬
  2. 각 segment rebuildIndex(truncateTail = isActive).log 전체 scan으로 인덱스 재구축 (인덱스가 0으로 깨져도 복구 가능)
  3. 활성 segment는 CRC/디코드 실패하는 부분 쓰기된 trailing record를 잘라냄 (전원 손실 대비)
  4. nextOffset = 마지막 정상 record offset + 1

③ 컨슈머 오프셋의 일생

"오프셋도 그냥 또 다른 로그" — 별도 저장소 없이 내부 토픽 append로 관리.

미커밋
fetch_offset = -1. 처음부터(또는 정책대로) 읽기 시작.
처리 중
record consume → application 처리. 아직 commit 안 함.
COMMIT
__consumer_offsets에 append + cache 갱신. 단 두 줄.
재시작
내부 토픽 scan → cache 재구축 (loaded N commits, M unique keys).
이어 읽기
마지막 commit offset+1부터 정확히 재개.
같은 (group, topic, partition) key로 반복 commit되면 옛 record는 garbage. log compaction 도입 시 최신값만 살아남음(MyKafka는 의미만 존재, 미구현). manual commit 권장 — auto-commit은 처리 전 commit되어 데이터 손실 위험.

출처: msa/kafka.md §2·§3·§5 + CRC 토론 정리 · 2026-05-26
함께 보기: msa/kafka-summary.html (CRC·차이 상세), msa/kafka.md §10 코드 reading map