1. Kafka에서 신뢰성이란?
Kafka는 대용량 이벤트 데이터를 안정적으로 전달하기 위한 분산 메시징 플랫폼이다
하지만 메시지를 Kafka에 저장한다고 해서 데이터 유실이나 중복 처리가 무조건 방지되는 것은 아니다
예를 들어 주문 서비스에서 주문 생성 이벤트를 Kafka에 전송했다고 가정했을 때 프로듀서가 메시지를 전송한 직후 네트워크 장애가 발생하면 다음과 같은 상황이 발생할 수 있다
- 브로커가 메시지를 저장하지 못했을 수 있다
- 브로커는 메시지를 저장했지만 프로듀서가 응답을 받지 못했을 수 있다
- 프로듀서가 재시도하면서 동일한 메시지가 중복 저장될 수 있다
- 컨슈머가 메시지를 처리한 뒤 오프셋을 커밋하기 전에 장애가 발생해 메시지를 다시 처리할 수 있다
이처럼 데이터 전달 과정에서는 여러 종류의 장애가 발생할 수 있다
그러므로 Kafka의 신뢰성을 이해하려면 프로듀서, 브로커, 컨슈머를 개별적으로 살펴보는 것뿐 아니라 전체 데이터 흐름을 함께 이해해야 한다.
Kafka의 신뢰성을 구성하는 핵심 요소
- 프로듀서가 메시지를 안전하게 전송하는 방법
- 브로커가 데이터를 복제하고 장애에 대응하는 방법
- 컨슈머가 메시지를 처리하고 오프셋을 관리하는 방법
- 재시도와 중복 처리 문제를 해결하는 방법
2. 신뢰성 있는 데이터 전달을 위한 고려 사항
Kafka에서 신뢰성을 설계할 때는 먼저 어떤 수준의 데이터 전달을 보장해야 하는지 정의해야 한다
일반적으로 데이터 전달 방식은 다음과 같이 구분할 수 있다
2.1 최대 한 번(At-most-once)
메시지를 최대 한 번 처리하는 방식
중복 처리를 방지하는 데 유리하지만, 장애 상황에서 메시지가 유실될 수 있다
예시
컨슈머가 메시지를 처리하기 전에 오프셋을 커밋하면, 처리 도중 장애가 발생하더라도 재시작 후 해당 메시지를 건너뛸 수 있다
데이터 유실보다 중복 처리가 더 큰 문제가 되는 상황에서는 고려할 수 있지만, 주문이나 결제처럼 데이터 유실이 허용되지 않는 시스템에서는 신중하게 적용해야 한다
2.2 최소 한 번(At-least-once)
메시지가 적어도 한 번 처리되도록 하는 방식
메시지 유실을 줄이는 데 유리하지만, 동일한 메시지가 여러 번 처리될 수 있다
예시
컨슈머가 데이터베이스에 주문을 저장한 뒤 오프셋을 커밋하기 전에 장애가 발생했다고 가정하고 재시작 후 Kafka는 해당 메시지가 아직 처리되지 않은 것으로 판단할 수 있으므로 같은 메시지를 다시 전달한다
이 경우 데이터베이스에는 동일한 주문이 두 번 저장될 수 있다
따라서 최소 한 번 전달을 사용하는 시스템에서는 중복 처리를 방지하는 별도의 설계가 필요하다
대표적으로 주문 ID를 고유 키로 사용하거나 데이터베이스의 고유 제약 조건을 활용할 수 있다
2.3 정확히 한 번(Exactly-once)
메시지가 중복 반영되지 않고 의도한 처리 결과가 한 번만 적용되도록 하는 방식
Kafka는 멱등적 프로듀서와 트랜잭션 등의 기능을 통해 특정 처리 흐름에서 정확히 한 번 의미 구조를 지원한다
다만 외부 데이터베이스나 외부 API 호출까지 자동으로 정확히 한 번 처리되는 것은 아니다
외부 시스템까지 포함하려면 트랜잭션 경계, 중복 제거, 멱등성 키 등의 추가적인 설계가 필요하다
세 가지 방식 중 무엇이 항상 가장 좋은 것은 아니다
데이터 유실, 중복 처리, 지연 시간 중 어떤 요소를 우선할 것인지에 따라 적절한 전략을 선택해야 한다
3. 브로커의 복제와 데이터 안정성
Kafka는 파티션의 복제본을 여러 브로커에 분산해 저장한다
특정 브로커에 장애가 발생하더라도 다른 복제본을 이용해 서비스를 지속할 수 있도록 하기 위해서다
예시
하나의 파티션에 복제 계수 3을 설정하면 다음과 같이 구성된다
- Leader: 프로듀서의 쓰기 요청을 처리하는 복제본
- Follower 1: 리더의 데이터를 복제하는 복제본
- Follower 2: 리더의 데이터를 복제하는 복제본
프로듀서는 일반적으로 리더에 메시지를 기록한다
팔로워는 리더의 데이터를 복제하며, 리더에 장애가 발생하면 조건을 충족하는 복제본이 새로운 리더로 선출될 수 있다
하지만 복제본이 여러 개 있다고 해서 데이터가 항상 안전한 것은 아니다
복제본이 충분히 동기화되지 않았거나 부적절한 설정으로 리더가 선출되면 데이터가 유실될 수 있다
3.1 ISR(In-Sync Replicas)
ISR은 리더와 동기화 상태를 유지하는 복제본의 집합
Kafka는 복제본의 동기화 상태를 고려해 장애 발생 시 리더를 선출한다
ISR에 포함된 복제본을 기반으로 리더를 선출하면 데이터 유실 위험을 줄일 수 있다
ISR에 포함되지 않은 복제본을 리더로 선출하는 것을 허용하는 것은 Option이다(Unclean Leader Eclection)
만약허용하면 서비스 가용성을 높일 수 있는 대신 데이터 유실 가능성이 커질 수 있다
3.2 replication.factor와 min.insync.replicas
두 설정은 함께 이해해야 한다
- replication.factor: 파티션 복제본의 총개수를 지정한다
- min.insync.replicas: 쓰기 요청을 성공으로 처리하기 위해 요구하는 최소 ISR 수를 지정한다
예시
복제 계수가 3이고 최소 ISR 수가 2라면, 세 복제본 중 최소 두 개가 ISR에 포함되어야 acks=all을 사용하는 쓰기 요청이 성공할 수 있다
브로커 한 대에 장애가 발생하더라도 나머지 두 복제본이 ISR을 유지한다면 쓰기를 계속할 수 있다
ISR이 하나만 남으면 데이터 안전성을 위해 쓰기 요청이 실패할 수 있다
이러한 설정은 장애 상황에서 무조건 쓰기를 허용하기보다 데이터 안정성을 우선하도록 만드는 장치다
3.3 Unclean Leader Election
Unclean Leader Election은 ISR에 포함되지 않은 복제본을 새로운 리더로 선출하는 방식이다
이 기능을 허용하면 ISR의 모든 복제본을 사용할 수 없는 상황에서도 서비스 복구가 가능할 수 있다
선택된 복제본에 최신 데이터가 없으면 일부 메시지가 유실될 수 있다
데이터 유실을 최소화하는 것이 중요한 시스템에서는 이 설정을 적용하는데 신중해야할 필요가 있다
4. 프로듀서의 신뢰성 설정
프로듀서는 Kafka에 메시지를 전송하는 역할을 담당한다
프로듀서의 설정에 따라 메시지 전송의 신뢰성과 지연 시간이 달라진다
4.1 acks 설정
acks는 프로듀서가 메시지 전송 성공을 판단하기 위해 기다리는 확인 수준을 결정한다
| acks=0 | 브로커의 응답을 기다리지 않음 | 지연 시간이 낮지만 전송 성공 여부를 확인하기 어려움 |
| acks=1 | 리더의 확인을 기다림 | 리더 장애 시 복제 전에 기록한 데이터가 유실될 수 있음 |
| acks=all | 현재 ISR의 확인을 기다림 | 복제 상태를 고려하므로 데이터 안정성을 높이는 데 유리함 |
acks=all을 설정은 모든 Kafka 브로커의 응답을 기다리는 것이 아니다
> 현재 ISR에 속한 복제본들의 확인을 기준으로 한다
acks=all만 설정한다고 해서 충분한 복제 안정성이 자동으로 보장되는 것은 아니다
복제 계수, 최소 ISR 수, 리더 선출 정책 등을 함께 고려해야 한다
4.2 재시도(Retries)
프로듀서는 네트워크 오류나 일시적인 브로커 문제로 요청이 실패하면 재시도할 수 있다
재시도는 일시적인 장애로 인한 메시지 유실 가능성을 줄이는 데 도움이 된다
재시도에는 주의할 점이 있다
브로커가 메시지를 정상적으로 저장했지만 응답이 네트워크에서 유실되었다고 가정해 보자
프로듀서는 전송이 실패했다고 판단해 같은 메시지를 다시 보낼 수 있다
이 경우 중복 기록이 발생할 가능성이 있다
이러한 문제를 해결하기 위해 Kafka는 멱등적 프로듀서를 제공한다
4.3 멱등적 프로듀서(Idempotent Producer)
멱등적 프로듀서는 프로듀서의 재시도로 인해 동일한 메시지가 중복 기록되는 문제를 방지한다
enable.idempotence=true
acks=all
멱등성 기능은 프로듀서 ID와 시퀀스 번호 등을 활용해 재시도로 인한 중복 기록을 식별한다
* 멱등적 프로듀서는 애플리케이션이 동일한 주문 이벤트를 별도의 새로운 전송으로 두 번 생성하는 문제까지 자동으로 해결하지는 않는다
5. 컨슈머의 신뢰성과 오프셋 관리
컨슈머는 Kafka에서 메시지를 읽고 처리한다
이때 중요한 것은 메시지 처리와 오프셋 커밋의 순서다
오프셋은 컨슈머가 어디까지 읽었는지를 나타내는 위치 정보다
컨슈머가 오프셋을 커밋하면 재시작 후에도 해당 위치를 기준으로 소비를 이어갈 수 있다
5.1 오프셋을 먼저 커밋하면 발생하는 문제
컨슈머가 메시지를 읽은 직후 오프셋을 먼저 커밋하고, 이후 데이터베이스에 저장한다면
- Kafka에서 메시지를 읽는다
- 오프셋을 커밋한다
- 데이터베이스 저장을 시도한다
- 저장 과정에서 장애가 발생한다
이 경우 컨슈머가 재시작하면 이미 커밋된 위치 이후부터 메시지를 읽을 수 있다
결과적으로 데이터베이스에 반영되지 않은 메시지를 건너뛸 위험이 있다
5.2 처리 후 오프셋을 커밋하면 발생하는 문제
데이터베이스 저장을 먼저 완료하고 오프셋을 커밋한다면
- Kafka에서 메시지를 읽는다
- 데이터베이스에 저장한다
- 오프셋을 커밋한다
이 방식은 처리 완료 전에 오프셋을 커밋하는 문제를 줄여준다
하지만 데이터베이스 저장이 성공한 뒤 오프셋 커밋 전에 장애가 발생하면 같은 메시지가 다시 처리될 수 있다
따라서 처리 후 커밋 방식은 일반적으로 최소 한 번 처리에 적합하지만, 중복 처리까지 방지하려면 추가적인 조치가 필요하다(멱등 처리가 필요하다)
5.3 자동 커밋과 수동 커밋
자동 커밋은 Kafka가 설정된 주기에 따라 오프셋을 커밋하도록 하는 방식이다
구현은 간단하지만 애플리케이션의 실제 처리 완료 시점과 커밋 시점이 일치하지 않을 수 있다
수동 커밋은 애플리케이션이 처리 완료 시점을 기준으로 오프셋 커밋을 직접 제어하는 방식이다
처리 순서와 오류 대응을 세밀하게 관리할 수 있지만, 커밋 실패나 재처리 상황을 직접 고려해야 한다
수동 커밋을 사용한다고 해서 중복 처리가 자동으로 사라지는 것은 아니다
중요한 것은 실제 처리 결과와 오프셋 상태 사이의 불일치를 어떻게 관리할 것인지다
6. 데이터 전달 신뢰성을 높이기 위한 설계
Kafka의 신뢰성을 높이려면 특정 설정 하나에 의존하기보다 전체 시스템의 동작을 함께 고려해야 한다
6.1 데이터 유실을 줄이는 구성
데이터 유실이 중요한 문제인 시스템에서는 다음 사항을 검토할 수 있다
- 충분한 복제 계수 설정
- 적절한 min.insync.replicas 설정
- 프로듀서의 acks=all 적용
- 멱등적 프로듀서 활성화
- 컨슈머의 처리 완료 후 오프셋 커밋
- 장애 상황에서의 복제 및 복구 테스트
이러한 설정은 데이터 유실 위험을 줄이는 데 도움이 되지만, 시스템 전체에서 데이터가 절대 유실되지 않는다는 의미는 아니다
6.2 중복 처리를 줄이는 구성
최소 한 번 전달 방식에서는 동일한 메시지가 다시 처리될 수 있으므로 중복 처리를 고려해야 한다
예시
주문 이벤트에 고유한 주문 ID를 부여하고 데이터베이스에서 해당 ID의 중복 저장을 방지할 수 있다
또는 처리 결과와 메시지 식별 정보를 함께 저장해 이미 처리한 이벤트인지 확인할 수 있다
이러한 방식은 Kafka의 메시지 전달 특성과 외부 시스템의 데이터 변경을 연결하는 데 유용하다
6.3 장애 상황을 가정한 테스트
설정이 올바르더라도 실제 장애 상황에서 예상한 대로 동작하는지 확인해야 한다
- 리더 브로커를 중단했을 때 새로운 리더가 정상적으로 선출되는가?
- ISR 수가 최소 요구치보다 적을 때 쓰기 요청이 어떻게 처리되는가?
- 프로듀서가 응답을 받지 못했을 때 재시도가 올바르게 작동하는가?
- 컨슈머가 데이터 처리 도중 종료되면 메시지가 어떻게 재처리되는가?
- 컨슈머 지연이나 복제 지연이 증가했을 때 이를 감지할 수 있는가?
장애 테스트를 통해 설정의 의미를 확인하고 운영 환경에서 발생할 수 있는 문제를 사전에 발견할 수 있다






