PaaS/MQ

Kafka 심화) 신뢰성 있는 데이터 전달

armyost 2026. 1. 23. 01:41
728x90

브로커 설정

복제 팩터

토픽 단위 설정은 replication.factor에, 자동으로 생성되는 토픽들에 적용되는 브로커 단위 설정은 default.replication.factor 설정에 잡아준다. 

 

이책에서 지금까지는 토픽의 복제 팩터가 3이라고 가정하였다. 이는 각 브로커가 3대의 서로 다른 브로커에 3개 복제된다는 것을 의미한다. 이엇은 합리적인 가정이고 또 카프카의 기본값이기다 하지만, 이설정은 사용자가 고칠수 있는 것이다. 그렇다면 토픽에 몇개의 레플리카가 적절한지 어떻게 결정할 수 있을까? 몇가지의 핵심 고려사항이 있다. 

 

- 가용성 : 레플리카가 하나뿐인 파티션은 정기적으로 브로커를 재시작하기만 해도 작동 불능에 빠진다. 레플리카 수가 더 많을수록 가용성은 떨어진다.

- 지속성 : 각 레플리카는 파티션 안의 모든 데이터의 복사본이다. 만약 파티션에 레플리카가 하나뿐이고 어떠한 이유에서 디스크가 사용 불가능하게 될 경우 해당 파티션의 모든 데이터는 유실된다. 복사본이 많을 수록 데이터가 유실될 가능성은 줄어든다. 

- 처리량 : 레플리카가 추가될 때마다 브로커간 트래픽 역시 늘어난다. 만약 우리가 특정 파티션에 10Mbps 의 속도로 쓰는데 레플리카가 하나뿐이라면 복제 트래픽은 하나도 없을 것이다. 하지만 레플리카가 2개라면 복제 트래픽은 10Mbps가 되고 , 3개라면 20Mbps가 되고, 5개라면 40Mbps가 될 것이다.

- 종단지연 : 쓰여진 메시지를 컨슈머가 읽을 수 있으려면 모든 인-싱크 레플리카에 복제되어야 한다. 이론적으로, 레플리카 수가 더 많을 수록 이들중 하나가 느려짐으로써 컨슈머까지 함께 느려질 가능성은 높아진다. 실제로는 어떤 이유에서건 특정한 브로커가 느려지면 해당 브로커를 사용하는 모든 클라이언트 역시 복제 팩터에 무관하게 느려질 것이다. 

- 비용 : 중요하지 않은 데이터에 대해 복제 팩터를 3 미만으로 잡아주는 가장 일반적인 이유는 바로 이것이다. 더 많은 레플리카를 가질수록 저장소와 네트워크에 들어가는 비용역시 증가한다. 

 

언클린 리더 선출

 unclean.leader.election.enable 설정의 기본값은 false로 잡혀 있다. 즉, 아웃-오브-싱크 레플리카는 리더가 될 수 없도록 되어 있는 것이다. 데이터 유실에 있어 가장 좋은 보장을 제공하는 만큼 이것은 가장 안전한 옵션이다. 즉, 우리가 앞에서 설명한 것과 같은 극단적인 불응 상황에서 일부 파티션들은 수동으로 복구될때까지 사용이 불가능한 상태로 남게 된다. 

 

최소 인-싱크 레플리카

만약 토픽에 레플리카가 3개 있고 min.insync.replicas를 2로 잡아 줬다면 프로듀서들은 3개의 레플리카 중 최소 2개가 인-싱크 상태인 파티션에만 쓸수 있다. 만약 3개 레플리카 모두가 인-싱크 상태라면, 모든 것이 정상적으로 작동한다. 이것은 세 개 중 하나의 레플리카가 작동 불능에 빠져도 마찬가지다. 하지만 만약 세 개 중 두개의 레플리카가 작동 불능에 빠질 경우, 브로커는 더이상 쓰기 요청을 받을 수 없을 것이다. 

 

레플리카를 인-싱크 상태로 유지하기

만약 레플리카가 replica.lag.time.max.ms 에 설정된 값보다 더 오랫동안 리더로부터 데이터를 읽어 오지 못하거나, 리더에 쓰여진 최신 메시지를 따라잡지 못하는 경우 동기화가 풀린상태 즉 아웃-오브-싱크 상태가 된다. 

 

