programming language/Kakfa

[Kafka 기초 학습] 카프카 기초 개념 2 - 자세한 개념과 프로듀서의 작동 방식, 그리고 topic 상세 정보 보기

공대키메라 2026. 6. 3. 16:58

지난 글에서는 kafka 가 뭔지 정말 대강 알아보고 실습 환경을 세팅했다. (지난 글 궁금하면 여기 클릭!)

 

이번 글에서는 카프카의 기초에 대해 더욱 다져보도록 하겠다.

 

다음 그림은 기본적인 kafka 구성도이다. 

출처 : https://learnbyinsight.com/2020/07/26/beginner-guide-to-understand-kafka/

 

하지만 필자는 다음과 같이 구성했다. 

 

 

위 그림도보고, 밑에 그림도 보면서 이해하려고 노력했다.

 

뭔가 전체적으로 보면 이해가 될 것 같으면서도 너무 개념이 많고 설명도 많다. 

 

그렇기에 좀 더 이해를 하고자 열심히 그려보았다. 무엇보다도 필자는 docker로 띄어서 하다보니 배로 헷갈렸다.


목표

1. 카프카의 기초 개념에 대해 좀 더 이해한다.

2. 카프카에서 프로듀서의 동작 방식을 이해한다.

3. 리더와 팔로워가 어떤것인지 파악한다.


0. 들어가기 앞서

시작하기에 앞서 이전에 설정했던 docker-compose.yml 파일을 보자.

 

version: '3.8'

networks:
  kafka-network:
    name: kafka-local-network
    driver: bridge

services:
  kafka-1:
    image: apache/kafka:latest
    container_name: kafka-1
    ports:
      - "19092:19092"
    environment:
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: 'controller,broker'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:19092'
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-1:9092,EXTERNAL://localhost:19092'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  kafka-2:
    image: apache/kafka:latest
    container_name: kafka-2
    ports:
      - "29092:29092"
    environment:
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      KAFKA_NODE_ID: 2
      KAFKA_PROCESS_ROLES: 'controller,broker'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:29092'
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-2:9092,EXTERNAL://localhost:29092'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  kafka-3:
    image: apache/kafka:latest
    container_name: kafka-3
    ports:
      - "39092:39092"
    environment:
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      KAFKA_NODE_ID: 3
      KAFKA_PROCESS_ROLES: 'controller,broker'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:39092'
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-3:9092,EXTERNAL://localhost:39092'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  kafka-setup:
    image: apache/kafka:latest
    container_name: kafka-setup
    networks:
      - kafka-network
    depends_on:
      - kafka-1
      - kafka-2
      - kafka-3
    command: >
      /bin/sh -c "
      echo 'Waiting for Kafka to be ready...' &&
      sleep 10 &&
      /opt/kafka/bin/kafka-topics.sh --create --if-not-exists --topic cluster-test --partitions 3 --replication-factor 3 --bootstrap-server kafka-1:9092 &&
      echo 'Topic created successfully!'
      "

 

여기서 kafka-setup부분이 있다. 

 

/opt/kafka/bin/kafka-topics.sh (카프카 토픽 관리 툴)를 사용하여 다음 작업을 수행한다.

  • --create: 토픽을 생성하라.
  • --if-not-exists: 만약 이미 같은 이름의 토픽이 있다면 무시하라. (재실행 시 에러 방지)
  • --topic cluster-test: 생성할 토픽의 이름은 cluster-test로 하라.
  • --partitions 3: 이 토픽을 3개의 파티션으로 나누어라. (병렬 처리 위함)
  • --replication-factor 3: 데이터의 복제본을 3개 만들어라. (고가용성 위함)
  • --bootstrap-server kafka-1:9092: 이 명령을 전달할 대상(클러스터의 진입점)은 kafka-1 컨테이너의 내부 포트 9092이다.

 

kafka-1, kafka2, kafka-3부분은 KAFKA_PROCESS_ROLES: 'controller,broker' 을 보면 브로커가 클러스트를 직접 관리하는 Kraft형식으로 주키퍼를 사용하지 않는다.

 

kafka는 현재 기점으로 4.3 버전까지 출시된 상태이다.

 

1. 카프카 지식 훑어보기

Kafka 를 사용하면서 알아야 할 지식이 많다. 같이 쭈욱 정리해보자.

 

