토픽, 파티션 그리고 오프셋
토픽 : 특정한 스트림 데이터를 의미한다.
- 데이터베이스 테이블과 유사함(제한사항 없는)
- 토픽을 제한없이 설정할 수 있다.
- 이름으로 구분되고 어떠한 메세지 포멧(binary, avro, parquet...)을 지원한다.
- 메세지의 순서를 데이터 스트림 이라고 한다.
- 참고로, 토픽을 쿼리할 수 없다. 카프카 프로듀서를 통해 데이터를 보내고 컨슈머를 통해 데이터를 읽는 식으로는 가능하다.
파티션과 오프셋
- 토픽은 파티션으로 분리된다. 각 파티션은 순서대로 정렬되어 나타난다.
- 각 파티션에 있는 메세지는 서로다른 파티션마다 incremental id를 가진다. 이를 오프셋이라고 한다.
- 데이터가 한번 파티션에 기록된다면 변경이 불가능하다.
주의해야하는 사항
- 파티션에 데이터가 기록이 된다면, 변경이 불가하다.
- 데이터는 한정된 시간에만 존재 가능하다(기본설정은 한 주이고 설정할 수 있다.)
- 오프셋은 같은 파티션에서만 의미있다. (파티션0의 오프셋3과 파티션1의 오프셋3은 서로 다른 데이터다.)
- 오프셋은 앞의 데이터가 삭제되더라도 그 오프셋을 재사용할 수 없다.(같은 순번(오프셋)을 가질 수 없다라는 의미)
- 파티션 내부에서만 순서가 보장된다. 파티션 끼리의 순서는 보장되지 않는다!
- 데이터는 키를 들고있다고 하더라도 파티션에 랜덤하게 들어간다.
프로듀서 + 메세지 키
프로듀서
- 프로듀서는 데이터를 (파티션으로 구성되어있는)토픽에다가 쓴다.
- 프로듀서는 어떤 파티션에 써야할지 미리 알고있다. 카프카 브로커(카프카 서버)가 그걸 갖게 된다.
- 어디에 쓸지 정하는 주체는 카프카 브로커가 아니라 프로듀서이다. 또한, 카프카 서버에서 어떤 파티션이 고장났을 경우에 어떻게 복구할지 프로듀서가 알게 된다.
- 프로듀서가 파티션으로 데이터를 보낼때 로드밸런서가 개입하게 된다. 프로듀서는 어떤 매커니즘에 따라서 모든 파티션에 걸쳐 데이터를 전송하기 때문이다.
- 카프카의 한 토픽의 파티션은 다수의 프로듀서로부터 데이터를 받아올 수 있기 때문에, 스케일링을 사용하여 파티션을 관리한다.
프로듀서: 메세징 키
- 프로듀서는 메세지 안에 메세지 키를 설정해줄 수 있다(키로 올 수 있는건 문자열, 숫자, 이진수, 등...).
- 만약, key = null 이라면 데이터는 라운드 로빈 상태로 보내진다 (partitoin0 -> partition1 -> partition2 ... 이런식으로 로드밸런싱이 이뤄짐)
- key != null이라면 해싱전략으로 인해 메세지는 같은 파티션에 들어갈 수 있다.
카프카 메세지 시리얼라이저
- 프로듀서로부터 입력값에 대해 직렬로 된 바이트만을 받고, 출력값으로서 바이트를 컨슈머에게 전송한다.
- 그러나, 메세지를 구성하고 보낼 때에는 바이트가 아니라서 시리얼화가 필수적이다. (객체와 데이터를 바이트로 변경)
- 공통된 시리얼라이저를 카프카에 갖고있다. (String, JSON, Int, Float, Avro, Protobuf..)
카프카 메세지 해싱전략?
- 키 해싱을 통해 카프카 파티셔너가 메세지를 받아서 전송할 파티션을 결정한다.
- 디폴트 카프카 파티셔너로 murmur2 알고리즘을 사용하여 키 해싱한다.
targetPartition = Math.abs(Utils.murmur2(keyBytes)) % (numPartitions - 1)
컨슈머 + 역직렬화
컨슈머
- 컨슈머는 카프카 브로커, 서버에 데이터를 요청하고 되돌아오는 응답을 받는다. (pull model 이라고 함)
즉, 데이터를 컨슈머에게 푸싱하는 건 카프카 브로커가 아니라 '풀 모델'이다. - 컨슈머는 어느 브로커로 부터 읽어야하는지 알기 때문에, 브로커가 죽더라도 컨슈머는 회복하는 방법을 알고 있다.
- 데이터는 파티션 내부적으로는 오름차순으로 읽히는데, 2개이상의 파티션으로부터 읽어올 땐 순서가 보장되지 않기에 주의해야한다.
컨슈머 역직렬화
- 프로듀서에 시리얼화 하는 과정이 있다면, 컨슈머에는 역-시리얼화(역직렬화)하는 과정이 존재한다.
- 바이너리 형식으로 존재하는 데이터를 읽어서 객체로 변환해야 하는데, 컨슈머는 메세지의 형식이 무엇인지 미리 알고 있어야 한다.
- 프로듀서가 전송하는 데이터 타입을 절대 변경해서는 안된다. 데이터 타입을 변경하고 싶다면 새로운 토픽을 만들어야 한다.
컨슈머 그룹 + 컨슈머 오프셋
- 모든 컨슈머는 컨슈머 그룹단위로 데이터를 읽어낸다.
- 같은 그룹에 속한 각각의 컨슈머는 각각 다른 파티션에서 읽게 될 것이다.
컨슈머가 더 많아진다면? / 하나의 토픽에 여러 컨슈머가 존재한다면?
- (좌) 하나의 컨슈머에서 하나의 파티션으로부터 읽는다. / 컨슈머4는 비활성(stand by)이 된다.
- (우) 토픽에 대해 다수의 컨슈머 그룹이 있는 것은 괜찮다. 오직 하나의 컨슈머가 하나의 파티션에 지정된다.
- 다수의 컨슈머 그룹이 필요한 이유? : 서비스당 하나의 컨슈머 그룹을 갖게 되므로 (group.id 로 그룹을 구분한다.)
컨슈머 오프셋
- 카프카는 컨슈머가 어디까지 읽었는지 저장해 놓는다.
- 데이터에 대한 처리를 컨슈머가 완료하면 컨슈머는 종종 오프셋을 커밋해야하고 카프카 브로커가 컨슈머 오프셋 토픽에 기록하라고 알린다. 오프셋을 커밋함으로써 어느만큼 성공적으로 읽었는지 카프카 브로커에게 알려줄 수 있게 된다.
- 왜 이런 작업을 하는가? : 만일 컨슈머가 죽으면 다시 돌아와서 읽었던 곳에서부터 커밋한 컨슈머 오프셋으로 인해 다시 읽을 수 있다.
다양한 전달 의미론(Delivery semantics for consumers)
- 가장 기본으로 자바 컨슈머는 자동적으로 오프셋을 기록한다(at least once)
- 만약 수동으로 커밋을설정하게 된다면 3가지 옵션이 존재한다.
- At least once (가장 선호됨)
- 메세지가 처리되고, 오프셋이 커밋된다.
- 만약 처리도중에 오류가 생긴다면, 메세지는 다시 읽게 된다.
- 이러한 처리로 인해 중복된 메세지의 처리가 생길 수 있다. 따라서, 시스템에 영향이 미치지 않았는지 (멱등한지(idempotent)) 확인해야하는 과정이 필수적이다.
- At most once
- 메세지를 받자마자 오프셋이 커밋된다.
- 만약 처리도중 오류가 발생한다면, 메세지는 손실될 수 있다. (그것들은 다시 읽히지 않을 수 있다.)
- Exactly Once
- 카프카를대 카프카 워크플로우를 진행할 때(토픽에서 읽고 그 결과를 토픽에 다시 기록하게 될 때) 트랜잭션 api를 사용한다. 또는, 카프카에서 외부 시스템으로 간다면 멱등 컨슈머를 사용해야한다.
- At least once (가장 선호됨)
'Kafka' 카테고리의 다른 글
[Kafka] 브로커, 토픽, 주키퍼 (0) | 2024.03.21 |
---|