백엔드 데이터 인프라 103편 — Kafka Geo-Replication (MirrorMaker 2)

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 103편. Kafka Geo-Replication — MirrorMaker 2 의 모든 것. 4가지 connector(MirrorSource·MirrorCheckpoint·MirrorHeartbeat·MirrorClient), Active-Active vs Active-Passive 운영, topic prefix·remote 토픽 패턴, DR 시나리오 step-by-step 까지 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 103편 — Kafka Geo-Replication (MirrorMaker 2)

이 글은 백엔드 데이터 인프라 시리즈 130편 중 103편이에요. 102편 에서 다중 DC(데이터센터) 패턴 을 잡았다면, 이번 103편은 cluster 간 복제 도구MirrorMaker 2 깊이.

MirrorMaker 2가 어렵게 느껴지는 이유

MM2(MirrorMaker 2) 가 어려운 건 옛 MM1 의 한계를 거의 다 해결하면서 구조 자체가 그만큼 복잡해졌기 때문이에요. MM1 은 consumer offset 추적도, ACL(접근 제어 목록) sync 도 못 했는데, MM2 는 둘 다 한다. 대신 봐야 할 게 늘었다.

첫째, 4가지 connector(복제 작업 단위) 의 역할 이 각각 다르다. MirrorSource·MirrorCheckpoint·MirrorHeartbeat·MirrorClient 가 각자 다른 일을 맡는다.

둘째, Topic 이름 변환 규칙. 원본 cluster 의 orders 가 mirror 후 primary.orders 로 바뀐다. 이 prefix 가 운영의 핵심.

셋째, Active-Active 시 무한 루프 방지. 양방향 mirror 는 같은 메시지가 끝없이 왕복 할 위험을 안고 있어서 heartbeat 와 prefix 로 차단 한다.

이 글에서 MM2 구조·4 connector·운영 패턴·DR(재해 복구) 시나리오까지 본다.

MirrorMaker 2 구조

MirrorMaker 2 = Kafka Connect cluster + 4 specialized connector

[Source Cluster A] ──→ [MM2 Worker] ──→ [Target Cluster B]
                        │
                        ├─ MirrorSourceConnector (topic 데이터 복제)
                        ├─ MirrorCheckpointConnector (consumer offset 추적)
                        ├─ MirrorHeartbeatConnector (heartbeat)
                        └─ MirrorMaker2 Driver (전체 조율)

MM2 는 내부적으로 Kafka Connect(분산 데이터 파이프라인 프레임워크) 인프라 를 가져다 쓴다. Connect Worker 가 connector 들을 실행 하는 구조.

4가지 Connector

1. MirrorSourceConnector

Topic 데이터 복제 를 맡는다. Source topic 의 메시지를 Target topic 으로 그대로 옮긴다.

Source: orders (partition 0~9, 1M messages)
   ↓ MirrorSourceConnector
Target: primary.orders (partition 0~9, 동일 메시지)

특징:

  • Source partition → Target partition 1:1 매핑 (partition 순서 유지)
  • Source key·value·timestamp·headers 모두 복제
  • Source-specific header 추가 (__MM2_SOURCE_PARTITION)

2. MirrorCheckpointConnector

Consumer offset 추적·변환 을 담당한다. DR 시나리오에서 가장 중요한 부품.

Source Cluster:
  Topic "orders", Consumer Group "workers" → offset 12345

   ↓ MirrorCheckpointConnector

Target Cluster:
  Topic "primary.orders" → offset 12345 (대략)
  Special topic "primary.checkpoints.internal"

DR 이 터져서 consumer 가 Target cluster 로 이동할 때 어디부터 다시 처리해야 하는지 가 여기서 결정난다. Source 에서 처리한 offset 을 Target 의 동등 offset 으로 변환해 둔다.

3. MirrorHeartbeatConnector

Cluster 간 heartbeat 을 쏜다. mirror 가 살아 있는지 를 모니터링하는 신호.

Source → "heartbeats" topic 에 주기적 heartbeat
   ↓ Mirror
Target → "primary.heartbeats" 받음
  • Heartbeat 받으면 = mirror 정상
  • 일정 시간 끊김 = alert
  • Heartbeat lag = mirror lag 측정 가능

4. MirrorMaker 2 Driver

위 3 connector 의 전체 lifecycle 을 조율 한다. 사용자가 직접 다루지는 않는다.

설정 — mm2.properties

# Cluster 정의
clusters = primary, secondary
primary.bootstrap.servers = kafka-dc1:9092
secondary.bootstrap.servers = kafka-dc2:9092

# Replication 흐름
primary->secondary.enabled = true
primary->secondary.topics = orders, payments, users.*