1.1) 데이터 및 논리적 단위 (Data & Logical Structure)

  • Messages/Records: 객체의 바이트 배열입니다. Key, Value, Timestamp로 구성되며 Kafka 시스템 내에서 통신 및 저장되는 가장 기본적인 데이터 단위입니다.
  • Topic: 카테고리별로 분류된 메시지 피드(Feed)입니다. 데이터가 기록되고 구독되는 논리적인 채널 역할을 합니다.

 

여기서 Topic 은 논리적인 채널이다. 물리적인 채널이 아니다! 

실제로는 브로커 서버들의 디스크에 분산되어 바이트(Byte) 배열로 기록되는 "실제 파일 구조" 를 가진다.

다만, 우리가 이해하기 편하게 "가상의 데이터 카테고리 이름" 을 부여한 것이다.

 

1.2) 데이터 생산 및 소비 (Core Actors)

  • Producer: Kafka Topic으로 메시지를 발행(Publish)하는 프로세스입니다.
  • Consumer: Topic을 구독(Subscribe)하여 발행된 메시지 피드를 순차적으로 가져와 처리하는 프로세스입니다.

 

1.3) 인프라 및 클러스터 상태 관리 (Infrastructure & State Management)

  • Broker: Topic을 호스팅하는 서버입니다. Kafka Server 또는 Kafka Node라고도 불리며, 실제 메시지 데이터가 디스크에 저장되는 공간입니다.
  • Cluster: 대용량 트래픽 처리와 확장을 위해 하나 이상의 Broker들로 구성된 그룹입니다.
  • Zookeeper: Broker, Topic, Consumer 등 Cluster의 전체적인 상태(State)와 메타데이터를 분산 환경에서 일관성 있게 유지하고 관리하는 시스템입니다.
  • Controller: Cluster 내의 Broker 중 하나가 이 역할을 맡으며, 모든 파티션의 Leader/Follower 관계를 유지하고 상태 변화를 관리합니다.
  • ISR (In-Sync Replica): 고가용성 및 장애 조치(Failover)를 지원하기 위한 복제 그룹입니다. Leader 파티션의 데이터를 지연 없이 동기화하고 있는 Follower들의 집합을 의미합니다.

 

현재 필자는 kafka-cluster의 이름을 "O1N_F_D-S2K6V8-A_G-p-A" 으로 했다. 

왜 이렇게 햇냐면 AI한테 해달라 하니 이렇게 해줬다 ㅎ...

 

contorller 가 또 있는데, 각각 브로커 내부에 파티션들은 리더와 팔로워가 존재한다.

자세한 설명은 뒤에서 하도록 하겠다.

 

1.4) 확장 생태계 (Data Pipeline & Ecosystem)

  • Connector: Kafka Topic을 기존 애플리케이션이나 외부 데이터 시스템(RDBMS, NoSQL 등)과 코딩 없이 연결해 주는 통합 컴포넌트입니다.
  • Stream Processor: 특정 Topic의 입력 스트림(Input stream)을 소비하여 실시간으로 변환, 집계 등을 수행한 뒤 그 결과를 다른 출력 Topic(Output topic)으로 생산하는 데이터 처리 프로세스입니다.

 

1.5) 주키퍼와 크래프트(Zookeeper & Kafka Raft (KRaft))

출처 : https://www.linkedin.com/pulse/kafkas-evolution-zookeeper-vs-kraft-anuj-tyagi-n2x4e/

 

필자가 읽고 있는 "실전 카프카 개발부터 운영까지" 도서는 zookeeper를 사용해서 설명하고 있다.

 

Kafka 3.5 버전 부터는 zookeeper를 deprecated 해버린 상태다.

 

출처 : https://kafka.apache.org/35/operations/zookeeper/

 

마침 이에 대해서 간단히 정리하려고했는데 다른분께서 잘 정리해서 참고했다.

