카프카는 데이터를 주고받는 중간다리, 메시지 브로커다

두 서버가 직접 데이터를 주고받게 만들면 언젠가 문제가 생긴다. 받는 쪽 서버가 잠깐 죽으면 보낸 요청은 그대로 사라지고, 응답을 기다리는 동안 보낸 쪽 서버의 성능도 같이 떨어진다. 아파치 카프카(Apache Kafka)는 이 문제를 “중간에 메시지를 대신 보관해 주는 저장소”를 두는 방식으로 푼다. 한마디로 카프카는 디스크 기반의 메시징 시스템이자 저장소이며, 생산자와 소비자 사이에서 메시지를 중개하는 브로커다. 이 글에서는 카프카가 무엇이고, 어떤 특징을 가졌으며, 실제로 어떤 상황에서 쓰이는지를 정리한다

카프카는 (그놈의)생산자와 소비자 사이의 저장소다

카프카를 이해하는 출발점은 세 가지 역할이다. 메시지를 보내는 쪽이 프로듀서(Producer, 생산자), 받는 쪽이 컨슈머(Consumer, 소비자), 그리고 그 사이에서 메시지를 보관하는 서버 집단이 카프카 클러스터(Cluster)다

[Producer] ──메시지 발행──▶ [Kafka Cluster] ──메시지 수신──▶ [Consumer]
  (생산자)                   (메시지 저장·중개)                (소비자)

흐름은 단방향이다. 프로듀서가 메시지를 만들어 카프카에 발행하면, 카프카가 이를 안정적으로 보관하고 있다가 컨슈머가 가져간다. 메시지가 프로듀서에서 만들어져 카프카에 저장됐다가 컨슈머로 흘러가는 스트리밍 구조다. 이때 주고받는 메시지 형식으로는 JSON이 흔히 쓰인다

클러스터라는 단어가 핵심이다. 카프카는 서버 한 대로 운영하는 경우가 드물다. 고가용성을 위해 여러 대의 서버(브로커)를 묶어 클러스터로 구성하는 것이 일반적인 형태다. 서버 한 대가 죽어도 나머지가 메시지를 이어받을 수 있도록 처음부터 그렇게 설계됐다

카프카의 네 가지 특징

카프카의 성격은 익숙한 관계형 데이터베이스(RDB)와 비교하면 선명해진다

토픽 기반의 비구조화된 데이터

RDB에 회원 정보를 저장하려면 먼저 member 테이블을 만들고 이름, 이메일, 패스워드 같은 컬럼 구조를 정해야 한다. 구조가 확정된 뒤에는 나이 값을 넣고 싶어도 컬럼을 바꾸기 전에는 넣을 수 없다

카프카는 다르다. 테이블 대신 토픽(Topic)이라는 카테고리에, 정해진 구조 없이 프로듀서가 원하는 형식대로 메시지를 자유롭게 발행한다. 원칙적으로 카프카가 보관하는 데이터는 비구조화되어 있다. 다만 실무에서는 발행자와 수신자가 서로 형식을 맞춰 쓴다. “형식을 강제받지 않지만, 합의해서 맞춰 쓴다”가 정확한 표현이다

구분RDB카프카
저장 단위테이블토픽
데이터 구조컬럼으로 사전 정의비구조화(형식은 발행자·수신자 합의)
구조 변경스키마 변경 필요제약 없음

복제 아키텍처를 통한 고가용성

RDB도 장애나 데이터 유실을 막기 위해 복제를 쓴다. 하지만 카프카는 애초에 고가용성을 목적으로 태어난 저장소라, 복제와 분산이 기본 전제다. 앞서 말한 클러스터 구성이 그 결과다. 브로커 한 대만 두는 단일 서버 구성은 예외적이고, 여러 브로커를 묶는 것이 카프카의 표준이다

고성능 비동기 메시징

RDB에 데이터를 저장할 때, 서버는 보통 저장이 끝날 때까지 기다린다. INSERT가 정상적으로 완료됐다는 응답을 받고 나서 다음 일을 하는 동기 방식이다

카프카는 비동기를 전제로 설계됐다. 프로듀서는 카프카에 메시지를 던진 뒤 “잘 저장됐다”는 응답을 기다리지 않고 곧바로 다른 일을 할 수 있다. 응답을 기다리며 멈춰 있지 않으니 서버 처리량이 올라간다. (엄밀히는 카프카도 발행 결과를 확인하는 동기 모드를 지원하지만, 성능을 위해 비동기로 쓰는 경우가 많다)

영구 저장이 아닌 임시 보관과 재처리

카프카는 RDB처럼 데이터를 영구히 저장하지 않는다. 메시지 브로커 목적으로 일정 기간만 임시 보관한다. 기본 보관 기간은 7일이다(log.retention.hours 기본값 168시간)

이 임시 보관이 안정성의 핵심이다. 컨슈머가 메시지를 가져가 처리하다 오류가 나도, 보관된 메시지를 다시 가져와 재처리할 수 있다. 한 번 소비했다고 메시지가 곧바로 사라지지 않기 때문에, 브로커 관점에서 충분한 안정성을 확보한다

