
Kafka 간보기에 이어서 이번 아티클에서는 이론적인 관점에서 카프카를 탐구하려 한다. 지난 아티클이 구현 리뷰에 초점을 맞췄다면, 이번에는 내부 동작 원리를 바탕으로 카프카에 대한 이해를 한 단계 더 높이는 게 목표다.
Kafka를 언제 어떻게 사용하냐면...
왜 Kafka인가?
가장 먼저 이해해야 할 것은 디커플링이다. 서비스가 늘어날수록 시스템은 복잡해지기 마련이다.

스파게티 아키텍처 (문제점): 각 서비스가 서로 직접 연결(Point-to-Point)되어 있어, 하나만 수정해도 전체 시스템에 영향을 전파한다.

Kafka 도입 후 (해결책): Kafka를 이벤트 버스로 활용한다. 발행자(Producer)는 데이터를 던지기만 하고, 구독자(Consumer)는 필요한 데이터만 가져간다. 서로의 존재를 몰라도 되므로 확장성과 유지보수성에 이점이 존재한다.
Kafka 핵심 역할

Kafka는 단순한 메시지 큐를 넘어 데이터 허브 역할을 수행한다.
데이터 허브 구조
- Producers: 모바일 앱, 웹 서버, IoT 기기 등에서 발생하는 로그와 이벤트를 생산한다.
- Kafka Cluster: 수천 개의 이벤트를 순서대로 저장하고 관리하는 중앙 저장소다.
- Consumers: Spark Streaming, Hadoop, 데이터 웨어하우스 등이 필요한 시점에 데이터를 가져가 처리한다.
대표적으로 Kafka는 다음과 같은 시나리오들에서 활용하기 적합하다.
① 로그 통합 파이프라인 (Log Aggregation)

수많은 웹 서버의 로그를 한 곳으로 모아 분석할 때 사용한다.
② 실시간 분석 및 CDC (Real-time Analytics)

데이터베이스의 변경 사항을 실시간으로 감지(CDC, Change Data Capture)하여 이벤트를 발행할 때 사용한다.
③ CQRS & 이벤트 소싱

명령(Command)과 조회(Query)를 분리하여 시스템 성능을 최적화한다.
Kafka는 모든 이벤트를 저장하는 Event Store 역할을 하며 이를 통해 읽기 전용 DB를 최신 상태로 유지한다.
Kafka 내부 구조 (브로커, 파티션, 오프셋)
브로커와 클러스터

Kafka는 여러 대의 서버로 구성된 클러스터로 운영된다. 각 서버를 브로커라고 부르며 이들은 데이터를 분산 저장하여 높은 가용성을 보장한다.
- Zookeeper / KRaft: 클러스터의 메타데이터를 관리하고 브로커의 상태를 감시하는 '컨트롤 타워' 역할을 한다. (최근에는 Zookeeper 의존성을 제거한 KRaft 모드가 Modern 한 방식이다.)
💡Zookeeper의 고질적인 문제
- 카프카 브로커의 상태를 Zookeeper에도 기록하고, 카프카 내부 컨트롤러도 알고 있어야 했다. 이로 인해 두 시스템 간의 데이터 동기화 과정에서 불일치가 발생할 위험이 컸다.
- 파티션 수가 늘어나면 Zookeeper가 감당해야 할 메타데이터 양이 그만큼 증가한다. 이로 인해 전체 클러스터의 성능이 저하되는 현상이 발생했다.
파티션 (병렬 처리의 핵심)
토픽(Topic)은 여러 개의 파티션으로 나뉜다. 이미지에서 보이듯 Topic A가 Partition 0과 1로 나뉘어 서로 다른 브로커에 저장된 것을 볼 수 있다.
- 병렬성: 파티션을 여러 개 두면 여러 컨슈머가 동시에 데이터를 읽을 수 있어 처리량이 증가한다.
- 순서 보장: Kafka는 동일 파티션 내에서는 메시지 순서를 보장하지만, 파티션 간의 순서는 보장하지 않는다.
리플리케이션 (Leader와 Follower)
데이터 유실을 막기 위해 파티션은 여러 브로커에 복제된다.
- Leader: 모든 읽기와 쓰기 요청을 처리한다. (다이어그램의 Leader 표기 참고)
- Follower: Leader의 데이터를 실시간으로 복제하여 대기한다. Leader 브로커가 장애를 일으키면 ISR(In-Sync Replicas) 그룹에 속한 Follower 중 하나가 새로운 Leader로 선출된다.
오프셋 (파티션 내 메시지 순번)
다이어그램의 [0][1][2]... 표시가 바로 오프셋이다.
- 불변성: 파티션에 들어온 메시지는 고유한 번호(오프셋)를 부여받으며 절대 변하지 않는다.
- Consumer Offset: 컨슈머는 자신이 어디까지 읽었는지 오프셋을 기록한다. 덕분에 컨슈머가 잠시 중단되어도 마지막으로 읽었던 지점부터 다시 시작할 수 있다.
Kafka 파티션 구조 (세그먼트)
파티션은 하나의 거대한 파일이 아니다