(https://www.linkedin.com/pulse/kafkas-evolution-zookeeper-vs-kraft-anuj-tyagi-n2x4e/)

 

이미 잘 정리되어 있는걸 내 언어로 하는것도 중요하지만... 너무 어려워서 그게 의미가 있나~ 싶은 정도라서 요약을 부탁했다.

더보기

기존 Kafka 아키텍처에서의 ZooKeeper 역할

ZooKeeper는 기존 Kafka 아키텍처의 핵심 구성 요소로서 다음과 같은 관리 작업을 수행합니다.

  • 컨트롤러 선출 (Controller Election): 임시(Ephemeral) Znode를 생성하여 Kafka 클러스터의 컨트롤러를 선출합니다.
  • 클러스터 멤버십 (Cluster Membership): 임시 Znode를 통해 개별 브로커의 활성 상태 및 가용성을 추적합니다.
  • 토픽 설정 (Topic Configuration): 토픽의 구성, 파티션 분배, 레플리카 등의 메타데이터를 저장합니다.
  • 접근 제어 목록 (ACLs): Kafka 토픽에 대한 권한 및 보안 정책을 유지하고 관리합니다.
  • 할당량 (Quotas): 클라이언트의 리소스 할당 제한을 통제합니다.

ZooKeeper 구조의 한계점

ZooKeeper는 안정적인 분산 코디네이션 시스템이지만, 대규모 트래픽을 처리하는 Kafka 환경에서는 다음과 같은 한계가 존재합니다.

  • 복잡한 배포 (Complex Deployment): Kafka와는 별개의 분산 시스템을 추가로 구축하고 유지해야 하므로 운영 오버헤드가 큽니다.
  • 확장성 한계 (Scalability Limits): 클라이언트 요청이 많아질 경우 ZooKeeper 자체가 병목(Bottleneck) 구간이 되어, 대규모 Kafka 클러스터의 확장을 저해합니다.
  • 일관성 종속성 (Consistency Dependencies): 강한 일관성(Strong consistency)을 보장하는 구조적 특성 때문에, 네트워크 파티션이나 장애 발생 시 오히려 가용성이 떨어지는 문제가 발생할 수 있습니다.
  • 느린 장애 조치 (Slow Failovers): 상태 변경에 대한 알림이 브로커들에게 전파되는 데 지연이 발생할 수 있어 장애 복구 시간이 길어집니다.

새로운 메타데이터 모드: KRaft (Kafka Raft)

KRaft(Kafka Raft Metadata mode)는 KIP-500 제안을 통해 도입된 Raft 알고리즘 기반의 새로운 합의 프로토콜입니다. ZooKeeper에 대한 외부 의존성을 완전히 제거하고, 메타데이터 관리를 Kafka 내부 아키텍처로 통합했습니다.

KRaft의 주요 특징

  • 이벤트 소싱 아키텍처 (Event-Sourced Architecture): 메타데이터의 변경 사항을 이벤트 스트림으로 취급하여, Kafka 내부의 __cluster_metadata 토픽에 저장합니다.
  • 아키텍처 단순화 (Simplified Architecture): 별도의 메타데이터 코디네이션 시스템이 필요하지 않습니다.
  • 리더-팔로워 모델 (Leader-Follower Model): 쿼럼(Quorum) 컨트롤러가 메타데이터 변경 사항을 다른 브로커들에게 복제하여 상태 일관성을 보장합니다.
  • 확장성 개선 (Improved Scaling): 메타데이터 처리의 병목 현상을 해소하여 단일 클러스터 내에서 훨씬 더 많은 파티션을 지원합니다.
  • 보안 강화 (Enhanced Security): 단일 시스템으로 통합됨에 따라 전체 보안 모델을 단순화하고 관리하기 쉽게 만듭니다.

KRaft 도입의 이점

  • 배포 및 운영 단순화: ZooKeeper가 제거되어 인프라 구성이 단순해지고 운영 복잡성이 크게 감소합니다.
  • 더 높은 확장성: 메타데이터 처리 오버헤드가 줄어들어 더 많은 수의 파티션과 브로커를 안정적으로 운영할 수 있습니다.
  • 빠른 장애 조치: 이벤트 기반의 메타데이터 동기화 방식을 통해 장애 발생 시 다운타임을 최소화합니다.
  • 보안 구조 일원화: 기존 Kafka와 ZooKeeper 간에 분리되어 있던 보안 모델의 불일치 문제를 해결하고 보안 체계를 일원화합니다.

 

그렇구나~ 하여간 KRaft를 사용하니까 어느 문제점이 있어서 KRaft 를 도입하게 되었고 장점이 뭔지 나중에 읽어보면 될 듯 하다.

 

1.6) 레플리케이션?