신뢰성 있는 시스템에서 프로듀서 사용하기

토픽별로 3개의 레플리카를 가지도록 브로커를 설정한 상태에서 언클린 리더 선출 기능을 끈다. 이렇게 되면 카프카 클러스터에 커밋된 메시지는 유실되지 않는다. 하지만 프로듀서가 메시지를 보낼때 acks=1 설정으로 보내도록 설정한다. 프로듀서에서 메시지를 전송해서 리더에는 쓰여졌지만, 아직 인-싱크 레플리카에 반영되지는 않은 상태다. 리더가 프로듀서에게 "메시지가 성공적으로 쓰여졌다"라고 응답을 보낸 직후 크래시가 나서 데이터가 레플리카로 복제되지 않는다. 다른 레플리카들은 여전히 인-싱크 상태로 간주되기 때문에 그중 하나가 리더가 될 것이다. 어떤 컨슈머도 이 메시지를 보지 못한 만큼 시스템의 일관성은 유지된다. 하지만 프로듀서의 입장에서 보면 메시지는 유실된 것이다. 

 

카프카에 메시지를 쓰는 애플리케이션을 개발하는 모든 이들이 신경써야 할 중요한 것이 두개가 있다.

- 신뢰성 요구 조건에 맞는 올바른 acks 설정을 사용한다. 

- 설정과 코드 모두에서 에러를 올바르게 처리한다.

 

응답 보내기

프로듀서는 아래 세가지 응답 모드 중 하나를 선택할 수 있다. 

- acks=0

프로듀서가 네트워크로 메시지를 전송한 시점에서 메시지가 카프카에 성공적으로 쓰여진 것으로 간주된다. 파티션이 오프라인이거나, 리더 선출이 진행중이거나 심지어 전체 카프카 클러스터가 작동 불능인 경우 에러가 발생하지 않을 것이다. 

- acks=1

이것은 리더가 메시지를 받아서 파티션 데이터 파일에 쓴 직후 응답 또는 에러를 보낸다는 것을 의미한다. 클라이언트로 응답이 간 상태에서 미처 팔로워로 복제가 완료되기 전에 리더가 정지하거나 크래시 날 경우 데이터가 유실될 수 있다. 

- acks=all

이것은 리더가 모든 인-싱크 레플리카가 메시지를 받아갈 때가지 기다렸다가 응답하거나 에러를 보낸다는 것을 의미한다. 

 

프로듀서 재시도 설정하기

프로듀서의 에러처리는 두 부분으로 나누어 지는데, 바로 프로듀서가 자동으로 처리해주는 에러와 프로듀서 라이브러리를 사용하는 개발자들이 처리해야 하는 에러다. 

 

에러 코드는 두 부류로 나뉘어 진다. 즉, 전송을 재시도 하면 해결될 수 있는것과 아닌 것이다. 예를 들어서, 브로커가 LEADER_NOT_AVAILABLE 에러코드를 리턴할 경우, 프로듀서는 전송을 재시도할 수 있다. 그러니까 아마도 새로운 브로커가 리더로 선출된 상활일 것이며 두번째 시도는 성공할 것이다. 즉 LEADER_NOT_AVAILABLE은 재시도 가능한 에러인 것이다. 반대로, 만약 브로커가 INVALID_CONFIG 예외를 리턴할 경우, 같은 메시지를 재전송한다고 해서 설정이 변경되지는 않는다. 이것이 재시도 불가능한 레어의 한 예이다. 

일반적으로 메시지가 유되지 않는 것이 목표인 경우, 가장 좋은 방법은 재시도 가능한 에러가 발생핬을 경우 프로듀서가 계속해서 메시지 전송을 재시도 하도록 설정하는 것이다. 그리고 재시도에 관한 한 가장 좋은 방법은 3장에서 권장한 것과 같이 재시도 수를 기본 설정값으로 내버려 두고 메시지 전송을 포기할 때까지 대기할수 있는 시간을 지정하는 delivery.time.ms 설정값을 최대로 잡아주는 것이다. 

