개발놀이터

아파치 카프카 (심화) 본문

배포/Apache Kafka

아파치 카프카 (심화)

마늘냄새폴폴 2025. 3. 18. 23:43

이번 포스팅에서는 아파치 카프카에 대해서 개념만 잡았던 이전 포스팅에 이어 조금 딥한 내용을 공부해보고 정리해보았습니다. 처음엔 카프카의 구성 요소를 짧에 짚고 넘어가고 이후 카프카의 주요 개념에 대해서 알아보도록 하겠습니다. 

 

아파치 카프카

카프카의 구성요소로는 Broker, Topic, Partition, Producer, Consumer, Consumer Group이 있습니다. 하나씩 간단하게만 정리해보겠습니다. 

 

  • 브로커 : 브로커는 카프카가 설치되어있는 물리적인 서버에 해당합니다. 이런 브로커들이 모여서 카프카 클러스터가 되는데 이 브로커는 카프카 그 자체라고 볼 수도 있습니다. 
  • 토픽 : 메세지가 저장되어있는 "논리적인" 공간입니다. 메세지를 생산하는 프로듀서는 이 토픽을 바라보고 생산하고 컨슈머는 이 토픽을 구독하게 됩니다. 하지만 데이터가 논리적인 공간이니만큼 실제 데이터가 이동하는 것은 아닙니다.
  • 파티션 : 파티션은 메세지가 저장되어있는 물리적인 공간입니다. 컨슈머가 토픽을 구독하여 메세지를 받긴 하지만 실제 데이터는 파티션에서 보내줍니다. 
  • 프로듀서 : 프로듀서는 토픽으로 메세지를 전송하는 객체 혹은 애플리케이션이 됩니다. 
  • 컨슈머 : 컨슈머는 메세지를 수신하는 객체 혹은 애플리케이션입니다. 
  • 컨슈머 그룹 : 컨슈머 그룹은 컨슈머들이 모여있는 논리적인 공간입니다. 컨슈머가 특정 토픽을 구독하면 해당 토픽에 존재하는 파티션과 컨슈머 내부에 있는 컨슈머들을 자동으로 연결해줍니다. 이 때의 대원칙은 

    1. 컨슈머는 직접 파티션을 선택할 수 없다.
    2. 컨슈머는 반드시 리더 파티션에서 메세지를 읽는다. (최신 버전에서는 팔로워 파티션에서도 읽는다고 하더군요)

    리더 파티션, 팔로워 파티션 이런건 추후에 더 깊이있게 정리할 예정입니다. 

간단하게 구성 요소에 대해서 알아봤으니 이제 주요 개념에 대해서 정리해보겠습니다. 

 

복제 (Replication)

카프카의 파티션은 높은 가용성, 높은 내구성을 위해 복제되는데 이 레플리케이션은 데이터베이스 레플리케이션과 매우 흡사합니다. 

 

파티션은 읽기와 쓰기를 담당하는 리더 파티션과 장애 상황에 대응하기 위한 팔로워 파티션으로 나눠집니다. 팔로워 파티션은 장애 상황에 대응하기 위해 리더 파티션의 데이터를 복제하면서 스탠바이하고 있습니다. 

 

앞서 언급했지만 컨슈머는 리더 파티션에서만 메세지를 읽을 수 있지만 최신 버전에서는 리더 파티션에 대한 부하 분산을 위해 팔로워 파티션에서도 메세지를 읽을 수 있다고 하더군요. 

 

파티션의 복제는 개발자가 직접 설정해줄 수 있는데 이 때 파티션의 복제는 카프카 클러스터의 수 (브로커 수) 를 넘지 못합니다. 

 

오프셋 (Offset)

오프셋은 각 파티션마다 어느정도 메세지가 진행되었는지 확인할 수 있는 인덱스이고 이 오프셋을 이용해서 컨슈머의 메세지 처리에 대한 추적이 가능합니다. 때문에 굉장히 중요한 개념 중 하나입니다. 

 

오프셋의 종류로는 

 

  • Current Offset : 현재 컨슈머가 처리하고 있는 오프셋입니다. 
  • Log-End Offset : 현재 큐에 들어간 가장 마지막 오프셋입니다. 
  • Lag : 현재 큐에 있는 가장 마지막 오프셋과 현재 컨슈머가 처리하고 있는 오프셋의 차이입니다. 이 정보가 카프카를 모니터링할 때 굉장히 중요한 지표가 됩니다. 왜냐하면 현재 카프카가 어느정도로 메세지가 밀려있는지 확인할 수 있기 때문이죠. 

 