카프카에서 레플리케이션은 각 메시지들을 여러 개로 복제해서 카프카 클러스터 내 브로커들에 분산시키는 동작을 의미한다.

 

위에 0. 들어가기 에서 kafka-setup부분이 있다. kafka-1이 생성되자 마자 topic을 생성하고, replication을 생성했다.

 

생성한 replication은 현재 kafka-1, kafka-2, kafka-3 에 하나씩 들어가 있다.

 

책에서는 다음과 같이 설명한다.

 

  • 테스트나 개발 환경 : 리플리케이션 팩터 수 1개
  • 운영 환경(로그성 메시지로서 약간의 유실 허용): 레플리케이션 팩터 수 2개
  • 운영 환경(유실 허용하지 않음): 레플리케잇녀 팩터 수 3개

 

이 레플리케이션을 통해서 우리는 고가용성(High Availabilty)를 보장할 수 있다.

 

1.7) 파티션?

하나의 토픽이 한 번에 처리할 수 있는 한계를 높이기 위해 포틱 하나를 여러 개로 나눠 병럴 처리가 가능하게 만든 것을 파티션이라고 한다.

 

파티션 수는 0부터 시작하며 파티션 수는 언제든지 생성 가능하지만 절대로 줄일 수 없으니 유의하자!

 

1.8) 세그먼트?

우리가 프로듀셔를 통해 보낸 메시지는 어디에 저장될까?

 

구체적으로 필자가 이전 글에서 kafka-1에서 메시지를 전송하면 kafka-2랑 kafka-3에서 이를 consumer에서 확인할 수 있었다.

 

이렇게 프로듀서에 의해 브로커로 전송된 메시지는 토픽의 파티션에 저장되며, 각 메시지들은 세그먼트 라는 로그 파일의 형태로 브로커의 로컬 디스크에 저장된다.

 

실제로 그런지 함 봐볼까?

 

자세히 보면 cluster-test-0, cluster-test-1, cluster-test-2가 보인다.

 

세그먼트들을 자세히 보고자 cluster-test-0에 들어갔다.

 

 

여기서 다음 명령어를 수행하자.

 

xxd 00000000000000000000.log

 

???

 

이렇게 세그먼트의 파일 내용을 출력해 봤는데, 프로듀서가 전송한 메시지가 브로커의 로컬 디스크 안에 저장되어 있음을 확인했다.

 

헷갈린다. 다시 정리하고 가자. 구조는 다음과 같다.

 

/tmp/kafka-logs/                     <-- (카프카가 데이터를 저장하는 최상위 루트 폴더)
  ├── cluster-test-0/                <-- (0번 파티션 전용 폴더)
  │     ├── 00000000000000000000.log <-- (1번째 세그먼트: 실제 데이터 파일)
  │     ├── 00000000000000000000.index
  │     ├── 00000000000000000000.timeindex
  │     │
  │     ├── 00000000000000010485.log <-- (2번째 세그먼트: 1번 파일이 꽉 차면 새로 생성됨)
  │     ├── 00000000000000010485.index
  │     └── ... (계속 추가됨)
  │
  ├── cluster-test-1/                <-- (1번 파티션 전용 폴더)
  │     └── 00000000000000000000.log 
  │
  └── cluster-test-2/                <-- (2번 파티션 전용 폴더)
        └── 00000000000000000000.log

 

현재 토픽을 파티션 3개로 쪼갰고, 3중 복제 설정을 했다.

결과적으로 모든 브로커가 3개 파티션의 데이터를 (원본이든 복제본이든) 하나씩 다 가지게 되었다.

 

내 상황과 이야기 해보면 kafka-1 에서 partitions 만들면 순서대로 clsuter-test-0, clsuter-test-1, clsuter-test-2 인데
kafka-2 에서는clsuter-test-1이 메인이고 kafka-3 에서는clsuter-test-2이 메인이고 이다.

 

해당 구조를 기억하자!

 

클러스터 ⊃ 브로커 ⊃ 토픽 ⊃ 파티션 ⊃ 세그먼트

 

다시 정리해보자.

 

파티션은 topic을 나눈 것이라면 세그먼트는 로그 파일의 형태로 브로커의 로컬 디스크에 저장한다.

 

여기에 파일명을 보면 이 숫자 자체가 offset이라고 한다.

 