대부분의 데이터베이스와 달리 Kafka는 파티션을 하나의 파일로 관리하지 않는다. 파티션은 여러 개의 세그먼트 단위로 쪼개져 저장된다.
- 물리적 디렉토리: 브로커의 파일 시스템에는 각 파티션 이름으로 된 디렉토리가 생성된다.
- 파일 분할: 그 안에 .log 파일들이 존재하며, 파일명은 해당 세그먼트의 시작 오프셋을 의미한다 (예: 000...1000.log).
- Active Segment: 현재 프로듀서가 데이터를 쓰고 있는 유일한 세그먼트를 말한다. 이 파일이 설정된 크기(기본 1GB)에 도달하면 닫히고 새로운 세그먼트가 생성된다.
세그먼트 내부 구조

세그먼트는 단순히 데이터만 저장하는 것이 아니라, 빠른 검색을 위해 인덱스 파일들과 함께 움직인다.
① .log file (실제 메시지 데이터)
- Append-only: 메시지가 들어오는 순서대로 뒤에 붙이기만 한다.
- Sequential I/O: 디스크 헤더가 움직일 필요가 거의 없어, SSD뿐만 아니라 HDD에서도 빠른 쓰기 속도를 보장한다.
② .index file (오프셋 인덱스)
- 역할: 특정 오프셋이 .log 파일의 어느 위치에 있는지 저장한다.
- 효율성: 모든 오프셋을 기록하지 않고 'Sparse Index(희소 인덱스)' 방식을 사용하여 메모리 효율을 극대화한다. 특정 오프셋을 찾을 때 이 파일을 보고 .log 파일로 이동한다.
③ .timeindex file (타임스탬프 인덱스)
- 역할: "어제 오후 2시 데이터부터 다시 읽고 싶어"라는 요청을 처리하기 위해 타임스탬프와 오프셋을 매핑해 둔다.
왜 이렇게 관리하는가?
| 장점 | 설명 |
| 빠른 읽기/쓰기 | 순차 I/O와 인덱스 파일을 통한 이진 탐색으로 O(log N) 수준의 빠른 검색 가능 |
| 데이터 삭제 효율 | 오래된 데이터를 지울 때 파일 전체를 지우는 것이 아니라, 닫힌 세그먼트 단위로 파일 자체를 삭제하여 시스템 부하를 최소화 |
| Zero-copy | 세그먼트는 커널 레벨의 Page Cache를 활용하므로, 데이터를 유저 공간으로 복사하지 않고 바로 네트워크로 전송 가능 |
핵심 포인트
Q: Kafka에서 특정 오프셋의 데이터를 어떻게 찾을 수 있을까?
A:
1. 먼저 파티션 내의 여러 세그먼트 파일명을 확인하여 해당 오프셋이 포함된 파일을 찾는다.
2. 해당 세그먼트의 .index 파일에서 목표 오프셋과 가장 가까운 물리적 위치를 알아낸다.
3. 마지막으로 .log 파일의 해당 위치로 이동하여 순차적으로 데이터를 읽어 타겟 오프셋을 찾아낸다.
Tip: Kafka는 메모리를 아예 안 쓰는 게 아니라, 운영체제의 페이지 캐시(Page Cache)를 활용하여 디스크 기반 저장소의 한계를 극복한다.
안정성을 위해 고려해야 되는 부분(리더 선출, In-Sync Replicas, Controller)

