Kafka in Action 스터디 발표 정리
카프카는 분산 스트리밍 플랫폼으로 세 가지 기능을 제공한다.
기존의 배치 처리 중심(Data at rest) 시스템에서 실시간 흐름 중심(Data in motion) 시스템으로의 전환을 가능하게 하는 것이 핵심이다.
카프카는 세 가지 전달 보장 수준을 제공한다.
| 수준 | 설명 | 특징 |
|---|---|---|
| At-most-once | 최대 한 번 | 유실 가능, 중복 없음. 속도가 중요한 로그 수집에 사용 |
| At-least-once | 최소 한 번 | 유실 없음, 중복 발생 가능. 대부분 시스템의 기본값, 멱등성 처리 필요 |
| Exactly-once | 정확히 한 번 (조건부) | Idempotent Producer + Transaction 지원 시 가능 |
카프카의 핵심 저장 구조다.
병렬 처리와 순서 보장의 단위다.
Kafka 3.3부터 Production-ready, Kafka 4.0에서는 ZooKeeper 모드가 완전 제거됐다.
별도 코디네이터 없이 자체적으로 메타데이터를 관리한다.
카프카는 JVM 힙 메모리 대신 OS의 페이지 캐시를 적극 사용한다.
기존 방식(read/write)은 커널 ↔ 유저 공간 간 불필요한 데이터 복사가 발생한다.
카프카는 리눅스의 sendfile() / Java NIO transferTo()를 사용해 커널 영역에서 NIC 버퍼로 직접 전송한다.
기존: Disk → 커널 → 유저 → 커널 → NIC (4회 복사)
Zero-copy: Disk → 커널 → NIC (2회로 단축)
CPU 사용량과 컨텍스트 스위칭을 줄여, 디스크 속도가 아닌 네트워크 대역폭이 병목이 되는 수준까지 처리량을 높일 수 있다.
⚠️ SSL/TLS 암호화를 적용하면 데이터가 유저 공간을 거쳐야 하므로 Zero-copy를 사용할 수 없다.
Producer (이벤트 생성)
└─ Partitioner
├─ Key 있음: 해시값으로 파티션 결정
└─ Key 없음: 라운드 로빈
Broker Cluster (Topic/Partition 저장)
└─ 각 Partition: Leader(쓰기/읽기) + Follower(복제)
Consumer Group (병렬 처리)
└─ 같은 Key → 같은 Partition → 순서 보장
Exactly-once는 마법이 아니다. 아래 세 가지가 결합되어야 성립하는 조건부 보장이다.
1. Idempotent Producer
Producer ID(PID)와 Sequence Number로 브로커가 중복 전송을 감지하고 제거한다.
네트워크 재시도 시에도 중복이 발생하지 않는다.
enable.idempotence=true
acks=all
max.in.flight.requests.per.connection ≤ 5
2. Transactions (2PC)
여러 파티션에 걸친 쓰기 작업을 원자적으로 처리한다.
Transaction Coordinator가 2-Phase Commit 프로토콜로 전체 커밋/롤백을 결정한다.
transactional.id 설정
initTransactions() → commitTransaction()
3. Consumer Isolation
트랜잭션이 성공적으로 커밋된 메시지만 읽는다.
진행 중이거나 실패한(aborted) 트랜잭션의 메시지는 무시한다.
isolation.level = read_committed
(기본값: read_uncommitted)
4. 외부 시스템으로 나가는 경우
카프카 외부(DB, S3 등)로 나가는 경우 카프카만으로는 Exactly-once를 보장할 수 없다.
타겟 시스템의 멱등성 처리나 2PC 지원이 별도로 필요하다.
컨슈머 그룹 멤버 변경 시 파티션 소유권을 재조정하는 과정이다.
이 동안 Stop-the-world(STW) 가 발생해 일시적으로 메시지 처리가 중단된다.
주요 발생 원인: Consumer 추가/제거, 배포(Rolling Update), Heartbeat 실패
전체 중단(Eager) 대신 영향을 받는 파티션만 점진적으로 재할당해 STW 시간을 최소화한다.
| 구분 | Eager (기존) | Cooperative (권장) |
|---|---|---|
| 동작 | 모든 컨슈머가 일단 멈춤 | 필요한 파티션만 이동 |
| 영향 | 전체 처리 중단 | 중단 최소화 |
컨슈머에 고유 ID를 부여해 재시작 시에도 그룹 탈퇴 없이 멤버십을 유지한다.
Rolling Update 시 불필요한 리밸런싱을 방지한다.
group.instance.id = "consumer-host-1"
session.timeout.ms = 30000+
특정 파티션만 Lag가 튄다면 Data Skew나 컨슈머 로직 병목을 의심해야 한다.
파티션 수는 컨슈머의 최대 병렬 처리 개수를 결정한다.
목표 Throughput ÷ 파티션당 처리량 = 최소 파티션 수
예) 100MB/s ÷ 10MB/s = 최소 10개
레코드 크기, 압축률, 네트워크 대역폭, 디스크 IO를 종합적으로 프로파일링해야 정확하다.
파티션은 늘리기는 쉽지만 줄이기는 어려우므로 초기 설계 시 여유 있게 잡는 게 낫다.
retention.ms vs cleanup.policy=compact코드를 거의 작성하지 않고도 데이터를 가져오거나 내보낼 수 있는 프레임워크다.
DB의 변경 사항을 실시간 이벤트 스트림으로 추출한다.
Debezium이 사실상 표준으로 쓰인다.
MySQL Binlog / PostgreSQL WAL / MongoDB Oplog
기존 레거시 DB를 수정하지 않고 MSA 환경으로 데이터를 실시간 동기화하거나, 캐시 갱신 트리거로 활용할 수 있다.
별도 클러스터 없이 Java/Scala 애플리케이션 내에서 동작하는 경량 스트림 처리 라이브러리다.
transactional.id)Kafka Streams를 SQL 문법으로 추상화한 스트리밍 DB다.
CREATE STREAM fraud_alert AS
SELECT * FROM payments WHERE amount > 10000;
대시보드용 실시간 집계나 간단한 ETL 파이프라인 구축에 적합하다.
| 구분 | JSON (Schemaless) | Avro (Schema-based) |
|---|---|---|
| 스키마 관리 | 없음 (데이터에 필드명 포함) | Schema Registry로 중앙 관리 |
| 용량 효율 | 낮음 (필드명 반복) | 높음 (바이너리, 필드명 제외) |
| 타입 안정성 | 약함 (파싱 시점 에러) | 강함 (컴파일/직렬화 시점 검증) |
| 가독성 | 우수 | 낮음 (바이너리라 도구 필요) |
초기에는 JSON이 편할 수 있지만, 서비스 규모가 커지면 Avro + Schema Registry 조합을 권장한다.
Producer와 Consumer 간의 명확한 데이터 계약(Contract)으로 필드 변경으로 인한 장애를 원천 차단할 수 있다.