💡 왜 카프카에서 고가용성과 복제가 중요할까?
오늘은 카프카의 고가용성과 복제 메커니즘에 대해 다루어 보겠습니다.
대규모 트래픽을 처리하는 현대의 백엔드 아키텍처에서 메시지 큐 또는 이벤트 스트리밍 플랫폼은 시스템의 혈관과도 같은 역할을 합니다. 만약 결제 시스템에서 주문 정보를 카프카로 발행했는데, 단일 장애점(SPOF)으로 인해 브로커 서버가 다운되면서 데이터가 유실된다면 어떻게 될까요?
고객의 결제는 완료되었지만 주문 생성은 누락되는 치명적인 데이터 정합성 문제가 발생합니다.
카프카는 애초에 분산 시스템으로 설계되었으며, 장애가 발생하는 것은 예외적인 상황이 아니라 언제든 일어날 수 있는 상수로 취급합니다. 서버의 하드웨어 결함, 네트워크 단절, 데이터센터의 정전 등 수많은 변수 속에서도 시스템이 중단 없이 서비스되고 데이터를 안전하게 보존할 수 있도록 만드는 핵심 기술이 바로 복제(Replication)와 이를 통한 고가용성(High Availability) 확보입니다.
카프카는 데이터를 여러 대의 서버에 분산하여 저장하고 복제함으로써, 한 대의 서버가 죽더라도 다른 서버가 즉각적으로 그 역할을 대신할 수 있는 견고한 아키텍처를 제공합니다.
🤦♂️ 카프카 장애 경험과 흔히 하는 실수들
카프카를 운영하거나 연동 개발을 하다 보면, 개념을 정확히 알지 못해 돌이킬 수 없는 장애를 겪곤 합니다.
1️⃣ 프로듀서의 전송 속도만 높이기 위해 Acks 옵션을 0이나 1로 설정하는 경우
개발 초기 환경이나 로컬 테스트에서는 데이터를 밀어 넣는 속도가 중요해 보일 수 있습니다. 그래서 프로듀서 설정에서 응답을 기다리지 않거나 리더 브로커의 응답만 받는 옵션을 사용하곤 합니다. 하지만 운영 환경에서 브로커 한 대가 갑자기 재시작되는 순간, 아직 다른 브로커로 복제되지 않은 최신 메시지들은 영기 속으로 사라지게 됩니다.
"메시지 유실이 절대 없어야 한다"는 비즈니스 요구사항을 무시한 채 성능만 쫓다가 발생하는 가장 흔하고 뼈아픈 실수입니다.
2️⃣ Replication Factor(복제 계수)만 높이면 무조건 안전하다고 맹신하는 것
카프카 토픽을 생성할 때 복제 계수를 3으로 설정했다고 안심하는 경우가 많습니다.
"서버 세 대에 복사되니까 한 대 죽어도 끄떡없겠지?"라고 생각하지만, 정작 최소 동기화 유지 복제본 수인 min.insync.replicas 설정을 기본값인 1로 방치하는 경우가 허다합니다. 이 경우 리더 브로커 혼자만 데이터를 가지고 있어도 프로듀서는 데이터 전송이 성공했다고 판단해 버립니다. 결국 복제 계수가 3이더라도 실질적인 고가용성은 전혀 확보되지 않은 상태로 아슬아슬하게 운영되는 것입니다.
3️⃣ Unclean Leader Election 옵션을 무분별하게 켜두는 것
장애가 발생했을 때 어떻게든 서비스가 멈추는 것(가용성 저하)을 막고자, 리더와 데이터 동기화가 완전히 끊어진 뒤쳐진 팔로워를 강제로 새로운 리더로 승격시키는 옵션입니다. 이 옵션이 켜져 있으면 토픽의 읽기/쓰기는 재개되지만, 기존 리더가 가지고 있던 최신 데이터는 영구적으로 날아가 버립니다.
데이터의 일관성보다 서비스의 생존이 압도적으로 중요한 시스템이 아니라면, 이 옵션은 데이터 정합성에 시한폭탄과 같습니다.
🛠️ 핵심 : 복제, 리더와 팔로워, 그리고 ISR과 Acks
이제 카프카가 고가용성을 달성하기 위해 내부적으로 어떤 메커니즘을 사용하는지 핵심 개념들을 하나씩 뜯어보겠습니다.
1️⃣ Replication (복제): 데이터의 생명줄
카프카의 복제는 토픽의 기본 단위인 파티션(Partition)을 기준으로 이루어집니다. 파티션은 데이터가 순차적으로 기록되는 논리적인 파일과 같습니다.
복제란 이 파티션의 복사본을 여러 카프카 브로커에 분산하여 저장하는 작업입니다.
토픽을 생성할 때 설정하는 replication.factor 값이 복제의 규모를 결정합니다. 예를 들어 replication.factor를 3으로 설정하면, 원본 파티션을 포함해 총 3개의 동일한 파티션 데이터가 서로 다른 브로커 3대에 나뉘어 저장됩니다. 브로커 하나가 물리적인 손상을 입어 완전히 파괴되더라도, 나머지 두 대의 브로커에 데이터가 고스란히 남아있으므로 서비스는 중단 없이 지속될 수 있습니다.
2️⃣ Leader (리더)와 Follower (팔로워): 역할의 분리
파티션이 여러 개로 복제되어 여러 브로커에 흩어져 있다면, 프로듀서나 컨슈머는 도대체 어느 브로커와 통신해야 할까요? 카프카는 혼란을 방지하기 위해 엄격한 역할 분리를 적용합니다. 바로 리더와 팔로워 개념입니다.
리더(Leader)는 복제된 파티션 중 단 한 개만 가질 수 있는 특별한 지위입니다. 프로듀서의 데이터 쓰기(Write)와 컨슈머의 데이터 읽기(Read) 요청은 오직 리더 파티션을 가진 브로커가 독점하여 처리합니다. (최근 카프카 버전에서는 가장 가까운 팔로워에서 읽어오는 기능이 추가되긴 했지만, 기본적이고 전통적인 구조는 리더가 모든 요청을 전담합니다.)
팔로워(Follower)는 리더 파티션을 제외한 나머지 복제본들입니다. 팔로워의 유일하고도 가장 중요한 임무는 리더의 데이터를 바라보며 자신에게 지속적으로 데이터를 복사해 오는 것(Fetch)입니다. 팔로워는 클라이언트의 직접적인 읽기/쓰기 요청을 처리하지 않고 오로지 데이터 동기화에만 집중합니다.
만약 리더 브로커가 장애로 다운되면, 카프카 클러스터는 즉시 팔로워 중 하나를 새로운 리더로 선출하여 서비스 장애 시간을 최소화합니다.
3️⃣ ISR (In-Sync Replicas): 신뢰할 수 있는 동기화 그룹
그렇다면 리더가 죽었을 때, 아무 팔로워나 새로운 리더가 될 수 있을까요? 절대 아닙니다. 리더와 데이터 동기화가 너무 뒤쳐진 팔로워가 리더가 되면 심각한 데이터 유실이 발생합니다. 그래서 카프카는 ISR (In-Sync Replicas) 이라는 중요한 논리적 그룹을 관리합니다.
ISR은 리더 파티션과 데이터를 완벽하게 동기화하고 있는 신뢰할 수 있는 복제본들의 모임입니다. 현재의 리더도 ISR 그룹에 포함되며, 리더의 데이터를 지연 없이 잘 따라오고 있는 팔로워들만이 ISR 그룹에 속할 수 있습니다.
리더는 팔로워들이 데이터를 잘 가져가고 있는지 지속적으로 감시합니다. 만약 특정 팔로워가 네트워크 지연이나 서버 부하로 인해 replica.lag.time.max.ms 설정 시간 동안 리더에게 데이터를 요청하지 않거나 동기화를 따라오지 못하면, 리더는 해당 팔로워를 ISR 그룹에서 추방(Evict)합니다.
오직 ISR 그룹에 속한 팔로워만이 새로운 리더로 선출될 자격을 가집니다. 이를 통해 카프카는 장애 조치 과정에서도 데이터 정합성을 철저하게 보장합니다.
4️⃣ Acks 옵션과 min.insync.replicas: 성능과 데이터 정합성의 줄다리기
프로듀서가 카프카로 메시지를 전송할 때, 성공으로 간주하는 기준을 정하는 것이 바로 Acks 옵션입니다. 이 옵션을 어떻게 설정하느냐에 따라 성능과 데이터 안전성이 극명하게 갈립니다.
Acks = 0
- 프로듀서는 리더 브로커로 메시지를 전송하고 응답을 기다리지 않습니다.
- 전송 즉시 다음 메시지를 보낼 수 있어 처리 속도는 최고조에 달합니다.
- 하지만 네트워크 문제로 메시지가 도달하지 못했거나, 브로커가 다운된 상태라도 프로듀서는 알 길이 없으므로 데이터 유실 확률이 가장 높습니다. (센서 데이터 로깅 등에 사용)
Acks = 1
- 프로듀서는 리더 브로커가 자신의 로컬 디스크에 메시지를 성공적으로 기록할 때까지만 기다립니다.
- 팔로워들이 데이터를 복제했는지는 확인하지 않습니다.
- 속도와 안전성의 적절한 타협점입니다. 하지만 리더가 메시지를 기록하고 프로듀서에게 성공 응답을 보낸 직후, 팔로워가 복제해 가기 전에 찰나의 순간 리더가 죽어버리면 해당 메시지는 유실됩니다.
Acks = all (또는 -1)
- 프로듀서는 리더 브로커뿐만 아니라, ISR 그룹에 속한 모든 팔로워가 데이터를 성공적으로 복제할 때까지 기다립니다.
- 데이터 유실이 발생하지 않는 가장 안전한 설정입니다. 금융, 결제 등 절대 데이터가 사라지면 안 되는 도메인에서 필수적으로 사용됩니다.
- 동기화를 모두 기다려야 하므로 처리 속도는 가장 느립니다.
✅ 중요 : min.insync.replicas의 역할
Acks=all을 사용할 때 반드시 같이 설정해야 하는 브로커(또는 토픽) 설정이 바로 min.insync.replicas입니다.
만약 브로커가 3대이고 복제 계수가 3인데, 리더를 제외한 나머지 2대의 팔로워가 모두 죽어 ISR 그룹에 리더 1대만 남았다고 가정해 봅시다. 이때 Acks=all로 전송하면 어떻게 될까요?
ISR에 본인(리더) 혼자밖에 없으므로, 리더에만 기록하고 바로 프로듀서에게 성공 응답을 줍니다. 결국 Acks=1과 똑같이 동작하여 고가용성이 무너집니다.
이를 방지하기 위해 min.insync.replicas를 2로 설정합니다. 이 설정은 "Acks=all 요청을 처리하려면 최소한 ISR 그룹에 2개 이상의 복제본이 살아있어야 한다"는 뜻입니다. 만약 ISR이 1개로 줄어들면, 브로커는 프로듀서의 쓰기 요청을 거부(NotEnoughReplicasException 발생)하여 비정상적인 상태에서의 데이터 기록을 원천 차단합니다. 가용성을 조금 희생하더라도 데이터의 완벽한 일관성을 지키는 안전장치입니다.
아래는 자바 스프링 부트 환경에서 완벽한 고가용성을 위한 카프카 프로듀서 설정 코드 예제입니다.
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class KafkaProducerConfig {
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
// 카프카 브로커 주소 설정
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// 데이터 정합성을 위한 최고 수준의 보장 설정
// 리더와 ISR 그룹의 모든 팔로워가 복제를 완료해야 성공으로 처리
configProps.put(ProducerConfig.ACKS_CONFIG, "all");
// 프로듀서의 재시도 횟수를 최대로 설정하여 일시적인 네트워크 오류 등에 대비
configProps.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);
// 멱등성 프로듀서 활성화 (재시도 중복 메시지 방지)
configProps.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true");
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
브로커 또는 토픽 레벨의 설정인 min.insync.replicas=2 는 서버 측 설정이므로 위 클라이언트 코드와 별개로 카프카 클러스터에 적용되어 있어야 위 코드가 완전한 효과를 발휘합니다.
📝 오늘의 3줄 요약
- 카프카의 복제(Replication)는 단일 서버 장애 상황에서도 시스템 중단과 데이터 유실을 막는 핵심 고가용성 메커니즘이다.
- 읽기와 쓰기는 오직 리더(Leader) 파티션이 전담하며, 팔로워(Follower)는 리더와 동기화하여 ISR(In-Sync Replicas) 그룹을 형성해 차기 리더 자격을 얻는다.
- 데이터 정합성이 중요하다면 프로듀서의 Acks를 all로 설정하고, 브로커의 min.insync.replicas를 2 이상으로 조합하여 운영해야만 진정한 고가용성을 확보할 수 있다.
'Kafka' 카테고리의 다른 글
| Kafka 데이터의 분산 처리 파티션과 브로커 (0) | 2026.03.04 |
|---|---|
| 카프카(Kafka) 핵심구조 Pub/Sub, 컨슈머, 프로듀서, 토픽 (0) | 2026.03.04 |
| [Kafka] 오프셋 커밋 동기 비동기, 리밸런스 리스너 (2) | 2022.01.24 |
| [Kafka] 리밸런싱 오프셋 커밋 subscribe poll 개념 설명 (0) | 2022.01.21 |
| Kafka Producer Client 주요 옵션 설명과 메시지 키/특정 파티션/동기-비동기 전송 (0) | 2021.12.02 |
댓글