Kafka 클러스터 내의 각 파티션은 Leader와 Follower로 구성된다.
- Leader (Broker 1, 2): 모든 데이터의 읽기/쓰기를 도맡는 역할이다.
- Follower (Broker 3): 데이터를 실시간으로 복제(Replication)하며 만약의 사태를 대비하는 역할이다.
- Controller: 브로커 중 한 대가 '메인(Controller)' 역할을 맡아 클러스터의 상태를 관리한다.
장애 발생과 감지 (Heartbeat Lost)
갑자기 Broker 2가 네트워크 장애나 하드웨어 이슈로 다운되었다고 가정해 보자.
- Heartbeat 중단: 브로커는 평소 Zookeeper(또는 KRaft)와 주기적으로 신호를 주고받는다. Broker 2의 신호가 끊기면 시스템은 "장애"로 판단한다.
- 장애 감지: Zookeeper 혹은 KRaft가 이 사실을 가장 먼저 알아차리고 클러스터의 '메인'인 Controller에게 긴급 전파한다.
리더 선출
이제 Broker 2가 관리하던 TopicA-P1 파티션의 리더 자리가 공석이 되었다.
- Controller의 개입: 알림을 받은 Controller는 즉시 리더 선출(Leader Election) 프로세스를 가동한다.
- ISR (In-Sync Replicas) 확인: 데이터가 최신 상태로 잘 복제되어 있던 Follower들(ISR 그룹) 중 하나를 고른다.
- 승격: 다이어그램에서는 Broker 3에 있던 Follower 파티션이 새로운 Leader로 승격된다.
복구 완료, 중단 없는 서비스
새로운 리더가 선출되면 클라이언트(Producer/Consumer)는 메타데이터를 갱신하고, 이제부터는 Broker 3을 통해 데이터를 주고받는다. 이 모든 과정이 불과 수 밀리초(ms) 내외로 이루어지기 때문에 사용자는 장애를 거의 느끼지 못한다.
핵심 요약
| 단계 | 핵심 동작 | 담당 주체 |
| 감지 | 세션 타임아웃 및 하트비트 소멸 확인 | Zookeeper / KRaft |
| 통지 | 브로커 장애 사실을 컨트롤러에게 알림 | Zookeeper / KRaft |
| 선출 | ISR 그룹 내에서 최적의 후계자(Follower) 선택 | Controller |
| 전파 | 새로운 리더 정보를 클러스터 전체에 공유 | Controller |
핵심은 ISR(In-Sync Replicas)이다. 복제가 뒤처진 Follower를 리더로 세우면 데이터 유실이 발생할 수 있기 때문에, Kafka는 평소에 복제 상태를 엄격히 관리하여 가용성과 일관성의 균형을 잡는다.
Kafka 프로듀서 (멱등성, 트랜잭션, 최적화)
멱등성

네트워크 오류로 인해 프로듀서가 ACK를 받지 못하면 프로듀서는 메시지 전송이 실패한 줄 알고 다시 보낸다. 이때 브로커에 데이터가 중복 저장되는 문제가 발생할 수 있는데 내부적으로 다음과 같이 해결한다.
- 프로듀서에 고유한 PID(Producer ID)를 부여하고, 메시지마다 Sequence Number를 매긴다.
- 브로커는 메모리에 PID별 마지막 시퀀스 번호를 기록해 둔다. 이미 처리된 번호가 들어오면 "Already processed! Discard message."라며 저장하지 않고 ACK만 다시 보낸다.
- 설정: enable.idempotence=true (최신 버전은 기본값)
프로듀서 최적화 (배치, 압축)
매 메시지마다 네트워크 요청을 보내는 것은 비효율적이다. Kafka는 '모아서 한 번에' 보내는 전략을 사용한다.
① 배치 전송

