티스토리 뷰

들어가며

이전 포스팅에서 두 가지 원천 데이터 Producer를 완성했다.

  • `dummy_generator.py` - OpenRTB 형식의 합성 광고 이벤트 생성기
  • `criteo_producer.py` - Criteo Attribution Dataset 재생기

이번 포스팅에서는 이 Producer들이 실제로 메시지를 보낼 Kafka 환경을 로컬에 구성하고, 

메시지가 정상적으로 흐르는지 end-to-end로 검증한다.


왜 로컬 Kafka인가

최종 목표는 EKS 위에 Kafka 클러스터를 올리는 것이다.

하지만 개발 단계에서 매번 클라우드 환경에 배포하면 검증 사이클이 느려진다. (그리고 비싸다...)

로컬 Docker로 먼저 검증하고, 이후 전환할 때는 config.py의 몇가지 설정만 바꾸면 된다.

 

KRaft vs ZooKeeper

Kafka는 전통적으로 ZooKeeper라는 별도 서비스가 클러스터 메타데이터를 관리했다.

하지만 Kafka 프로젝트의 방향은 명확히 KRaft로 이동했다.

버전 내용
Kafka 3.3 (2022) KRaft 정식 GA
Kafka 3.x ZooKeeper / KRaft 둘 다 지원
Kafka 4.0 (2024) ZooKeeper 지원 완전 제거

 

KRaft는 Kafka 자체에 Raft 합의 알고리즘을 내장해서 ZooKeeper 없이 동작한다.

컨테이너 1개로 구성이 단순해지고, 나중에 올릴 EKS Kafka도 최신 버전 기준이므로 일관성 유지 차원에서도 KRaft를 선택했다.


docker-compose.yaml 파일 구성

services:
  kafka:
    image: apache/kafka:3.9.0
    container_name: kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
      KAFKA_LOG_RETENTION_HOURS: 720
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
    volumes:
      - kafka-data:/var/lib/kafka/data
    healthcheck:
      test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

volumes:
  kafka-data:
    driver: local

주요 설정 설명

  • `LISTENERS` - Kafka가 컨테이너 내부에서 실제로 바인딩하는 주소
  • `ADVERTISED_LISTENERS` - 외부 클라이언트에게 알려주는 주소 

Producer가 처음 Kafka에 연결하면 Kafka는 `ADVERTISED_LISTENER`에 적힌 주소를 돌려준다.

Producer는 이 주소로 실제 메시지를 보낸다.

컨테이너 내부 IP를 그대로 돌려주면 호스트 머신에서 접근할 수 없으므로 `localhost:9092`로 명시한다.

 

CONTROLLER 리스너

`CONTROLLER://:9093`은 KRaft 내부 Raft 합의 통신 전용 포트다.

`PLAINTEXT`와 `CONTROLLER`는 리스너에 붙이는 이름(라벨)이고,

