카프카핵심가이드7장 - 신뢰성 있는 데이터 전달
카프카 핵심가이드 책을 읽은 내용 정리
신뢰성
reliability 신뢰성은 시스템의 속성 중 하나다.
신뢰성 보장
신뢰성에 대해 이야기 나올때는 보통 “보장”이라는 단어가 언급됨.
- 보장 : 다양한 상황에서도 시스템이 일정한 행동을 수행하는걸 지킨다는 의미
- 예시: 관계형 데이터베이스에서 ACID 트랜잭션 처리의 보장
이러한 보장을 통해 시스템이 어떻게 작동할지 알 수 없는 상황에서도 신뢰할 수 있음
Kafka가 “보장”하는 사항
- 파티션 내 메시지 순서 보장 : 만약 메시지 A 다음에 B가 쓰여졌다면, 동일한 프로듀서가 동일한 파티션에 썻을 경우, 카프카는 B의 오프셋이 A보다 큰 것을 보장
- 모든 인-싱크 레플리카에 메시지가 쓰인 후에야 메시지가 ‘커밋’됨
- 커밋된 메시지는 최소 하나의 레플리카가 남아 있는 한 유실되지 않음
- 컨슈머는 커밋된 메시지만 읽을 수 있음
이러한 기본적인 보장들은 실뢰성 있는 시스템을 구축하기 위해 사용될 수 있지만, 그자체로는 시스템 전체를 신뢰성 있게 만들어 주지는 못함.
복제
파티션별로 다수의 레플리카를 유지한다는 턱성과 함게 카프카의 신뢰성 보장의 핵심.
하나의 메시지를 여러 개의 레플리카에 씀으로써 카프카는 크래시가 나더라도 메시지의 지속성을 유지
- 카프카 토픽은 파티션으로 구성되며, 각 파티션은 순서를 보장
- 각 파티션은 여러 레플리카를 가질 수 있으며, 그 중 하나가 리더가 됨
- 리더 레플리카에 이벤트가 쓰이고 대체로 리더에서 읽혀짐
- 리더가 작동 불능이 되면, 인-싱크 레플리카 중 하나가 새 리더가 됨
인-싱크 레플리카의 조건
- 주키퍼와의 활성 세션이 존재
- 최근 n초 이내에 리더로부터 메시지를 읽음
- 최신 메시지에 렉이 없음
아웃 오브 싱크 레플리카의 조건
- 주키퍼와의 연결이 끊기거나 새 메시지를 읽지 못하는 경우
- 최근 10초간 업데이트를 따라잡지 못할 때
아웃 오브 싱크 레플리카가 동기화를 맞추면 다시 인-싱크 레플리카가 될 수 있음
인-싱크 레플리카가 줄어들면 복제 팩터가 줄어들고, 중단 시간과 데이터 유실 위험이 커짐
동기화가 살짝 늦은 인-싱크 레플리카는 프로듀서와 컨슈머를 느리게 만들 수 있음.
프로듀서와 컨슈머는 메시지가 커밋되기 전, 모든 인-싱크 레플리카가 해당 메시지를 받을 때까지 기다려야하기 때문.
레플리카의 동기화가 풀리면 더 이상 이 레플리카가 메시지를 받을 때까지 기다릴 필요가 없어짐.
인-싱크 레플리카 수가 줄어들면 파티션의 실질적인 복제 팩터가 줄어들면서 중단 시간이 길어지거나 데이터가 유실될 위험성은 높아짐.
브로커 설정
메세지 저장 신뢰성 관련된 카프카의 브로커의 설정 매개변수는 3개가 존재.
브로커 단위(모든 토픽 제어)/토픽 단위(특정 토픽 제어) 에서 적용 가능
토픽 단위에서 신뢰성 트레이드 오프를 제어할 수 있다는 것은 신뢰성이 필요한 토픽과 아닌 토픽을 같은 카프카 클러스터에 저장할 수 있다는 것을 의미
복제 팩터
reflication.factor
: 토픽 단위 설정default.replication.factor
: 브로커 단위 설정
복제 팩터가 N이면, N-1개의 브로커가 중단되더라도 여전히 데이터를 읽거나 쓸 수 있음
복제 팩터가 클수록 가용성과 신뢰성은 늘어나고 장애가 발생할 가능성은 줄어듦
반대로 복제팩터가 N이라는 것은 최소한 N개의 브로커가 필요할 뿐만 아니라 N개의 복사본을 저장해야 하므로 N배의 디스크 공간이 필요하다는 얘기임.
복제 팩터 설정 시 고려 사항
가용성
레플리카가 하나뿐인 파티션은 브로커가 재시작되면 작동 불능이 될 수 있음레플리카 수가 많을수록 가용성이 증가
지속성
레플리카는 파티션 안의 모든 데이터를 복사
만약 레플리카가 하나뿐이고 해당 디스크가 손상되면 데이터 유실이 발생할 수 있음복사본이 많을수록 데이터 유실 위험 낮음
처리량
레플리카가 많아질수록 브로커 간 트래픽이 증가중단 지연
쓰여진 메시지가 모든 인-싱크 레플리카에 복제되어야 컨슈머가 읽을 수 있음레플리카 수가 많을수록 이들 중 하나가 느려져 전체 시스템이 느려질 가능성이 높아짐
비용
많은 레플리카를 가질수록 저장소와 네트워크 비용이 증가
레플리카 위치도 중요
언클린 리더 선출
unclean.leader.election.enable
: 브로커 단위 설정 (기본값: false). 기본적으로 아웃-오브-싱크 레플리카는 리더가 될 수 없음으로 설정
리더 레플리카가 작동 불능 상태인데 인-싱크 레플리카도 없다면 어떻게 하나?
- 예전 리더(마지막 인-싱크 레플리카)가 복구될 때까지 해당 파티션은 오프라인 상태가 됨
- 아웃-오브-싱크 레플리카가 새 리더가 되게함 => 이경우는 일부 메시지가 유실되고 일관성이 깨질 위험이 있음.
최소 인-싱크 레플리카
min.insync.replicas
: 토픽/브로커 단위 설정
인-싱크 레플리카 수는 최소 몇 개가 있어야 메시지를 커밋할 수 있는지를 결정
예를 들어 커밋된 데이터를 2개 이상의 레플리카에 쓰고자 한다면, 인-싱크 레플리카의 최소값을 더 높게 잡아줄 필요가 있음.
만약 토픽에 레플리카가 3개 있고, min.insync.replicas
를 2로 잡아줬다면 프로듀서들은 3개의 레플리카 중 최소 2개가 인-싱크 상태인 파티션에만 쓸 수 있음.
만약 3개 레플리카 모두가 인-싱크 상태라면, 모든 것이 정상 작동함.
2개의 레플리카가 작동 불능일 경우, 브로커는 쓰기 요청을 받지 못하고 프로듀서는 NotEnoughReplicasException 을 발생시킴
레플리카를 인-싱크 상태로 유지하기
레플리카가 아웃-오브-싱크 상태가 될 수 있는 조건을 조정할 수 있는 브로커 설정
zookeeper.session.timeout.ms
: 카프카 브로커가 주키퍼로 하트비트 전송을 멈출 수 있는 최대 시간replica.lag.time.max.ms
: 리더로부터 데이터를 읽지 못하거나, 따라잡지 못한 시간이 이 설정 값보다 길면 아웃-오브-싱크 상태가 됨. 컨슈머의 최대 지연에도 영향을 줌
디스크에 저장하기
카프카는 세그먼트를 교체할 때(기본값: 1GB)와 재시작 직전에만 메시지를 디스크로 플러시하며, 그 외의 경우에는 리눅스의 페이지 캐시 기능에 의존함
각각 데이터의 복제본을 가지고 있는, 서로 다른 랙이나 가용영역(예를 들어 aws의 다른 리전 여러개를 묶는 느낌)에 위차한 세 대의 장비가 리더의 디스크에 메시지를 쓰는 것보다 더 안전.
flush.messages
: 디스크에 저장되지 않은 최대 메시지 수flush.ms
: 얼마나 자주 메시지를 디스크에 저장할지 설정. 이 기능을 사용하기 전에 fsync가 어떻게 카프카의 처리량에 영향을 미치는지는fsync
에 대해 찾아봐야함
신뢰성 있는 시스템에서 프로듀서 사용하기
사용 가능한 가장 높은 신뢰성 결정을 브로커에 적용하더라도, 프로듀서 역시 신뢰성이 있도록 설정을 잡아주지 않는다면 시스템 전체로서는 여전히 데이터가 유실될 수 있음.
프로듀서가 acks=1 설정일때(리더만 메세지를 받았을 때 성공으로 침)
- 프로듀서에서 메시지를 전송해 리더에는 쓰여졌지만, 아직 인-싱크 레플리카에 반영되지 않은 상태
- 리더가 프로듀서한테 응답을 보낸 직후 크래시가 나서 데이터가 레플리카로 복제되지 않고 유실
- 어떤 컨슈머도 보지 못한다는 관점에서 시스템의 일관성은 유지
- 프로듀서 입장에선 이 리더에 쓰여진 메세지는 유실
프로듀서가 acks=all 설정일때(모든 인-싱크 레플리카가 메시지를 받았을 때 성공으로 침)
- 새 리더는 아직 선출중이라고 가정하면, 카프카는 Leader not Available 응답을 보냄
- 이 시점에서 프로듀서가 올바르게 에러를 처리하지 않고 쓰기가 성공할때까지 재시도 하지 않는다면 메세지는 유실
프로듀서를 개발할 때 중요한 요소
- 올바른 acks 설정
- 적절한 에러처리
응답 보내기
아래의 세가지 응답모드중 하나를 선택할 수 있음
acks=0
프로듀서가 메시지를 네트워크로 전송한 시점에서 메시지가 카프카에 성공적으로 쓰여진 것으로 간주. 지연은 낮지만(이 설정으로 벤치마크를 돌리는 경우가 많은 이유) 종단 지연은 개선되지 않음.(컨슈머는 메시지가 모든 인-싱크 레플리카로 복제되지 않는 한 해당 메시지를 볼 수 없음)acks=1
리더가 메시지를 받아 데이터 파일에 쓴 직후 응답 또는 에러를 보낸다는 것을 의미.
리더에 메시지가 쓰여졌지만 클라이언트로 응답이 간 상태에서 미처 파롤워로 복제가 완료되기 전에 리더가 정지하거나 크래쉬 날 경우 데이터가 유실 될 수 있음.
이 설정을 사용할 경우 메시지를 복제하는 속도보다 더 빨리 리더에 쓸 수 있기 때문에 불완전 복제 파티션(under-replicated partition, URP)이 발생할 수 있음.acks=all
모든 인-싱크 레플리카가 메시지를 받아갈 때까지 기다렸다가 응답
지연이 가장 길지만 신뢰성이 가장 높은 설정
브로커의min.insync.replicas
설정과 함께 사용하면, 응답이 오기 전 얼마나 많은 레플리카에 메시지가 복제될지 조정 가능
프로듀서 재시도 설정하기
프로듀서가 처리하는 에러는 크게 두가지로 나뉨.
- 프로듀서가 자동으로 처리하는 에러
- 개발자가 직접 처리해야 하는 에러
에러 코드
- 재시도 가능한 에러
LEADER_NOT_AVAILABLE
: 재시도하면 해결 가능
- 재시도 불가능한 에러
INVALID_CONFIG
: 재시도해도 해결 안됨
재시도와 주의깊은 에러 처리는 각 메시지가 ‘최소 한 번’ 저장되도록 보장할 수는 있지만, ‘정확히 한 번’은 보장할 수 없음
추가적인 에러 처리
개발자의 입장에서 다른 종류의 에러들도 처리할 수 있어야함
- 메세지 크기나 인가관련 에러와 같이 재시도 불가능한 브로커 에러
- 메세지가 브로커에 전송되기 전에 발생한 에러(직렬화 과정에서 발생한 메세지 형식 에러 등)
- 프로듀서가 모든 재전송 시도를 소진했거나, 재시도 과정에서 가용 메모리가 다 차서 발생하는 에러
- 타임아웃
신뢰성 있는 시스템에서 컨슈머 사용하기
컨슈머는 카프카에 커밋된 데이터만 읽을 수 있음. 즉, 모든 인-싱크 레플리카에 쓰여진 후부터 읽을 수 있음. 즉, 컨슈머는 일관성이 보장된 데이터만 읽음
메시지를 읽는 도중 누락을 방지하려면, 배치 단위로 메시지를 읽어온 뒤, 마지막 오프셋을 확인하고, 그 오프셋에서부터 새로운 배치를 요청해야 함
오프셋이 언제 어떻게 커밋되는지에 대해 신경써야하는 이유
컨슈머가 중단될 경우, 다른 컨슈머 또는 재시작된 기존 컨슈머는 어디서부터 작업을 재개해야 할지 알아야 하는데 이를 위해 오프셋을 커밋하는 과정이 필요
컨슈머가 메시지를 누락할 수 있는 경우, 읽기는 했지만, 처리를 완료하지 않은 이벤트들의 오프셋을 커밋하는 경우. 이렇게 하면 다른 컨슈머가 작업을 물려받았을 때 이 메시지를 건너뛰게 되므로 영원히 처리되지 않음.
커밋된 메세지 vs 커밋된 오프셋
커밋된 메세지: 모든 인-싱크 레플리카에 쓰여져서 컨슈머가 읽을 수 있는 메세지
커밋된 오프셋: 컨슈머가 특정 파티션 어느 오프셋까지의 모든 메세지를 받아서 처리를 완료했는지 알리기 위해 카프카에 알리는 것
신뢰성 있는 처리를 위해 중요한 컨슈머 설정
group.id
: 컨슈머가 구독한 토픽의 모든 메시지를 읽으려면 필요auto.offset.reset
: 커밋된 오프셋이 없거나, 없는 오프셋을 요청할 때 컨슈머가 어떻게 할지를 설정
earliest
: 유효한 오프셋이 없으면 처음부터 읽기 시작. 중복 발생 가능, 데이터 유실 최소화latest
: 파티션의 끝에서부터 읽기 시작. 중복 최소화, 데이터 유실 가능
enable.auto.commit
: 자동으로 오프셋을 커밋할 것인지 설정. 일정 주기에 따라 자동 커밋을 하거나, 코드에서 직접 커밋할 수 있음. 자동 커밋은 처리되지 않은 오프셋이 실수로 커밋되지 않도록 보장해주지만 단점으로는 우리가 메시지 중복처리 제어는 불가능함auto.commit.interval.ms
: 자동 커밋 주기를 설정. 자주 커밋할수록 오버헤드는 늘어나지만, 중복은 줄어듦
컨슈머에서 명시적으로 오프셋 커밋하기
신뢰성 있는 컨슈머를 개발할 때 고려해야 할 사항
- 메세지 처리 먼저, 오프셋 커밋은 나중에
그렇지 않으면 메시지를 누락할 수 있음
커밋 빈도는 성능과 크래시 발생 시 중복 개수 사이의 트레이드 오프
커밋은 빈도가 잦으면 오버헤드, 크래시 시 중복 처리가 늘어남
정확한 시점에 정확한 오프셋을 커밋
처리가 완료된 메세지의 오프셋만을 커밋
읽기만 하고 처리되지 않은 메시지를 커밋하면 누락이 발생함
리밸런스
파티션 할당을 재조정 할 수 있음
컨슈머는 재시도를 해야할 수도 있다
마지막으로 처리한 레코드의 오프셋을 커밋하거나, 재시도 토픽에 기록해두고 재시도를 해야 함
컨슈머가 상태를 유지해야할 수도 있다
poll()
메서드 호출 간에 상태를 유지해야할 수도 있음예를들어, 평균값을 계산하려면, 오프셋을 커밋할 때 그 중간 상태를 별도로 기록해둘 필요가 있음
시스템 신뢰성 검증하기
세 개의 계층에 걸쳐서 검증을 수행할 것을 제안
설정 검증하기
애플리케이션 로직과 격리된 채 브로커와 클라이언트 설정을 검증하는 것은 쉽고, 아래의 이유로 인해 권장함
- 우리가 선택한 구상이 요구 조건을 충족시킬 수 있는지 확인하는데 도움이 됨
- VerifiableProducer, VerifiableCopnsumer 도구를 사용하여 자동화된 테스팅 프레임워크로에 포함된 형태로든 실행이 가능
VerifiableProducer, VerifiableConsumer 이란?
VerifiableProducer : 메시지를 생성해 브로커에 전송하고 성공 여부를 출력
VerifiableConsumer : 생성된 메시지를 소비하고, 커밋과 리밸런스 정보를 출력
검증 시나리오
- 리더 선출: 리더를 정지시키면 어떻게 될까? 프로듀서와 컨슈머가 평상시처럼 작동을 재개하느데까지 얼마나 걸릴까?
- 컨트롤러 재선출: 컨트롤러가 재시작한 뒤 시스템이 재개되는데 얼마나 걸릴까?
- 롤링 재시작: 메시지 유실없이 브로커들을 하나씩 재시작시킬 수 있을까?
- 비정상 리더 선출 테스트: (각 레플리카의 동기화가 풀리도록 하기 위해) 한 파티션의 모든 래플리카들을 하나씩 중단시킨 다음 아웃-오브-싱크 상태가 된 브로커를 시작시키면 어떻게 될까? 작업을 재개하려면 어떻게 해야할까? 이것은 용인할 수 있는 수준인가?
애플리케이션 검증하기
브로커와 클라이언트 설정이 검증되면, 애플리케이션 레벨에서 신뢰성 보장을 위한 테스트를 진행해야함
애플리케이션 로직이 카프카의 클라이언트 라이브러리와 상호작용하는 커스텀 오류 처리 코드나 오프셋 커밋, 리밸런스 리스너와 같은 부분을 확인
카프카는 장애 주입을 위한 자체적인 프레임워크 Trogdor 트록도르 테스트 프레임워크를 테스트 프레임워크를 포함함
테스트해야 할 상황
- 클라이언트와 브로커 간의 연결이 끊어짐
- 클라이언트와 브로커 간의 긴 지연
- 디스크 용량 부족
- 디스크 멈춤
- 리더 선출
- 브로커/컨슈머/프로듀서 롤링 재시작 시
프로덕션 환경에서 신뢰성 모니터링 하기
애플리케이션 테스트 후에도 운영 환경에서 지속적으로 모니터링해야함
카프카의 자바 클라이언트들은 클라이언트 쪽 상태와 이벤트를 모니터링 할 수 있게 해주는 JMX 지표를 포함.
프로듀서의 경우, 신뢰성 측면에서 가장 중요한 두 지표는 레코드별 에러율(error rate)과 재시도율(retry rate).(초 단위 평균값)
컨슈머의 경우, 가장 중요한 지표는 컨슈머 랙(consumer lag). 컨슈머가 브로커 내 파티션에 커밋된 가장 최신 메시지에서 얼마나 뒤떨어져 있는지를 가리킴.
요약
신뢰성이란 단순히 카프카의 특정 기능의 문제가 아님.
애플리케이션 아키텍처, 애플리케이션이 프로듀서와 컨슈머 API를 사용하는 방식, 프로듀서와 컨슈머 설정, 토픽 설정, 브로커 설정을 포함해서 총체적으로 신뢰성 있는 시스템을 구축할 필요가 있음.