배치 처리는 분명 강력하지만, 사용자가 결제를 누른 순간 이상 거래를 탐지하거나, 재고가 떨어지는 즉시 알림을 보내야 하는 상황에서는 한계가 분명합니다. 데이터가 발생하는 즉시 흘려보내고 처리하는 스트리밍 파이프라인이 필요합니다. 그 중심에 가장 널리 쓰이는 분산 메시징 플랫폼 Apache Kafka가 있습니다.
이 글은 Kafka를 처음 접하는 엔지니어를 위해 핵심 개념과 토픽 설계, 그리고 첫 파이프라인 구성을 따라가며 설명합니다. 운영 단계에서 만나는 흔한 함정도 함께 다룹니다.
스트리밍이 푸는 문제
전통적인 시스템에서 서비스끼리 직접 통신하면 N개 서비스 사이에 N제곱에 가까운 연결이 생깁니다. 한 서비스가 느려지면 연쇄적으로 전파됩니다. Kafka는 이 사이에 로그 기반 버퍼를 두어 생산자와 소비자를 분리합니다. 생산자는 빠르게 쓰고, 소비자는 자기 속도로 읽습니다.
핵심은 Kafka가 메시지를 큐처럼 소비 후 삭제하지 않고, 설정된 보존 기간 동안 디스크에 순서대로 보관하는 분산 로그라는 점입니다. 덕분에 여러 소비자가 같은 데이터를 독립적으로 읽고, 장애 시 원하는 지점부터 다시 읽을 수 있습니다.
토픽과 파티션 설계
토픽은 메시지의 논리적 분류이고, 파티션은 그 토픽을 물리적으로 쪼갠 병렬 처리 단위입니다. 파티션 수가 곧 최대 병렬 소비자 수를 결정합니다. 처리량 목표가 초당 30만 건이고 컨슈머 한 개가 초당 3만 건을 처리한다면 최소 10개 파티션이 필요합니다.
파티션 키 선택도 중요합니다. 같은 키를 가진 메시지는 항상 같은 파티션으로 가서 순서가 보장됩니다. 사용자 ID를 키로 쓰면 한 사용자의 이벤트 순서는 지켜지지만, 특정 헤비 유저에게 쏠리면 파티션 불균형이 생길 수 있습니다.
프로듀서와 컨슈머 구성
프로듀서에서 가장 중요한 설정은 acks입니다. acks=all로 두면 리더와 모든 동기화 복제본이 기록을 확인한 뒤 응답하므로 데이터 유실을 막지만 지연이 늘어납니다. 컨슈머 측에서는 오프셋 커밋 시점이 핵심입니다.
props.put("acks", "all");
props.put("enable.idempotence", "true");
props.put("max.in.flight.requests.per.connection", 5);
// 컨슈머: 처리 완료 후 수동 커밋으로 at-least-once 보장
props.put("enable.auto.commit", "false");
처리 전에 오프셋을 자동 커밋하면 처리 도중 장애 시 메시지가 유실됩니다. 처리 완료 후 수동 커밋하면 최소 한 번(at-least-once) 전달이 보장되며, 멱등 처리를 더하면 사실상 정확히 한 번 효과를 얻습니다.
운영 시 주의점
가장 흔한 장애는 컨슈머 랙(lag) 폭증입니다. 소비 속도가 생산 속도를 못 따라가면 랙이 쌓이고, 보존 기간을 넘기면 미처리 메시지가 삭제됩니다. Burrow나 Kafka Exporter로 랙을 상시 모니터링하고, 임계치 초과 시 컨슈머를 오토스케일하세요.
- 리밸런싱 폭풍: 컨슈머 추가/제거 시 잦은 재할당, static membership으로 완화
- 핫 파티션: 키 분포 불균형, 키 설계 재검토 또는 파티션 증설
- 중복 소비: 멱등 키와 idempotent producer로 방어
정리
Kafka는 생산자와 소비자를 분리하는 분산 로그로 실시간 파이프라인의 토대를 제공합니다. 파티션 수로 병렬성을 정하고, acks와 수동 커밋으로 신뢰성을 확보하며, 컨슈머 랙을 끊임없이 관측하는 것이 운영의 핵심입니다. 작은 토픽 하나로 시작해 처리량과 신뢰성 요구에 맞춰 점진적으로 확장하길 권합니다.