# 옵션
primary->secondary.replication.factor = 3
primary->secondary.tasks.max = 10
primary->secondary.refresh.topics.enabled = true
primary->secondary.refresh.topics.interval.seconds = 60

# Active-Active 도 가능
# secondary->primary.enabled = true
# secondary->primary.topics = ...

# Topic naming
primary->secondary.replication.policy.separator = .
primary->secondary.replication.policy.class = \
    org.apache.kafka.connect.mirror.DefaultReplicationPolicy

실행:

$ bin/connect-mirror-maker.sh mm2.properties

Topic Naming — primary.orders 패턴

기본 정책은 <source-cluster>.<topic> 이에요.

Source: kafka-dc1 의 "orders"
   ↓
Target: kafka-dc2 의 "primary.orders"

이렇게 prefix 를 붙이는 이유는 무한 루프 방지 때문.

Active-Active 환경:
  DC1 의 "orders" → DC2 의 "primary.orders"
  DC2 의 "primary.orders" → DC1 의 "secondary.primary.orders"  (이걸 방지)

MirrorSourceConnector자기가 mirror 한 topic 은 다시 mirror 하지 않는다. Prefix 로 자기 origin 을 식별하는 거.

IdentityReplicationPolicy — Prefix 없음 (특수)

replication.policy.class = \
    org.apache.kafka.connect.mirror.IdentityReplicationPolicy

원본 이름을 그대로 가져간다 (ordersorders). Active-Passive DR 에 잘 맞는다. consumer 가 Target 으로 갈아탈 때 같은 topic 이름 이라 자연스러우니까.

단점은 Active-Active 무한 루프 위험. heartbeat 와 source header 로 막긴 하지만 신중하게 봐야 한다.

Consumer Offset Translation — DR 의 핵심

시나리오

DC1 (Source):
  Consumer "workers" 가 orders 의 offset 50000 까지 처리

   ↓ MirrorCheckpoint 가 추적

DC2 (Target):
  primary.orders 에 같은 메시지들이 있음 (다른 offset 일 수 있음)
  "primary.checkpoints.internal" 에 mapping 저장:
    workers: orders[0]=50000 → primary.orders[0]=49997

DR 트리거 시:

Consumer 가 DC2 로 이동
  ↓
RemoteClusterUtils.translateOffsets() 호출
  ↓
DC2 의 적절한 offset 으로 자동 변환
  ↓
처리 재개 (가능한 한 정확한 지점)

at-least-once 보장. 약간의 중복은 생길 수 있지만 손실은 없다.

ACL · Config Sync

primary->secondary.sync.topic.acls.enabled = true
primary->secondary.sync.topic.configs.enabled = true

Source topic 의 ACL 과 config 도 Target 으로 같이 sync 된다. DR 시 동일 환경 을 만들어 두는 장치.

운영 패턴

Pattern 1: Active-Passive DR

clusters = primary, dr
primary.bootstrap.servers = ...
dr.bootstrap.servers = ...

primary->dr.enabled = true
primary->dr.topics = .*

# Identity policy (DR 용)
primary->dr.replication.policy.class = \
    org.apache.kafka.connect.mirror.IdentityReplicationPolicy

평소엔 primary 만 운영하고, DR 이 터지면 dr 로 failover.

Pattern 2: Active-Active

clusters = us-east, eu-west

us-east->eu-west.enabled = true
us-east->eu-west.topics = orders

eu-west->us-east.enabled = true
eu-west->us-east.topics = orders

각 region 의 사용자가 local 에 read/write 하고, 데이터는 양방향으로 sync 된다. naming convention 이 정말 중요해진다 (prefix 로 region 을 구분).

Pattern 3: Aggregate

clusters = us, eu, asia, global

us->global.enabled = true
eu->global.enabled = true
asia->global.enabled = true

3개의 local 을 1개의 global 로 모으는 형태. 분석·ML 용도로 자주 쓴다.

실행 — Connect Worker 모드

Standalone (개발용)

$ bin/connect-mirror-maker.sh mm2.properties

Distributed (운영)

운영에서는 여러 MM2 worker 를 cluster 형태로 띄운다 (Kafka Connect distributed mode).

# 각 worker 에서
$ bin/connect-mirror-maker.sh mm2-worker.properties

worker 간 자동으로 작업을 분담하고, 한 worker 가 죽어도 다른 worker 가 인수한다.

DR 시나리오 Step-by-Step

Step 1: Mirror 정상 작동 확인

# heartbeat topic 확인
$ kafka-console-consumer.sh --bootstrap-server kafka-dr:9092 \
    --topic primary.heartbeats --from-beginning

# Mirror lag 확인
$ kafka-consumer-groups.sh --bootstrap-server kafka-dr:9092 \
    --describe --group connect-mirror-maker-2-source

Step 2: DR 트리거 (primary 죽음)