현재 컨슈머가 처리하고 있는 Current Offset은 컨슈머가 보통 업데이트를 하게 됩니다. 이 때 오프셋을 업데이트하는 것을 오프셋 커밋이라고 부르고 이 오프셋 커밋에는 자동 커밋, 수동 커밋 이렇게 두가지가 존재합니다. 

 

오프셋 커밋을 자동으로 하게 되면 설정이 간단해서 구현하기가 쉽다는 장점이 있지만 메세지가 중복처리 될 수 있다는 단점이 있습니다. 만약 오프셋 커밋을 수동으로 하게 된다면 구성이 복잡하지만 중복 처리 가능성을 낮춰준다는 장점이 있습니다. 

 

여기서 잠깐!

메세지가 중복처리 된다는 개념이 어떤 개념인지 잘 와닿지 않는데 어떤 상황인지 살펴보겠습니다. 

 

만약 컨슈머가 메세지를 처리하던 도중 예상치못한 장애가 발생하게 되면 어떻게 될까요? 이 정보가 카프카로 되돌아오고 이 때 카프카는 Current Offset을 기준으로 메세지를 재시도하도록 유도합니다. 

 

그럼 같은 메세지가 중복으로 컨슈머에게 도달하게 되는 것이죠. 만약 이 과정에서 데이터베이스 쓰기 연산이라도 있었다면 메세지가 중복으로 돌아와서 데이터베이스 쓰기 연산을 한번 더 진행하게 됩니다. 

 

이 과정이 바로 메세지 중복이 된다는 개념입니다. 

 

그럼 수동으로 오프셋을 커밋하면 중복이 해결되느냐? 그건 또 아닙니다. 

 

케이스는 두가지가 있을 수 있습니다. 

 

  1. 메세지를 처리하고 오프셋 커밋 : 메세지를 처리하고 오프셋을 커밋하는 경우 메세지가 처리되는 과정에서 장애가 발생하면 중복 처리 가능성이 매우 높아집니다. 
  2. 오프셋을 커밋하고 메세지를 처리하는 경우 : 오프셋을 먼저 커밋하면 메세지를 처리하는 과정에서 장애가 발생하면 메세지를 재시도 해야하는데 오프셋이 이미 커밋되어서 메세지가 유실되어버립니다. 

보통 이런 경우 메세지를 처리하는 것을 카프카 트랜잭션 API를 이용해서 처리합니다. 하지만! 카프카 트랜잭션 API도 만능은 아닙니다. 

 

https://coding-review.tistory.com/566

 

메세지 브로커의 근심과 걱정

마이크로 서비스와 메세지 브로커는 뗄 수 없는 사이입니다. 서로 다른 도메인이 여러개의 서버로 나눠져 있는 상황에서 모든 서버에 동일하게 데이터를 전달해야 하는 경우에 메세지 브로커만

coding-review.tistory.com

 

만약 데이터베이스 연산이 끼어있으면 어떻게 할까요? 위의 링크에 잘 정리되어 있습니다. 그 얘기는 본 포스팅과 맞지 않으니 생략하도록 하겠습니다. 

 

간단하게 정리하자면 메세지 처리와 데이터베이스 연산을 카프카 트랜잭션으로 묶을 수 없어서 Outbox Pattern을 이용해야하고 Polling Publisher Pattern, Log Tailing Pattern 이렇게 두가지 방식으로 구현할 수 있습니다. 

 

저장과 압축

카프카는 내부적으로 컨슈머가 메세지를 소비하더라도 일정시간동안 가지고 있는 특징이 있습니다. 이렇게 하는 이유는 컨슈머가 내부적인 장애로 인해 메세지를 다시 소비해야하는 경우를 위한 것이죠. 카프카의 저장 정책에는 시간 기반, 크기 기반 이렇게 두 가지가 있으니 한번 천천히 살펴보죠. 

 

시간 기반은 특정 시간이 지나면 메세지가 자동으로 삭제되는 정책입니다. 반대로 크기 기반은 메세지의 크기가 특정 바이트를 넘어가면 자동으로 삭제하는 것이죠. 물론 이 둘을 혼용해서 사용할 수도 있습니다. 

 

카프카가 메세지를 삭제하는 것도 나름의 정책이 있습니다. 가장 기본적인 정책은 시간기반, 크기기반의 삭제 정책입니다. 이는 앞서 언급했듯이 시간이 지나면, 크기가 크다면 삭제하는 정책입니다. 

 

두 번째 삭제 정책은 압축해서 삭제하는 방법입니다. 파티션 내부 메세지에는 여러가지 메타데이터가 존재하는데 여기에는 오프셋도 있고 실제 메세지 데이터도 있고 "키"라는 것도 있습니다. 

 