사례 1 – MSA 환경의 서버 간 비동기 통신

카프카의 쓰임은 사실 하나다. 메시지를 넣고 가져가는 브로커. 그 하나가 상황에 따라 다르게 보일 뿐이다. 가장 대표적인 것이 MSA(마이크로서비스 아키텍처) 환경의 비동기 통신이다

모노리식은 모든 서비스가 한 서버 안에 들어간 구조이고, MSA는 서비스별로 서버와 DB를 분리한 구조다. 주문을 처리하는 ORDER 서버와 재고를 관리하는 PRODUCT 서버가 각자의 DB를 갖고 따로 떨어져 있다고 하자. 사용자가 주문을 넣으면 ORDER는 주문 내역을 자기 DB에 기록하지만, 재고 감소는 PRODUCT에 요청해야 한다

요청 방법은 두 가지다. 첫째, HTTP API로 직접 호출하는 동기 통신. 둘째, 카프카에 “재고 감소” 메시지를 발행하는 비동기 통신

동기 방식(직접 HTTP 호출)에는 두 가지 약점이 있다

  • PRODUCT 서버가 잠깐 다운되면 ORDER가 보낸 요청은 받아 줄 곳이 없어 그대로 유실된다
  • ORDERPRODUCT의 응답이 올 때까지 기다리므로, 사용자에게 주는 응답도 그만큼 늦어진다

카프카를 끼우면 두 약점이 사라진다.

[사용자] ─주문─▶ [ORDER] ─주문 기록─▶ [ORDER DB]
                 │
                 └─"재고 감소" 메시지 발행─▶ [Kafka] ◀─메시지 수신─ [PRODUCT] ─▶ [PRODUCT DB]

ORDER는 재고 감소 메시지를 카프카에 발행하고, 응답을 기다리지 않은 채 사용자에게 곧바로 “주문 완료”를 응답한다. PRODUCT가 그 순간 죽어 있어도 상관없다. 메시지는 카프카에 물리적으로 저장돼 있으니, 서버가 되살아난 뒤 읽어 가면 된다. 요청 유실이 방지되고 재처리가 가능해지며, 응답을 기다리지 않으니 성능도 올라간다

관점동기 HTTP 호출카프카 비동기
수신 서버 장애 시요청 유실메시지 보관 후 재처리
응답 대기필요(성능 저하)불필요(성능 향상)
결합도높음낮음

여기서 한 걸음 더 나아가면 카프카는 메시지 버스로도 쓸 수 있다. 주문 메시지 하나를 상품 서버, 알림 서버, 통계 서버가 각자 가져가 재고를 줄이고, 알림을 보내고, 통계를 집계한다. 같은 메시지를 여러 서버가 독립적으로 소비하는 이 구조가 오히려 카프카의 강점이 더 잘 드러나는 활용이다. 메시지 버스는 메시지 허브 또는 터미널로 이해하면 된다

사례 2 – 여러 서버의 로그·이벤트 수집

여러 서버에서 쏟아지는 로그를 한곳에 모아 분석할 때도 카프카가 중개 창구가 된다

서버 여러 대가 로그가 발생할 때마다 카프카에 프로듀서로서 메시지를 발행한다. 반대편에서는 로그 가공 전용 서버가 컨슈머로서 그 메시지를 가져가 정제한다. 대표적으로 Logstash가 이 역할을 맡지만, 반드시 Logstash일 필요는 없다. 어떤 컨슈머 서버든 붙일 수 있다

[Server 1] ┐
[Server 2] ┼─로그 발행─▶ [Kafka] ─소비─▶ [Logstash] ─정제─▶ [Elasticsearch] ─▶ [Kibana 대시보드]
[Server 3] ┘

Logstash가 정제한 로그를 검색 엔진인 Elasticsearch에 넘기고, 이를 Kibana 대시보드와 연동하면 로그를 시각적으로 검색하고 볼 수 있다. 같은 구조를 로그가 아니라 AI 학습용 데이터 수집에도 쓸 수 있다. 각 서버의 데이터를 취합·정제해 Hadoop 같은 대규모 저장소에 쌓는 식이다

핵심은 동일하다. 카프카가 중간에서 메시지를 저장해 두고, 필요한 컨슈머가 자기 속도로 가져간다

사례 3 – 데이터 파이프라인과 카프카 커넥트

특정 시스템의 데이터를 다른 시스템으로 옮기는 작업을 ETL(추출·변환·적재, Extract·Transform·Load)이라 부른다. 카프카는 이 데이터 파이프라인에도 자주 쓰인다. DB1(회원 DB)에 데이터가 들어올 때마다 백업 목적으로 DB2에도 동일하게 넣어야 한다고 하자. 설계 방법을 단계별로 비교하면 카프카가 왜 필요한지 드러난다