전송 실패한 메시지를 재시도하는 것은 두 메시지가 모두 브로커에 성공적으로 쓰여지는, 결과적으로 메시지가 중복도리 위험을 내포한다. 재시도와 주의 깊은 에러 처리는 각 메시지가 '최소 한 번' 저장되도록 보장할 수 는 있지만, '정확히 한번'은 보장할 수 없다. enable.idempotence=true 설정을 잡아줌으로써 프로듀서가 추가적인 정보를 레코드에 포함할수 있도록, 그리고 이를 활용해서 브로커가 재시도로 인해 중복된 메시지를 건너 뛸 수 있도록 할 수 있다. 

 

추가적인 에러처리

프로듀서에 탑재된 재시도 기능을 사용하는 것은 메시지 유실없이 다양한 종류의 에러를 올바르게 처리할 수 있는 쉬운 방법이다. 하지만, 개발자 입장에서는 다른 종류의 에러들 역시 처리할 수 있어야 한다. 여기에는 다음과 같은 것들이 포함된다.

- 메시지 크기에 관련되었거나 인가 관련 에러와 같이 재시도가 불가능한 브로커 에러

- 메시지가 브로커에 전송되기 전에 발생한 에러

- 프로듀서가 모든 재전송 시도를 소진했거나 , 재시도 과정에서 프로듀서가 사용하는 가용 메모리가 메시지로 가득차서 발생하는 에러

- 타임아웃

 

신뢰성 있는 시스템에서 컨슈머 사용하기

특정 카프카 컨슈머가 작동을 정지하면, 또 다른 컨슈머 입장에서는 어디서부터 작업을 재개해야할지 알아야할 필요가 있다. 컨슈머가 읽어온 오프셋을 '커밋'해야 하는 이유가 여기에 있다. 읽고 있는 각 파티션에대해 어디까지 읽었는지를 저장해 둬야 해당 컨슈머나 다른 컨슈머가 재시작한 뒤에도 어디서부터 작업을 계속해야 할지 알수 있기 때문이다. 컨슈머가 메시지를 누락할 수 있는 경우는 대게 읽기는 했지만 아직 처리는 완료되지 않은 이벤트들의 오프셋을 커밋하는 경우다.  

 

신뢰성 있는 처리를 위해 중요한 컨슈머 설정

우리가 원하는 수준의 신뢰성을 갖는 컨슈머를 설정하기 위해 알아두어야 하는 컨슈머 속성은 다음과 같이 네 개가 있다. 

- group.id : 같은 그룹 ID를 갖는 두개의 컨슈머가 같은 토픽을 구독할 경우, 각각의 컨슈머에는 해당 토픽 전체 파티션의 서로 다른 부분집합이 할당되므로 각각은 서로 다른 부분의 메시지만을 읽게 된다. 

- auto.offset.reset : earliest를 사용한다면, 유효한 오프셋이 없는 한 컨슈머는 파티션의 맨 앞에서부터 읽기를 시작하게 된다. 

- enable.auto.commit : 오프셋을 커밋하게 할 것인가, 아니면 코드에서 직접 오프셋을 커밋하게 할 것인가? 자동 오프셋 커밋의 주된 이점은 애플리케이션에서 컨슈머를 사용할 때 걱정거리가 한개 준다는 점이다. 

- auto.commit.interval.ms는 5초마다 커밋하는 것이 기본값이다. 일반적으로 자주 커밋할 수 록 오버헤드 역시 늘어나지만 컨슈머가 정지햇을 때 발생할 수 있는 중복의 수는 줄어든다.

 

컨슈머에서 명시적으로 오프셋 커밋하기

더 세밀한 제어가 필요해서 오프셋 커밋을 직접 수행하기로 했다면, 이러한 결정이 정확성과 성느에 미치는 영향에도 신경을 써야한다. 

- 메시지 처리 먼저, 오프셋 커밋은 나중에

만약 풀링 루프에서 모든 처리를 하고 루프 사이의 상태는 저장하지 않는다면, 이것은 쉽다. 자동 오프셋 커밋 설정을 사용하거나 폴링 루프틔 끝에서 오프셋을 커밋하거나 아니면 루프안에서 일정한 주기로 오프셋을 커밋함으로써 오버헤드와 중복 처리 회피 사이의 요구 조건의 균형을 맞찬다던가 하면 된다. 