예를 들어, 맨 처음 시작점은 0000000일 것이다. 0번 offset에서 시작하니 말이다. 

 

그런데 이 파일에 메시지가 계속 쌓여서 현재 1110까지 쌓았다고하자. 그 다음에 들어올 세그먼트의 이름은 그럼 1111이다. 

 

뭐? 오프셋? 이건 갑자기 뭐야?

 

오프셋?

파티션의 메시지가 저장되는 위치를 오프셋이라고 한다. 오프셋은 순차적으로 증가하는 숫자 형태로 되어 있다.

 

Gemini에게 구조에 대해서 다시 질문했다.

 

제목: 카프카 데이터 저장 구조와 오프셋(Offset)의 역할

1. 카프카의 물리적 데이터 계층 구조
카프카의 데이터는 '토픽 > 파티션 > 세그먼트'라는 물리적인 공간 구조를 가집니다.
- 토픽 (Topic): 카프카 내부의 논리적인 데이터 그룹 (예: 책 제목)
- 파티션 (Partition): 병렬 처리를 위해 토픽을 여러 개로 나눈 단위 (예: 책의 1권, 2권)
- 세그먼트 (Segment): 파티션 내부에 데이터가 저장되는 실제 물리 파일 (예: 1권을 100페이지씩 나눈 낱권)

2. 오프셋(Offset)의 위치와 의미
오프셋은 데이터를 담는 공간이나 폴더가 아니라, 세그먼트 파일 안에 기록되는 개별 '메시지의 고유 번호(순번)'를 뜻합니다. (예: 종이에 적힌 줄 번호)
- 포함 관계: 세그먼트 > 실제 메시지 > 오프셋(순번)
- 핵심 역할: 컨슈머(워커 서버)가 데이터를 파티션 안에서 정확히 어디까지 읽었는지 기억하게 해주는 '절대적인 기준점(인덱스)'입니다. 서버 장애로 재부팅되더라도 오프셋 번호가 있기 때문에 중복 처리 없이 다음 데이터부터 안전하게 이어서 읽어올 수 있습니다.

 

 

 

2. 프로듀서의 동작 방식

https://dzone.com/articles/take-a-deep-dive-into-kafka-producer-api

 

가장 상단의 ProducerRecord는 카프카로 전송하기 위한 실제 데이터이다.

레코드는 이전에 프로듀서가 브로커로 전송하거나 컨슈머가 읽어가는 데이터 조각 이라고 했다.

이 레코드는 토픽, 파티션, 키, 밸류로 구성되어 있다.

 

프로듀서가 카프카로 레코드를 전송할 때 카프카의 특정 토픽으로 메시지를 전송한다.

 

그 밑을 보면 send()을 한다. 그러면 메시지는 시리얼라이저(serializer) -> 파티셔너(partitioner)로 간다.

 

2.1) ProducerRecord 생성 및 전송

  • 애플리케이션에서 카프카로 보낼 데이터를 담아 ProducerRecord 객체를 생성합니다.
  • 목적지인 Topic과 실제 데이터인 Value는 필수 값이며, 메시지를 구분할 Key와 특정 Partition 번호는 선택적으로 지정할 수 있습니다.
  • 객체 생성이 완료되면 send() 메서드를 호출하여 전송 과정을 시작합니다.

 

2.2) 직렬화 (Serializer)

  • 네트워크 통신을 통해 데이터를 전송해야 하므로, Key와 Value 객체를 바이트 배열(Byte Array) 형태로 변환합니다.

 

2.3) 파티셔닝 (Partitioner)

  • 직렬화된 메시지가 대상 토픽 내의 여러 파티션 중 어느 파티션으로 저장될지 결정하는 단계입니다.
  • ProducerRecord에 특정 파티션이 명시되어 있다면 해당 파티션으로 라우팅됩니다.
  • 파티션이 없고 Key가 존재한다면, Key값을 해싱하여 특정 파티션에 일관되게 할당합니다.
  • 둘 다 없다면 일반적으로 라운드 로빈(Round-robin) 등의 방식을 사용하여 파티션에 분산시킵니다.

 