primary 장애 발생
→ DNS 변경: kafka-app.example.com → kafka-dr.example.com
→ Consumer 들이 dr cluster 로 자동 재연결

Step 3: Consumer Offset 변환

DR 시 consumer 가 새 cluster 에서 시작 offset 을 자동으로 변환받는다.

// Consumer 측 코드 (RemoteClusterUtils 활용)
Map<TopicPartition, OffsetAndMetadata> offsets = 
    RemoteClusterUtils.translateOffsets(props, "primary", "workers",
                                         Duration.ofSeconds(10));
consumer.commitSync(offsets);

Step 4: 운영 회복 확인

- Consumer lag 모니터링
- 데이터 일관성 확인
- Application metric 정상 확인

Step 5: Primary 복구 후 (Optional)

- DC1 복구
- Mirror 방향 임시 역전 (DR → Primary 로)
- 동기 완료 후 다시 Primary 로 전환

모니터링 메트릭

MM2 JMX(Java 모니터링 표준 인터페이스):

  • mirror-source-connector — 복제 throughput
  • record-byte-rate — 초당 바이트
  • record-age-ms-avg — 메시지 age (mirror lag 추정)
  • checkpoint-latency-ms — checkpoint 동기 latency

핵심은 record-age-ms-avg = source 메시지가 target 에 도착하기까지의 평균 지연.

한계·실무 함정

1. MM2 Worker 자체 가용성

MM2 worker 도 cluster 로 운영해야 한다. 한 worker 가 죽으면 자동으로 인수하지만, 최소 3 worker 는 띄워두는 게 안전.

2. Source ↔ Target 보안 매핑

cluster 마다 ACL 과 인증이 다르다. MM2 가 양쪽에 연결할 자격증명 을 모두 들고 있어야 한다.

3. Topic auto-create

primary->secondary.refresh.topics.enabled=true (기본값) 이면 source 에 새 topic 이 생기는 즉시 자동으로 mirror 가 시작된다. 원치 않으면 topic whitelist 를 명시.

4. Offset Translation 의 한계

완벽한 1:1 mapping 은 아니다. 일부 중복 처리가 생길 수 있으니 consumer 쪽에서 멱등성을 챙긴다.

5. WAN 대역폭

Mirror 는 모든 mirror topic 의 트래픽이 WAN(광역 네트워크) 을 통과 하는 구조. 불필요한 topic 은 mirror 하지 말 것.

6. Active-Active 의 cycle

Identity policy 와 Active-Active 를 같이 쓰면 prefix 가 없어서 무한 루프 위험 이 있다. Source header 로 막지만 신중하게 검증해야 한다.

시험 직전 한 번 더 — MirrorMaker 2 함정 압축 노트

  • MirrorMaker 2 = DC 간 mirror 표준 도구, Kafka Connect 기반
  • 4가지 Connector — MirrorSource (데이터) · MirrorCheckpoint (offset 추적) · MirrorHeartbeat (cluster 간 heartbeat) · MirrorMaker 2 Driver (조율)
  • MirrorSourceConnector = topic 데이터 복제, partition 1:1 매핑
  • MirrorCheckpointConnector = consumer offset 추적·translation (DR 핵심)
  • MirrorHeartbeatConnector = mirror 정상 모니터링
  • Topic Naming = 기본 <source-cluster>.<topic> (예: primary.orders)
  • 이유 = 무한 루프 방지 (이미 mirror 된 topic 은 다시 mirror X)
  • IdentityReplicationPolicy = prefix 없음, DR 시 같은 topic 이름 (Active-Passive 용)
  • Consumer Offset Translation = RemoteClusterUtils.translateOffsets()
  • DR 시 consumer 이동 → 자동 offset 변환 → at-least-once 보장
  • ACL·Config Sync = sync.topic.acls.enabled·sync.topic.configs.enabled
  • 운영 패턴 3가지 = Active-Passive DR · Active-Active · Aggregate
  • 실행 = Standalone (개발) vs Distributed (운영, 여러 worker)
  • 한 worker 죽으면 다른 worker 자동 인수
  • DR Step-by-Step = 정상 확인 → DR 트리거 (DNS) → Offset 변환 → 모니터링 → 복구 후 reverse mirror
  • 모니터링 = record-age-ms-avg (mirror lag 핵심)
  • 함정 — MM2 worker 가용성 (3+ worker)
  • 함정 — 양쪽 cluster 인증 매핑
  • 함정 — refresh.topics.enabled 자동 mirror
  • 함정 — Offset Translation 완벽 1:1 X (멱등성)
  • 함정 — WAN 대역폭 비용
  • 함정 — Active-Active + Identity policy = cycle 위험

공식 문서: Kafka Geo-Replication 에서 MM2 자세한 설정·시나리오를 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!