`LISTENER_SECURITY_PROTOCOL_MAP`에서 이 이름에 실제 보안 프로토콜(`PLAINTEXT, SSL등)을 연결한다.

 

단일 브로커 필수 설정

Kafka는 내부 시스템 토픽 (`__consumer_offsets`, `__transcation_state`)의 복제 인수 기본값이 3이다.

브로커가 1개인 로컬 환경에서는 `Not enough replicas` 오류가 발생하므로 반드시 1로 낮춰야한다.


토픽 설계 

파티션 수: 3

50개 캠페인이 파티션 키 (`campaign_id`)로 분산된다.

파티션 수는 나중에 줄일 수 없으므로 처음에 결정이 중요하다.

 

보존기간: 토픽별 차등 설계

Kafka의 보존 기간과 광고 전환 어트리뷰션 윈도우는 레이어가 다른 문제다.

Kafka (전송 버퍼)  →  S3 Bronze (영구 저장)  →  Silver/Gold (분석)
  보존: 며칠            보존: S3 정책 따라          전환 추적: 여기서

 

전환 어트리뷰션은 S3에 저장된 Bronze/Silver 데이터로 한다.

Kafka의 보존 기간이 의미하는 것은 "Consumer(Glue ETL)가 장애 시 얼마나 기다려줄 수 있는가"다.

 

그래서 아래와 같은 근거로 인해 토픽별 보존기간을 설정해두었다.

토픽 보존 기간 근거
ad-requests 72시간 고볼륨. 72시간 내 S3 적재 못 하면 파이프라인 자체가 문제
ad-impressions 72시간 동일
ad-clicks 14일 클릭 기반 어트리뷰션 윈도우 고려
ad-conversions 30일 업계 전환 윈도우 최대치. 가장 중요하고 볼륨은 가장 적음

 

Kafka는 브로커 기본값과 토픽 개별값 두 계층으로 보존 기간을 관리한다.

토픽 생성 시 `--config retention.ms=값`을 지정하면 브로커 기본값을 덮어쓴다.

kafka-topics.sh --create \
  --topic ad-conversions \
  --partitions 3 \
  --replication-factor 1 \
  --config retention.ms=2592000000   # 30일

실행 및 검증

1. Kafka 기동

docker compose -f infra/docker-compose.yaml up -d
docker inspect kafka --format='{{.State.Health.Status}}'
# healthy 확인

2. 토픽 생성

./infra/kafka-topics.sh

토픽 생성

3. Producer 실행 

`dummy_generator.py`를 통한 더미데이터 생성

앞선 포스팅에서 한 로컬 DRY 모드가 아니라 kafka 토픽으로 전송시키는 것.

cd code/producers
source .venv/bin/activate
python dummy_generator.py

더미 데이터 생성

4. 메시지 수신 확인

kafka 토픽으로 약 3,484건의 데이터를 전송하였고,

이를 kafka-console-consumer를 통해 실제로 Kafka에 메시지가 쌓였는지도 확인해보았다.

 

kafka-console-consumer는 Kafka 내장 CLI Consumer로,

토픽에 저장된 메시지를 터미널에 출력해준다. (Producer 없이도 과거에 쌓인 메시지를 읽어볼 수 있음 --from-beginning 옵션)

docker exec kafka /opt/kafka/bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 \
  --topic ad-clicks \
  --from-beginning \
  --max-messages 3

Kakfa내 실제 메시지 확인

5. 파티션 분산 확인

토픽별 파티션 3개에 메시지가 고르게 분산됐는지 숫자로 확인해보았다.

우선은 ad-click 토픽만을 확인했고, 해당 토픽의 1,2,3 번째 파티션에 각각 16,16,21개의 메시지들이 저장된것을 확인하였다.

docker exec kafka /opt/kafka/bin/kafka-get-offsets.sh \
  --bootstrap-server localhost:9092 \
  --topic ad-clicks

토픽 별 메시지 수 확인

해당 값이 정상적인 값인지 확인하기 위해 3,484건을 기준으로 각 단계 기대값을 계산해보았다.

request: 3,484건 x 1,000 = 3,484건 (항상 발행)
impression: 3,484건 x 0.800 = 2,787건 (fill_rate가 80%로 가정했으니까)
click: 2,787건 x 0.025 = 70건 (CTR이 2.5%로 가정했으니까)
conversion: 70건 x 0.035 = 2건

 

그럼 위 기대값을 바탕으로 ad-click 토픽에는 70건이 있어야하지만, 실제 결과는  53건이었다.

이는 어디까지나 70건이 기대값이기 때문이다.

 

실제는 53건이다. 이항분포의 표준편차(√(n × p × (1-p)) ≈ 8.3건) 범위를 고려하면 정상 편차다.

 


다음 글

이제 데이터 셋 작업은 모두 완료했다.

다음 단계는 본격적인 메달리온 아키텍처 구현인데 그중 첫번째인 Bronze 레이어 구축이다.

 

  • S3 버킷 및 Iceberg 테이블 설계
  • Glue Streaming Job 작성 (Kafka -> S3 raw 적재)
  • Athena로 적재 결과 확인

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함