[book] 실전 카프카-개발부터 운영까지
- 아래책을 기반으로 함.
- 실전 카프카-개발부터 운영까지
- 일부 항목에 대한 정리 중
3장 카프카 기본 개념과 구조
3.1 카프카 기초 다지기
- 카프카를 구성하는 주요 요소
- 주키퍼
- 카프카 또는 카프카 클러스터
- 브로커 : 카프카 애플리케이션이 설치된 서버 또는 노드
- 프로듀서 : 카프카로 메시지를 보내는 역할을 하는 클라이언트를 총칭
- 컨슈머 : 카프카에서 메시지를 꺼내가는 역할을 하는 클라어ㅇ언트를 총칭
- 토픽 : 카프카는 메시지 피드들을 토픽으로 구분하고 각 토픽의 이름은 카프카 내에서 고유함
- 파티션 : 병렬처리 및 고성능을 얻기 위해 하나의 토픽을 여러개로 나눈것을 말함
- 세그먼트 : 프로듀서가 전송한 실제 메시지가 브로커의 로컬 디스크에 저장되는 파일을 말함
- 메시지 또는 레코드 : 프로듀서가 브로커로 전송하거나 컨슈머가 읽어가는 데이터 조각을 말함
3.1.1 리플리케이션
- 각 메시지들을 여러개로 복제해서 카프카 클러스터내 브로커들에 분산시키는 동작을 의미
- 토픽 생성 명령어중 replication-factor 라는 옵션
- 카프카 내 몇개의 리플리케이션을 유지하겠다라는 의미
- 1이면 리플리케이션이 1개, 3이면 원본을 포함한 리플리케이션이 3개
- 카프카 내 몇개의 리플리케이션을 유지하겠다라는 의미
- 리플리케이션 팩터 수가 커지면 안정성은 높아지지만 그만큼 브로커 리소스를 많이 사용
- 복제에 대한 오버헤드 발생
- 1(개발환경)~3(리얼 환경)을 권장
3.1.2 파티션
- 하나의 토픽이 한번에 처리할 수 있는 한계를 높이기 위해 토픽 하나를 여거 개로 나눠 병렬 처리가 가능하게만든 것을 파티션
- 하나를 여러개로 나누면 분산처리도 가능
- 나뉜 파티션 수만큼 컨슈머를 연결할 수 있음
- 파티션 수는 초기 생성후 늘릴 수는 있지만, 한번 늘어나면 절대로 줄일수는 없음
- 초기엔 2또는 4로 생성후 모니터링하면서 조금씩 늘이자.
3.1.3 세그먼트
- 파티션에서 확장된 개념
- 프로듀셔에 의해 브로커로 전송된 메시지는 토픽의 파티션에 저장, 각 메시지들은 세그먼트라는 로그 파일의 형태로 브로커의 로컬 디스크에 저장됨
- 토픽, 파티션, 세그먼트의 관계도
3.2 카프카의 핵심 개념
- 카프카가 높은 처리량과 안정성을 지니게 된 특성들에 대해 설명을 하나씩 알아보자
3.2.1 분산 시스템
- 클러스터의 리소스가 한계치에 도달하거나 문제 발생시 브로커를 추가하는 방식으로 확장이 가능
3.2.2 페이지 캐시
- 높은 처리량을 얻기 위한 기능
- 카프카는 OS 페이지 캐시를 활용하는 방식으로 설계
- 페이지 캐시는 직접 디스크에 일고쓰는 대신 물리 메모리중 애플리케이션이 사용하지 않는 일부 잔여 메모리를 활용
- 디스크 I/O에 대한 접근이 줄어 성능이 향상됨
- 카프카가 직접 디스크에 읽고쓰지 않고 페이지 캐시를 통해 동작함
3.2.3 배치 전송 처리
- 카프카는 프로듀서, 컨슈머 클라이언트들과 서로 통신하면서 수 많은 메시지를 주고 받음. 이 때 발생하는 많은 통신을 묶어서 처리 할 수 있다면, 단건으로 통신할 때에 비해 네트워크 오버헤드를 줄일 수 있어 빠르고 효율적으로 처리 할 수 있음.
- mysql batch insert 랑 비슷하지 않을까? 생각됨.
3.2.4 압축 전송
- 카프카는 메시지 전송 시 좀 더 성능이 높은 압축 전송을 사용하는 것을 권장
- 압축 타입별로 테스트 후 결정을 추천
- 높은 압축률 : gzip, zstd
- 빠른 응답 속도 : snappy, lz4
- 배치 전송과 결합해 사용할 경우 더욱 높은 효과를 얻을 수 있음
3.2.5 토픽, 파티션, 오프셋
- 카프카는 토픽(topic)이라는 곳에 데이터를 저장하는데 메일 전송 시스템의 이메일 주소 정도의 개념으로 이해하면 됨.
- 토픽은 병렬 처리를 위해 여러개의 파티션이라는 단위로 나뉨
- 파티션의 메시지가 저장되는 위치를 오프셋이라고 부르며, 순차적으로 증가하는 숫자(64비트 정수) 형태
- 각 파티션에서의 오프셋은 고유한 숫자로, 카프카에서는 오프셋을 통해 메시지의 순서를 보장하고 컨슈머에서는 마지막까지 읽은 위치를 알 수 있음.
3.2.6 고가용성 보장
- 카프카는 고가용성 보장을 위해 리플리케이션 기능을 제공
- 토픽 자체를 복제하는 것이 아니라 토픽의 파티션을 복제하는 것임
- 토픽을 생성할 때 옵션으로 리플리케이션 팩터 수를 지정할 수 있음
- 원본과 리플리케이션을 구분하기 위해 리더(leader)와 팔로워(follower)라고 구분을 함
- 팔로워 수가 많을 수록 브로커의 디스크 공간도 소비되므로 이상적인 리플리케이션 팩터 수를 유지할 필요가 있으며 일반적으로 3으로 구성하도록 권장
- 리더(leader)는 프로듀서, 컨슈머로부터 오는 모든 읽기와 쓰기 요청을 처리
- 팔로워(follower)는 오직 리더로부터 리플리케이션
3.2.7 주키퍼의 의존성
- 주키퍼는 반드시 홀수로 구성해야 함
- 지노드(znode)를 이용해 카프카의 메타 정보가 주키퍼에 기록되며, 주키퍼는 이러한 지노드를 이용해 브로커의 노드 관리, 토픽 관리, 컨트롤러 관리 등을 수행
- Apache Kafka 2.8 버전부터 주키퍼 대신 kraft 를 사용할 수 있으나 아직은 개발 단계
3.3 프로듀서의 기본 동작과 예제 맛보기
- 프로듀셔는 카프카의 토픽으로 메시지를 전송하는 역할을 담당
3.3.1 프로듀서 디자인
- 프로듀서 디자인 개요
- ProducerRecord라고 표시된 부분은 카프카로 전송하기 위한 실제 데이터이며, 레코드는 토픽, 파티션, 키, 밸류로 구성 됨.
- 프로듀서가 카프카로 레코드를 전송할 때, 카프카의 특정 토픽으로 메시지를 전송
- 레코드에서 토픽과 밸류(메시지 내용)는 필수 값이며, 특정 파티션을 지정하기 위한 레코드의 파티션과 특정 파티션에 레코드들을 정렬하기 위한 레코드의 키는 선택사항(옵션)
- 각 레코드들은 프로듀서의 send() 메소드를 통해 시리얼라이저(serializer), 파티셔너(partitioner)를 거침.
- 프로듀서 레코드의 파티션을 지정했을 경우
- 파티셔너는 아무 동작하지 않고 지정된 파티션으로 레코드를 전달.
- 프로듀서 레코드의 파티션을 지정 안했을 경우
- 키를 가지고 파티션을 선택해 레코드를 전달 함. 기본적으로 라운드 로빈(round robin) 방식으로 동작.
- send() 메소드 동작 이후 레코드들을 파티션별로 잠시 모아 둠.
- 모아둔 이유는 프로듀서가 카프카로 전송하기 전, 배치 전송하기 위함.
- 전송이 실패하면 재시도 동작이 이뤄지고, 지정된 횟수 만큼의 재시도가 실패하면 최종 실패 전달. 전송이 성공하면 메타데이터를 리턴하게 됨
3.3.2 프로듀서의 주요 옵션
- 기본값으로 써도 되지만 옵션을 알아야 효율적으로 사용 가능하다
Producer Option | 설명 |
---|---|
bootstrap.servers | 카프카 클러스터는 클러스터 마스터라는 개념이 없으므로, 클러스터 내 모든 서버가 클라이언트의 요청을 받을 수 있음. 클라이언트가 카프카 클러스터에 처음 연결하기 위한 호스트와 포트정보를 나타 냄. |
client.dns.lookup | 하나의 호스트에 여러 IP를 매핑해 사용하는 일부 환경에서 클라이언트가 하나의 IP와 연결하지 못할 경우에 다른 IP로 시도하는 설정. use_all_dns_ips가 기본값으로, DNS에 할당된 호스트의 모든 IP를 쿼리하고 저장. 첫 번째 IP로 접근 실패 시, 종료하지 않고 다음 IP로 접근 시도. resolve_canonical_bootstrap_servers_only 옵션은 Kerberos 환경에서 FQDN을 얻기 위한 용도로 사용 됨. |
acks | 프로듀서가 카프카 토픽의 리더 측에 메시지를 전송한 후 요청을 완료하기를 결정하는 옵션. 0, 1, all(-1)로 표현하며, 0은 빠른 전송을 의미하지만, 일부 메시지 손실 가능성 있음. 1은 리더가 메시지를 받았는지 확인하지만, 모든 팔로워를 전부 확인하지 않음. 대부분 기본값으로 1을 사용. all은 팔로워가 메시지를 받았는지 여부 확인. 다소 느릴 수 있으나 하나의 팔로우가 있는 한 메시지 손실은 없음. |
buffer.memory | 프로듀서가 카프카 서버로 데이터를 보내기 위해 잠시 대기(배치 전송이나 딜레이 등)할 수 있는 전체 메모리 바이트(byte) |
compression.type | 프로듀서가 메시지 전송 시 선택할 수 있는 압축 타입. none, gzip, snappy, lz4, zstd 중 선택 |
enbale.idempotence | 설정을 true로 하는 경우 중복 없는 전송이 가능하며, 이와 동시에 max.in.flight.requests.per.connection은 5 이하, retries는 0 이상, acks는 all로 설정해야 함. |
max.in.flight.requests.per.connection | 하나의 커넥션에서 프로듀서가 최대한 ACK 없이 전송할 수 있는 요청 수. 메시지의 순서가 중요하다면 1로 설정할 것을 권장하지만 성능은 다소 떨어짐. |
retries | 일시적인 오류로 인해 전송에 실패한 데이터를 다시 보내주는 횟수 |
batch.size | 프로듀서는 동일한 파티션으로 보내는 여러 데이터를 함께 배치로 보내려고 시도. 적절한 배치 크기 설정은 성능에 도움을 줌. |
linger.ms | 배치 형태의 메시지를 보내기 전 추가적인 메시지를 위해 기다리는 시간을 조정하고, 배치 크기에 도달하지 못한 상황에서 linger.ms제한 시간에 도달 했을 때 메시지를 전송. |
transactional.id | ‘정확히 한 번 전송’을 위해 사용하는 옵션. 동일한 TransactionalId에 한해 정확히 한 번을 보장. 옵션을 사용하기 전 enable.idempotence를 true로 설정해야 함. |
3.3.3 프로듀서 예제
- 프로듀셔의 전송 방법은 아래와 같이 3종류가 있으나 굳이..
- 메시지를 보내고 확인하지 않기
- 동기 전송
- 비동기 전송
3.4 컨슈머의 기본 동작과 예제 맛보기
- 컨슈머는 카프카의 토픽에 저장되어있는 메시지를 가져오는 역할을 담당
- 단순하게 메시지만 가져오는 기능만 하는 것이 아니라 내부적으로는 컨슈머 그룹, 리밸런싱 등 여러 동작을 수행 함
3.4.1 컨슈머의 기본 동작
- 프로듀서가 카프카의 토픽으로 메시지를 전송하면 해당 메시지들은 브로커들의 로컬 디스크에 저장
- 컨슈머를 이용해 토픽에 저장된 메시지를 가져올 수 있음.
- 컨슈머 그룹은 하나 이상의 컨슈머들이 모여 있는 그룹을 의미
- 컨슈머는 반드시 컨슈머 그룹에 속하게 됨. 그리고 이 컨슈머 그룹은 각 파티션의 리더에게 카프카 토픽에 저장된 메시지를 가져오기 위한 요청을 보냄.
- 이때 파티션 수와 컨슈머 수(하나의 컨슈머 그룹 안에 있는 컨슈머 수)는 1:1로 매핑되는 것이 이상적 임.
- 파티션 수보다 컨슈머 수가 많게 구현하는것은 바람직하지 않음
- 컨슈머 수가 많다고 빠르게 가져오거나 처리량이 높아지지 않고 그냥 대기 상태로만 존재하기 때문
3.4.2 컨슈머의 주요 옵션
Consumer Option | 설명 |
---|---|
bootstrap.servers | 프로듀서와 동일하게 브로커의 정보를 입력. |
fetch.min.bytes | 한 번에 가져올 수 있는 최소 데이터 크기. 지정한 크기보다 작은 경우, 요청에 응답하지 않고 데이터가 누적될 때까지 대기. |
group.id | 컨슈머가 속한 컨슈머 그룹을 식별하는 식별자. 동일한 그룹 내의 컨슈머 정보는 모두 공유 됨. |
heartbeat.interval.ms | 하트 비트가 있다는 것은 컨슈머의 상태가 active임을 의미. session.timeout.ms와 밀접한 관계가 있으며, session.timeout.ms보다 낮은 값으로 설정해야 함. 일반적으로 session.timeout.ms의 1/3로 설정. |
max.partition.fetch.bytes | 파티션 당 가져올 수 있는 최대 크기 |
session.timeout.ms | 이 옵션을 이용해, 컨슈머가 종료된 것인지를 판단. 컨슈머는 주기적으로 하트 비트를 보내야 하고, 만약 이 시간 전까지 하트 비트를 보내지 않았다면 해당 컨슈머는 종료된 것으로 간주하고 컨슈머 그룹에서 제외 후, 리밸런싱을 시작 함. |
enable.auto.commit | 백그라운드로 주기적으로 오프셋을 커밋 |
auto.offset.reset | 카프카에서 초기 오프셋이 없거나 현재 오프셋이 더 이상 존재하지 않는 경우에 다음 옵션으로 reset 함. <ul><li>earliest: 가장 초기의 오프셋값으로 설정</li><li>latest: 가장 마지막 오프셋값으로 설정</li><li>none: 이전 오프셋값을 찾지 못하면 에러를 나타냄.</li></ul> |
fetch.max.bytes | 한 번의 가져오기 요청으로 가져올 수 있는 최대 크기 |
group.instance.id | 컨슈머의 고유 식별자. 만약 설정한다면 static 멤버로 간주되어, 불필요한 리밸런싱을 하지 않음. |
isolation.level | 트랜잭션 컨슈머에서 사용되는 옵션. <ul><li>read_uncommited(Default) : 모든 메시지를 읽음</li><li>read_committed : 트랜잭션이 완료된 메시지만 읽음</li></ul> |
max.poll.records | 한 번의 poll() 요청으로 가져오는 최대 메시지 수. |
partition.assignment.strategy | 파티션 할당 전략.(Default : range) |
fetch.max.wait.ms | fetch.min.bytes에 의해 설정된 데이터보다 적은 경우 요청에 대한 응답을 기다리는 최대 시간. |
3.4.3 컨슈머 예제
- 컨슈머에서 메시지를 가져오는 방법은 크게 세가지
- 오토 커밋
- 오프셋을 주기적으로 커밋함, 관리자가 오프셋을 따로 관리하지 않아도 됨
- 일부 메시지를 못 가져오거나 중복으로 가져오는 경우 있음
- 동기 가져오기
- 메시지를 가져온 후 처리까지 완료하고 현재의 오프셋을 커밋
- 속도는 느림, 메시지 손실 거의 없음, 메시지 중복으로 가져오는 경우 있음
- 비동기 가져오기
- 오토 커밋
3.4.4 컨슈머 그룹의 이해
- 컨슈머는 컨슈머 그룹안에 속한 것이 일반적인 구조, 하나의 컨슈머 그룹 안에 여러 개의 컨슈머가 구성될 수 있음.
- 토픽의 파티션과 일대일로 매핑되어 메시지를 가져오게 됨.
- 왼쪽에 peter-01이라는 토픽과 3개의 파티션으로 구성되어 있고, 오른쪽에는 consumer group 01이라는 컨슈머 그룹 아이디를 가진 컨슈머 그룹이 있으며, peter-01 토픽의 파티션 수와 동일한 3개의 컨슈머가 속해 있음.
- 컨슈머들은 하나의 컨슈머 그룹 안에 속해 있으며, 그룹 내의 컨슈머들은 서로의 정보를 공유 함.
- 예를 들어 Consumer01이 문제가 생겨 종료됐다면, Consumer02 또는 Consumer03은 Consumer01이 하던 일을 대신해 peter-01 토픽의 partition0을 컨슘하기 시작 함.
4장 카프카의 내부 동작 원리와 구현
- 카프카의 내부 동작 원리와 구현에서 가장 중요한 부분중 하나는 리플리케이션 동작
- 리플리케이션에 이어, 리더와 팔로워의 역할과 리더에포크와 복구 동작을 배워보고 리플리케이션 동작과 관련있는 컨트롤러와 컨트롤러의 동작, 로그와 로그 컴팩션에 대해 알아보자
4.1 카프카 리플리케이션
- 카프카는 초기 설계 단계부터 일시적인 하드웨어 이슈로 브로커 한두대에서 장애가 발생하더라도 중앙 데이터 허브로서 안정적인 서비스가 운영될 수 있도록 구성됨
4.1.1 리플리케이션 동작 개요
- 카프카는 브로커의 장애에도 불구하고 연속적으로 안정적인 서비스 제공함으로써 데이터 유실을 방지하며 유연성을 제공한다.
- 카프카의 리플리케이션 동작을 위해 토픽 생성 시 필숫값으로 replication factor라는 옵션을 설정해야 함.
- 카프카는 해당 옵션을 이용해 관리자가 지정한 수만큼의 리플리케이션을 가질 수 있기 때문에 N개의 리플리케이션이 있는 경우 N - 1까지의 브로커 장애가 발생해도 메시지 손실 없이 안정적으로 메시지를 주고받을 수 있음
4.1.2 리더와 팔로워
- 토픽 상세보기 명령어를 실행해보면 출력내용중 파티션의 리더라는 부분이 있음
- 모두 동일한 리플리케이션이라하더라도 리더만의 역할이 따로 있음
- 카프카는 내부적으로 모두 동일한 리플리케이션들을 리더와 팔로워로 구분하고, 각자의 역할을 분담시킨다.
- 리더는 리플리케이션 중 하나가 선정되며, 모든 읽기와 쓰기는 그 리더를 통해서만 가능하다.
- 프로듀서는 모든 리플리케이션에 메시지를 보내는 것이 아니라 리더에게만 메시지를 전송한다.
- 또한 컨슈머도 오직 리더로부터 메시지를 가져온다.
- 리더와 팔로워의 관계
- 위 그림에서 peter-test01 토픽의 파티션 수는 1이고, 리플리케이션 팩터 수는 3을 나타냄.
-
그림에서 처럼 토픽의 파티션 번호를 함께 표시하는데, peter-test01-0은 peter-test01 토픽의 0번 파티션이라는 뜻 임.
- 프로듀서는 peter-test01 토픽으로 메시지를 전송하는데, 파티션의 리더만 읽고 쓰기가 가능하므로 0번 파티션의 리더로 메시지를 보냄
- 컨슈머 동작에서도 역시 0번 파티션의 리더로부터 메시지를 가져옴
- 나머지 팔로워들은 리더에 문제가 발생하거나 이슈가 있을 경우를 대비해 언제든지 새로운 리더가 될 준비를 함.
- 따라서 컨슈머가 토픽의 메시지를 꺼내 가는 것과 비슷한 동작으로 지속적으로 파티션의 리더가 새로운 메시지를 받았는지 확인하고, 새로운 메시지가 있다면 해당 메시지를 리더로부터 복제 함
4.1.3 복제 유지와 커밋
- 리더와 팔로워는 ISR(InSyncReplica)라는 논리적 그룹으로 묶여 있음.
- 이렇게 리더와 팔로워를 별도의 그룹으로 나누는 이유는 기본적으로 해당 그룹 안에 속한 팔로워들만이 새로운 리더의 자격을 가질 수 있기 때문임
- 즉, ISR 그룹에 속하지 못한 팔로워는 새로운 리더의 자격을 가질 수 없음.
- ISR내의 팔로워들은 리더와의 데이터 일치를 유지하기 위해 지속적으로 리더의 테이터를 따라게가 되고, 리더는 ISR 내 모든 팔로워가 메시지를 받을 때까지 기다림.
- 하지만 팔로워가 네트워크 오류, 브로커 장애 등 이유로 리더로부터 리플리케이션하지 못하는 경우도 발생할 수 있고. 이렇게 뒤처진 팔로워는 이미 리더와의 데이터가 불일치한 상태에 놓여 있기 때문에 만약 이 팔로워에게 새로운 리더를 넘겨준다면 데이터의 정합성이나 메시지 손실 등의 문제가 발생할 수 있음.
- 따라서 파티션의 리더는 팔로워들이 뒤처지지 않고 리플리케이션 동작을 잘하고 있는지 감시함
- 즉 리더에 뒤처지지 않고 잘 따라잡고 있는 팔로워들만이 ISR 그룹에 속하게 되며, 리더에 장애가 발생할 경우 새로운 리더의 자격을 얻을 수 있는 것임.
- 리더와 팔로워 중 리플리케이션 동작을 잘하고 있는지 여부 등은 누가 어떤 기준으로 판단할까?
- 리더는 읽고 쓰는 동작은 물론, 팔로워가 리플리케이션 동작을 잘 수행하고 있는지도 판단 함.
- 팔로워가 특정 주기의 시간만큼 복제 요청을 하지 않는다면, 리더는 해당 팔로워가 리플리케이션 동작에 문제가 발생했다고 판단해 ISR 그룹에서 추방 함.
- ISR 내에서 모든 팔로워의 복제가 완료되면, 리더는 내부적으로 커밋되었다는 표시를 하기 됨.
- 마지막 커밋 오프셋 위치는 하이워터마크(high water mark) 라고 부름.
- 즉, 커밋되었다는 것은 리플리케이션 팩터 수의 모든 리플리케이션이 전부 메시지를 저장했음을 의미
- 이렇게 커밋된 메시지만 컨슈머가 읽어갈 수 있음.
- 카프카에서 커밋되지 않은 메시지를 컨슈머가 읽을 수 없게 하는 이유는 바로 메시지의 일관성을 유지하기 위함.
- 커밋 메시지
- 위의 그림은 프로듀서가 첫 번째 test message1 이라는 메시지를 peter-test01 토픽으로 보냈고 모든 팔로워가 리플리케이션 동작을 통해 모두 저장했으며 커밋까지 완료된 상태임
- 이어서 두번째 test message2 라는 메시지를 전송했고 이 메시지는 리더만 저장한 상태이고 팔로워들은 아직 해당 메시지에 대해 리플리케이션 동작을 하기전의 상태
- 여기서 커밋되기전 메시지를 컨슈머가 읽을 수 있다고 가정하고 어떤 일이 일어나는지 알아보자
- 1) 컨슈머 A 는 jade-test01 토픽을 컨슘 함
- 2) 컨슈머 A는 jade-test01 이 토픽의 파티션 리더로부터 메세지를 읽어갑니다. 읽어간 메세지는 test message1,2
- 3) jade-test01 토픽의 파티션 리더가 있는 브로커에 문제가 발생할 팔로워 중 하나가 새로운 리더가 됩니다.
- 4) 프로듀서가 보낸 test message2 메세지는 아직 팔로워들에게 리플리케이션이 되지 않은 상태에서 새로운 리더로 변경 됐으므로, 새로운 리더는 test message1 메세지만 갖고 있음
- 5) 새로운 컨슈머 B가 jade-test01 토픽 컨슘함
- 6) 새로운 리더로 부터 메세지를 읽어가고, 읽어간 메세지는 오직 test message1 임
- 이런식의 시나리오 일 경우 컨슈머 A 와 B는 jade-test01 이라는 동일한 토픽의 파티션을 읽었지만, 컨슈머 A는 test message1,2 를 가져왔고, 컨슈머 B는 test message1 만 가져오게 됩니다.
- 이 처럼 커밋되지 않는 메세지를 읽게 된다면 같은 토픽을 읽었음에도 메세지가 일치하지 현상이 발생할 수 있게 됩니다.
-
따라서 카프카에서는 이러한 메세지 불일치 현상을 방지하고자 커밋된 메세지만 컨슈머가 읽어 갈수 있도록 구현되어 있습니다.
- 모든 브로커는 재시작될 때, 커밋된 메시지를 유지하기 위해 로컬 디스크의 ‘replication-offset-checkpoint’ 라는 파일에 마지막 커밋 오프셋 위치를 저장함.
- replication-offset-checkpoint’ 파일은 브로커 설정 파일에서 설정한 로그 디렉토리 경로에 있으며, 브로커 설정 파일의 로그 디렉토리는 /data/kafka-logs로 설정되어 있으므로, 해당 디렉토리 하위에 위치.
4.1.4 리더와 팔로워의 단계별 리플리케이션 동작
- 카프카로 수많은 메세지의 읽고 쓰기를 처리하는 리더는 매우 바쁘게 동작을 합
- 이렇게 바쁜 리더가 리플리케이션 동작을 위해 팔로워들과 많은 통신을 주고 받거나 리플리케이션 동작에 많은 관여를 한다면 그 결과는 리더의 성능은 떨어지고 카프카의 장점의 빠른 성능을 내기도 어려울 것 임
- 그래서 카프카는 리더와 팔로워간의 리플리케이션 동작을 처리할때 서로 통신을 최소화 할 수 있도록 설계함으로 리더의 불하를 줄였음
- 리더와 팔로워간의 리플리케이션 동작에 대한 내용
- 1) 프로듀서가 토픽으로 메세지를 전송
- 2) 리더만 메세지를 수신 받아 저장(시나리오에서는 지금 메세지는 0번 오프셋에 위치함을 가정)
- 3) 팔로워들은 리더에게 메세지 가져오기(fetch) 요청을 보낸 후 새로운 메세지 가 있다는 사실을 인지하고 메세지를 리플리케이션 함
- 4) 리더는 모든 팔로워가 리더에게 메세지를 리플리케이션하기 위한 요청을 보냈다는 사실을 알고 있음
- 5) 하지만 리더는 팔로워들이 리플리케이션 동작이 성공했는지 실패했는지 여부는 알지 못함
- 카프카에서 리더와 팔로워간의 ACK 를 주고 받는 통신이 없으며, ACK 를 제거함으로 성능이 더욱 좋아짐
- 아래 부터 ACK 통신없이 안정적인 리플리케이션 처리에 대한 내용이 있음
- 6) 리더는 프로듀서로 부터 다음 메세지를 수신 하게 되면 저장하게 됩니다(여기서 수신받은 메세지는 1번 오프셋이라고 가정)
- 7) 팔로워들은 리더에게 새로운 메세지인 1번 오프셋에 대한 리플리케이션을 요청 합니다.
- 8) 팔로워들로 부터 1번 오프셋에 대한 리플리케이션 요청을 받은 리더는 팔로워들의 0번 오프셋에 대한 리플리케이션 동작이 성공했음을 인지하고 오프셋 0번 대해서 커밋 표시를 한 후에 하이워터마크를 증가 시키게 됩니다.
- 9) 팔로워들로 부터 1번 오프셋 메세지에 대한 리플리케이션 요청을 받은 리더는 응답시 0번 오프셋의 메세지가 커밋되었다는 내용도 함께 전달하게 됩니다.
- 10) 리더의 응답을 받은 모든 팔로워는 0번 오프셋 메세지가 커밋이 된 내용을 인지를 하게 되고, 리더와 동일하게 커밋이 된것을 표시 하게 되며, 그 다음 1번 오프셋 메세지를 리플리케이션 하게 합니다.
- 위의 시나리오상에서 팔로워가 0번 오프셋에 대한 복제가 성공하지 못했다면 팔로워는 1번 오프셋에 대한 리플리케이션 요청이 아닌 0번 오프셋에 대한 리플리케이션 요청을 보냄
- 따라서 리더는 팔로워들이 보내는 리플리케이션 요청의 오프셋을 보고 팔로워들이 어느 위치의 오프셋까지 리플리케이션을 성공했는지를 인지 할 수 있게 됩
- 다른 유사한 메세징 시스템에서는 ACK 통신을 통해서 메세지를 잘 받고 하였는지를 체크하지만, 카프카에서는 ACK 통신을 제거하였다는 부분이 중요한 차이점 입니다.
- 대량의 메세지를 처리할 경우 ACK 통신을 주고 받는 것도 부하 이면서 성능저하의 요소가 됨
-
그래서 카프카의 특징이 ACK 통신을 제외하였음에도 불구하고 팔로워와 리더간의 리플리케이션 동작이 매우 빠르면서도 신뢰할 수 있다는 점임
- 카프카에서 리더와 팔로워들의 리플리케이션 동작 방식은 리더가 푸시(push) 하는 방식이 아니라, 팔로워가 풀(pull) 하는 방식으로 동작하며, 리플리케이션에서 리더의부하를 줄여주기 위해서 팔로워 풀(pull) 형태로 구현됨
4.1.5 리더에포크와 복구
- 리더에포크(leader epoch) 는 카프카의 파티션들이 복구 동작을 할 때 메세지의 일관성을 유지하기 위한 용도로 이용되며, 컨트롤러에 의해 관리되는 32비트의 숫자로 표현됨
- 리더에포크(leader epoch) 정보는 리플리케이션 프로토콜에 의해 전파 되며 리더가 변경 되면 변경 된 새로운 리더에 대한 정보를 팔로워에게 전달 함.
-
리더에포크는 복구 동작 시 하이워터마크를 대체하는 수단으로 활용 됨
- 리더에포크를 사용하지 않은 장애 복구 과정
- 위의 이미지는 리더에포크를 사용하지 않는 장애 복구 과정을 의미
- 위의 jade-test01 토픽은 파티션수 1 , 리플리케이션 팩터 2 , min.insync.replicas 1 로 설정 이며 예제에서는 리더에포크가 없다는 가정으로 장애 복구 과정은 아래와 같이 진행됩니다.
- 1) 리더는 프로듀서로 부터 message1 메세지를 받고 0번 오프셋에 저장, 팔로워는 리더에게 0 번 오프셋에 대한 가져오기 요청
- 2) 가져오기 요청을 통해 팔로워는 message1 메세지를 리더로부터 리플리케이션함
- 3) 리더는 하이워터마크를 1로 올림
- 4) 리더는 프로듀서로 부터 다음 메세지인 message2를 받은 뒤 1번 오프셋에 저장
- 5) 팔로워는 다음 메세지인 message2에 대해 리더에게 가져오기 요청을 보내고 응답으로 리더의 하이워터마크 변화를 감지하고 자신의 하이워터마크도 1로 올림
- 6) 팔로워는 1번 오프셋의 message2 메세지를 리더로 부터 리플리케이션함
- 7) 팔로워는 2번 오프셋에 대한 요청을 리더에게 보내고, 요청을 받은 리더는 하이워터마크를 2로 올림
- 8) 팔로워는 2번 오프셋인 message2 메세지까지 리플리케이션을 완료하였지만 아직 리더로부터 하이워터마크를 2로 올리는 내용을 전달받지 못한 상태
- 9) 이 상태에서 예상치 못한 장래로 팔로워가 다운됨
- 여기까지가 위의 “복구관련 이미지 1” 에 해당되는 내용
- 장애에서 복구된 파로워의 상태(리더에포크 사용하지 않음)
- 위의 “복구관련 이미지2” 는 장애가 발생한 팔로워가 종료 된 후 장애 처리가 완료된 상태를 나타냅니다.
- 장애에서 복구된 팔로워는 카프카 프로세스가 시작되면서 내부적으로 메세지 복구 동작을 시작하게 됩니다.
- 1) 팔로워는 자신이 갖고 있는 메세지들 중에서 자신의 워터마크보다 높은 메세지들은 신뢰할 수 없는 메세지로 판단하고 삭제
- (따라서 1번 오프셋의 message2는 삭제됨)
- 2) 팔로워는 리더에게 1번 오프셋의 새로운 메세지에 대한 가져오기 요청을 보냅니다.
- 3) 이 순간 리더였던 브로커가 예상치 못한 장애로 다운되면서, 해당 파티션에 유일하게 남아 있던 팔로워가 새로운 리더로 승격 합니다.
- 1) 팔로워는 자신이 갖고 있는 메세지들 중에서 자신의 워터마크보다 높은 메세지들은 신뢰할 수 없는 메세지로 판단하고 삭제
- 팔로워가 새로운 리더로 승격된 후의 상태(리더에포크 사용하지 않음)
- 위의 그림 “복구관련 이미지 3” 는 팔로워가 새로운 리더로 승격된 후의 상태를 나타내게 됩니다.
- 그림에서 알 수 있듯이 기존의 리더는 1번 오프셋의 message2 라는 메세지를 가지고 있었지만, 팔로워는 message2 라는 메세지가 없이 새로운 리더로 승격이 되었습니다.
-
결국은 새로운 리더는 message2 라는 메세지를 갖고 있지 않으며, 리더와 팔로워간의 리플리케이션이 작동하고 있었지만 리더가 변경되는 과정에서 message2 라는 메세지가 손실된 것 입니다.
- 이번에는 리더에포크를 사용 된다는 상황으로 장애 와 복구에 대한 내용을 확인 해보도록 하겠습니다.
- 장애에서 복구된 팔로워의 상태(리더에포크 사용)
-
위의 그림 “복구관련 이미지4” 는 리더와 팔로워의 리플리케이션 동작 이후 그리고 팔로워가 장애로 종료 된 후 막 복구된 상태 이후의 과정을 나타내고 있습니다.
-
앞에서의 동작은 카프카 프로세스가 시작되면서 복구 시 자신의 하이워터마크 보다 높은 메세지를 즉시 삭제 하였습니다.
- 하지만 리더에포크를 사용하는 경우에는 하이워터마크 보다 앞에 있는 메세지를 무조건 삭제 하는 것 아닌 리더에게 리더에포크 요청을 보냅니다.
- 1) 팔로워는 복구 동작을 하면서 리더에게 리더에포크 요청를 보냄
- 2) 요청받은 리더는 리더에포크의 응답으로 “1번 오프셋의 message2까지” 라고 팔로워에게 보냄
- 3) 팔로워는 자신의 하이워터마크보다 높은 1번 오프셋의 message2를 삭제하지 않고 리더의 응답을 화인 후에 오프셋2 인 message2 까지 자신의 하이워터마크를 상향 조정 합니다.
- 팔로워가 새로운 리더로 승격된 후의 상태(리더에포크 사용)
- 위의 그림 “복구관련 이미지 5” 는 리더가 예상치 못한 장애로 다운 되면서 팔로워가 새로운 리더로 승격된 후의 상태를 나타냅니다.
-
리더에포크를 적용하지 않는 경우에는 팔로워가 복구 과정에서 자신의 하이워터마크보다 높은 message2 를 삭제하였지만 리더에포크를 활용하는 경우에는 삭제 동작을 하기 전에 리더에포크 요청과 응답 과정을 통해서 팔로워의 하이워터마크를 올릴 수 있게 되며, 메세지 손실이 발생하지 않게 됩니다.
- 리더에포크를 사용하지 않았을 경우의 다른 예제는 굳이.
4.2 컨트롤러
- 컨트롤러는 카프카에서 리더 선출 하는 역할을 맡고 있음
- 카프카 클러스터 중 하나의 브로커가 컨트롤러 역할을 하게 되며, 파티션의 ISR 리스트 중에서 리더를 선출 합니다.
- 리더를 선출하기 위해 ISR 리스트 정보는 안전한 저장소에 보관되어 있어야 하는데, 가용성 보장을 위해서 주키퍼에 저장되어 있습니다.
- 컨트롤러는 브로커가 실패하는 것을 예의 주시하고 있으며, 만약 브로커의 실패가 감지가 되면 즉시 ISR 리스트 중 하나를 새로운 파티션 리더로 선출 하며, 그리고 새로운 리더 정보를 주키퍼에 기록하고, 변경된 정보를 모든 브로커에게 전달 함.
- 파티션의 리더가 다운됐다는 것은 해당 파티션의 리더가 없는 상태를 의미,
- 카프카 클라이언트인 프로듀서나 컨슈머가 해당 파팋션으로 읽기나 쓰기가 불가능
- 클라이언트에 설정되어 있는 재시도 숫자만큼 재시도를 하게 됨
- 카프카 클라이언트인 프로듀서나 컨슈머가 해당 파팋션으로 읽기나 쓰기가 불가능
- 애기치 않는 장애로 인한 리더 선출 과정
- 1) 파티션 0 의 리더인 1번 브로커가 예기치 않게 다운이 됩니다.
- 2) 주키퍼는 1번 브로커와의 연결이 끊어진 후, 0번 파티션의 ISR에서 변화가 생겼음을 감지합니다.
- 3) 컨트롤러는 주키퍼 워치를 통해 0번 파티션에 변화가 생긴것을 감지하고 해당 파티션의 ISR 중에서 새로운 리더를 선출 합니다.
- 4) 컨트롤러는 0번 파티션의 새로운 리더가 어떤 번호의 브로커가 되었다는 정보를 주키퍼에 기록 합니다.
-
5) 이렇게 갱신된 정보는 현재 활성화 상태인 모든 브로커에게 전파 됩니다.
- 리더 선출 과정은 컨트롤러에 의해 이루어집니다.
- 파티션이 하나인 경우 컨트롤러가 새로운 리더를 선출하고 리더 정보를 주키퍼에 기록며 다른 브로커에게 업데이트 정보를 전파하는 데는 그리고 오랜 시간이 걸리지 않습니다.
- 예를 들어 하나의 파티션에서 리더 선출 과정에서 0.2초만에 완료가 될 경우 토픽에 1개 파티션만 있다면 0.2초만에 리더선출 과정이 완료 될 것 입니다.
-
하지만 1만개의 파티션에 대해서 리더 선출이 이루어져야 한다면 전체 작업 소요시간은 약 2,000초가 걸리게 되며 분으로 환산하면 30춘이 조금 넘는 시간 입니다.
- 1대의 브로커의 장애에 의해 리더 선출 과정에서 30여 분간 통신이 끊어지는 상황이 발생된다면, 카프카 사용에 매우 어려운 상황을 직면 하게 될 것 입니다.
- 이런 상황을 개선하고자 2018년 11월 릴리즈된 카프카 버전 1.1.0 버전 부터는 리더 선출 작업 속도가 빨라지게 개선 되었습니다.
-
카프카 1.0.0 버전에서 6분 30초가 소요 되었던 리더 선출 과정에서 불필요한 로깅을 없애고 주키퍼 비동기 API가 반영된 카프카 1.1.0 버전에서는 약 3초만에 완료 되었습니다.
- 제어된 종료에 의한 리더 선출 과정
- 1) 관리자가 브로커 종료 명령어를 실행하고, SIG_TERM 신호가 브로커에게 전달 됩니다.
- 2) SIG_TERM 신호를 받은 브로커는 컨트롤러에게 알립니다.
- 3) 컨트롤러는 리더 선출 작업을 진행하고, 해당 정보를 주피커에 기록 합니다.
- 4) 컨트롤러는 새로운 리더 정보를 다른 브로커들에게 전송하게 됩니다.
- 5) 컨트롤러는 종료 요청을 보낸 브로커에게 정상 종료한다는 응답을 보내게 됩니다.
-
6) 응답을 받은 브로커는 캐시에 있는 내용을 디스크에 저장하고 종료 하게 됩니다.
-
제어된 종료 와 예기치 않은 종료(급작스러운 종료)와의 큰 차이는 다운타임 입니다.
-
제어된 종료를 사용하면 카프카 내부적으로 파티션들이 다운타임을 최소화 할 수 있습니다. 이유는 브로커가 종료 되기 전, 컨트롤러는 해당 브로커가 리더도 할당된 전체 파티션에 대해서 리더 재 선출 작업을 진행하기 때문 입니다.
-
물론 제어된 종료라도 리더 선출 작업 시간 동안 일시적인 다운타임은 발생할 수 있습니다. 다만 리더 선출 작업 대상 파티션들의 리더들이 활성화된 상태에서 컨트롤러는 순차적으로 하나의 파티션 마다 리더를 선출하게 되므로 결과적으로 각 파티션별 다운타임을 최소화 할 수 있습니다.
-
예기치 않은 종료(장애에 의한)에 의해 리더 선출 과정은 이미 대상 파티션들의 리더가 종료 된 상태가 되고, 파티션들의 다운타임은 새로운 리더 선출 작업이 완료 될 때 까지 지속되게 됩니다. 컨트롤러는 순차적으로 하나의 파티션마디 리더를 선출하게 되며, 첫번째 대상 파티션의 다운타임은 길지 않을수 있지만 마지막 리더 선출 대상의 파티션은 다운타임이 오랜시간이 걸려서 수행될 것 입니다.
-
또한 제어된 종료의 경우 (리더)브로커는 자신의 모든 로그를 디스크에 동기화한 후 종료됨에 따라 이후 다시 브로커가 재시작할 때 로그 복구 시간이 짧아지게 됩니다. 다양한 장점이 있는 제어된 종료를 사용하려면 control.shutdown.enable = true 설정이 브로커 설정 파일인 server.properties 파일에 설정이 되어 있어야 합니다.
-
브로커의 설정파일에 옵션이 명시되어 있지 않다고 해서 옵션이 반영되지 않은 것은 아닙니다. 기본값은 따로 명시하지 않아도 적용되므로 현재 브로커의 설정을 확인해볼 필요는 있습니다.
-
카프카에서 제공하는 kafka-configs.sh 명령어를 이용해서 –broker 옵션과 확인하고자 하는 브로커 아이디를 통해서 현재 브로커의 설정 상태를 확인할 수 있습니다.
- 현재 사용하는 브로커의 제어된 종료 설정 상태를 확인 하여 설정이 되어 있지 않다면 설정하는 것을 권장됩니다
4.3 로그(로그 세그먼트)
-
카프카의 토픽으로 들어오는 메세지(레코드)는 세그먼트(segement, 로그 세그먼트 라고도 함) 라는 파일에 저장되게 됨.
-
메세지는 정해진 형식에 맞추어 순차적으로 로그 세그먼트 파일에 기록 됩니다. 로그 세그먼트에는 메세지의 내용만 저장되는 것이 아니라 메세지의 키, 밸류, 오프셋, 메세지 크기 같은 정보도 함께 저장 되며, 로그 세그먼트 파일들은 브로커의 로컬 디스크에 보관되어 있습니다.
- 하나의 로그 세그먼트 크기가 너무 커져버리면 파일을 관리하기 어렵기 때문에 로그 세그먼트의 최대 크기는 1GB 가 기본값 입니다.
-
로그 세그먼트가 1GB보다 커지는 경우에는 기본적으로 롤링(rolling) 전략을 적용합니다.
-
로그 세그먼트의 크기가 1GB에 도달하면 해당 세그먼트 파일을 클로즈(close) 하고, 새로운 로그 세그먼트를 생성하는 방식으로 진행 합니다.
-
1GB 크기에 설정에 의해서 파일이 롤링이 되지만, 파일이 무한정 늘어날 경우를 대비하여 관리자는 로그 세그먼트에 대한 관리 계획을 수립해야 합니다.
- 관리 계획으로는 크게 로그 세그먼트의 삭제 또는 컴팩션(compaction) 으로 구분할 수 있습니다.
4.3.1 로그 세그먼트 삭제
-
로그 세그먼트 삭제 옵션은 브로커의 설정 파일인 server.properties 에서 log.cleanup.policy 가 delete 로 명시되어야 합니다.
-
해당 값은 기본값으로 적용됨으로 별도로 설정하지 않아도 로그 세그먼트는 삭제 정책이 적용되게 됩니다.
4.3.2 로그 세그먼트 컴팩션
-
컴팩션(compaction) 은 카프카에서 제공하는 로그 세그먼트 관리 정책 중 하나로, 로그를 삭제하지 않고 컴팩션하여 보관할 수 있는 방법 입니다.
-
로그 컴팩션은 기본적으로 로컬 디스크에 저장되어 있는 세그먼트를 대상으로 실행되는데, 현재 활성화된 세그먼트는 제외하고 나머지 세그먼트들을 대상으로 컴팩션이 실행 됩니다.
- 컴팩션하더라도 카프카의 로컬 디스크에 로그를 무기한 보관한다면, 로그의 용량은 감당할 수 없이 커질 것 입니다.
-
따라서 카프카에서는 단순하게 메세지를 컴팩션만 해서 보관하기 보다는 좀더 효율적인 방법으로 컴팩션을 합니다.
- 카프카에서 로그 세그먼트를 컴팩션하면 메세지(레코드)의 키값을 기준으로 마지막의 데이터만 보관하게 됩니다.
-
메세지의 키값을 기준으로 컴팩션하는 방법이 다소 생소 할 수 있으며, 로그 컴팩션 기능을 이용하는 대표적인 예제는 바로 카프카의 __consumer_offset 토픽 입니다.
-
__consumer_offset 토픽은 카프카의 내부 토픽으로, 컨슈머 그룹의 정보를 저장하는 토픽 입니다. 각 컨슈머 그룹의 중요한 정보는 해당 컨슈머 그룹이 어디까지 읽었는지를 나타내는 오프셋 커밋 정보이며, __consumer_offset 에 키(컨슈머 그룹명, 토픽명)와 밸류(오프셋 커밋 정보) 형태로 메세지가 저장 됩니다.
-
로그 컴팩션의 장점은 빠른 장애 복구 입니다. 장애 복구 시 전체 로그를 복구하지 않고, 메세지의 키를 기준으로 최신의 상태만 복구를 합니다. 따라서 전체 로그를 복구할 때 보다 복구 시간을 줄일 수 있다는 장점이 있습니다.
- 컨슈머가 처리한 메세지들에서 오류가 발견되어 재처리가 필요한 상황이라고 가정한다면 로그 컴팩션 기능이 사용하지 않는다면 재처리를 위해서 모든 로그를 다시 적용을 해야 겠지만, 로그 컴팩션을 이용하여 마지막 메세지만 빠르게 처리할 수 있습니다.
4.4 정리
5장 프로듀서의 내부 동작 원리와 구현
5.1 파티셔너
- 카프카의 토픽은 성능 향상을 위한 병렬 처리가 가능하도록 파티션으로 나뉘고 하나 또는 둘 이상의 파티션으로 구성됨
- 그리고 프로듀서가 카프카로 전송한 메시지는 해당 토픽 내 각 파티션의 로그 세그먼트에 저장됨
- 따라서 프로듀서는 토픽으로 메시지를 보낼 때 해당 토픽의 어느 파티션으로 메시지를 보낼지 결정해야하는데 이때 사용하는 것이 파티셔너 Partitioner 임
- 프로듀서가 타피션을 결정하는 알고리즘은 기본적으로 메시지의 키를 해시 처리하는 방식
- 메시지 키 값이 동일하면 모두 같은 파티션으로 전송됨
- 카프카는 클라이언트의 처리량을 높이기 위해 토픽의 파티션을 늘릴 수 있는 기능을 제공.
- 파티션 수가 변경됨가 동시에 메시지의 키와 매핑된 해시 테이블도 변경됨
- 프로듀서가 동일한 메시지의 키를 이용해 메시지를 전송하더라도 파티션의 수를 늘린 후에는 다른 파티션으로 전송될 수 있음
- 파티션 수가 변경됨가 동시에 메시지의 키와 매핑된 해시 테이블도 변경됨
- 가령 프로듀서가 user123이란 key 로 토픽A로 메시지를 전송하는 경우
- 파티션이 2개일 경우 파티션1번에 저장되었다고 하더라도
- 파티션이 4개로 늘어나는 경우, 이후의 메시지는 파티션 3번에 저장될수 있음
5.1.1 라운드 로빈 전략
- 프로듀서의 메시지중 레코드(메시지)의 키값은 필수값이 아님, 관리자는 레코드 키값을 지정하지 않고 메시지를 전송할 수 있음
- 키값은 null이 되고 기본값인 라운드 로빈 알고리즘을 사용해 프로듀서의 목적지 토픽의 파티션들로 레코드를 랜덤 전송함.
- 파티셔너를 거친 후의 레코드들은 배치 처리를 위해 프로듀서의 버퍼 메모리 영역에서 잠시 대기 후 카프카로 전송됨.
- 배치처리를 위해 잠시 메시지들이 대기하는 과정에서 라운드 로빈 전략은 효율을 떨어뜨림
- null 값으로 보내면 특정 토픽에 파티션이 여러개 있다고 하면, 각 파티션에 레코드를 라운드 로빈 방식으로 전송하게 됨
- 결국 배치를 수행하기 위한 최소값에 미치지 못해 프로듀서내에서 대기하게 됨
- null 값으로 보내면 특정 토픽에 파티션이 여러개 있다고 하면, 각 파티션에 레코드를 라운드 로빈 방식으로 전송하게 됨
5.1.2 스티키 파티셔닝 전략
- 라운드 로빈 전략에서 지연시간이 불필요하게 증가하는 비효율적인 전송을 개선하고자 2019년 부터 나온 전략이 스티키 파티셔닝 전략임
- 라운드로빈 전략에서 배치 전송을 위한 필요 레코드 수를 채우지 못해 카프카로 배치 전송을 하지 못했던 것과 달리 스티키 파티셔닝은 하나의 파티션 레코드 수를 먼저 채워서 카프카로 빠르게 배치 전송하는 전략을 말함
- 프로듀서는 키값이 null인 레코드1을 보내고 파티셔너는 키 값이 null인 레코드를 확인하고 배치를 위해 임의의 토픽A-파티션0에 레코드1을 담아 놓는다. 그리고 레코드2를 보내면 조금전 토픽A-파티션0에 레코드2를 담아놓는다.
- 이런 방법으로 배치를 위한 레코드 수에 도달할때까지 다른 파티션으로 보내지 않고 동일한 파티션으로 레코드를 담는다.
- 이런 방식이 성능을 높일지 모르겠지만, 이것은 기본 설정에 비해 30% 이상 지연시간이 감소했다고 보고되고 있다.
- 카프카로 전송하는 메시지의 순서가 그다지 중요하지 않는 경우라면 스티키 파티셔닝 전략을 적용하기를 권장한다.
5.2 프로듀서의 배치
- 카프카에선 토픽의 처리량을 높이기 위해 토픽을 파티션으로 나눠 처리하며, 카프카가 클라이언트 프로듀서에서는 처리량을 높이기 위해 배치 전송을 권장한다.
- 그래서 프로듀서에서는 카프카로 전송하기 전 배치 전송을 위해 토픽의 타피션별로 레코드들을 잠시 보관하고 있다.
- 프로듀서으 ㅣ배치 구성도
- 프로듀서는 배치 전송을 위해 다음과 같은 옵션들을 제공합니다.
- buffer.memory: 카프카로 메시지들을전송하기 위해 담아두는 프로듀서의 버퍼메모리 옵션.
- batch.size: 배치 전송을 위해 메시지들을 묶는 단위를 설정하는 배치 크기옵션.
- linger.ms: 배치전송을 위해 버퍼 메모리에서 대기하는 메시지들의 최대 시간을 설정하는 옵션입니다.
- 프로듀서의 배치 전송 방식은 단건의 메시지를 전송하는 것이 아니라 한번에 다량의 메시지를 묶어 전송하는 방법
- 불필요한 I/O를 줄일수 있다. 다만 지연이 발생할 수 있다.
- 처리량을 높이려면 batch.size와 linger.ms 값을 크게 설정해야하고 지연없는 전송이 목표라면 batch.size와 linger.ms으 ㅣ값을 작게 설정해야한다.
- 주의 사항은 버퍼 메모리 크기가 충분히 커야한다는 점이다
- buffer.memory 크기는 반드시 batch.size 보다 커야 한다.
5.3 중복 없는 전송
- 중복없이 전송(멱등성 전송)을 알아 보겠다.
- 명등성이란 동일한 작업을 여러 번 수행하더라도 결과가 달라지지 않는 것을 의미합니다.
- 메시지 시스템들의 메시지 전송 방식을 먼저 알아 보자.
- 적어도 한 번 전송 (ACK 받지 못하면 한번더 보냄)
- 프로듀서가 메시지를 브로커에게 보내고 브로커가ACK를 보내지 않았다면 프로듀서 입장에서는 브로커가 메시지를 받았는지 아닌지 정확하게 알 방법이 없습니다. 하지만 메시지에대한 ACK를 받지 못한 프로듀서는 ‘적어도 한번 전송 방식’에 때라서 메시지를 다시 한번 전송합니다. 이렇게 최소한 하나의 메시지는 반드시 보장하는것이 ‘적어도 한번 전송’방식이며 기본적으로 카프카는 이와같이 동작합니다.
- 장애 상황에 따라 일부 메시지 중복이 발생할 수는 있지만, 최소한 하나의 메시지는 반드시 보장한다는 것이 적어도 한 번 전송방식
- 최대 한 번 전송 (한번만 보냄)
- 위와같이 프로듀서가 브로커에게 메시지를 보냈는데 브로커의 장애로 ACK를 보내지 못했다면 ACK를 받지 못한 프로듀서는 다음 이전 메시지를 제전송하지 않고 다음 메세지를 보냅니다. 메시지의 중복 가능성 회피를 위합입니다. 대량의 로그 수집이나 IoT같은 환경에서 사용하곤 합니다.
- 일부 메시지가 손실 되더라도 높은 처리량을 필요로 하는 곳에서 사용
- 적어도 한번 전송 vs 최대 한번 전송
- 적어도 한번 전송은 메시지 손실 가능성은 없지만 메시지 중복 가능성이 존재하고, 최대 한 번 전송은 메시지 손실 가능성은 있지만 메시지 중복 가능성은 없음
- 중복 없는 전송
- 프로듀서가 ACK를 받지 못했을때 메시지를 재전송하는것은 적어도 한 번 전송 방식과 유사하지만 중복없는 전송은 PID를 해더에 달고 데이터를 주고받습니다.그렇기때문에 브로커가 PID를 보고 해당 해더값을 가진 메시지가 한번 더오면 저장하지않고 ACK만 전송합니다. PID는 프로듀서에 의해 중복되지않게 자동생성됩니다. 그리고 내부적으로만 이용되기 때문에 사용자에게 따로 노출되지 않습니다. 하지만 중복없는 전송의 단점은 오버해드가 존재한다는 것인데 이를 최소화 하기 위해서 단순한 숫자 필드만 추가하는 방법으로 구현됐습니다. 기존 대비 성능은 20% 감소하는 것으로 알려짐
- 중복없는 전송을 위한 프로듀서 설정
- enable.Idempotence:
- true
- 프로듀서가 중복 없는 전송을 허용할지 결정하는 옵션입니다. 기본 값은 false이므로. 이 옵션을 설정하기 원한다면 true로 변경해야 합니다. true로 변경 시 다음에 나오는 옵션들도 반드시 변경해야 합니다. 그렇지 않으면 ConflgExceptlonOI 발생합니다.
- max.in.flight.requests. per.connection:
- 1~5
- ACK를 받지 않은 상태에서 하나의 커넥션에서 보낼 수 있는 최대 요청 수입니다. 기본값은 5이며. 5 이하로 설정해야 합니다.
- acks :
- all
- 프로듀서 acks와 관련된 옵션으로서, 기본값은 1이며 a U 로 설정해 야 합니다.
- retries :
- 5
- ACK를 받지 못한 경우 재시도를 해야 하므로 0보다 큰 값으로 설정 해야 합니다.
- enable.Idempotence:
- 중복없는 전송을 위한 프로듀서 설정
- 프로듀서가 ACK를 받지 못했을때 메시지를 재전송하는것은 적어도 한 번 전송 방식과 유사하지만 중복없는 전송은 PID를 해더에 달고 데이터를 주고받습니다.그렇기때문에 브로커가 PID를 보고 해당 해더값을 가진 메시지가 한번 더오면 저장하지않고 ACK만 전송합니다. PID는 프로듀서에 의해 중복되지않게 자동생성됩니다. 그리고 내부적으로만 이용되기 때문에 사용자에게 따로 노출되지 않습니다. 하지만 중복없는 전송의 단점은 오버해드가 존재한다는 것인데 이를 최소화 하기 위해서 단순한 숫자 필드만 추가하는 방법으로 구현됐습니다. 기존 대비 성능은 20% 감소하는 것으로 알려짐
- 적어도 한 번 전송 (ACK 받지 못하면 한번더 보냄)
5.4 정확히 한 번 전송
- 은행에 송금,입금 등과같이 매우 민감한 데이터의 전송은 위의 전송방식보단 좀 더 견고한 전송방식이 필요합니다. 이럴때 카프카에서 정확히 한번 전송 방식을 처리합니다. 어플리케션에선 굉장히 구현하기가 힘들 수 밖에 없습니다.
- 카프카에서 정확히 한 번 전송은 트랜잭션과 같은 전체적인 프로세스 처리를 의미하며 중복없는 전송은 정확히 한번 전송의 일부 기능이라 할 수 있습니다.
- 카프카에서 정확히 한번 처리를 담당하는 별도의 프로세스를 트랜잭션api라고 부릅니다.
5.4.1 디자인
- 정확히 한번 방식으로 메시지를 전송할 때 프로듀서가 보내는 메시지들은 원자적으로 처리되어 전송에 성공하거나 실패하게 됩니다.
- 이때 트랜잭션 코디네이터 라는 것이 서버측에 존재합니다.
- 이 트랜잭션 코디네이터의 역할은 프로듀서에 의해 전송된 메시지를 관리하며 커밋,또는 중단 등을 표시합니다.
- 카프카에서는 오프셋정보를 카프카의 내부 토픽에 저장하는데 트랜잭션도 동일하게 트랜잭션 로그를 카프카 내부 토픽인 __transaction_state에 저장합니다.
- 이 역시 토픽이므로 파티션 수와 리플리케이션 팩터수가 존재하며 브로커의 설정을 통해 관리자가 설정할 수 있습니다.
- 기본값
- transaction.state.log.num.partltlons=50
- transaction.state.log.replication.factor=3
- 모든정보의 로그는 트랜잭션 코디네이터가 직접 기록합니다.
5.4.2 프로듀서 예제 코드
- N/A
5.4.3 단계별 동작
- 정확히 한번 전송하기 위해서는 트랙잭션 API를 이용한다
- 가장 먼저 수행하는 작업은 트랜잭션 코디네이터 찾기이다.
-
- 브로커에게 FindCoordinatorRequest를 보내서 트랜잭션 코디네이터를 찾는다.
- 이때 트랜잭션 코디네이터의 주 역할은 PID와 transactional.id를 매핑하고 해당 트랜잭션 전체를 관리하는 것입니다.
- 만약 트랜잭션 코디네이터가 존재하지 않으면, 신규 트랜잭션 코디네이터가 생성됨 * __transaction_state 토픽의 파티션 번호는 transactional.id 를 기반으로 해시되어 결정되고, 이 파티션의 리더가 있는 브로커가 트랜잭션 코디네이터의 브로커로 최종 선정된다.
- 브로커에게 FindCoordinatorRequest를 보내서 트랜잭션 코디네이터를 찾는다.
-
- 프로듀서 초기화.
- 프로듀서는 initTransactions()메소드를 이용해서 트랜잭션 전송을 위한 InitPidRequest를 트랜잭션 코디네이터로 보냅니다.
- 이때 transactional.id (TID)가 설정된 경우 InitPidRequest 와 함께 트랜잭션 코디네이터에게 전송됨
- 트랜잭션 코디네이터는 TID, PID (Producer ID)를 매핑하고 해당 정보를 트랜잭션 로그에 기록
- 그 다음 PID 에포크를 한 단계 올리는 동작을 해서 이전의 동일한 PID와 이전 에포크에 대한 쓰기 요청은 무시됨
- 프로듀서 초기화.
-
- 트랜잭션 시작 동작.
- 프로듀서는 beginTransaction()메소드를 이용해 새로운 트랜잭션의 시작합니다.
- 이때 프로듀서 내부적으론 트랜잭션의 시작이지만 첫번째 레코드가 전송될 때 까지 트랜잭션이 시작된것은 아닙니다.
- 트랜잭션 시작 동작.
-
- 트랜잭션 상태 추가 동작입니다.
- 프로듀서는 토픽파티션 정보를 트랜잭션 코디네이터에게 전달하고, 코디네이터는 해당 정보를 트랜잭션 로그에 기록합니다.
- 기본값으로 1분동안 트랜잭션 상태 업데이트가 없다면 트랜잭션은 실패처리가 됩니다.
- 트랜잭션 상태 추가 동작입니다.
-
- 메시지 전송
- 이때 프로듀서는 대상 토픽의 파티션으로 메시지를 전송합니다.
- 해당 메시지에는 PID, 에포크, 시퀀스 번호가 함께 포함되어 전송됨.
- 메시지 전송
-
- 트랜잭션 종료 요청 동작.
- 메시지전송을 완료한프로듀서는 commitTransaction()메소드 또는 abortTransaction()메소드중 하나를 반드시 호출해야 합니다.
- 해당 메소드의 호출을 통해 트랜잭션이 완료됨을 트랜잭션 코디네이터에게 알립니다.
- 이때 트랜잭션 코디네이터는 두단계 커밋과정을 시작하며 첫번째로 트랜잭션 로그에 해당 트랜잭션에 대한 PrepareCommit 또는 PrepareAbort 을 기록합니다.
- 트랜잭션 종료 요청 동작.
-
- 사용자 토픽에 표시하는 단계.
- 트랜잭션 코디네이터는 두 번째 단걔로서 트랜잭션 로그에 기록된 토픽의 파티션에 트랜잭션 커밋 표시를 기록
- 여기서 기록하는 메시지가 바로 컨트롤 메시지임 * 예를 들어 트랜잭션 프로듀서가 파티션 0에 메시지를 전송했고 해당 메시지의 오프셋이 1이라 가정해보면
- 트랜잭션 코디네이터는 파티션0에 트랜잭션 커밋표시 메시지를 기록하고, 이 추가 메시지(컨트롤 메시지)로 인해 파티션0의 마지막 오프셋은 2로 증가함
- 이 메시지는 해당 PID의 메시지가 제대로 전송됐는지 여부를 컨슈머에게 나타내는 용도로 사용됨
- 따라서 트랜잭션이 커밋이 끝나지 않은 메시지는 컨슈머에게 반환하지 않으며, 오프셋의 순서 보장을 위해 트랜잭션 성공 또는 실패를 나타내는 Last stable offset 라를 오프셋을 유지하게 됨.
- 사용자 토픽에 표시하는 단계.
-
- 트랜잭션 완료.
- 트랜잭션 코디네이터는 완료됨 이라고 트랜잭션 로그에 기록합니다.
- 그리고 프로듀서에게 해당 트랜잭션이 완료됨을 알린다음 해당 트랜잭션에 대한 처리는 모두 마무리 됨.
- 트랜잭션을 이용하는 컨슈머는 read_committed서정을 하면 트랜잭션에 성공한 메시지들만 읽을 수 있게 됨..
- 트랜잭션 완료.
5.4.4 예제 실습
- N/A
-
5.5 정리
6장 컨슈머의 내부 동작 원리와 구현
- 컨슈머의 주요 역할은 카프카에 저장된 메시지를 가져오는 것
6.1 컨슈머 오프셋 관리
- 컨슈머의 동작 중 가장 핵심은 오프셋 관리입니다.
- 컨슈머는 카프카에 저장된 메시지를 꺼내오는 역할을 하기 때문에 컨슈머가 메시지를 어디까지 가져왔는지를 표시하는것은 매우 중요합니다.
- 컨슈머가 어떠한 문제로 동작을 멈추고 재시작하는 경우나 컨슈머가 구동중인 서버에 문제가 발생해서 다른 컨슈머가 역할을 대신하는등 새로운 컨슈머가 메시지를 가져올 수 있어야만 장애로 부터 빠르게 복구 될 수 있습니다.
- 카프카에서 메시지의 위치를 나타내는 위치를 오프셋이라고 부르고 오프셋은 숫자 형태로 나타냅니다.
- 컨슈머 그룹은 자신의 오프셋 정보를 카프카에서 가장 안전한 저장소인 토픽에 저장합니다.
- 즉 __consumer_offsets 토픽에 각 컨슈머 그룹별로 오프셋 위치 정보가 기록됩니다.
- 모든 컨슈머 그룹의 정보가 저장되는 __consumer_offsets 토픽은 다음과 같은 파티션 수와 리플리케이션 팩터 수를 갖고 있으며, 내부 토픽이지만 파티션 수와 리플리케이션 팩터수는 브로커의 설정 파일인 server.properties 에서 관리자가 원하는 값으로 변경할수 있습니다.
- offsets.topic.num.partitions: 기본값 50
- offsets.topic.replication.factor : 기본값 3
6.2 그룹 코디네이터
- 컨슈머들은 하나의 컨슈머 그룹의 구성원이 되고 컨슈머 그룹 내의 각 컨슈머들은 서로 자신의 정보를 공유하면서 하나의 공동체로 동작합니다.
- 컨슈머 내의 컨슈머들은 언제든지 자신이 속한 컨슈머 그룹에서 떠날 수 있으며 새로운 컨슈머가 합류할 수 있어서, 컨슈머 그룹은 이러한 변화를 인지하고 각 컨슈머들에게 작업을 균등하게 분배해야 합니다.
- 컨슈머 그룹에서 각 컨슈머들에게 작업을 균등하게 분해하는 동작을 컨슈머리벨런싱 이라고 합니다.
- 컨슈머 그룹은 안정적인 컨슈머 그룹 관리를 위해 별도의 코디네이터가 존재하는데
- 이를 카프카에서는 그룹 코디네이터 라고 부릅니다.
- 그룹코디네이터의 목적은 컨슈머 그룹이 구독한 토픽의 파티션들과 그룹의 맴버들을 트래킹 하는것입니다.
- 파티션 또는 그룹의 멤버에 변화가 생기면 작업을 균등하게 재분배 하기 위해 컨슈머 리벨런싱 동작이 발생합니다.
- 그룹 코디네이터는 각 컨슈머 그룹별로 존재합니다.
- 이러한 그룹 코디네이터는 카프카 클러스터 내의 브로커 중 하나에 위치합니다.
- 컨슈머 그룹 동작순서
-
- 컨슈머는 컨슈머 설정값 중에서 bootstrap .brokers 리스트에 있는 브로커에게 컨슈머 클라이언트와 초기 커넥션을 연결하기 위한 요청을 보냅니다.
-
- 해당 요청을 받은 브로커는 그룹 코디네이터를 생성하고 컨슈머에게 응답을 보냅니다. 컨슈머 그룹의 첫 번째 컨슈머가 등록될 때까지 아무작업도 일어나지 않습니다.
-
- 그룹 코디네이터는 group.initial.rebalance.delay.ms의 시간 동안 컨슈머의 요청을 기다립니다.
-
- 컨슈머는 컨슈머 등록 요청을 그룹 코디네이터에게 보냅니다. 이때 가장 먼저 요청을 보내는 컨슈머가 컨슈머 그룹의 리더가 됩니다.
-
- 컨슈머 등록 요청을 받은 그룹 코디네이터는 해당 컨슈머 그룹이 구독하는 토픽 파 티션 리스트 등 리더 컨슈머의 요청에 응답을 보냅니다.
-
- 리더 컨슈머는 정해진 컨슈머 파티션 할당 전략에 따라 그룹 내 컨슈머들에게 파티션을 할당한 뒤 그룹 코디네이터에게 전달합니다.
-
- 그룹 코디네이터는 해당 정보를 캐시하고 각 그룹 내 컨슈머들에게 성공을 알립니다.
-
- 각 컨슈머들은 각자 지정된 토픽 파티션으로부터 메시지들을 가져옵니다
-
- 단순히 컨슈머에서 bootstrap.brokers 와 group.id만 설정하면 컨슈머 그룹이 생성되어 카프카로부터 구독한 메시지를 읽어온다 생각했지만
-
실제로는 컨슈머 그룹과 그룹 코디네이터가 서로 긴밀하게 내용을 주고 받으며 컨슈머 그룹이 안정적으로 메세지를 읽어 갈 수 있도록 유지하기 위해 노력합니다.
- 이제 컨슈머 그룹은 그룹 코디네이터와 연결되어 관리를 받게 됩니다.
- 컨슈머들은 현재 자신이 속한 컨슈머 그룹에서 빠져나갈수도, 새롭게 합류할 수도 있는데 이는
- 컨슈머 코디네이터에게 컨슈머가 join / leave 요청을 보냄으로 자연스럽게 처리됩
- 컨슈머가 장애로 leave 요청을 보내지 못하고 종료되면 그룹 코디네이터는 어떻게 이를 감지할까?
- 컨슈머들의 변경을 감지하기 위해 그룹 코디네이터와 컨슈머들은 서로 하트비트를 주고 받습니다.
- 그룹 코디네이터는 하트비트 옵션을 통해 컨슈머의 상태를 확인하며, 문제가 발생했다고 판단되면 컨슈머 리밸런싱 동작을 통해 컨슈머 그룹의 전체 균형을 다시 맞춤
6.3 스태틱 멤버십
- 점검등으로 관리자는 컨슈머 그룹내의 컨슈머들을 하나씩 순차적으로 재시작 하고 싶은 경우가 있을 것임.
- 하트비트 주기, 세션 타임아웃등의 설정으로 인해 하나의 컨슈머가 재시작 될 때마다 전체 리밸런싱이 일어나며 이 작업동안 컨슈머들은 일시 중지하므로 이는 매우 번거로운 작업이 될 것입니다. - 컨슈머 리밸런싱은 경우에 따라 매우 높은 비용이 지출되는 과정입니다.
- 컨슈머의 재시작으로 인해 리밸런싱이 일어나는 배경
- 일반적으로 컨슈머 그룹 동작에서는 각 컨슈머를 식별하기 위해 엔티티ID를 부여함.
- 엔티티ID는 컨슈머 그룹 내에서 임시로 사용되는 값
- 컨슈머가 재시작되면 새로운 컨슈머로 인식되어 새로운 엔티티ID가 부여되고 그러 인해 리밸런싱이 일어남
- 대용량 메시지들을 처리하는 컨슈머 그룹이라면 리벨런싱 동작으로 인해 원래 상태를 복구하는데 상당한 시간이 소요 될 수 있습니다. 이런 불필요한 리벨런싱을 방어하기 위해 카프카 2.3버전 부터 스태틱 멤버십이라는 개념이 도입됐습니다
- 스태틱 멤버쉽이란?
- 컨슈머 그룹내에서 컨슈머가 재시작 등으로 그룹에서 나갔다가 다시 합류하더라도 리벨런싱이 일어나지 않게 합니다.
- 즉 컨슈머마다 인식할 수 있는 ID를 적용함으로써 다시 합류하더라도 그룹 코디네이터가 기존 구성원임을 인식할 수 있게 하는 것입니다.
- 스태틱 멤버십 기능이 적용된 컨슈머는 그룹에서 떠날 때 그룹 코디네이터에게 알리지 않으므로 불필요한 리밸런싱도 발생하지 않습니다.
- 정리하면 표준 컨슈머를 사용하는 경우 컨슈머의 오류가 발생하여 해당 컨슈머를 잠시 제거하고 다시 컨슈머 그룹에 합류하면 총 2번의 리밸런싱이 발생하지만 스태택 멤버쉽을 적용한 컨슈머를 활용하는 경우에는 session.timeout.ms 에 저장된 시간을 넘어가지 않는다면 컨슈머를 잠시 제외해도 리밸런싱 동작이 발생하지 않게 됩니다.
6.4 컨슈머 파티션 할당 전략
- 프로듀서의 파티셔너라는 기능은 레코드의 토픽을 어느 파티션으로 전송 할지를결정하는 역할을 했습니다.
- 컨슈머의 동작에서도 이와 유사하게 대상 토픽의 어느 파티션으로 부터 레코드를 읽어 올지를 결정합니다.
- 컨슈머 그룹의 리더 컨슈머가 정해진 파티션 할당 전략에 따라 각 컨슈머와 대상 토픽의 파티션을 매칭시킵니다.
- 파티션 할당 전략은 컨슈머 옴션의 partition, assignment.strategy로 표시하며,
- RangeAssigno(레인지 전략), RoundRobinAsstgnor(라운드 로빈 전략), StixkyAsstgnor(스티키 전략),CooperativeStixkyAssignor(협력적 스티키 전략)라 는 총 네 가지를 제공합니다.
- 레인지 파티션 할당 전략
- 파티션 할당전략의 기본값, 토픽별로 할당 전략을 사용함, 동일한 키를 이용하는 2개 이상의 토픽을 컨슘할 때 유용
- 라운드 로빈 파티션 할당 전략
- 사용가능한 파티션과 컨슈머들을 라운드 로빈으로 할당 균등한 분배가능
- 스티키 파티션 할당 전략
- 컨슈머가 컨슘하고 있는 파티션을 계속 유지할 수 있음
- 협력적 스티키 파티션 할당 전략
- 스티키 방식과 유사하지만, 전체 일시 정지가 아닌 연속적인 재조정 방식
6.4.1 레인지 파티션 할당 전략
- 레인지 파티션 할당 전략인 RangeAssignor는 파티션 할당 전략중 기본값으로서, 각 토픽별로 할당 전략을 사용하게 됩니다.
- 먼저 구독하는 토픽에 대한 파티션을 순서대로 나열한 후 컨슈머를 순서대로 정렬합니다.
- 그 후 각 컨슈머가 몇개의 파티션을 할당해야 하는지 전체 파티션 수를 컨슈머 수로 나눕니다.
- 컨슈머 수와 파티션 수가 일치하면 균등하게 할당될 수 있지만 균등하게 나눠지지 않는 경우는 앞쪽의 컨슈머들은 추가 파티션을 할당받게 됩니다.
- 레인지 파티션 할당 전략
- 불균형하게 할당이 될 수 있는데 , 이처럼 불균형한 레인지 파티션 할당 전략을 사용하는 이유는 동일한 레코드 키를 사용하고 하나의 컨슈머 그룹이 동일한 파티션 수를 가진 2개 이상의 토픽을 컨슘할때 유용할 수 있습니다.
- 이와같은 특수한 경우에 좋지만 컨슈머에 균등하게 파티션이 분배돼야 하는 작업에서는 비효율 적입니다.
6.4.2 라운드 로빈 파티션 할당 전략
- 먼저 컨슘해야 하는 모든 파티션과 컨슈머 그룹 내 모든 컨슈머를 나열한 후 라운드 로빈으로 하나씩 파티션과 컨슈머를 할당하는 전략입니다.
- 라운드 로빈 파티션 할당 전략
- 특정 상황에서는 일부 컨슈머에 파티션이 몰릴 수도 있습니다. 예를들어 파티션이 1개인 토픽, 2개인 토픽, 3개인 토픽이 있고 3개의 컨슈머가 각각 토픽 1번, 토픽 2번, 토픽 3번을 매칭하면 아래와 같이 라운드-로빈 로직으로 인해 일부 컨슈머에 파티션이 몰릴 수 있으므로 주의해야 합니다.
6.4.3 스티키 파티션 할당 전략
- 컨슈머 그룹의 리밸런싱 동작으로 인해 파티션이 재할당 된다면
- 기존에 매핑됐던 파티션과 동일한 컨슈마가 다시 매핑되리라고는 보장할 수 없습니다.
- 이러한 재 할당 작업이 발생하더라도 기존에 매핑됐던 파티션과 컨슈머를 최대한 유지하려고 하는 전략이 스티키 파티션 할당전략입니다.
- 해당전략을 사용하는목적은 두가지 목적으로
- 첫번째는 가능한 균형잡힌 파티션 할당이고,
- 두번째 목적은 재할당이 발생할때 되도록 기존의 할당된 파티션 정보를 보장하는 것입니다.
- 둘중에 첫번째 목적의 우선순의가 더 높습니다.
- 스티키 파티션의 재할당 동작 원리 규칙
- 컨슈머들의최대 할당된 파티션의 수의 차이는 1
- 기존에 존재하는 파티션 할당은최대한 유지함
- 재할당 동작 시 유효하지 않은 모든 파티션 할당은 제거함
- 할당되지 않은 파티션들은 균형을 맞추는 방법으로 컨슈머들에 할당
- 스티키 파티션 할당 전략은 최대한 컨슈머들의 균형을 맞추고 기존 컨슈머에 할당된 파티션을 유지함으로써 컨슈머에 새로 할당하는 파티션 수를 최소화 함
- 최소한의 움직임으로 컨슈머를 할당할 수 있으므로 라운드 로빈 할당 전략보다 효율적임
6.4.4 협력적 스티키 파티션 할당 전략
- 스티키 파티션 할당전략과 결과적으론 동일한 방식입니다.
-
한가지 차이점이 있다면 컨슈머 그룹 내부의 리벨런싱 동작이 한층 더 고도화 됐다는 점입니다.
- 기존의 리밸런싱 동작은 한번에 모든 파티션 할당 작업이 끝난다는 장점이 있지만, 전체 컨슈머가 일시적으로 멈춘 상태에서 리밸런싱이 이루어진다는 제약이 있음
- 협력적 스티키 파티션 할당 전략은 되도록 동작 중인 컨슈머들에게 영향을 주지 않는 상태에서 몇 차례에 걸쳐 리밸런싱이 이루어짐
-
해당전략은 안전하게 파티션의 소유권을 이동하기 위해 리밸런싱 작업이 수차례 걸쳐 진행하는 것도 나쁘지 않다는 아이디어에서 출발했습니다.
- 동작 순서
-
- 리벨런싱시작
-
- 그룹내 컨슈머들이 그룹 합류 요청과 자신들이 컨슘하는 토픽의 파티션 정보를 그룹 코디네이터로 전송
-
- 그룹 코디네이터는 해당 정보를 조합해 컨슈머 그룹의 리더에게 전송
-
- 컨슈머 그룹의 리더는 현재 컨슈머들이소유한 파티션 정보를 활용해 제외해야 할 파티션 정보를 담은 새로운 파티션 할당 정보를 컨슈머 그룹 멤버들에게 전달
-
- 새로운 파티션 할당정보를 받은 컨슈머 그룹 멤버들은 현제의 파티션 할당 전략과 차이를 비교해 보고 필요없는 파티션을 골라 제외합니다. 이전파티션 할당정보와 새로운 파티션 할당정보가 동일한 파티션 들에 대해서는 작업을 수행할 필요없습니다.
-
- 제외된 파티션 할당을 위해 컨슈머들은 다시 합류 요청을 합니다. 여기서 두번제 리벨런싱 시작
-
- 컨슈머 그룹의 리더는 제외된 파티션을 적절한 컨슈머에게 할당합니다.
-
6.5 정확히 한 번 컨슈머 동작
- 기존에 정확히 한 번 전송의 개념과 프로듀서가 카프카로 레코드(메시지)를 정확히 한 번 전송하는 동작을 살펴 보았다.
- 프로듀서의 정확히 한 번 전송 동작을 위해 브로커 측에서는 전체 트랜잭션을 관리하면서 프로듀서의 동작을 보조하는 별도의 트랜잭션 코디네이터가 필요했다.
- 트랜잭션 코디네이터는 프로듀서의 정확한 한 번 전송이 성공하면 해당 레코드의 트랜잭션 성공을 표시하는 특수한 메시지를 추가한다.
- 따라서 컨슈머는 트랜잭션 코디네이터가 특수한 메시지를 표시한 레코드만 읽는다면 정확히 한 번 읽을 수 있다.
- 이번 장은 컨슈머에서의 정확히 한번 전송이 어떻게 동작하는지
- 즉 프로듀서가 정확히 한 번 보낸 레코드를 컨슈머가 어떻게 읽을 수 있는지 알아보자 (예제 생략)
- 트랜잭션 컨슈머라고 해서 정확히 한번만 가져오는 것은 아니다.
- 프로듀서의 경우 트랜잭션 코디네이터와 통신하면서 해당 트랜잭션이 정확하게 처리되는 것을 보장했지만, 컨슈머의 경우 트랜잭션 프로듀서가 보낸 메시지만 가져올 수 있는지에 대해서만 옵션으로 선택 할 수 있다. 컨슈머는 트랜잭션 코디네이터와 통신하는 부분이 없으므로 정확히 메시지를 한 번 가져오는지는 보장할 수 없다.
- 또한 컨슈머에 의핸 컨슘된 메시지가 다른 싱크 저장소로 중복 저장될 수 있다. 만약 컨슈머가 정확히 한번 메시지를 가져왔다고 가정하더라도 컨슈머가 가져온 메시지를 다른 애플리케이션에 저장하는 과정에서 중복 처리되는 경우가 있다. 하지만 카프카 클라이언트인 컨슈머는 다른 싱크 저장소로 메시지들이 중복 저장되는 결과를 알 수 없으므로 정확히 한번 저장을 보장할 수 없다.
- 프로듀서의 경우 트랜잭션 코디네이터와 통신하면서 해당 트랜잭션이 정확하게 처리되는 것을 보장했지만, 컨슈머의 경우 트랜잭션 프로듀서가 보낸 메시지만 가져올 수 있는지에 대해서만 옵션으로 선택 할 수 있다. 컨슈머는 트랜잭션 코디네이터와 통신하는 부분이 없으므로 정확히 메시지를 한 번 가져오는지는 보장할 수 없다.
- 컨슈머의 동작까지 정확히 한번 처리가 가능해지려면
- 컨슈-메시지 처리-프로듀신 동작이 하나의 트랜잭션으로 처리돼야 한다.
- 컨슈-메시지 처리-프로듀싱의 트랜잭션에서는 sendOffsetsToTransaction 메소드를 이용하며 컨슈머 그룹의 오프셋 커밋을 트랜잭션에 포함시킨다.
6.6 정리
- N/A
참고
* https://www.linkedin.com/pulse/kafka-producer-overview-sylvester-daniel
* https://blog.voidmainvoid.net/361
* https://velog.io/@fj2008/프로듀서-내부동작원리
* 기타 등등