첫 번째 안은 서버가 DB1에 넣은 뒤 곧바로 DB2에도 넣는 것이다. 두 INSERT가 모두 끝나야 사용자에게 응답하므로, DB2 때문에 성능이 떨어진다. 두 번째 안은 서버가 DB1에 넣은 뒤 카프카에 “이 데이터를 DB2에 넣어라” 메시지를 발행하고, 별도의 컨슈머 서버가 그 메시지를 받아 DB2에 넣는 것이다. 서버는 발행 즉시 사용자에게 응답하니 빠르다. 하지만 프로듀서 설정과 별도 컨슈머 서버 구축이라는 번거로움이 남는다

이 번거로움을 없애는 것이 카프카 커넥트(Kafka Connect)다

[DB1] ─변경 감지─▶ [Kafka Connect(Source)] ─▶ [Kafka] ─▶ [Kafka Connect(Sink)] ─▶ [DB2]

카프카 커넥트는 별도의 송수신 애플리케이션 없이 카프카와 외부 시스템을 연결해 주는 전용 서버다. DB1에 변경이 생기면 커넥트가 이를 감지해 카프카에 메시지로 발행하고, 다시 커넥트가 그 메시지를 꺼내 DB2에 직접 넣는다. 프로듀서·컨슈머를 직접 만들 때 필요한 오류 처리, 재처리, 커밋 관리 같은 작업이 커넥트 안에 내장돼 있다

그래서 RDB에서 Elasticsearch로, RDB에서 S3로, RDB에서 Hadoop으로 데이터를 옮기는 ETL 작업에 커넥트가 널리 쓰인다. 정리하면 카프카 커넥트는 프로듀서·컨슈머를 직접 개발하지 않고도 시스템 간 데이터 이동을 맡길 수 있는 완성형 서버다

사례 4 – 실시간 변환·집계와 카프카 스트림즈

카프카는 받은 메시지를 가공해 새로운 데이터 흐름을 만드는 데도 쓰인다. 주문이 발생하면 주문 토픽에 메시지가 쌓이는데, 이를 컨슘해 주문 건수·수량·총액을 집계한 뒤 주문통계 토픽에 새 메시지로 다시 발행하는 식이다. 수신 → 재가공 → 재발행. 기존 데이터를 변환해 새 흐름을 만들어 낸다

이 작업을 도와주는 것이 카프카 스트림즈(Kafka Streams)다. 카프카 커넥트가 별도로 띄우는 서버라면, 카프카 스트림즈는 애플리케이션에 추가하는 라이브러리다. 예를 들어 스프링으로 메시지를 다룬다면 의존성으로 추가해서 쓴다

스트림즈의 핵심 가치는 트랜잭션 보장이다. 수신·재가공·재발행이라는 일련의 과정에서 한 단계라도 실패하면 모든 작업을 취소하고 처음 수신부터 다시 시작하도록 보장한다. 직접 구현하면 복잡한 오류 처리와 재처리를 카프카 스트림즈가 대신 해 주는 것이다. 카프카는 이렇게 “한 메시지당 정확히 한 번” 처리하는 것을 exactly-once라 부르며, 스트림즈가 이를 보장한다

ZooKeeper는 이제 없다 – KRaft로의 전환

카프카를 다룬 오래된 자료에는 클러스터 옆에 ZooKeeper가 함께 그려져 있는 경우가 많다. ZooKeeper는 브로커 메타데이터와 클러스터 조정을 담당하던 외부 시스템이었다. 하지만 이제 최신 기준이 아니다

2025년 3월 릴리스된 Apache Kafka 4.0에서 ZooKeeper 모드가 완전히 제거됐다. 대신 카프카 자체의 Raft 기반 메타데이터 시스템인 KRaft가 유일한 운영 모드가 됐다. KRaft에서는 클러스터 내부의 컨트롤러 노드들이 복제 로그로 메타데이터를 직접 관리한다. 별도의 ZooKeeper 앙상블을 유지할 필요가 없어져 배포와 운영이 단순해졌다

기존 ZooKeeper 기반 클러스터는 4.0으로 곧장 올릴 수 없다. 3.x(권장 3.9)에서 KRaft로 먼저 마이그레이션한 뒤 4.0으로 올려야 한다. 지금 카프카를 새로 배운다면 ZooKeeper는 “예전에 이런 게 있었다” 정도로만 알아 두고 KRaft를 전제로 이해하면 된다

정리

정리하면 카프카는 생산자와 소비자 사이에서 메시지를 임시 저장하고 중개하는 브로커다. MSA 비동기 통신, 로그 수집, 데이터 파이프라인, 실시간 집계처럼 쓰임새가 여러 갈래로 보이지만 본질은 하나다. 메시지를 넣고, 보관하고, 가져간다. 두 시스템을 직접 연결하는 대신 그 사이에 카프카를 두면 되는지부터 판단하면, 대부분의 활용 사례는 자연스럽게 설명된다

출처 – 인프런 강의 중 [핵심만 빠르게 끝내는 실전 카프카(kafka)]