이번 글에서는 Kafka의 Partition과 Offset에 대해 공부한 내용을 정리하려고 한다.
본 글의 목차는 다음과 같다.
1. Events, Streams, and Topics
2. Partition
3. Partitioning
4. Partition 복제
5. Consumer의 데이터 읽기 방식: Pull
6. Commit/Offset
Events, Streams, and Topics
`Partition`에 대해 알아보기 전에, Event, Stream 그리고 Topic에 대해 먼저 알아보자
`Event`는 데이터의 단위이다. 이는 특정 작업이나 상태 변경을 나타낸다.
`Stream`은 Kafka에서 연속적으로 흐르는 Event들의 집합이다.
`Topic`은 데이터를 구분하기 위한 단위로 events stream이 이 Topic에 저장된다. Producer는 데이터를 특정 Topic에 게시하며, Consumer는 해당 Topic을 구독하여 데이터를 읽는다. 예를 들어, 뉴스 토픽에는 뉴스와 관련된 내용, 로그 토픽에는 로그와 관련된 내용을 저장한다.
위 그림을 보면 이들을 쉽게 이해할 수 있다.
Partition
Kafka의 Topic은 여러 `Partition`으로 나뉘어 각 서버(Broker)에 분산되어 저장된다.
그렇다면 `Partition`은 뭘까? `Partition`을 정의하면 다음과 같다.
Topic에 속한 Record(데이터)를 실제 저장소에 저장하는 물리적 단위
각각의 `Partition`은 Append-only 방식으로 데이터를 기록하며, 순차적인 로그 구조를 유지한다.
Producer가 보낸 데이터는 `Partition`에 저장되며, Partition 내부에서 데이터는 FIFO(First-In-First-Out) 구조로 처리된다.
하지만 일반적인 큐와는 달리, 데이터를 처리하거나 삭제하지 않고 지정된 보존 기간 동안 데이터를 유지한다.
이러한 `Partition`의 갯수는 사용자가 직접 지정할 수 있지만, 제한 사항이 하나 있다.
Partition수를 늘릴 수는 있지만 줄일 수 는 없다는 것이다.
이는 `Partition` 구조가 데이터의 저장 및 소비 방식에 영향을 미치기 때문이다. `Partition`을 줄이는 경우 기존 데이터를 재분배해야 하며, 데이터 순서등을 보장하기 어려워질 수 있다.
따라서, 처음 Topic을 생성할 때는 적절한 `Partition` 수를 결정하는 것이 중요하다.
각 `Partition`의 모든 Record에는 고유한 `Offset` 값이 부여된다.
`Offset`은 `Partition` 내에서 Record의 위치를 나타내는 순차적인 숫자로, 이를 통해 `Partition`내의 데이터 순서를 보장한다.
위 그림에서 볼 수 있듯이, 각 `Partition`은 0부터 시작하는 `Offset` 값을 가지고 있으며, 새 데이터가 추가될 때마다 `Offset`이 1씩 증가한다.
Consumer는 이 `Offset`을 활용하여 데이터를 읽으며, 읽은 마지막 `Offset` 정보를 저장해두어 이후 처리 중단 시에도 이어서 데이터를 처리할 수 있다. 이는 뒤에서 더 설명할 예정이다.
Partitioning
그렇다면 Producer는 Topic에 데이터를 push할 때, 어느 `Partition`에 넣을까?
여기에는 `Key 기반 Partition 결정 방식`, `Round-Robin 방식`, `사용자 정의 방식`의 크게 3가지 방법이 있다.
Key 기반 Partition 결정
Producer는 메시지에 `Key`를 설정할 수 있는데, 이 key를 해싱하여 Partition을 결정한다.
이는 동일한 `Key`를 가진 데이터는 항상 동일한 `Partition`에 저장된다.
예를 들어, 사용자 ID를 Key로 설정하면 해당 사용자의 모든 이벤트가 같은 Partition에 저장된다.
Round-Robin 방식 (Key가 없는 경우)
`Key`를 설정하지 않은 경우, Kafka는 `Round-Robin` 방식을 사용하여 Partition을 선택한다.
이는 Partition을 순차적으로 순회하면서 데이터를 분산한다. 따라서, 데이터를 균등하게 분산 시켜 부하를 최소화하는데 효과적이다.
사용자 정의 방식
Kafka는 Producer가 사용자 정의 Partitioner를 구현할 수 있도록 지원한다.
특정 조건에 따라 데이터를 특정 Partition에 저장해야하는 경우, 데이터의 부하 분산을 사용자 정의 방식으로 처리하려는 경우 등의 사용된다.
Partition 복제
Kafka는 고가용성을 위해 `Partition`을 복제하여 저장한다. 이를 통해 하나의 Broker가 장애를 일으켜도 다른 Broker에서 데이터를 사용할 수 있다. 예를 들어, 복제 수를 3으로 설정한 경우, 한 `Partition`의 3개의 복사본이 존재한다.
또한 복제 수를 N으로 설정할 경우, 1개의 `리더(Leader)`와 N-1개의 `팔로워(Follower)`로 나뉘게된다.
리더는 각 Partition의 모든 읽기와 쓰기 작업을 수행하고, 팔로워는 리더의 데이터를 복제받아 저장하는 역할을 한다. 즉, 읽기/쓰기 작업에는 관여하지 않과 리더의 데이터를 동기화하며 대기 상태에 있는다.
이를 통해, 리더가 장애를 일으켜도 Kafka는 자동으로 팔로워 중 하나를 새로운 리더로 선출한다. 즉, 복제수가 N일 경우 최대 N-1개의 장애까지 견딜 수 있다.
또한, Kafka는 `Partition`의 리더를 모든 Broker에 균등하게 분배한다. 이로써 특정 Broker에 부하가 물리지 않도록 하며, 시스템 성능을 최적화한다.
Consumer의 데이터 읽기 방식: Pull
Kafka에서 Consumer는 `Pull`을 사용하여 필요한 데이터를 직접 요청해 가져온다.
이렇게 하면 자신의 처리 속도에 맞춰 데이터를 가져오므로 과부하를 피할 수 있고, Consumer가 명시적으로 데이터를 가져오기 때문에 메시지 유실을 방지하고 안정적으로 데이터를 처리할 수 있다.
앞서 설명했듯 Kafka의 `Partition`은 Append-only 로그 방식으로 데이터를 저장하기 때문에, Consumer는 데이터를 쓰여진 순서대로 읽을 수 있다.
Consumer는 `offset`을 기반으로 데이터를 읽는다. 이 때, `commit`을 통해 `offset` 정보를 저장하는데, commit에는 `자동 commit`과 `수동 commit`이 있다.
`자동 commit`은 주기적으로 offset 정보를 자동으로 저장하는 방법이다. 이는 간편하지만, 메시지를 완전히 처리하기 전에 offset이 commit 될 수 있다는 단점이 있다.
`수동 commit`은 말그대로 메시지 처리가 완료된 후, 명시적으로 offset을 commit한다. 이는 데이터 처리의 신뢰성을 높이지만, 구현이 복잡할 수 있다.
예를 들어, consumer가 `Partition`에서 저장된 offset 6을 기준으로 데이터를 읽는다.
즉, 6번 record를 가져와 데이터를 처리한다.
데이터 처리가 완료되면, commit을 통해 offset 정보를 저장한다. 이는 kafka의 `__consumer_offsets`라는 내부 토픽에 기록되며 이후 consumer는 다음 offset (7)부터 데이터를 읽기 시작한다.
Commit/Offset
위 그림은 위 설명에 대해 보여준다.
Consumer가 `poll()`을 하면 이전에 commit한 offset이 있으면 그 offset 이후의 record를 읽어온다.
읽어온 다음 마지막 읽어온 record의 offset을 `commit`하여 저장한다. 이때, `commit`에는 앞서 설명했듯이 자동 commit과 수동 commit이 있다.
따라서, `commit`은 메시지가 성공적으로 처리되었음을 나타내며, 이후 consumer는 `commit`된 offset 다음부터 데이터를 읽는다.
만약 consumer가 중단되거나 장애가 발생해 다시 시작될 경우, Kafka는 마지막으로 commit된 offset에서부터 읽기 시작한다.
Kafka 클러스터 예시
마무리
이번 글에서는 Kafka의 `Partition`, `Offset` 그리고 Consumer가 어떻게 데이터를 가져오는지에 대해 정리해보았다.
Kafka의 이러한 구조 덕분에 높은 성능과 신뢰성을 유지할 수 있는 것 같다.
앞으로 Kafka를 사용할 때 어떤 방식으로 설계하고 최적화해야 할지 더 명확한 기준을 세울 수 있을 것 같다.
다음에는 Kafka 설치에 대한 글을 포스팅할 예정이다.
'Data Engineering > Kafka' 카테고리의 다른 글
[Kafka] 간단한 Producer/Consumer 실습 (Java) (0) | 2025.01.10 |
---|---|
[Kafka] Apache Kafka 설치 (0) | 2025.01.08 |
[Kafka] Zookeeper와 KRaft(Kafka Raft) (0) | 2025.01.07 |
[Kafka] Apache Kafka 개요 (0) | 2024.12.28 |