2026-06-21 Kafka in 8 minutes 영상

Apache Kafka Explained in 8 Minutes

https://www.youtube.com/watch?v=o2UkE1vNIN8

주문을 받고 처리하는 시스템을 가정한다. 그러면 주문을 하면 payment, shipping, inventory 등 여러 서비스가 주문을 처리해야 한다. 가장 쉬운 건 순서대로 하는 것이다. 주문을 처리할 때 await 같은 걸 쓴다고 생각하자.

async function processOrder(order) {
  await paymentService.process(order);
  await shippingService.process(order);
  await inventoryService.process(order);
}

작은 시스템에서는 이렇게 해도 된다. 하지만 주문에 묶인 서비스가 많아지면 관리가 힘들어진다. 단순히 관리 문제뿐 아니라 중간에 하나가 느려지거나 실패하면 전체 주문 처리에 영향을 준다. 그래서 서비스 간의 의존성을 줄이는 게 좋다. 주문 서비스가 다른 서비스에 상관없이 동작하도록 만들어야 하는 것이다.

이를 해결하는 게 Kafka 같은 메시지 큐 시스템이다.

Producer는 Kafka에 메시지를 보낸다. Consumer는 Kafka에서 메시지를 읽는다. 프로듀서는 어떤 컨슈머가 이걸 소비하는지 전혀 알지 못하고 반대도 마찬가지다. kafka는 중간에서 오는 메시지를 저장한다.

근데 이런 메시지 큐 시스템은 많은데 kafka는 뭐가 다를까?

kafka는 append-only commit log라고 볼 수 있다.

프로듀서가 메시지를 보내면 카프카는 offset이라는 고유한 ID를 할당하고 메시지를 저장한다. 뒤로 돌아가서 메시지를 수정하거나 컨슈머가 읽었다고 해서 메시지를 지우는 그런 건 절대 하지 않는다. append-only니까. 일종의 큐 같은 것이다.

일반적인 메시지 큐는 모든 메시지를 모든 컨슈머에 대해 추적해야 한다. 어떤 게 소비되었는지 등등. 카프카는 그저 이걸 쌓기만 한다. 물론 이런 로그가 너무 늘어나면 여러 기기에 분산해야 하고 이것도 카프카가 처리한다.

어떻게 이걸 나눌까? 토픽, 파티션으로.

토픽은 하나의 이벤트 스트림이다. 예를 들어 주문 토픽, 결제 토픽, 배송 토픽 등. 토픽도 상당히 큰 단위이기 때문에 이를 파티션으로 나눈다.

문제는 순서다. 순서는 하나의 파티션 안에서만 보장된다. 따라서 순서대로 처리되어야 하는 메시지가 있다면(예를 들어 주문 하나가 발송하는 결제, 배송, 재고 처리 등) 이걸 하나의 파티션에 넣어야 한다.

이런 게 가능하게 하기 위해서 각 이벤트는 key-value로 구성된다. key는 일종의 ID로 hash를 통해서 이벤트가 어떤 파티션에 들어갈지 결정된다. 같은 key를 가진 이벤트는 늘 같은 파티션에 들어가게 된다. 즉 같은 order에 대한 이벤트라면 같은 key를 주면 같은 파티션에 들어가게 되어 순서가 보장된다.

이건 파티션의 숫자가 일정할 때의 이야기다. 파티션이 늘어나면 해시 계산이 달라져서 같은 key가 갑자기 다른 파티션에 들어가게 될 수도 있다. 그래서 보통 처음부터 파티션을 충분히 많이 잡는다.

데이터 읽기 #

데이터를 읽을 때 컨슈머는 offset만 신경쓰면 된다.

컨슈머는 주기적으로 자신이 읽은 offset을 커밋한다. 그래서 중간에 읽던 중 장애가 나거나 버그가 나도 이전 커밋으로 돌아가서 그 offset부터 다시 읽으면 된다. 컨슈머는 그러니까 자신이 어디까지 읽었는지만 offset을 통해서 관리하는 것이다.

읽기의 스케일링을 위해서는 컨슈머 그룹을 사용한다. 같은 토픽을 읽는 컨슈머들을 묶은 게 컨슈머 그룹이다. 하나의 파티션이 하나의 컨슈머 그룹에 연결됨. 컨슈머를 추가하면 카프카는 자동으로 파티션을 재분배한다. 이때 당연한 거지만 파티션보다 active consumer가 많을 수는 없다. 만약 10개의 컨슈머가 있고 6개의 파티션이 있다면 4개의 컨슈머는 놀게 된다.

이런 컨슈머 그룹은 같은 데이터를 받는 여러 컨슈머가 있을 때 도움이 된다. 예를 들어 주문을 했을 때 이메일을 보내는 시스템이 있다고 하자. 주문이 너무 많으면 2개의 서버에서 이를 처리하도록 할 수 있는데 그러면 2개의 컨슈머가 같은 주문 이벤트를 받게 된다. 근데 그냥 처리하면 사용자한테 이메일이 2번 간다. 이럴 때 컨슈머 그룹을 사용하면 카프카가 이를 알아서 분배한다. 1개의 컨슈머만 그걸 처리하도록.

시스템 다운에 대처하기 위해서 파티션들이 레플리카를 가질 수도 있다. 그러면 레플리카 중 하나가 리더가 되고 나머지는 팔로워가 된다. 프로듀서는 언제나 리더에게 메시지를 보낸다. 컨슈머는 기본적으로 리더에서 메시지를 읽지만 팔로워에서 읽도록 설정할 수도 있다. 리더가 다운되면 팔로워 중 하나가 리더로 승격(promote)된다.

속도 #

이게 어떻게 이렇게 빠를까? 카프카는 디스크에 데이터를 저장하고 이건 일반적으로 느린데 말이다.

카프카는 append-only이기 때문에 디스크에 순차적으로 데이터를 쓴다. 디스크에 순차적으로 쓰는 건 랜덤하게 쓰는 것보다 훨씬 빠르다.

또 페이지 캐싱도 있다. 프로듀서가 메시지를 쓰고 컨슈머가 메시지를 읽을 때 이게 아직 페이지 캐시에 있을 수 있다. 디스크에서 읽는 것보다 훨씬 빠르다.

카프카는 데이터를 내보낼 때 zero-copy 전송을 사용한다. 보통의 방식과 달리 이렇게 하면 시스템 메모리에서 네트워크 소켓으로 바로 바이트를 보낼 수 있다.

클러스터의 관리 #

카프카는 서버 클러스터를 어떻게 관리할까? 또 어떤 게 리더인지 알까?

원래는 주키퍼를 썼지만 카프카4부터는 KRaft라는 자체적인 클러스터 관리 시스템이 도입되었다. KRaft는 리더 선출, 메타데이터 관리, 클러스터 구성 관리 등을 담당한다.