프로듀서 내부의 Record Accumulator가 메시지를 모은다.
전송 트리거 조건은 두 가지다.
- batch.size: 버퍼에 쌓인 데이터 크기가 설정치에 도달했을 때.
- linger.ms: 데이터가 다 안 찼더라도, 정해진 시간이 지났을 때.
② 압축

배치가 형성되면 Compressor를 거쳐 압축된다.
- 네트워크 대역폭 절약 및 디스크 I/O 감소. CPU 자원을 조금 더 쓰더라도 전체 시스템 처리량을 비약적으로 높인다.
트랜잭션

여러 토픽이나 파티션에 데이터를 보낼 때 원자성을 보장해야 하는 경우가 있다 (예: 주문 처리와 재고 차감).
- 트랜잭션이 Commit 되어야만 컨슈머(Read Committed 모드)가 데이터를 읽을 수 있다. 만약 중간에 에러가 발생해 Abort 되면 브로커에 저장된 데이터는 무시된다. 이를 통해 Exactly-once Semantics (EOS)를 구현할 수 있다.
핵심 요약
| 기능 | 핵심 키워드 | 포인트 |
| 멱등성 | PID, Sequence Number | 네트워크 재시도로 인한 중복 저장을 브로커 단에서 필터링 |
| 배치 전송 | batch.size, linger.ms | 처리량과 지연 시간 사이의 트레이드오프를 조절 |
| 압축 | Snappy, LZ4, Zstd | 네트워크 부하를 줄여 전체적인 시스템 효율을 극대화 |
| 트랜잭션 | Atomic Write, Transaction Coordinator | 여러 파티션에 대한 쓰기 작업을 논리적인 하나의 단위로 묶어 원자성을 보장 |
Kafka 컨슈머 (리밸런싱, 오프셋 커밋)
리밸런싱 (컨슈머 그룹 동적 관리)

컨슈머 그룹 내에 새로운 컨슈머가 합류하거나 기존 컨슈머가 이탈할 때, 파티션 소유권을 재조정하는 과정을 리밸런싱이라고 한다.
동작 과정:
- 감지: 그룹 코디네이터(브로커)가 멤버 변화를 감지한다.
- 리더 선정: 컨슈머 중 하나가 리더(Leader)로 선출된다.
- 전략 수립: 리더 컨슈머가 파티션 할당 계획을 세운다.
- 동기화: 코디네이터가 이 계획을 모든 컨슈머에게 전달하여 소유권을 확정한다.
주의점: 리밸런싱이 일어나는 동안 컨슈머 그룹 전체가 일시적으로 멈출 수 있으므로(Stop-the-world), 빈번한 리밸런싱은 성능 저하의 원인이 된다.
컨슈머 생존 관리

브로커는 컨슈머가 정상인지 두 가지 기준으로 판단한다.
아래 설정값이 적절하지 않으면 불필요한 리밸런싱이 반복됩니다.
- session.timeout.ms (하트비트): 컨슈머가 살아있음을 알리는 신호다. 네트워크 장애나 컨슈머 프로세스 자체가 죽었을 때 이를 감지한다.
- max.poll.interval.ms (처리 시간): poll()을 호출한 뒤 다음 poll()까지의 간격이다. 메시지 처리가 너무 오래 걸리면(Logic Stuck), 브로커는 해당 컨슈머가 문제가 있다고 판단해 리밸런싱을 시작한다.
오프셋 커밋 전략 (정합성과 성능의 트레이드오프)