2.4) 배치 누적 (Record Accumulator)

  • 고성능 처리와 네트워크 I/O 효율을 위해 파티셔너를 통과한 메시지를 즉시 브로커로 보내지 않습니다.
  • 다이어그램에 표시된 것처럼 목적지(Topic-Partition)별로 메모리 버퍼에 배치(Batch) 단위로 메시지를 묶어서 누적합니다.

 

2.5) 카프카 브로커 전송 및 응답 처리

  • 백그라운드의 I/O 스레드가 버퍼에 누적된 배치를 카프카 브로커(Kafka Broker)로 전송합니다.
  • 성공 시: 브로커가 메시지를 안전하게 기록하면, 해당 메시지의 토픽, 파티션, 오프셋 정보가 담긴 메타데이터(Metadata)를 프로듀서에게 반환합니다.
  • 실패 시 (Fail?): 전송 중 에러가 발생하면 재시도 가능한 성격의 에러인지 판별합니다.
  • 재시도 (Retry?): 네트워크 순단이나 리더 선출 중 발생하는 일시적인 에러라면 다시 전송 큐에 넣어 재시도합니다.
  • 예외 발생 (Throw Exception): 메시지 크기 초과 등 재시도해도 해결되지 않는 에러이거나, 설정된 최대 재시도 횟수를 초과하면 애플리케이션으로 예외를 던집니다.

여기서 배치 방식은 일정한 메시지가 꽉 차게 되면 전송하는 방식인데 이게 갑자기 뚝 끊겨서 안보내질 수 도 있다.

 

그래서 카프카에서는 이미 최대 대기 시간을 지정할 수 있다. (linger.ms 설정 사용) 

 

또한, 지금은 기본값으로 스티키 파티셔닝을 한다고 한다.

 

즉, 하나의 파티션에 레코드 수를 먼저 채워서 빠르게 배치전송할 수 있는 전략을 도입했다고 한다.

 

3. 내부 topic 정보 분석하기

각 브로커에 파티션이 3개가 있으며 총 9개의 파티션이 있다.

 

브로커들에서 모든 파티션이 즉각즉각 사용되는것은 아니다. 리더가 중심이 되고, 팔로워들이 끊임없이 리더의 메시지들을 복제한다.  핫 스탠바이(Hot Standby) 라고도 할 수 있다.

 

여기서 누가 리더인지 다음 명령어를 통해서 알아보자.

 

docker exec -it kafka-1 /opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-1:9892 \
> --describe --topic cluster-test

 

 

 

  • Leader: 해당 파티션의 읽기와 쓰기를 전담하는 리더 브로커의 ID
  • Replicas: 원본과 복제본이 물리적으로 저장된 전체 브로커 ID 목록
  • 결론: Replicas 목록에서 Leader로 지정된 번호를 제외한 나머지 번호들이 바로 팔로워(Follower) 브로커

 

 

필자는 여기서 한참을 해맸는데 각각 옆에 붙어있는 번호들은 Node의 ID이다.

 

위 docker-compose.yml을 보면 kafka-1, kafka-2, kafka-3 순서대로 1,2,3을 부여했다.

 

Topic: cluster-test TopicId: CXJ_chL2SwOcbd1qxm1j7w PartitionCount: 3 ReplicationFactor: 3 Configs: min.insync.replicas=1

 

현재 특정 토픽에 대해 파티션이 3개고, 레플리케이션 팩터가 3개로 3개씩 복사가 되도록 했다.

 

min.insync.replicas=1? 이거는 또 뭐지?

 

 

  • 프로듀서가 데이터를 보냈을 때, 최소 1대의 브로커(리더)만 디스크 저장에 성공하면 최종 성공(ACK)으로 간주합니다.
  • 동작: 팔로워들이 데이터를 복제할 때까지 기다리지 않고 프로듀서에게 성공 응답을 보냅니다.
  • 치명적 단점: 리더가 데이터를 저장한 직후, 팔로워가 복제해 가기 전에 서버가 죽어버리면 데이터가 영구 유실됩니다.
  • 실무 표준 (Best Practice): 데이터 유실을 막기 위해 프로덕션 환경에서는 반드시 이 값을 2 로 설정합니다. (리더 쓰기 성공 + 최소 1대의 팔로워 복제 성공까지 보장)

 

 

그 뒤에 출력값들을 분석하자.

 

Topic: cluster-test     Partition: 0     Leader: 3     Replicas: 3,1,2     Isr: 1,2,3     Elr: LastKnownElr:

 

 