- 커밋 빈도는 성능과 크래시 발생시 중복 개수 사이의 트레이드 오프다. 

커밋 작업은 상당한 성능 오버헤드를 수반한다. 이것은 acks=all 설정과 함께 쓰기 작업을 수행하는 것과 비슷한데, 다만 특정 컨슈머 그룹의 모든 오프셋 커밋이 동일한 브로커로 간다는 점이 다르다. 커밋 주기는 성능과 중복 발생의 요구조건 사이에서 균형을 맞춰야 한다. 메시지를 읽어올 때마다 커밋하는 방식은 매우 낮은 빈도로 메시지가 들어오는 토픽에나 사용할 수 있다. 

- 정확한 시점에 정확한 오프셋을 커밋하자

언제나 처리가 완료된 메시지의 오프셋을 커밋하는 것이 중요하다는 걸 기억하자. 

- 리벨런스

할당된 파티션이 해제되기 전에 오프셋을 커밋하고, 새로운 파티션이 할당되었을때 애플리케이션이 보유하고 있던 상태를 삭제해주는 작업을 포기한다.

- 컨슈머는 재시도를 해야할 수 도 있다. 

만약 레코드 #30 처리에 실패한 상태에서 #31 처리에 성공할 경우 #31의 오프셋을 커밋하면 안된다. 이것은 #30을 포함한 #31까지의 모든 레코드를 처리했다고 표시하는 것과 같은 결과를 초래하는데, 이는 우리가 원하는 것이 아닐 것이다. 

 

설정 검증하기

org.apache.kafka.tools 패키지에는 VerifiableProducer, VerifiableConsumer 클래스가 포함되어 있는데, 이들 각각은 명령줄 툴 형태로든 자동화된 테스팅 프레임워크에 포함된 형태로든 실행이 가능하다. 검증용 프로듀서에는 일반적인 프로듀서와 똑같이 acks.retries.delivery.timeout.ms 등의 설정값을 잡아줄 수 있을 뿐더러 메시지를 쓰는 속도 역시 정해줄 수 있다. 그리고 나서 검증용 프로듀서를 실행시키면 브로커에 전송된 각 메시지마다 성공 혹은 에러를 출력한다. 

 

애플리케이션 검증하기

다양한 장애 상황에 대해 테스트를 수행할 것을 권장한다.

- 클라이언트가 브로커 중 하나와 연결이 끊어짐

- 클라이언트와 브로커 사이의 긴 지연

- 디스크 꽉 참

- 디스크 멈춤

- 리더 선출

- 브로커 롤링 재시작

- 컨슈머 롤릴 재시작

- 프로듀서 롤릴 재시작

 

프로덕션 환경에서 신뢰성 모니터링 하기

그렇다고 해서 데이터가 예상한 대로 흐르고 있는지 프로덕션 시스템을 지속적으로 모니터링 하는 것을 그냥 넘어가서는 안된다. 이벤트 전송 도중에 발생하는 프로듀서 에러 포그 역시 모니터링 해주어야 한다. 남은 재시도 횟수가 0인 이벤트가 보인다면, 프로듀서에서 재시도 횟수가 고갈된 것이다. 컨슈머 쪽에서 가장 중요한 지표는 컨슈머 렉 이다. 이 지표는 컨슈머가 브로커 내 파티션에 커밋된 가장 최신 메시지에서 얼마나 뒤떨어져 있는지를 가리킨다. 랙은 계속해서 약간 오르락 내리락 하게 되어있다. 여기서 중욯나 것은 컨슈머가 점점 더 뒤쳐지는게 아니라, 계속해서 따라붙는 것이다. 컨슈머 랙 값이 어느정도 오르락내리락 하는 만큼, 이 지표에 전통적인 정보를 설정하는 것은 약간 어려울 수 있다. kafka.server:type=BrokerTopicMetrics.name=FailedProduceRequestsPerSec와 kafka.server:type=BrokerTopicMetrics,name=FailedFetchRequestsPerSec지표값을 수집할 것을 권장한다.