컨슈머가 어디까지 읽었는지 기록하는 커밋은 데이터 유실과 중복 처리에 직접적인 영향을 미친다.
① 자동 커밋
- 특징: enable.auto.commit=true. 일정 간격마다 자동으로 오프셋을 기록한다.
- 단점: 이미지에서 보듯, 처리가 끝나기도 전에 커밋이 먼저 발생할 수 있다. 이때 앱이 크래시 나면 데이터가 유실(Data Loss)되는 치명적인 상황이 발생한다.
② 수동 커밋
- 특징: enable.auto.commit=false. 비즈니스 로직이 완전히 끝난 뒤 직접 commitSync()를 호출한다.
- 장점: 처리가 완료된 것만 커밋하므로 데이터 유실이 없다. (안전함)
- 단점: 브로커로부터 ACK를 받을 때까지 대기하므로 처리량이 약간 낮아질 수 있다.
핵심 요약
| 핵심 키워드 | 포인트 |
| 리밸런싱 | 파티션 소유권을 재분배하는 과정이며, 코디네이터와 리더 컨슈머가 협력하여 수행 |
| Timeout 설정 | 가용성을 위해 session.timeout을, 비즈니스 로직 안정성을 위해 max.poll.interval을 튜닝 |
| 자동 커밋의 단점 | 메시지 처리 완료 여부와 상관없이 커밋되므로 데이터 유실 가능성 |
| 수동 커밋의 장점 | 적어도 한 번(At-least-once) 전달을 보장하기 위한 필수 전략 |
실무에서는 재시도 로직이나 순서 보장이 중요하다면 commitSync와 적절한 예외 처리를 조합하는 것이 가장 견고하다.
출처
카카오 면접관이 알려주며 가장 쉽게 배우는 Kafka| Hong - 인프런 강의
현재 평점 4.8점 수강생 632명인 강의를 만나보세요. Spring Boot 3.x + Kotlin으로 Kafka 클러스터를 구축하고, PostgreSQL CDC와 Debezium Connect, Apache Avro 스키마를 활용하여 실시간 이벤트 스트리밍이 가능한
www.inflearn.com
https://devocean.sk.com/community/detail.do?ID=165478&boardType=DEVOCEAN_STUDY&page=1
[Kafka KRU] Consumer 내부 동작 원리와 구현
devocean.sk.com
https://blog.naver.com/adamdoha/222183734423
[Kafka] 기본 개념 및 생태계
서론 이 포스팅은 데브원영님의 Youtube 영상을 듣고 내용을 정리한 것입니다. Before Kafka ➡️ 엔드...
blog.naver.com
https://tech.kakaopay.com/post/ifkakao2024-delayed-transfer/
지연이체 서비스 개발기: 은행 점검 시간 끝나면 송금해 드릴게요! (feat. 발표 후기) | 카카오페이
Kafka 기반 지연이체 서비스를 재설계하고 개발한 경험과 if(kakao) 발표 소감을 공유합니다.
tech.kakaopay.com
Kafka 이벤트 전송 최적화: Partitioner와 batch 설정 이해하기
[kt cloud 플랫폼Innovation팀 오준영] Kafka 이벤트 전송 최적화: Partitioner와 batch 설정 이해하기 Kafka에서 Producer는 기본적으로 Sticky Partitioner를 사용하여, 이벤트를 batch 단위로 묶어 효율적으로 전송
tech.ktcloud.com
신입사원 개발 정복기 #1.이벤트 응모를 위한 카프카 메시지큐 적용기
Feel My Code
techblog.lotteon.com
Apache Kafka 간략하게 살펴보기
본 문서는 “카프카 핵심 가이드 (제이펍)” 를 참고하였습니다.
medium.com
https://tech.kakao.com/posts/602
신뢰성 있는 카프카 애플리케이션을 만드는 3가지 방법 / 제3회 Kakao Tech Meet - tech.kakao.com
9월 14일에 진행한 제3회 Kakao Tech Meet의 발표 영상과 발표자 이...
tech.kakao.com
'Dev' 카테고리의 다른 글
| PostgreSQL WAL (Write-Ahead Logging) 데이터 구조 (0) | 2026.05.20 |
|---|---|
| 코빗은 왜 체결 엔진에 Rust를 선택했을까? (feat. 추리) (1) | 2026.04.22 |
| 샤갈!! 저 Temporal Workflow 엔진 도입했어요!! (0) | 2026.04.12 |
| 와 CDC(Change Data Capture)! Debezium 로그 기반 아시는구나! (0) | 2026.04.08 |
| Kafka 간보기 (1) | 2026.04.07 |