"지금부터 cluster-test 토픽의 첫 번째 데이터 조각(Partition 0)에 대해 브리핑하겠습니다.

이 0번 조각의 대장(Leader)은 3번 브로커가 맡고 있으며,

만약을 대비해 3번, 1번, 2번 브로커(Replicas)가 각자의 물리 디스크에 이 0번 조각을 똑같이 복사하여 저장하고 있습니다."

 

라고 말하는 것이다.

 

즉, 각 브로커에 파티션이 3개씩 있자나. 그런데 각각에 0번 파티션에 대해서 다 복사를

3번째 노드의 0번 파티션이 3개의 0번 파티션 중에 leader라는 말이다.

 

Topic: cluster-test     Partition: 1     Leader: 1     Replicas: 1,2,3    Isr: 1,2,3     Elr: LastKnownElr:

 

"지금부터 cluster-test 토픽의 두 번째 데이터 조각(Partition 1)에 대해 브리핑하겠습니다.

이 0번 조각의 대장(Leader)은 1번 브로커가 맡고 있으며,

만약을 대비해 1번, 2번, 3번 브로커(Replicas)가 각자의 물리 디스크에 이 1번 조각을 똑같이 복사하여 저장하고 있습니다."

 

Topic: cluster-test     Partition: 2     Leader: 2     Replicas: 2,3,1     Isr: 1,2,3     Elr: LastKnownElr:

 

"지금부터 cluster-test 토픽의 세 번째 데이터 조각(Partition 2)에 대해 브리핑하겠습니다.

이 2번 조각의 대장(Leader)은 2번 브로커가 맡고 있으며,

만약을 대비해 2번, 3번, 1번 브로커(Replicas)가 각자의 물리 디스크에 이 2번 조각을 똑같이 복사하여 저장하고 있습니다."

 

어후 어렵네... 

4. /tmp/kafka-logs 폴더 내부 보기

mini pc 환경에서 kafka-2에 접속해서 /tmp/kafka-logs 폴더에서 ls명령어를 실행하면 다음과 같이 나온다. 

 

이것들이 뭔지 간단히 알아보자.

 

  • cluster-test-0, 1, 2 (고가용성 3중 복제 증명)
    • --replication-factor 3 설정의 물리적 결과입니다.
    • 해당 브로커가 자신이 리더인 파티션 원본뿐만 아니라, 다른 브로커들의 백업(Follower) 데이터까지 모두 디스크에 저장하고 있음을 보여줍니다.

 

  • __cluster_metadata-0 (KRaft 모드 / 주키퍼 독립)
    • 과거 Zookeeper가 외부에 따로 저장하던 클러스터 상태 정보(리더 위치, 브로커 생존 여부)를 카프카 스스로 내부 토픽에 저장하고 관리하는 디렉터리입니다.

 

  • meta.properties (브로커 물리적 신분증)
    • 설정 파일로 주입했던 CLUSTER_ID와 고유 번호(NODE_ID)가 텍스트로 박혀있는 파일입니다. 클러스터 소속을 증명합니다.

 

  • __consumer_offsets-0 ~ 49 (네트워크 통신 정상 동작)
    • 컨슈머가 데이터를 어디까지 읽었는지 기록하는 카프카 전용 내부 토픽(기본 50개 파티션)입니다.
    • 앞서 실행한 컨슈머 스크립트가 포트 매핑을 뚫고 성공적으로 데이터를 소비했기 때문에 카프카 엔진이 즉시 자동 생성한 것입니다.

 

직접 그림을 그려보고 정리하다보니 내용이 너무 어렵기도 하고 너무 내용이 많다. 

 

필자가 막 정리하다보니 적을건 많고 내용은 어렵다 보니 내가 이해한것이 맞는지도 의심이 간다. 

 

다음 글에서는 하이워터마크와 리더 에포크가 무엇인지, 그리고 controller와 그러한 리더 선출 전략을 알아보려고 한다.

 


참고

https://learnbyinsight.com/2020/07/26/beginner-guide-to-understand-kafka/

https://www.linkedin.com/pulse/kafkas-evolution-zookeeper-vs-kraft-anuj-tyagi-n2x4e/

https://docs.confluent.io/platform/current/kafka-metadata/kraft.html#configure-cp-with-kraft