신뢰성 보장# 보장: 서로 다른 상황에서도 시스템이 지킬 것이라고 보장되는 행동 아파치 카프카가 보장하는 것파티션 안의 메시지들 간에 순서를 보장한다.만약 메시지 A 다음에 B가 쓰여졌다면, 동일한 프로듀서가 동일한 파티션에 썼을 경우, 카프카는 B의 오프셋이 A보다 큰 것을 보장한다. 컨슈머 역시 A를 읽어온 다음에 B를 읽게 된다. 클라이언트가 쓴 메시지는 모든 인-싱크 레플리카의 파티션에 쓰여진 뒤에야 커밋된 것으로 간주한다.프로듀서는 메시지가 완전히 커밋된 다음 응답이 올지, 리더에게 쓰여진 다음 응답이 오지 아니면 네트워크로 전송된 다음 바로 응답이 올지 선택할 수 있다. 커밋된 메시지들은 최소 1개의 작동 가능한 레플리카가 남아 있는 한 유실되지 않는다. 컨슈머는 커밋된 메시지만 읽을 수 있다. 이러한 보장들만으로는 시스템 전체를 완전히 신뢰성 있게 만들어 주지는 못한다.신뢰성 있는 시스템을 구축하는 데는 트레이 오프가 있고, 카프카는 이러한 트레이드오프들을 조절할 수 있도록 개발자나 운영자가 설정 매개변수를 조절함으로써 어느 정도의 신뢰성이 필요한지를 결정할 수 있도록 개발되었다. 이러한 트레이드오프는 대개 메시지를 저장하는데 있어서의 신뢰성과 일관성이 우선이냐, 아니면 가용성, 높은 처리량, 낮은 지연, 하드웨어 비용과 같은 다른 고려사항이 우선이냐의 문제인 경우가 많다. 하나의 메시지를 여러 개의 레플리카에 씀으로써 카프카는 크래시가 나더라도 메시지의 지속성을 유지한다. 동기화가 살짝 늦은 인-싱크 레플리카는 컨슈머를 느리게 만들 수 있다.이유: 프로듀서와 컨슈머는 메시지가 커밋되기 전, 모든 인-싱크 레플리카가 해당 메시지를 받을 때까지 기다려야 하기 때문이다. 브로커 설정# 브로커 단위에 적용되어 시스템 안의 모든 토픽들을 제어할 수도 있고 토픽 단위에서 적용되어 특정 토픽을 제어할 수도 있다. 복제 팩터# 2가지 프로퍼티replication.factor
: 토픽 단위 설정default.replicatoin.factor
: 자동으로 생성되는 토픽들에 적용되는 브로커 단위 설정 예시: 복제 팩터가 3이라면, 각 파티션이 3대의 서로 다른 브로커에 3개 복제된다는 것을 의미한다. 복제 팩터 역시 변경이 가능하다. 복제 팩터가 N이면장점: N-1개의 브로커가 중단되더라도 토픽의 데이터를 읽거나 쓸 수 있다. 단점: 최소한 N개의 브로커가 필요한 뿐더러 N개의 복사본을 저장하므로 N배의 디스크 공간이 필요하다. 토픽에 몇 개의 레플리카가 적절할지 결정할 고려 사항가용성: 레플리카 수가 많을수록 가용성이 늘어난다. 지속성: 복사본이 더 많을수록 모든 데이터가 유실될 가능성은 줄어든다. 처리량: 레플리카가 추가될 때마다 브로커간 트래픽이 늘어난다. 클러스터의 크기와 용량을 산정할 때는 이를 고려할 필요가 있다. 종단 지연: 레플리카 수가 더 많을수록 이들 중 하나가 느려짐으로써 컨슈머까지 함께 느려질 가능성은 높아진다. 비용: 더 많은 레플리카를 가질수록 저장소와 네트워크에 들어가는 비용 역시 증가한다. 많은 저장 시스템들이 각 블록을 3개로 복제해서 저장하는 경우가 많기 때문에 카프카를 설정할 때 복제팩터를 2로 잡아줌으로써 저장 비용을 줄이는 것이 합리적인 경우가 있다. 레플리카의 위치 역시 중요하다.이유: 같은 파티션의 모든 레플리카들이 같은 랙에 설치되어 있는 브로커들에 저장되었는데 랙 스위치가 오작동할 경우, 복제 팩터하고는 상관없이 해당 파티션을 사용할 수 없게 될 것이다. 결론: 랙 단위 사고를 방지하기 위해서 브로커들을 서로 다른 랙에 배치한 뒤 broker.rack
브로커 설정 매개변수에 랙 이름을 잡아 줄 것을 권장한다. 언클린 리더 선출# unclean.leader.election.enable
아웃-오브-싱크 레플리카가 리더가 될 수 있는지 여부 기본값은 false
‘클린’의 의미: 새 리더를 선출할 때 커밋된 데이터의 유실이 없음을 의미인-싱크 레플리카 중에 리더를 선출하면 커밋된 데이터가 유실되지 않는다. 아웃-오브-싱크 레플리카가 리더가 될 수 있도록 허용할 경우 데이터 유실과 일관성 깨짐의 위험성이 있다.하지만, 아웃-오브-싱크 레플리카가 리더가 될 수 없는 경우 모든 인-싱크 레플리카가 오프라인 상태일 때 복구되는 것을 기다려야해서 가용성이 줄어든다. 운영자 입장에서는 이러한 상황이 닥쳤을 때 파티션을 사용 가능한 상태로 만들기 위해서 true
로 바꾼뒤 클러스터를 시작하는 것도 가능하다.이 경우 클러스터가 복구된 뒤 설정값을 false
로 되돌려주는 것을 잊지 말자. 최소 인-싱크 레플리카# min.insync.replicas
카프카가 보장하는 신뢰성에 따르면, 데이터는 모든 인-싱크 레플리카에 쓰여진 시점에 커밋된 것을 간주된다.‘모든’이 단 한 개의 레플리카를 의미할 수도 있는데도 말이다. 한 개의 레플리카만 인-싱크인 상태에서 이 레플리카가 작동 불능에 빠지면 데이터는 유실될 수 있다. 커밋된 데이터를 2개 이상의 레플리카에 쓰고자 한다면 min.insync.replicas
를 2로 잡아주면 된다.상황1: 3개의 레플리카가 모두 인-싱크 상태라면, 모든 것이 정상적으로 작동한다. 상황2: 하나의 레플리카가 작동 불능에 빠져도, 모든 것이 정상적으로 작동한다. 상황3: 2개의 레플리카가 작동 불능에 빠지면, 브로커는 더이상 쓰기 요청을 받을 수 없다. 대신, 데이터를 전송하고자 시도하는 프로듀서는 NotEnoughReplicasException
을 받게 된다. min.insync.replicas
만큼 레플리카가 인-싱크 상태가 아니라면 해당 레플리카는 사실상 읽기 전용이 된다.레플리카를 인-싱크 상태로 유지하기# 레플리카가 아웃-오브-싱크 상태가 될 수 있는 두 가지 이유레플리카가 주키퍼와의 연결이 끊어짐 리더 업데이트 내역을 따라가는 데 실패해서 복제 랙이 발생 카프카는 이 두 조건에 대한 카프카 클러스터의 민감도를 조절할 수 있는 브로커 설정을 가지고 있다. zookeerper.session.timeout.ms
설명: 카프카 브로커가 주키퍼로 하트비트 전송을 멈출 수 있는 최대 시간을 정의한다. 기본값: 2.5.0부터 6초에서 18초로 증가했다. 조정 기준: 가비지 수집이나 네트워크 상황과 같은 무작위적인 변동에 영향을 받지 않을 만큼 높게, 하지만 실제로 작동이 멈춘 브로커가 적시에 탐지될 수 있을 만큼 충분히 낮게 설정될 필요가 있다. replica.lag.time.max.ms
설명: 리더로부터 데이터를 읽어 오지 못하는 최대 시간을 정의한다. 기본값: 2.5.0에서 10초에서 30초로 증가했다. 주의점: 컨슈머의 최대 지연에도 영향을 준다. 기본값 기준으로 모든 레플리카에 도착햇허 컨슈머가 이 메시지를 읽을 수 있게 되는데 최대 30가 걸릴 수 있다. 디스크에 저장하기# 카프카는 세그먼트를 교체할 때와 재시작 직전에만 메시지를 디스크로 플러시하며, 그 외의 경우에는 리눅스이 페이지 캐시 기능에 의존한다.배경: 각각 데이터의 복제본을 가지고 있는, 서로 다른 랙이나 가용 영역에 위치한 세 대의 장비가 리더의 디스크에 메시즈를 쓰는 것보다 더 안전하다는 판단이 있다. 하지만, 브로커가 디스크에 더 자주 메시지를 저장하도록 설정하는 것은 가능하다.flush.messages
: 디스크에 저장되지 않는 최대 메시지 수flush.ms
: 얼마나 자주 디스크에 메시지를 저장하는지 신뢰성 있는 시스템에서 프로듀서 사용하기# 신뢰성 설정을 브로커에 적용하더라도, 프로듀서 역시 신뢰성이 있도록 설정을 잡아 주지 않는다면 시스템 전체로서는 여전히 데이터가 유실될 수 있다. 2가지 예시브로커에 언클린 선출 기능을 끈 상태에서, 프로듀서가 메시지를 보낼 때 acks=1
설정을 한 경우리더가 프로듀서에게 “메시지가 성공적으로 쓰여졌다.“라고 응답을 보낸 직후 크래시가 나서 데이터가 레플리카로 복제되지 않으면, 프로듀서 입장에서 보면 메시지는 유실될 것이다. 브로커에 언클린 선출 기능을 끈 상태에서, 브로커가 메시지를 보낼 때 acks=all
설정을 했지만, “Leader not Available” 응답에 대한 재시도를 하지 않은 경우프로듀서가 에러를 처리하지 않고 쓰기가 성공하지 성공할 때까지 재시도도 하지 않은 경우, 메시지는 유실될 수 있다. 카프카에 메시지를 쓰는 애플리케이션을 개발하는 모든 이들은 신경써야 할 중요한 2개가 있다.신뢰성 요구 조건에 맞는 올바른 acks
설정을 사용한다. 설정과 코드 모두에서 에러를 올바르게 처리한다. 응답 보내기# acks=0
프로듀서가 네트워크로 메시지를 전송한 시점에 메시지가 카프카에 성공적으로 쓰여진 것으로 간주한다. 보장: 우리가 전송하는 객체가 직렬화될 수 없거나 네트워크 카드가 오작동할 경우 여전히 에러를 받는다. 문제점: 파티션이 오프라인이거나, 리더 선출이 진행중이거나, 심지어 전체 카프카 클러스터가 작동 불능인 경우 에러가 발생하지 않는다. acks=1
리더가 메시지를 받아서 파니션 데이터 파일에 쓴 직후 응답 또는 에러를 보낸다. 문제점1: 일부 메시지가 리더에 성공적으로 쓰여서 클라이언트로 응답이 간 상태에서 팔로워로 복제가 완료되기 전에 리더가 정지하거나 크래쉬 날 경우에 데이터가 유실될 수 있다. 문제점2: 메시지를 복제하는 속도보다 더 빨리 리더에 쓸 수 있기 때문에 불완전 복제 파티션이 발생할 수 있다. acks=all
리더가 모든 인-싱크 레플리카가 메시지를 받아갈 때까지 기다렸다가 응답하거나 에러를 보낸다.브로커의 min.insync.replicas
설정과 함께, 이 설정은 응답이 오기전까지 얼마나 많은 레플리카에 메시지를 복제될 것인지를 조절할 수 있게 해준다. 장점: 프로듀서는 메시지가 완전히 커밋될 때까지 메시지를 재전송해서 가장 안전한 옵션이다. 문제점: 프로듀서는 모든 인-싱크 레플리카가 메시지를 받을 때까지 가디린 뒤에야 해당 메시지 배치에 완료 표시를 하고 작업을 진행하기 때문에 지연이 가장 길어지는 옵션이다. 프로듀서 재시도 설정하기# 프로듀서의 에러 처리는 두 부분으로 나누어지는데, 바로 프로듀서가 자동으로 처리해주는 에러와 프로듀서 라이브러리를 개발자들이 처리해야 하는 에러다.예시:LEADER_NOT_AVAILABLE
에러 코드를 리턴한 경우, 프로듀서는 전송을 재시도할 수 있다.INVALID_CONFIG
에러 코드를 리턴할 경우, 같은 메시지를 재전송한다고 해서 설정이 변경되지 않는다. 메시지가 유실되지 않는 것이 목표일 경우, 가장 좋은 방법은 재시도 가능한 에러가 발생했을 경우 프로듀서가 계속해서 메시지 전송을 재시도하도록 설정하는 것이다.재시도에 관한 가장 좋은 방법재시도 수를 기본 설정값(MAX_INT
)으로 내버려 두고 메시지 전송을 포기할 때까지 대기할 수 있는 시간을 지정하는 deliver.timeout.ms
설정값을 최대로 잡아 주는 것이다. 전송 실패한 메시지를 재시도하는 것은 메시지가 중복될 위험을 내포한다.원인: 실패했다고 생각했지만 실제로는 아닌 메시지와 재전송된 메시지가 모두 전송된 경우 결론: 재시도는 각 메시지가 ‘최소 한 번’ 저장되도록 보장할 수 있지만, ‘정확히 한 번’은 보장할 수 없다. 해결 방법: enable.idempotence=true
설정을 잡아줌으로써 프로듀서가 추가적인 정보를 레코드에 포함할 수 있도록, 그리고 이를 활용해서 브로커가 재시도로 인해 중복된 메시지를 건너뛸 수 있도록 할 수 있다. 추가적인 에러 처리# 다양한 종류를 에러를 에러 핸들러를 작성해 자유롭게 처리할 수 있다. 신뢰성 있는 시스템에서 컨슈머 사용하기# 컨슈머는 카프카에 커밋된 데이터만 읽을 수 있다. 즉, 컨슈머는 일관성이 보장되는 데이터만 읽는다. 컨슈머가 해야 할 일은 어느 메시지까지 읽었고 어디까지는 읽지 않았는지를 추적하는 것이다. 컨슈머가 읽는 과정컨슈머는 메시지를 배치 단위로 읽어온 뒤 배치별로 마지막 오프셋을 확인한다. 브로커로부터 받은 마지막 오프셋 값에서 시작하는 다른 메시지 배치를 요청한다. 이렇게 함으로써 컨슈머는 메시지 누락 없이, 언제나 새로운 데이터를 올바른 순서로 읽을 수 있다. 컨슈머는 각 파티션에 대해서 어디까지 읽었는지를 커밋해둬야, 해당 컨슈머나 다른 컨슈머가 재시작한 뒤에도 어디서부터 작업을 계속할지 알 수 있다. 컨슈머가 메시지를 누락할 수 있는 경우는 처리 완료하지 않은 이벤트들의 오프셋을 커밋하는 경우다. 신뢰성 있는 처리를 위해 중요한 컨슈머 설정# group.id
: 같은 그룹 ID를 갖는 두 개의 컨슈머가 같은 토픽을 구도할 경우, 각각의 컨슈머에는 해당 토픽 전체 파티션의 서로 다른 부분집합이 할당되므로 각각은 서로 다른 부분의 메시지만 읽게 된다.auto.offset.reset
: 커밋된 오프셋이 없을 때나 컨슈머가 브로커에 없는 오프셋을 요청할 때 컨슈머가 어떻게 해야 할지를 결정한다.earliest
를 설정한 경우: 유효한 오프셋이 없는 한 컨슈머는 파티션의 맨 앞에서부터 읽기를 시작하게 된다.장점: 데이터 유실을 최소화할 수 있다. 단점: 컨슈머는 많은 메시지들을 중복 처리하게 될 수 있다. latest
를 설정한 경우: 파티션의 끝에서부터 읽기를 시작한다.장점: 중복 처리는 최소화할 수 있다. 단점: 컨슈머가 일부 메시지는 누락할 것이다. enable.auto.commit
: 자동 오프셋 커밋 기능은 우리가 처리하지 않은 오프셋을 실수로 커밋하는 사태가 발생하지 않도록 보장해준다.자동 오프셋 커밋 기능 단점: 메시지 중복 처리를 우리가 제어할 수 없다. 읽어온 메시지 중 일부만을 처리했고 아직 자동 커밋이 되지 않은 상태에서 컨슈머가 멈추면, 컨슈머를 다시 시작시켰을 때 메시지 중복 처리를 피할 수 없다. auto.commit.interval.ms
: 만약 오프셋 자동 커밋을 활성화한 경우, 이 설정을 통해 커밋되는 주기를 설정할 수 있다.기본값: 5초 컨슈머가 poll()
을 호출할 때 이전의 자동 커밋 시각으로부터 설정한 시간이 지났는지 확인하고 지났다면 커밋을 하게된다. 자주 커밋할 수 록 오버헤드 역시 늘어나지만 컨슈머가 정지했을 때 발생할 수 있는 중복의 수는 줄어든다. 컨슈머에서 명시적으로 오프셋 커밋하기# 데이터를 신뢰성 있게 다루는 컨슈머를 개발할 때 고려해야 할 중요한 사항들을 알아본다. 메시지 처리 먼저, 오프셋 커밋은 나중에 커밋 빈도는 성능과 크래시 발생시 중복 개수 사이의 트레이드오프다. poll 루프 안에 여러 번 커밋하거나 루프가 몇 번 지나갈 때마다 커밋하는 것 사이에서 선택할 수 있다. 커밋 작업은 상당한 성능 오버헤드를 수반한다. 메시지를 읽어올 때마다 커밋하는 방식은 매우 낮은 빈도로 메시지가 들어오는 토픽에나 사용할 수 있다. 정확한 시점에 정확한 오프셋을 커밋하자 폴링 루프 중간에서 커밋할 때 흔히 하는 실수로 마지막으로 처리된 메시지의 오프셋이 아닌, 마지막으로 읽어온 메시지의 오프셋을 커밋하는 것이다. 언제나 처리가 완료된 메시지의 오프셋을 커밋하는 것이 중요하다. 리밸런스 애플리케이션을 설계할 때는 컨슈머 리밸런스가 발생할 것이라는 것과 이것을 적절히 처리해 줄 필요가 있다는 점을 기억해야 한다. 예시: 할당된 파티션이 해제되기 전에 오프셋을 커밋하고, 새로운 파티션이 할당되었을 때 애플리케이션이 보유하고 있던 상태가 있다면 이를 삭제해주는 작업을 작성해야한다. 컨슈머는 재시도를 해야 할 수도 있다. 상황에 따라 poll()
을 호출하고 레코드를 처리한 뒤, 일부 레코드는 처리가 완료되지 않아서 나중에 처리되어야할 수 있다.예시: 카프카에서 읽어 온 레코드를 데이터베이스에 쓰려고하는데, 정작 그 데이터베이스가 현재 사용 가능하지 않아서 나중에 재시도해야 할 수 있다. 만약 #30
처리에 실패한 상태에서 #31
처리에 성공할 경우, #31
의 오프셋을 커밋하면 안 된다. 시도가능한 패턴방법1: 마지막으로 처리에 성공한 레코드의 오프셋을 커밋하고, 나중에 처리해야 할 레코드들을 버퍼에 저장한다. 컨슈머의 pause()
메서드를 호출해서 추가적인 poll()
호출이 데이터를 리턴하지 않도록 한 뒤, 레코드 처리를 계속한다. 방법2: 별도의 토픽에 쓴 뒤 계속 진행한다.별도의 컨슈머 그룹을 사용해서 재시도 토픽에 저장한 레코드들을 처리하거나, 주 토픽과 재시도 토픽을 모두 구독하는 컨슈머를 하나 둬서 재시도 사이에는 재시도 토픽 구독을 잠시 멈추도록 할 수도 있다. 이 패턴은 많은 메시지 교환 시스템에서 사용되어 온 데드 레터 큐 시스템과 비슷하다. 컨슈머가 상태를 유지해야 할 수도 있다. 애플리케이션에 따라서는 poll()
메서드 호출 간에 상태를 유지해야 할 수도 있다. 예시: 이동평균을 계산하고자 한다면, 새 메시지들은 폴링해 올 때마다 평균 값을 업데이트 해줘야 한다. 시스템 신뢰성 검증하기# 설정 검증하기# 우리가 선택한 구상이 요구 조건을 충족시킬 수 있는지 확인하는 데 도움이 된다. 시스템의 예상 작동을 추론해 보기 위한 좋은 방법이다. 카프카는 이러한 검증 작업을 위한 두 개의 중요한 툴을 포함한다. VerifiableProducer
, VerifiableConsumer
클래스가 포함되어 있는데, 이들 각각은 명렬줄 툴 형태로든 자동화된 테스팅 프레임워크에 포함된 형태로든 실행히 가능하다.검증용 프로듀서에는 akcs
, retries
, delivery.timeout.ms
등의 설정값을 잡아줄 수 있을 뿐더러 메시지를 쓰는 속도 역시 정해줄 수 있다. 검증용 컨슈머는 이와 반대 방향으로 확인 작업을 수행한다. 어떤 테스트를 할 것인지도 고려해야 한다.예시1: 리더를 정지시키면 어떻게 될까? 프로듀서와 컨슈머가 평상시처럼 작동을 재개하는데까지 얼마나 걸릴까? 예시2: 컨트롤러가 재시작한 뒤 시스템이 재개되는 데 얼마나 걸릴까? 예시3: 메시지 유실 없이 브로커들을 하나씩 재시작시킬 수 있을까? 예시4: 한 파티션의 모든 레플리카들을 하나씩 중단시킨 다음 아웃-오브-싱크 상태가 된 브로커를 시작시키면 어떻게 될까? 작업을 재개하려면 어떻게 해야 할까? 시나리오를 고른 뒤 검증용 프로듀서를 실행시키고, 검증용 컨슈머를 실행시키고, 해당 시나리오대로 실행해본다. 아파치 카프카 소스코드 저장소는 방대한 테스트 스위트를 포함한다. 스위트 안에 포함된 많은 테스트들은 위에서 설명한 것 과같은 원리로 작동하며, 롤링 업데이트가 작동하는지의 여부를 확인하기 위해 검증용 프로듀서와 컨슈머를 사용한다. 애플리케이션 검증하기# 애플리케이션 로직이 카프카의 클라이언트 라이브러리와 상호작용하는 커스텀 에러 처리 코드, 오픗세 커밋, 리밸런스 리스너와 같은 곳들을 확인한다. 우리는 개발 프로세스의 일부로서 애플리케이션의 통합 테스트를 수행할 것을 권장하며, 다양한 장애 상황에 대해 테스트를 수행할 것 역시 권장한다. 테스트를 위해 가상의 네트워크나 디스크 장애를 발생시킬 수 잇는 좋은 툴들이 많다. 프로덕션 환경에서 신뢰성 모니터링하기# 프로듀서의 경우: 레코드별 에러율과 재시도율가 중요한 지표이다. 컨슈머의 경우: 컨슈머 랙이 중요한 지표다.컨슈머 랙: 컨슈머가 브로커 내 파티션에 커밋된 가장 최신 메시지에서 얼마나 뒤떨어져 있는지를 나타낸다. 컨슈머 랙을 확인하기 위해 링크드인이 개발한 Burrow를 사용하면 이 작업을 좀 더 쉽게할 수 있다. 쓰여진 데이터가 적절한 시기에 읽혀진다는 것도 모니터링 해야한다.데이터가 적절한 시기에 읽히도록 하려면, 언제 데이터가 생성되었는지 알아야 한다. 버전 0.10.0부터, 모든 메시지는 이벤트가 생성된 시점을 가리키는 타임스탬프를 포함한다.단, 이 값은 이벤트를 전송중인 애플리케이션이나 관련 설정이 잡혀 있는 브로커에 의애 재정의될 수 있음을 기억하자. 클라이언트와 종단 간 데이터 흐름을 모니터링하는 것 외에도, 카프카 브로커는 브로커가 클라이언트로 보내는 에러 응답률을 보여주는 지표들을 포함한다.kafka.server:type=BrokerTopicMetrics,name=FailedProduceRequestsPerSec
와 kafka.server:type=BrokerTopicMetrics,name=FailedFetchReqeustsPerSec
지푯값을 수집할 것을 권장한다. Please enable JavaScript to view the comments powered by Disqus. comments powered by