카프카는 이 키를 기반으로 메세지를 관리하게되죠. 압축 정책은 키가 중복되면 이전 메세지를 곧바로 지우게 되는 것입니다. 이렇게 함으로써 메세지를 항상 최신 상태를 유지하는 것이죠. 이러한 정책은 데이터가 항상 최신으로 유지되어야하는 IoT 서비스에서 주로 사용됩니다. 

 

리밸런싱

카프카의 리밸런싱은 

 

https://coding-review.tistory.com/504

 

아파치 카프카 (개념)

저번 포스팅에서 파트2를 시작하면서 메세지 브로커의 장을 열었습니다. 메세지 브로커를 이용해서 서버간 통신을 조금 더 매끄럽게 진행할 수 있다는 것을 알았고 어떤 모델이 있는지 알았습

coding-review.tistory.com

 

여기에 자세히 설명이 되어있습니다. 요약하자면 특정 파티션에 연결된 컨슈머 그룹 중 하나의 컨슈머에서 장애가 발생해서 메세지가 유실될 위험에 처했다면 즉시 파티션이 다른 컨슈머를 바라봄으로써 파티션에 존재하는 메세지가 유실되지 않도록 처리하는 것입니다. 

 

리밸런싱은 로드밸런싱과 결이 조금 다른데 로드 밸런싱은 부하를 분산시키는 것에 초점이 맞춰져 있다면 리밸런싱은 메세지가 유실되는 것을 방지하기 위함에 초점이 맞춰져있습니다. 

 

ISR과 ACK

ISR은 In-Sync Replication의 약자로 복제한 파티션들의 집합입니다. 만약 ISR설정을 2로 지정한다면 파티션이 리더 파티션을 포함하여 2개가 복제되어 유지된다는 말입니다. 

 

만약 ISR을 설정할 때 최대 응답시간을 넘어가는 복제본 파티션들은 ISR에 등록되지 않고 넘어간다는 특징이 있습니다. 

 

앞선 개념들은 컨슈머에 초점이 맞춰져 있었다면 ISR과 ACK은 프로듀서에 초점이 맞춰져있는 개념입니다. 프로듀서가 카프카에게 메세지를 전달할 때 카프카에 메세지가 제대로 도착했는지 여부를 확인해 프로듀서가 메세지를 재전송할 것인지를 확인하는 프로세스이기 때문입니다. 

 

ISR과 ACK은 서로 짝꿍처럼 붙어다니는데 ACK을 먼저 살펴보겠습니다. 

 

ACK에는 세가지 단계가 존재합니다. 0, 1, all 이렇게 세가지이죠. 0인 경우 ISR에 메세지가 제대로 복제되었는지 여부를 확인하지 않고 종료해버립니다. 이 때문에 0인 경우 응답속도는 매우 빠르지만 카프카 내부적으로 복제가 이루어지지 않은 경우 메세지 손실의 위험이 있습니다. 

 

1의 경우 ISR에 있는 리더 파티션만 메세지를 전달받은 경우 잘 받았다는 응답을 프로듀서로 보냅니다. 만약 이 응답이 오지 않으면 프로듀서는 재전송하지 않습니다. 

 

마지막으로 all의 경우 ISR에 등록된 모든 복제본에 메세지가 적힌 이후에 프로듀서에게 응답을 보냅니다.

 

프로듀서의 경우 카프카로부터 응답이 오지 않으면 메세지를 재전송하지 않기 때문에 적절한 ACK설정을 해주어야합니다. 

 

메세지가 유실되어도 상관없고 엄청난 퍼포먼스가 필요하다면 0을, 최소한의 메세지 일관성이 필요하다면 1을, 강력한 메세지 일관성이 필요하다면 all을 선택해야합니다. 

 

근데.. 이거 어디서 많이 보던건데..? 

 

맞습니다. 바로 데이터베이스 레플리케이션 비동기 복제, 반동기 복제, 동기 복제의 개념과 정확히 일치합니다. 컴퓨터 공학은 여기서 쓰던 개념이 저기서도 쓰이고 저기서 쓰이던 개념이 여기서도 쓰이고 모든게 이어져있다는 느낌이 듭니다. 

 

마치며

오늘은 카프카에 대해서 조금 심화과정을 공부해봤습니다. 지금 제가 해보고싶은건 카프카 파티션을 동적으로 설정하고 컨슈머도 동적으로 늘려서 확장하는 방법인데 조만간 AWS를 활용해서 실습해보고싶습니다. 

 

오늘 포스팅은 여기서 마치도록 하겠습니다. 오늘도 즐거운 하루 되세요~