고성능 분산 이벤트 스트리밍 플랫폼(distributed event streaming platform)
Apache Kafka는 여러 대의 분산 서버에서 대량의 데이터를 처리하는 분산 메시징 시스템입니다.
카프카는 링크드인에서 최초로 개발되었으며, 현재는 아파치 재단에서 오픈소스로 관리하고 있습니다.
메세지 큐 (Message Queue, MQ) 란?
메시지 큐는 메시지 지향 미들웨어(MOM : Message Oriented Middleware)를 구현한 시스템으로 프로그램(프로세스) 간의 데이터를 교환할 때 사용하는 기술이다.
메시지 지향 미들웨어(Message Oriented Middleware:MOM)는 비동기 메시지를 사용하는 각각의 응용프로그램 사이의 데이터 송수신을 의미하고, **이를 구현한 시스템을 메시지큐(Message Queue:MQ)**라 합니다.
많이 사용하는 오픈소스 MQ로는 RabbitMQ ActiveMQ RedisQueue등이 있습니다.
Kafka는 이벤트 스트리밍 플랫폼으로서 여러가지 역할을 할 수 있고 MQ처럼 메시지 브로커 역할을 할 수 있도록 구현하여 사용할 수도 있으며 기존 범용 메시지브로커들과 비교했을때 아래와 같은 특징을 가집니다.
- 대용량의 실시간 로그 처리에 특화되어 TPS가 우수하다. - 고성능
- 분산 처리에 효과적으로 설계 되어 병렬처리와 확장(Scaleout), 고가용성(HA) 용이 - 클러스터링
- 발행/구독(Publish-Subscribe) 모델 ( Push-Pull 구조 )
- 메시지를 받기를 원하는 컨슈머가 해당 토픽(topic)을 구독함으로써 메시지를 읽어 오는 구조
- 기존에 퍼블리셔나 브로커 중심적인 브로커 메시지와 달리 똑똑한 컨슈머 중심
- 브로커의 역할이 줄어들기 때문에 좋은 성능을 기대할 수 있음
- 파일 시스템에 메시지를 저장함으로써 영속성(durability)이 보장
- 장애시 데이터 유실 복구 가능
- 메시지가 많이 쌓여도 성능이 크게 저하되지 않음
- 대규모 처리를 위한 batch 작업 용이
Message Queue 기본 구조
용어 정리
- producer: 정보를 제공하는 자
- consumer: 정보를 제공받아서 사용하려는 자
- Queue: producer의 데이터를 임시 저장 및 consumer에 제공하는 곳
주목해야할 부분은 Queue 인데, MQ에서 메세지는 Endpoint 간에 직접적으로 통신하지 않고, 중간데 Queue를 통해 중개된다는 점이다.
MQ 장점
- 비동기: queue라는 임시 저장소가 있기 때문에 나중에 처리 가능
- 낮은 결합도: 애플리케이션과 분리
- 확장성: producer or consumer 서비스를 원하는대로 확장할 수 있음
- 탄력성: consumer 서비스가 다운되더라도 애플리케이션이 중단되는 것은 아니며 메시지는 지속하여 MQ에 남아있다.
- 보장성: MQ에 들어간다면 결국 모든 메시지가 consumer 서비스에게 전달된다는 보장을 제공한다.
메세지 브로커 / 이벤트 브로커
Kafka 가 아닌 일반적인 형태의 네트워크 통신은 아래와 같이 구성된다.
각 개체가 직접 연결하며 통신하게 된다. 전송속도가 빠르고 전송 결과를 신속하게 알 수 있는 장점이 있는 반면에, 특정 개체에 장애가 발생한 경우 메세지를 보내는 쪽에서 대기 처리 등을 개별적으로 해주지 않으면 장애가 전파될 수 있다. 또한 참여하는 개체가 많아질 수록 각 개체를 연결해줘야 한다. (=> 시스템이 커질수록 확장성이 좋지 않아진다)
이러한 형태의 단점을 극복하고자 나온게 Pub/Sub 모델이다.
Pub/Sub 모델은 비동기 메세징 전송 방식으로, 발신자의 메세지에는 수신자가 정해져 있지 않은 상태로 publish 한다. 그리고 이를 Subscribe(구독)을 한 수신자만 정해진 메세지(topic)을 받을 수 있다. 이처럼 수신자는 발신자 정보가 없어도 원하는 메세지만 수신할 수 있으며, 이런 구조 덕분에 높은 확장성을 확보할 수 있다.
메세지 브로커
publisher가 생산한 메세지를 메세지 큐에 저장하고, 저장된 데이터를 consumer가 가져갈 수 있도록 중간 다리 역할을 해주는 브로커(broker)라고 볼 수 있다.
보통 서로 다른 시스템(혹은 소프트웨어) 사이에서 데이터를 비동기 형태로 처리하기 위해 사용한다. (대규모 엔터프라이즈 환경의 미들웨어로서의 기능)
이러한 구조를 보통 pub/sub 구조라고 하며 대표적으로는 Redis, RabbitMQ 소프트웨어가 있고, GCP의 pubsub, AWS의 SQS 같은 서비스가 있다.
이와 같은 메세지 브로커들은 consumer가 큐에서 데이터를 가져가게 되면 즉시 혹은 짧은 시간 내에 큐에서 데이터가 삭제되는 특징들이 있다.
이벤트 브로커
이벤트 브로커 또한 기본적으로 메세지 브로커의 큐 기능들을 가지고 있어 메세지 브로커의 역할도 할 수 있다.
메세지 브로커와의 가장 큰 차이점은,
이벤트 브로커는 publisher가 생산한 이벤트를 이벤트 처리 후에 바로 삭제하지 않고 저장하여, 이벤트 시점이 저장되어 있어서 consumer가 특정 시점부터 이벤트를 다시 consume 할 수 있는 장점이 있다. (예를 들어 장애가 일어난 시점부터 그 이후의 이벤트를 다시 처리할 수 있음)
또한 대용량 처리에 있어서는 메세지 브로커보다는 더 많은 양의 데이터를 처리할 수 있는 능력이 있다.
이벤트 브로커에는 Kafka, AWS의 kinesis 같은 서비스가 있다.
메시지 큐의 사용해야 할 이유
- 이중화를 통한 지속성
- 트래픽 폭주 데이터를 대기열에 넣음으로써 우리는 높은 트래픽 급증으로 인해 평소보다 조금 더 오래 걸리더라도 데이터가 유지되고 결국 처리될 것이라고 확신할 수 있다.
- 웹 애플리케이션 페이지로딩 시간 향상
- 효율을 위한 일괄 처리
- 비동기 메시징
비동기 프로그래밍 패턴을 구현하는 훌륭한 방법 - 데이터 계약을 사용하여 분리
- 트랜잭션 순서 및 동시성 문제 줄을 서면 주문을 보장하고 동시에 처리되는 개수의 수를 제어할 수 있다.
- 확장성 향상 응용 프로그램의 여러 부분을 분리하고 독립적으로 확장할 수 있다.
- 복원력 생성 대기열별로 서로 다른 구성 요소를 분리하면 본질적으로 더 많은 복원력을 얻을 수 있습니다. 주문 백엔드 처리의 일부가 약간 지연 되더라도 웹 사이트는 계속 작동 할 수 있습니다
- 하나의 트랜잭션
- 큰 작업을 여러 작은 작업으로 나누기 메시지 큐의 또 다른 좋은 용도는 더 큰 작업을 많은 작은 조각으로 나눈 다음 모두 대기열에 넣는 것입니다.
- 모니터링
메시지 큐의 종류
1) Redis 의 동작 방식 및 특징
- Redis 는 데이터베이스, 캐시, 메시지 브로커 및 스트리밍 엔진으로 사용되는 인메모리 데이터 구조 저장소이다.
- 구성요소
- publisher: 메세지를 게시(pub)
- channel: 메세지를 쌓아두는 queue
- subscriber: 메세지를 구독(sub)
- 동작
- pusblisher가 channel에 메세지 게시
- 해당 채널을 구독하고 있는 subscriber가 메세지를 sub해서 처리함
- 특징
- channel은 이벤트를 저장하지 않음.
- channel에 이벤트가 도착 했을 때 해당 채널의 subscriber가 존재하지 않는다면 이벤트가 사라짐
- subscriber는 동시에 여러 channel을 구독할 수 있으며, 특정한 channel을 지정하지 않고 패턴을 설정하여 해당 패턴에 맞는 채널을 구독할 수 있다
- 장점
- 처리 속도가 빠름
- 캐시의 역할도 가능
- 명시적으로 데이터 삭제 가능
- 단점
- 메모리 기반이므로 서버가 다운되면 Redis 내의 모든 데이터가 사라짐
- 이벤트 도착 보장을 못함
2) RabbitMQ 의 동작 방식 및 특징
- RabbitMQ는 AMQP 프로토콜을 구현한 메세지 브로커이다.
AMQP(Advanced Message Queuing Protocol)?
: 메세지 지향 미들웨어를 위한 개방형 표준 응용 계층 프로토콜
: Client와 Middleware broker간의 메시지를 주고받기 위한 프로토콜
- 구성 요소
- producer: 메세지를 보냄
- exchange: 메세지를 목적지(큐)에 맞게 전달
- queue: 메세지를 쌓아둠
- consumer: 메세지를 받음
- 메세지 처리 과정
- Producer 가 Broker로 메세지를 보냄
- Broker 내 Exchange(메세지 교환기) 에서 해당하는 key에 맞게 큐에 분배한다. (Binding or Routing 이라고 함)
- topic 모드 : Routing Key가 정확히 일치하는 Queue에 메세지 전송 (Unicast)
- direct 모드 : Routing Key 패턴이 일치하는 Queue에 메세지 전송 (Multicast)
- headers 모드 : [Key:Value] 로 이루어진 header값을 기준으로 일치하는 Queue에 메세지 전송 (Multicast)
- fanout 모드 : 해당 Exchange에 등록도니 모든 Queue에 메세지 전송 (Broadcast)
- 해당 큐에서 Consumer가 메세지를 받는다.
- 장점
- Broker 중심적인 형태로 publisher와 consumer 간의 보장되는 메세지 전달에 초점을 맞추고, 복잡한 라우팅 지원
- 클러스터 구성이 쉽고 Manage UI 가 제공되며 플러그인도 제공되어 확장성이 뛰어남
- 20kb/sec 정도의 속도
- 데이터 처리 보단, 관리적 측면이다 다양한 기능 구현을 위한 서비스를 구축할 때 사용
- 단점
- MQ Server가 종류 후 재기동되면 기본적으로 Queue 내용은 모두 제거된다. (데이터 손실의 위험성)
- 성능 문제
- Producer와 Consumer 간의 결합도가 높다
3) Kafka의 동작 방식 및 특징
- LinkedIn에서 개발된 pub-sub 모델의 메시지큐 방식 기반, 분산 메시징 시스템이다.
- 구성 요소
- Event: kafka에서 producer 와 consumer가 데이터를 주고받는 단위. 메세지
- Producer: kafka에 이벤트를 게시(post, pop)하는 클라이언트 어플리케이션
- Consumer: Topic을 구독하고 이로부터 얻어낸 이벤트를 받아(Sub) 처리하는 클라이언트 어플리케이션
- Topic: 이벤트가 모이는 곳. producer는 topic에 이벤트를 게시하고, consumer는 - topic을 구독해 이로부터 이벤트를 가져와 처리. 게시판 같은 개념
- Partition: Topic은 여러 Broker에 분산되어 저장되며, 이렇게 분산된 topic을 partition이라고 함
- Zoopeeper: 분산 메세지의 큐의 정보를 관리
- 동작 원리
- publisher는 전달하고자 하는 메세지를 topic을 통해 카테고리화 한다.
- subscriber는 원하는 topic을 구독(=subscribe)함으로써 메시지를 읽어온다.
- publisher와 subscriber는 오로지 topic 정보만 알 뿐, 서로에 대해 알지 못한다.
- kafka는 broker들이 하나의 클러스터로 구성되어 동작하도록 설계
- 클러스터 내, broker에 대한 분산처리는 ZooKeeper가 담당한다.
- 장점
- 대규모 트래픽 처리 및 분산 처리에 효과적
- 클러스터 구성, Fail-over, Replication 같은 기능이 있음
- 100Kb/sec 정도의 속도 (다른 메세지 큐 보다 빠름)
- 디스크에 메세지를 특정 보관 주기동안 저장하여 데이터의 영속성이 보장되고 유실 위험이 적다. 또한 Consumer 장애 시 재처리가 가능하다.
Kafka RabbitMQ Redis Pub/Sub
라우팅 | 기본기능으로 라우팅에 대해서 지원하지 않는다. Kafka Streams를 활용하여 동적라우팅을 구현할 수 있다. | Direct, Fanout, Topic, Headers의 라우팅 옵션을 제공하여 유연한 라우팅이 가능하다. | - |
프로토콜 | 단순한 메시지 헤더를 지닌TCP 기반 custom 프로토콜을 사용하기 때문에 대체가 어렵다. | AMQP, MQTT, STOMP 등 여러 메세징 플랫폼을 지원한다. | RESP (Redis Serialization Protocol) - TCP 통신 |
우선순위 | 변경 불가능한 시퀀스 큐로, 한 파티션 내에서는 시간 순서를 보장한다. 하지만 여러 파티션이 병렬로 처리할 때는 시간 순서 보장 못함 | priority queue를 지원하여 우선 순위에 따라서 처리가 가능하다. | 우선순위 처리는 커녕 이벤트가 도착할 지 보장도 못함 |
이벤트 저장 in Queue | 이벤트를 삭제하지 않고 디스크에 저장함으로 영속성(durability)이 보장되고, 재처리가 가능하다. | 메세지가 성공적으로 전달되었다고 판단될 경우 메세지가 큐에서 삭제되기 때문에 재처리가 어렵다 | 저장하지 않음. 심지어 channel에 이벤트가 도착했을 때 해당 채널의 subscriber가 존재하지 않으면 이벤트 사라짐 |
장점 | • 이벤트가 전달되어도 삭제하지 않고 디스크에 저장 | ||
• 고성능, 고가용성, 분산처리에 효과적 | |||
• producer 중심적 (많은 양의 데이터를 병렬 처리) | • 오래전에 개발되어 제품 성숙도가 크다 | ||
• 필요에 따라 동기/비동기식 가능 | |||
• 유연한 라우팅 | |||
• producer/consumer간의 보장되는 메세지 전달 | |||
• Manage UI 기본 제공 | |||
• 데이터 처리보단 관리적 측면이나 다양한 기능 구현을 원할 때 사용 | • channel을 구독하는 모든 subscriber가 이벤트를 받기 때문에 synchronization 문제에서 kafka보다 덜하다 | ||
• 미들웨어가 필요 없어서 가볍다 | |||
단점 | • 범용 메세징 시스템에서 제공되는 다양한 기능이 제공되지 않음 | • Kafka 보다 느림 | • 이벤트 도착 보장을 못함 |
4) 사용 구분
- 대용량 데이터 처리, 실시간, 고성능, 고가용성이 필요한 경우, 또는 저장된 이벤트를 기반으로 로그를 추적하고 재처리 하는게 필요한 경우 kafka를 쓰자.
- 복잡한 라우팅을 유연하게 처리해야하고, 정확한 요청-응답이 필요한 Application을 쓸 때 또는 트래픽은 작지만 장시간 실행되고 안정적인 백그라운드 작업이 필요한 경우 RabbitMQ를 쓰자.
- 이벤트 데이터를 DB에 저장하기 때문에 굳이 미들웨어에 이벤트를 저장할 필요가 없는 경우, consumer에게 굳이 꼭 알람이 도착해야한다는 보장 없이 알람처럼 push 보내는것만 중요하다면 유지보수가 편한 Redis를 사용하자.
카프카(Kafka)란?
Apache Kafka는 고성능 데이터 파이프라인, 스트리밍 분석, 데이터 통합 및 미션 크리티컬 애플리케이션을 위해 오픈 소스 **분산 이벤트 스트리밍 플랫폼(distributed event streaming platform)**입니다.
이벤트 스트리밍은 인체의 중추 신경계에 해당하는 디지털 처리 방식입니다.이는 비즈니스가 점점 더 소프트웨어화, 자동화되는 'always-on' 세상을 위한 기술 기반입니다. Kafka의 이벤트 스트리밍은 Fortune 100대 기업의 60% 이상을 포함하여 수많은 산업 및 조직의 다양한 사용 사례 에 적용됩니다.
- 증권 거래소, 은행 및 보험과 같은 실시간으로 지불 및 금융 거래를 처리
- 물류 및 자동차 산업과 같이 자동차, 트럭, 차량 및 선적을 실시간으로 추적하고 모니터링
- 공장 및 풍력 발전 단지와 같은 IoT 장치 또는 기타 장비의 센서 데이터를 지속적으로 캡처하고 분석
- 소매, 호텔 및 여행 산업, 모바일 애플리케이션과 같은 고객 상호 작용 및 주문을 수집하고 즉시 대응
- 병원에서 치료 중인 환자를 모니터링하고 상태 변화를 예측하여 응급 상황에서 시기 적절한 치료를 보장
- 회사의 여러 부서에서 생성된 데이터를 연결, 저장 및 사용 가능하게 만듦
- 데이터 플랫폼, 이벤트 중심 아키텍처 및 마이크로서비스(MSA)의 기반 역할
Apache Kafka는 분산 메시징 시스템입니다. Kafka는 메시지를 저장하고 처리하는 플랫폼으로, 실시간 데이터 스트리밍 애플리케이션에 사용됩니다. Kafka는 높은 처리량, 내구성, 확장성을 제공하며, 다양한 종류의 애플리케이션에서 사용할 수 있습니다.
Kafka는 실시간 데이터 스트리밍 애플리케이션에 사용되는 인기 있는 메시징 시스템입니다. Kafka는 높은 처리량, 내구성, 확장성을 제공하며, 다양한 종류의 애플리케이션에서 사용할 수 있습니다.
특징
Kafka는 다음과 같은 특징을 가지고 있습니다.
- 높은 처리량: Kafka는 100,000개(10만개)이상의 메시지를 초당 처리할 수 있습니다.
- 내구성: Kafka는 데이터를 분산 저장하고 복제하여 장애 발생 시에도 데이터를 안전하게 보관할 수 있습니다.
- 확장성: Kafka는 클러스터로 구성하여 수요에 따라 쉽게 확장할 수 있습니다.
- 다양한 애플리케이션 지원: Kafka는 다양한 종류의 애플리케이션에서 사용할 수 있습니다.
카프카의 탄생 배경
비즈니스 소셜 네트워크 서비스인 링크드인 (linked-in) 에서 개발했다.
다음은 카프카 개발 전 링크드인의 데이터 처리 시스템이다.
기존 데이터 시스템의 문제점
각 애플리케이션과 DB가 end-to-end 로 연결되어 있고(각 파이프라인이 파편화 되어있음), 요구사항이 늘어남에 따라 데이터 시스템 복잡도가 높아지면서 다음과 같은 문제가 발생하게 되었다.
1. 시스템 복잡도 증가 (Complexity)
- 통합된 전송 영역이 없어 데이터 흐름을 파악하기 어렵고, 시스템 관리가 어려움
- 특정 부분에서 장애 발생 시 조치 시간 증가 (=> 연결 되어있는 애플리케이션들을 모두 확인해야 하기 때문에)
- HW 교체 / SW 업그레이드 시 관리포인트가 늘어나고, 작업시간 증가 (=> 연결된 애플리케이션에 side effect 가 없는지 확인해야 함)
2. 데이터 파이프라인 관리의 어려움
- 각 애플리케이션과 데이터 시스템 간의 별도의 파이프라인 존재하고, 파이프라인 마다 데이터 포맷과 처리 방식이 다름
- 새로운 파이프라인 확장이 어려워지면서, 확장성 및 유연성이 떨어짐
- 또한 데이터 불일치 가능성이 있어 신뢰도 감소
이러한 문제를 해결하기 위해 새로운 시스템의 개발 필요성이 높아졌고, 다음과 같은 목표를 가지고 새로운 시스템을 개발했다.
모든 시스템으로 데이터를 전송할 수 있고, 실시간 처리도 가능하며, 급속도로 성장하는 서비스를 위해 확장이 용이한 시스템을 만들자!
이렇게 모든 이벤트/데이터의 흐름을 중앙에서 관리하는 카프카를 개발하게 되었다.
카프카 적용 후
다음은 카프카를 적용한 후 링크드인의 데이터 처리 시스템이다.
카프카를 적용함으로써 앞서 말했던 문제점들이 어느정도 완화되었다.
- 모든 이벤트/데이터의 흐름을 중앙에서 관리할 수 있게 됨
- 새로운 서비스/시스템이 추가되도 카프카가 제공하는 표준 포맷으로 연결하면 되므로 확장성과 신뢰성이 증가
- 개발자는 각 서비스간의 연결이 아닌, 서비스들의 비즈니스 로직에 집중 가능
카프카의 동작 방식 및 특징
카프카는 Pub-Sub 모델의 메세지 큐 형태로 동작한다.
우선 카프카를 이해하기 위해서는 메세지/이벤트 브로커와 메세지 큐에 대한 선제적인 이해가 필요하다.
예시
Kafka를 사용하는 몇 가지 예는 다음과 같습니다.
- 로그 수집: Kafka는 로그 데이터를 수집하고 저장하는 데 사용할 수 있습니다.
- 이벤트 처리: Kafka는 이벤트 데이터를 처리하고 분석하는 데 사용할 수 있습니다.
- 데이터 분석: Kafka는 데이터를 분석하고 시각화하는 데 사용할 수 있습니다.
- 애플리케이션 통합: Kafka는 애플리케이션을 통합하고 데이터를 공유하는 데 사용할 수 있습니다.
Redis와 Kafka
Redis와 Kafka를 함께 사용하는 이유는 다음과 같습니다.
- 고가용성: Redis는 인메모리 데이터베이스이기 때문에 매우 빠르고, Kafka는 분산 메시징 시스템이기 때문에 많은 데이터를 처리할 수 있습니다. 따라서 Redis와 Kafka를 함께 사용하면 시스템의 가용성을 높일 수 있습니다.
- 확장성: Redis는 클러스터링을 지원하여 수백 개의 노드로 확장할 수 있고, Kafka는 브로커를 추가하여 확장할 수 있습니다. 따라서 Redis와 Kafka를 함께 사용하면 시스템을 쉽게 확장할 수 있습니다.
- 내구성: Redis는 데이터를 자동으로 복제하여 장애 발생 시에도 데이터를 보호할 수 있습니다. Kafka는 데이터를 로그에 저장하여 장애 발생 시에도 데이터를 복구할 수 있습니다. 따라서 Redis와 Kafka를 함께 사용하면 시스템의 내구성을 높일 수 있습니다.
- 효율성: Redis는 캐싱에 사용되어 데이터베이스의 부하를 줄일 수 있고, Kafka는 데이터를 전송하는 데 사용되어 애플리케이션 간의 통신을 효율적으로 할 수 있습니다. 따라서 Redis와 Kafka를 함께 사용하면 시스템의 효율성을 높일 수 있습니다.
Redis와 Kafka는 모두 오픈 소스 소프트웨어이기 때문에 비용이 저렴합니다. 또한, Redis와 Kafka는 모두 많은 개발자들이 사용하고 있기 때문에 문서와 튜토리얼이 풍부합니다. 따라서 Redis와 Kafka를 함께 사용하면 시스템의 개발과 유지보수를 쉽게 할 수 있습니다.
Redis와 Kafka는 모두 뛰어난 성능과 기능을 제공하는 소프트웨어입니다. 따라서 Redis와 Kafka를 함께 사용하면 시스템의 가용성, 확장성, 내구성, 효율성, 비용, 개발 편의성 등을 높일 수 있습니다.
전자상거래
전자상거래 분야에서는 Apache Kafka를 활용해 더 많은 기회를 확보할 수 있습니다. 페이지 클릭, 좋아요, 검색, 주문, 장바구니 및 재고와 같은 데이터를 처리할 수 있기 때문입니다.
이벤트 스트리밍 플랫폼?
Kafka는 세 가지 주요 기능을 결합하여 end-to-end 이벤트 스트리밍을 구현할 수 있습니다.
- 이벤트 스트림을 지속적으로 발행(publish-write), 구독(subscribe-read) 합니다.
- 이벤트 스트림을 원하는 만큼 내구성 있고 안정적으로 저장(store) 합니다 . KafkaCluster(broker)
- 이벤트 스트림 을 발생 시 또는 소급하여 처리(Process) 합니다**.**
그리고 이 모든 기능은 분산되고 확장성이 뛰어나고 탄력적이며 내결함성이 있으며 안전한 방식으로 제공됩니다. Kafka는 베어메탈 하드웨어, 가상 머신, 컨테이너, 온프레미스 및 클라우드에 배포할 수 있습니다. Kafka 환경을 자가 관리하거나 다양한 공급업체에서 제공하는 완전 관리형 서비스를 사용할 수 있습니다.
카프카 구성 요소
카프카 클러스터
- 메세지를 저장하는 저장소
- 하나의 여러개의 브로커(각각의 서버)로 구성이 됨
- 브로커들이 메세지를 나눠서 저장, 이중화 처리, 장애가 나면 대체 함
- 데이터를 이동하는데 필요한 핵심 역할을 맡음
주키퍼 클러스터(앙상블)
- 카프카 클러스터 관리
- 카프카 클러스터와 관련된 정보가 기록이되고 관리가 됨
Topic
- 메시지를 전송하거나 소비할 때 Topic을 반드시 입력한다.
- Consumer는 자신이 담당하는 Topic의 메시지를 처리한다.
- 한 개의 토픽은 한 개 이상의 파티션으로 구성된다.
Partition
- 각각의 메시지를 목적에 맞게 구분할 때 사용한다.
- 분산 처리를 위해 사용된다.
- Topic 생성 시 partition 개수를 지정할 수 있다. (파티션 개수 변경 가능. *추가만 가능)
- 파티션이 1개라면 모든 메시지에 대해 순서가 보장된다.
- 파티션 내부에서 각 메시지는 offset(고유 번호)로 구분된다.
- 파티션이 여러개라면 Kafka 클러스터가 라운드 로빈 방식으로 분배해서 분산처리되기 때문에 순서 보장 X
- 파티션이 많을 수록 처리량이 좋지만 장애 복구 시간이 늘어난다.
Offset
- 컨슈머에서 메세지를 어디까지 읽었는지 저장하는 값
- 컨슈머 그룹의 컨슈머들은 각각의 파티션에 자신이 가져간 메시지의 위치 정보(offset) 을 기록
- 컨슈머 장애 발생 후 다시 살아나도, 전에 마지막으로 읽었던 위치에서부터 다시 읽어들일 수 있다.
Producer
- 메시지를 만들어서 카프카 클러스터에 전송한다.
- 메시지 전송 시 Batch 처리가 가능하다.
- key값을 지정하여 특정 파티션으로만 전송이 가능하다.
- 전송 acks값을 설정하여 효율성을 높일 수 있다.
- ACKS=0 -> 매우 빠르게 전송. 파티션 리더가 받았는 지 알 수 없다.
- ACKS=1 -> 파티션 리더가 받았는지 확인. 기본값
- ACKS=ALL -> 파티션 리더 뿐만 아니라 팔로워까지 메시지를 받았는 지 확인
Consumer
- 카프카 클러스터에서 메시지를 읽어서 처리한다.
- 메세지를 Batch 처리할 수 있다.
- 한 개의 컨슈머는 여러 개의 토픽을 처리할 수 있다.
- 메시지를 소비하여도 메시지를 삭제하지는 않는다. (Kafka delete policy에 의해 삭제)한 번 저장된 메시지를 여러번 소비도 가능하다.
- 컨슈머는 컨슈머 그룹에 속한다.
- 한 개 파티션은 같은 컨슈머그룹의 여러 개의 컨슈머에서 연결할 수 없다.
Broker
- 실행된 카프카 서버를 말한다.
- 프로듀서와 컨슈머는 별도의 애플리케이션으로 구성되는 반면, 브로커는 카프카 자체이다.
- Broker(각 서버)는 Kafka Cluster 내부에 존재한다.
- 서버 내부에 메시지를 저장하고 관리하는 역할을 수행한다.
Zookeeper
- 분산 애플리케이션 관리를 위한 코디네이션 시스템
- 분산 메시지큐의 메타 정보를 중앙에서 관리하는 역할
Kafka 주요 개념 및 용어
- KafkaCluster : 카프카의 브로커들의 모임. Kafka는 확장성과 고가용성을 위하여 broker들이 클러스터로 구성
- Broker : 각각의 카프카 서버, 동일 노드에 여러 브로커를 띄울 수 있다.
- Zookeeper : 카프카 클러스터 정보 및 분산처리 관리 등 메타데이터 저장. 카프카를 띄우기 위해 반드시 실행되어야 함(곧 카프카 클러스터와 통합 예정)
- Producer : 메시지(이벤트)를 발행하여 생산(Wirte) 하는 주체
- Consumer : 메시지(이벤트)를 구독하여 소비(Read) 하는 주체
토픽, 파티션, 오프셋
- 카프카에 저장되는 메시지는 topic으로 분류, topic은 여러개의 patition으로 나눠짐
- Topic : 메시지를 구분하는 단위
- 파일시스템의 폴더, 메일함과 유사함 ex) 주문용 토픽, 결제용 토픽 등
- 각각의 메시지를 알맞게 구분하기 위한 목적으로 사용
- 프로듀서와 컨슈머가 토픽을 기준으로 메시지를 주고 받음
- Partition : 메세지를 저장하는 물리적인 파일
- 한 개의 토픽은 한 개 이상의 파티션으로 구성
- 파티션은 메시지 추가만 가능한 파일(append-only)
- offset : 파티션내 각 메시지의 저장된 상대적 위치
- 프로듀서가 넣은 메시지는 파티션의 맨 뒤에 추가 (Queue)
- 컨슈머는 오프셋 기준으로 마지막 커밋 시점부터 메시지를 순서대로 읽어서 처리함
- 파티션의 메시지 파일은 처리 후에도 계속 저장되어 있며 설정에 따라 일정시간 뒤 삭제됨
프로듀서
- Producer: 메시지(이벤트)를 발행하여 생산(Wirte) 하는 주체
- 프로듀서는 메시지 전송시 토픽을 지정
- 파티션은 라운드로빈 방식 혹은 파티션 번호를 지정하여 넣을 수 있음
- 같은 키를 갖는 메시지는 같은 파티션에 저장 되며 순서 유지
컨슈머
- Consumer : 메시지(이벤트)를 구독하며 소비(Read)하는 주체
- Consumer Group
- 메시지를 소비하는 컨슈머들의 논리적 그룹
- Topic의 파티션은 컨슈머그룹과 1:N 매칭 관계로 동일 그룹내 한 개의 컨슈머만 연결가능 하다.
- 즉 컨슈머그룹에 속한 컨슈머들은 한 파티션을 공유할 수 없음
- 한 컨슈머그룹 기준으로 파티션의 메시지는 순서대로 처리
- 한 개 파티션을 서로 다른 그룹의 컨슈머는 공유할 수 있음
- 이로써 파티션의 메시지는 순서대로 처리되도록 보장
- 특정 컨슈머에 문제가 생겼을때 Fail over를 통한 리밸런싱 가능
- 보통 파티션과 컨슈머는 1:1이 best practice로 봄
Why Kafka?
고성능
- 다중 프로듀서, 다중 컨슈머가 상호 간섭없이 메시지를 쓰고 읽어서 처리
- 디스크 기반의 이벤트 보존
- 지속해서 보존 가능, 데이터 유실 위험이 적고 컨슈머가 항상 안떠있어도 됨.
- 장애 발생시 유실 복구 가능(재처리)
- 파티션 파일은 OS 페이지 캐시를 통해 IO를 메모리에서 처리하여 성능이 유리
- Zero Copy를 통해 디스크 버퍼에서 네트워크 버퍼로 직접 데이터 복사
- 브로커가 하는일이 비교적 단순 - 똑똑한 컨슈머
- 브로커는 컨슈머와 파티션간 맵핑 관리만 하며 성능에 집중
- 메시지 필터, 메시지 재전송과 같은 일은 프로듀서, 컨슈머에 위임
- batch 기능을 제공하여 동시 처리량 증가
- 프로듀서 : 일정 크기만큼 메시지를 모아서 전송
- 컨슈머 : 최소 크키만큼 메시지를 모아서 읽어옴
- 확장성(scale out) : 수평 확장이 쉽게 가능 > 브로커,파티션, 컨슈머 추가
고가용성(HA- High Availability)
- Kafka의 topic은 partition이라는 단위로 쪼개어져 클러스터의 각 서버들에 분산되어 저장되고, 고가용성을 위하여 복제(replication) 설정을 할 경우 이 또한 partition 단위로 각 서버들에 분산되어 복제되고 장애가 발생하면 partition 단위로 fail over가 수행된다.
- Replication : 토픽내 파티션의 복제본. replication-factor를 통해 개수를 지정할 수 있다.
- 복제수(replication factor)만큼 파티션의 복제본이 각 브로커에 생김
- 토픽 생성시 복제수를 2로 하면 파티션이 2개가 각각의 브로커에 생김
- **leader**와 **follower**로 구성
- 프로듀서&컨슈머는 리더를 통해서만 메시지 처리
- 팔로워는 리더가 속한 브로커에서 메시지를 복제함
- 리더가 속한 브록커가 장애나면 다른 팔로워가 리더가 되어서 처리
성능
- 파티션 파일은 OS 페이지캐시 사용
- 파티션에 대한 파일 IO를 메모리에서 처리
- 서버에서 페이지캐시를 카프카만 사용해야 성능에 유리
- Zero Copy
- 디스크 버퍼에서 네트워크 버퍼로 직접 데이터 복사
- 컨슈머 추적을 위해 브로커가 하는 일이 비교적 단순
- 메시지 필터, 메시지 재전송과 같은 일은 브로커가 하지 않음
- 프로듀서, 컨슈머가 직접 해야 함
- 브로커는 컨슈머와 파티션 간 매핑 관리
- 메시지 필터, 메시지 재전송과 같은 일은 브로커가 하지 않음
- 묶어서 보내기, 묶어서 받기 (batch)
- 프로듀서: 일정 크기만큼 메시지를 모아서 전송 가능
- 컨슈머: 최소 크기만큼 메시지를 모아서 조회 가능
- 낱개 처리보다 처리량 증가
카프카와 성능
- 수평확장이 용이한 구조를 가지고 있어서 처리량 증대(확장)가 쉬움
- 1개 장비의 용량 한계 -> 브로커 추가, 파티션 추가
- 컨슈머가 느림 -> 컨슈머 추가 (+파티션 추가)
주요 설계 특징
왜 하나의 topic을 여러개의 partition으로 분산시키는가?
- 병렬로 처리하기 위해 분산 저장 한다.
- 카프카의 토픽에 메세지가 쓰여지는 것도 어느정도 시간이 소비된다. 몇 천건의 메세지가 동시에 카프카에 write 되면 병목현상이 발생할 수 있다.
- 따라서 파티션을 여러개 두어서 분산 저장함으로써 write 동작을 병렬로 처리할 수 있다.
- 다만, 한번 늘린 파티션은 절대 줄일 수 없기 때문에 운영 중에, 파티션을 늘려야 하는건 충분히 검토 후 실행되어야 한다. (최소한의 파티션으로 운영하고 사용량에 따라 늘리는 것을 권장한다)
- 파티션을 늘렸을 때 메세지는 Round-Robin 방식으로 쓰여진다. 따라서 하나의 파티션 내에서는 메세지 순서가 보장되지만, 파티션이 여러개일 경우에는 순서가 보장되지 않는다.
컨슈머 그룹은 왜 존재할까?
- consumer의 묶음을 consumer group이라고 한다
- 컨슈머 그룹은 하나의 topic에 대한 책임을 갖고 있다.
- 즉 어떤 consumer가 down된다면, 파티션 재조정(리밸런싱)을 통해 다른 컨슈머가 해당 파티션의 sub을 맡아서 한다. offset 정보를 그룹간에 공유하고 있기 때문에 down 되기 전 마지막으로 읽었던 메세지 위치부터 시작한다.
카프카 아키텍처
- 카프카 클러스터 (kafka cluster): 하나 이상의 카프카 브로커들의 집합이다. 특징에서 알아보았듯 카프카는 확장성과 내결함성을 위해 브로커들을 클러스터로 구성한다.
- 여러대의 브로커를 구성한 클러스터를 의미 합니다.
- 브로커 (broker): 브로커는 개별 카프카 서버로 보면 된다. 브로커는 프로듀서로부터 메시지를 전달받아, 토픽에 저장하고, 컨슈머에 전달하는 역할을 한다. 브로커는 여러개의 토픽을 가질 수 있다.
- 카프카 애플리케이션이 설치된 서버 또는 노드를 의미 합니다.
- 토픽 (topic): 토픽을 데이터가 저장되는 단위라고 할 수 있다. 토픽은 이름으로 식별된다. 토픽에 한번 추가된 데이터는 수정될 수 없다.
- 카프카는 메시지 피드들을 토픽으로 구분하고, 각 토픽의 이름은 카프카 내에서 고유 합니다.
- 파티션 (partition): 카프카의 확장성을 위해 토픽은 1개 이상의 파티션으로 나뉠 수 있다. 레코드에 키가 없다면 라운드 로빈으로 파티션에 나뉘어 저장되고, 같은 키를 가진 레코드는 같은 파티션에 저장된다.
- 병렬 처리 및 고성능을 얻기 위해 하나의 토픽을 여러개로 나눈 것을 의미 합니다
- 오프셋 (offset): 파티션에 저장된 레코드는 증가하는 정수 ID를 갖고, 이를 오프셋이라고 부른다. 오프셋은 0부터 시작하며, 파티션에 레코드가 저장될 때 마다 시퀀셜하게 증가한다. 특정 파티션의 각 레코드는 고유한 오프셋을 갖지만, 서로 다른 파티션 간에는 고유하지 않다. 파티션에서 데이터를 읽을 때 작은 것부터 큰 순서대로 읽는다.
- 레코드 (record): 파티션에 저장되는 데이터이다. Key, Value, Timestamp, Compression Type, Optional Headers, Partition and Offset id 로 구성된다.
- 프로듀서가 브로커로 전송하거나 컨슈머가 읽어가는 데이터 조각을 말합니다.
- 프로듀서 (producer): 카프카에 요청하여 토픽에 레코드를 추가하는 클라이언트이다. 카프카의 구성 요소가 아니며, 카프카 외부에서 카프카에 요청하는 애플리케이션이다.
- 카프카로 메세지를 보내는 역할을 하는 클라이언트로 총칭 합니다.
- 컨슈머 (consumer): 하나 이상의 파티션과 토픽으로부터 레코드를 읽어오는 클라이언트이다. 기본적으로 사용 가능한 가장 낮은 오프셋부터 높은 오프셋까지 순서대로 레코드를 읽어온다. 하나의 토픽의 여러 파티션으로부터 레코드를 읽어올 때는 순서가 보장되지 않는다. 파티션 0, 1, 2 로부터 레코드를 읽어올 때 파티션 0의 레코드만 바라봤을 때는 순서가 보장되지만, 읽어온 전체 레코드를 바라볼대는 파티션 0 ~ 2의 레코드가 순서와 상관없이 섞여있을 수 있다.
- 카프카에서 메세지를 꺼내가는 역할을 하는 클라이언트를 총칭 합니다.
- 컨슈머 그룹 (consumer group): 동일한 컨슈머 인스턴스를 여러개 생성하여 컨슈머 그룹을 구성할 수 있다. 컨슈머 그룹을 구성하는 여러 컨슈머는 동일한 토픽의 각자 다른 파티션을 도맡아 메시지를 컨슘할 수 있다. 예를 들어 토픽 A에 파티션이 0, 1, 2 가 생성되어 있고, 컨슈머 그룹 A에 컨슈머 a, b, c가 있다고 가정하자. 이 경우 컨슈머 a는 파티션 0을, 컨슈머 b는 파티션 1을, 컨슈머 c는 파티션 2를 컨슘한다.
- 세그먼트(segment) : 프로듀서가 전송한 실제 메세지가 브로커의 로컬 디스크에 저장되는 파일을 말합니다.
Broker / 브로커
브로커는 하나의 서버(또는 인스턴스) 당 하나의 데몬 프로세스로 동작하여 메시지 수신/전달 요청을 받아들입니다.
이것을 여러 대의 클러스터로 구성할 수 있으며 브로커를 추가함으로써 수신/전달의 처리량 향상, 스케일 아웃이 가능합니다.
브로커에서 받은 데이터는 모두 디스크로 내보내기(영속화)가 이루어져 디스크의 총 용량에 따라 장기간 데이터를 보존할 수 있습니다.
Producer / 프로듀서
프로듀서는 프로듀서 API를 이용하여 브로커에 데이터를 송신하기 위해 구현된 애플리케이션입니다.
각종 로그 전송 및 미들웨어와 연동하여 동작하기 때문에 프로듀서 API를 내포한 도구, 미들웨어를 통해 이용하는 형태 등으로 다양합니다.
- 프로듀서가 토픽의 파티션에 메시지를 송신할 때 버퍼 기능처럼 프로듀서의 메모리를 이용하여 일정량을 축적 후 송신
- 데이터의 송신에 대해서는 지정한 크기까지 메시지가 축적되거나
- batch.size
- 지정한 대기 시간에 도달하는 것 중 하나를 트리거로 전송
- 토픽에 메시지 전송 시 파티셔닝
- Key의 해시값을 사용한 송신
- 메시지는 Key와 Value로 이뤄져 있는데 이 Key를 이용하여 송신처 파티션을 결정
- 동일한 Key를 가진 메시지는 동일한 ID를 가진 파티션에 송신
- partitionkey를 이용하면 메시지 순서 보장이 가능하나 대신 imbalance를 챙겨야 함
- 라운드 로빈에 의한 송신
- 메시지 Key를 지정하지 않고 Null로 한 경우 여러 파티션으로 메시지 송신을 라운드 로빈 방식으로 실행
- callback을 통해 브로커로 메시지 송신 결과를 비동기 처리 할 수 있음
- future.get()을 하면 브로커 상태와 설정에 따라 쓰레드가 무한 점유될 수 있으므로 매우 위험
- CompletableFuture를 이용
- Key의 해시값을 사용한 송신
Consumer
컨슈머 API를 이용해 브로커에서 메시지를 취득하도록 구현된 애플리케이션입니다. 브로커는 메시지를 디스크에 영속화하기 위해 브로커에 도달하는 즉시 컨슈머에서 취득해야 하는 제약이 없어 디스크에 보관된 동안은 메시지 취득이 가능합니다. 일정 기간 데이터를 축적한 스토리지에서 데이터 추출 및 시간 처리를 위한 애플리케이션의 데이터 입력 등으로 이용됩니다.
- RetryTemplate 등을 통해 컨슈머 장애시 재처리를 시도하도록 해야함
Consumer Group
카프카에서는 컨슈머가 카프카 클러스터에서 메시지를 얻어 처리합니다. 이때 컨슈머는 컨슈머그룹이라 불리는 하나 이상의 컨슈머들로 이루어진 그룹을 형성하여 메시지를 얻습니다. 컨슈머 그룹은 Group ID라는 ID로 구분됩니다. 이 Group ID는 KafkaConsumer를 생성할 떄 지정하는 옵션으로 group.id라는 파라미터로 지정하며, 특정 컨슈머는 여러 컨슈머 그룹에 속하지 않고 항상 하나의 컨슈머 그룹에 속합니다.
카프카 클러스터에서 수신할 메시지는 컨슈머 그룹 안에서 어느 하나의 컨슈머가 수신합니다. 즉, 카프카 클러스터에서 수신할 메시지를 동일 컨슈머 그룹에 속하는 컨슈머 사이에서 분산하여 수신합니다. 이는 컨슈머에서 분산 스트림 처리도 고려해 설계된 것입니다.
메시지를 컨슈머 그룹의 어느 컨슈머가 수신하는가에 대한 할당은 수신할 토픽에 존재하는 파티션과 그룹 내 컨슈머를 매핑함으로써 가능합니다. 컨슈머와 파티션의 매핑은 각 파티션에 반드시 하나의 컨슈머가 매핑됩니다. 반대로 파티션 수에 따라 하나의 컨슈머에 여러 파티션이 할당되는 경우가 있습니다. 컨슈머 그룹에서 기대한 대로 분산하여 메시지를 수신하기 위해서는 파티션 수가 적어도 각 컨슈머 그룹에 속하는 컨슈머보다 많아야 합니다. 토픽의 파티션보다 컨슈머 쪽이 많을 경우 파티션이 할당되지 않은 컨슈머가 발생할 수 있습니다.
토픽 (topic)
Events
이벤트란, 과거에 일어난 사실을 뜻한다. 이벤트는 발생함으로 인해 변화된 상태를 가지고 시스템 사이를 오가는, 불변하는 데이터이다.
Streams
이벤트 스트림이란, 관련된 이벤트들을 뜻한다.
Topics
이벤트 스트림이 카프카에서는 토픽이란 이름으로 저장된다. 카프카의 세계에서는 토픽이 구체화된 이벤트 스트림을 뜻한다. 토픽은 연관된 이벤트들을 묶어 영속화하는데, 이는 데이터베이스의 테이블이나 파일 시스템의 폴더들에 비유할 수 있다.
토픽은 카프카에서 Producer 와 Consumer 를 분리하는 중요한 컨셉이다. Producer 는 카프카의 토픽에 메시지를 저장 (Push) 하고 Consumer 는 저장된 메시지를 읽어 (Pull) 온다. 하나의 토픽에 여러 Producer / Consumer 가 존재할 수 있다.
Event 가 관련된 것들끼리 모여 Stream 을 이루게 되고, 이것이 카프카에 저장될 때 Topic 의 이름으로 저장된다.
Partitions
토픽에 대한 대량의 메시지 입출력을 지원하기 위해, 브로커 상의 데이터를 읽고 쓰는 것은 파티션이라는 단위로 분할합니다.
토픽을 구성하는 파티션은 브로커 클러스터 안에 분산 배치되며 프로듀서로부터 메시지 수신, 컨슈머에게 배달을 분산 실시함으로써 하나의 토픽에 대한 대규모 데이터 수신과 전달을 지원하게 됩니다.
- 적정 파티션 수
- 구성 및 요구 사항에 따라 다르기 때문에 시스템을 설계할 때 고려
- 메시지 처리 속도, 컨슈머 그룹 내 컨슈머 개수, 컨슈머내 스레드 수 등을 동시에 고려해야 함
- 파티션 수는 증가할 수는 있지만 한 번 증가한 파티션 수는 다시 줄일 수 없음
- 구성 및 요구 사항에 따라 다르기 때문에 시스템을 설계할 때 고려
카프카의 토픽들은 여러 파티션으로 나눠진다. 토픽이 카프카에서 일종의 논리적인 개념이라면, 파티션은 토픽에 속한 레코드를 실제 저장소에 저장하는 가장 작은 단위이다. 각각의 파티션은 Append-Only 방식으로 기록되는 하나의 로그 파일이다.
참고한 포스팅에서 Record 와 Message 는 위의 그림에서 표현하는 것과 같이 파티션 내에 저장되는 메시지라는 의미로 사용되었다.
Offsets and the ordering of messages
파티션의 레코드는 각각이 Offset 이라 불리는, 파티션 내에서 고유한 레코드의 순서를 의미하는 식별자 정보를 가진다. 하지만 카프카는 일반적으로 메시지의 순서를 보장하지 않는다. 위에서 각각의 파티션은 Append-Only 방식으로 레코드를 기록한다 했고, 내부적으로 Offset 이라는 정보를 사용하는데 왜 메시지의 순서가 보장되지 않는 것일까? 자세한 내용은 아래의 내용을 보면 이해할 수 있고, 간단하게는 파티션 간에는 순서가 보장되지 않으므로 결국 메시지의 순서가 보장되지 않는다 할 수 있다.
Offset 정보는 카프카에 의해서 관리되고, 값이 계속 증가하며 불변하는 숫자 정보이다. 레코드가 파티션에 쓰여질 때 항상 기록의 맨 뒤에 쓰여지고, 다음 순서의 Offset 값을 갖게 된다.
아래의 그림은 세 개의 파티션을 갖는 토픽을 보여준다. 레코드는 각 파티션의 마지막에 추가된다. 아래에서 볼 수 있듯, 메시지는 파티션 내에서는 유의미한 순서를 가지고 이를 보장하지만, 파티션 간에는 보장되지 않는다.
Partitions are the way that Kafka provides Scalability
카프카의 클러스터는 브로커라 불리는 하나 혹은 그 이상의 서버들로 이루어지는데, 각각의 브로커는 전체 클러스터에 속한 레코드의 서브셋을 가진다.
카프카는 토픽의 파티션들을 여러 브로커에 배포하는데 이로 인해 얻을 수 있는 다음과 같은 이점들이 있다.
- 만약 카프카가 한 토픽의 모든 파티션을 하나의 브로커에 넣을 경우, 해당 토픽의 확장성은 브로커의 I/O 처리량에 의해 제약된다. 파티션들을 여러 브로커에 나눔으로써, 하나의 토픽은 수평적으로 확장될 수 있고 이로 인해 브로커의 처리 능력보다 더 큰 성능을 제공할 수 있게 된다.
- 토픽은 여러 Consumer 에 의해 동시에 처리될 수 있다. 단일 브로커에서 모든 파티션을 제공하면 지원할 수 있는 Consumer 수가 제약되는데, 여러 브로커에서 파티션을 나누어 제공함으로써 더 많은 Consumer 들이 동시에 토픽의 메시지를 처리할 수 있게 된다.
- 동일한 Consumer 의 여러 인스턴스가 서로 다른 브로커에 있는 파티션에 접속함으로써 매우 높은 메시지 처리량을 가능케 한다. 각 Consumer 인스턴스는 하나의 파티션에서 메시지를 제공받고, 각각의 레코드는 명확한 처리 담당자가 존재함을 보장할 수 있다.
Partitions are the way that Kafka provides Redundancy
카프카는 여러 브로커에 동일 파티션의 복사본을 두 개 이상 유지한다. 이러한 복사본을 Replica 라 하는데, 브로커에 장애가 발생했을 때에도 장애가 발생한 브로커가 소유한 파티션의 복사본을 통해 컨슈머에게 지속적으로 메시지를 제공할 수 있게 한다.
Writing Records to Partitions
Using a Partition Key
Producer 는 파티션 키를 사용해서 특정한 파티션에 메시지를 전달할 수 있다. 파티션 키는 Application Context 에서 파생될 수 있는 어떤 값이든 될 수 있다. 고유한 장비의 ID 나 사용자의 ID 는 좋은 파티션 키를 만들 수 있다.
기본적으로 파티션 키는 해싱 함수를 통해 전달되고 해싱 함수는 동일한 키를 갖는 모든 레코드들이 동일한 파티션에 도착하는 것을 보장한다. 파티션 키를 지정할 경우 다음 그림과 같이 관련된 이벤트를 동일한 파티션에 유지함으로써 전달된 순서가 그대로 유지되는 것을 보장할 수 있다.
파티션 키를 사용한 방법이 위와 같이 발생한 이벤트의 순서를 보장할 수 있다는 장점을 가진 반면, 키가 제대로 분산되지 않는 경우 특정 브로커만 메시지를 받는 단점도 존재한다.
예를 들어, 고객의 ID 가 파티션 키로 사용되고 어떤 고객이 트래픽의 90% 를 생성한다 하자. 이 경우, 대부분의 어플리케이션 수행 동안 하나의 파티션에서 90% 의 트래픽을 감당하게 된다. 전체 트래픽이 작은 경우는 이를 무시할 수 있지만, 만약 트래픽이 많은 경우에 이와 같은 상황이 발생한다면 이는 종종 브로커의 장애로 이어질 수 있다.
이런 이유로, 파티션 키를 사용하고자 한다면 이들이 잘 분산되는 지를 확인할 필요가 있다.
Allowing Kafka to decide the partition
Producer 가 레코드를 생성할 때 파티션 키를 명시하지 않으면, Kafka 는 Round-Robin 방식을 사용해 파티션을 배정한다. 이러한 레코드들은 해당 토픽의 파티션들에 고루 분배된다.
그러나, 파티션 키가 사용되지 않을 경우 레코드들의 순서는 보장되지 않는다. 위에서 파티션 키를 사용할 경우와 반대로, 순서가 보장되지 않는 대신 파티션의 분배는 고루 이루어진다.
Writing a customer partitioner
특정 상황에서는, Producer 가 서로 다른 비즈니스 규칙을 사용한 자체 파티셔너 구현을 사용할 수도 있다.
Reading records from partitions
Producer 가 레코드 생성할 때 어떤 파티션에 이를 저장할 지를 위에서 봤으니, 이제는 Consumer 가 파티션에서 레코드를 어떻게 읽는 지를 알아볼 차례이다.
보통의 pub/sub 모델과는 달리, Kafka 는 메시지를 Consumer 에 전달 (push) 하지 않는다. 대신, Consumer 가 카프카의 파티션으로부터 메시지를 읽어 (pull) 가야 한다. 컨슈머는 브로커의 파티션과 연결해, 메시지들이 쓰여진 순서대로 메시지를 읽는다.
메시지의 Offset 은 Consumer 측의 커서와 같이 동작한다. Consumer 는 메시지의 오프셋을 추적하고 이를 통해 이미 소비한 메시지를 저장한다. 메시지를 읽고 난 뒤 Consumer 는 파티션의 다음 Offset 으로 커서를 이동하고, 다시 메시지를 읽고, .. 이 과정을 계속 반복한다.
각 파티션에서 마지막으로 소비된 메시지의 Offset 을 기억함으로써 Consumer 는 어떤 시점에 파티션의 어떤 위치에서 재시작할 수 있다. 이를 통해 Consumer 는 장애를 복구한 뒤 메시지 소비를 재시작할 수 있다.
파티션은 하나 혹은 그 이상의 Consumer 들로부터 소비될 수 있고, 각각은 서로 다른 Offset 을 가지고 메시지를 읽을 수 있다. 카프카는 Consumer Group 이라는 개념을 사용하는데, 이는 어떤 토픽을 소비하는 Consumer 들을 그룹으로 묶은 것이다. 동일한 Consumer Group 에 있는 Consumer 들은 동일한 Group-Id 를 부여받는다. 파티션 내의 메시지는 그룹 내의 Consumer 중 하나에 의해서만 소비되는 것을 보장한다.
아래의 그림은 Consumer 그룹 내에서 파티션들이 어떻게 소비되는 지를 보여준다.
Consumer Group 은 Consumer 들이 병렬적으로 메시지를 소비할 수 있도록 한다. 병렬 처리를 가능케 함으로써 처리량은 매우 높아지지만, 그룹의 Maximum Parallelism 은 토픽의 파티션 개수와 동일하다.
예를 들어, N 개의 파티션이 존재하는 토픽에 N + 1 개의 Consumer 가 있는 경우, N 개의 Consumer 들은 파티션을 각각 배정받지만 남은 Consumer 는 다른 Consumer 가 장애가 발생하지 않는 한, Idle 상태로 파티션에 배정되기를 기다리게 된다. 이는 Hot Failover 를 구현하는 좋은 전략이 될 수 있다.
아래의 그림은 위의 예시를 표현한 것이다.
여기서 중요한 것은, Consumer 의 개수가 토픽의 병렬성 정도를 결정하지 않고, 결정하는 것은 파티션의 개수라는 것이다. 하지만 파티션을 늘리는 것이 항상 장점으로 작용하지는 않는다.
Offset
카프카의 컨슈머가 poll() 을 호출할 때마다, 컨슈머 그룹은 카프카에 저장되어 있지만 아직 읽지 않은 메시지들을 가져와 처리한다. 이렇게 할 수 있는 것은 컨슈머 그룹이 카프카의 메시지를 어디까지 읽었는 지를 저장하고 있기 때문인데, 어디까지 읽었는 지를 Offset 이라는 값으로 저장하게 된다. 카프카에 저장되는 각 레코드들은 파티션 별로 독립적인 Offset 값을 가지게 되고, 컨슈머 그룹이 파티션 별로 마지막으로 읽은 레코드의 Offset 을 저장함으로써 읽은 메시지와 읽지 않은 메시지를 구분한다.
그러면 컨슈머에 어떤 문제가 발생해 기존에 관리하던 오프셋 정보가 날아간다면? 컨슈머들은 파티션 별로 모든 메시지를 다시 읽고 중복 처리해야 할까?
정답부터 말하자면, 그렇지 않다. 카프카의 컨슈머 그룹은 위와 같은 문제가 발생하는 것을 막기 위해 Offset 을 내부적으로 저장하기도 하지만 카프카 내에 별도로 내부적으로 사용하는 토픽을 생성하고, 그 토픽에 오프셋 정보를 저장하게 되어있다.
위와 같이 오프셋 정보를 카프카의 내부 토픽에 저장하는 것을 Commit 이라 하는데, 카프카에서는 커밋과 관련된 여러 방법을 제공한다.
위에서 언급했듯, 컨슈머는 내부적으로 오프셋 정보를 저장하고 이를 사용해 읽지 않은 메시지들만 파티션에서 읽어올 수 있다. 하지만 어떤 상황에는 내부적으로 존재하는 정보를 사용할 수 없는 경우가 존재하는데, 다음의 경우에 그러한 문제들이 생길 수 있다.
- 컨슈머가 갑자기 다운될 경우
- 컨슈머 그룹에 새로운 컨슈머가 조인할 경우
위의 경우에, 컨슈머 그룹은 기존에 컨슈머 별로 할당된 파티션에 문제가 생긴다. 새로 조인한 컨슈머가 파티션을 배정받지 않아 노는 경우가 생기거나, 기존에 파티션을 배정받아 처리하던 컨슈머가 다운됨으로 인해 해당 파티션을 처리할 컨슈머가 존재하지 않게 된다.
컨슈머 그룹은 이를 해결하기 위해 그룹 내에서 컨슈머들의 리밸런싱을 수행하게 되는데, 이 때 컨슈머들이 기존에 배정받은 파티션과 다른 파티션을 할당받게 될 수 있어 내부적으로 저장한 오프셋 정보를 사용할 수 없게 된다.
카프카에서는 이를 위해 내부 토픽에 오프셋 정보를 저장하고, 오프셋 값을 업데이트하는 것을 커밋이라 부른다. 리밸런스를 거치게 되면 컨슈머들이 기존과 다른 파티션을 할당받을 수 있기 때문에 가장 최근에 커밋된 오프셋 값을 가져와 메시지를 다시 읽는 과정을 수행한다.
커밋은 어플리케이션의 설정에 따라 자동으로 오프셋 정보를 커밋하는 방법과 수동으로 오프셋을 커밋하는 방법이 나눠진다.
- 각 파티션에서 수신한 메시지에는 각각 일려번호를 부여
- 파티션 단위로 메시지 위치를 나타내는 오프셋이라는 관리 정보를 이용해 컨슈머가 취득하는 메시지의 범위 및 재시도를 제어
- 오프셋 설정 값 종류
- Log-End-Offsset(LEO) : 파티션 데이터의 끝
- Current Offset : 컨슈머가 어디까지 메시지를 읽었는가를 나타냄
- Commit Offset : 컨슈머가 어디까지 커밋했는지를 나타냄
- 컨슈머는 메시지에 대해 확인했음을 다시 브로커에게 알리는데 이것이 바로 Commit Offset임
- 자동 오프셋 리셋
- 컨슈머가 시작할 때 오프셋 커밋 기록이 존재하지 않거나 기록되어 있어도 유효하지 않은 경우 사용하는 정책
- 유효하지 않은 경우는 메시지가 retention이 지나 없는 경우
- 컨슈머 애플리케이션에서 컨슈머를 생성할 때 지정하는 auto.offset.reset=earliest 옵션으로 설정
latest 해당 파티션의 가장 새로운 오프셋으로 초기화 카프카 클러스터에 이미 존재하는 메시지는 처리 되지 않음 earliest 해당 파티션에 존재하는 가장 오래된 오프셋으로 초기화 카프카 클러스터에 이미 존재하는 메시지 모두에 대해 처리 실시 none 유효한 오프셋 커밋 정보가 없는 경우에 예외를 반환 seek() 메서드 등으로 명시적으로 오프셋을 지정해야 함 - 컨슈머가 시작할 때 오프셋 커밋 기록이 존재하지 않거나 기록되어 있어도 유효하지 않은 경우 사용하는 정책
Offset Commit
컨슈머는 어느 메시지까지 처리를 완료했는지 카프카 클러스터에 기록을 남길 수 있습니다. 정확하게는 다음 수신 및 처리해야 할 메시지의 오프셋 기록입니다.
오프셋 커밋의 기록은 컨슈머 그룹 단위로 이루어집니다. 컨슈머 그룹마다 각 토픽의 파티션에서 어느 오프셋까지 처리 완료했는지 정보를 기록합니다. 오프셋 커밋은 처리 완료 여부를 메시지마다 기록하는 것이 아니라 처리를 완료한 메시지 중에서 최대의 오프셋을 기록하는 형태로 이루어집니다.
이것은 카프카가 임의로 메시지를 처리하는 것이 아니라 파티션 안의 메시지를 연속적으로 처리하는 것을 가정하고 있기 때문입니다.
- 중간에 메시지가 처리되지 않을 경우 무한 루프가 돌 수 있음 (수기로 메시지 커밋 하는 경우)
- __consumer_offsets 라는 전용 토픽에 기록
- Auto Offset Commit (자동 커밋)
- 자동 오프셋 커밋은 일정 간격마다 자동으로 오프셋 커밋을 하는 방식
- 설정한 주기에 따라 poll() 을 호출할 때 자동으로 마지막 오프셋을 커밋한다.
- enable.auto.commit=true 설정
- auto.commit.interval.ms=5000 설정을 통해 오프셋 커밋의 간격 설정. 기본 5초
- 장점 : 컨슈머는 오프셋 커밋을 명시적으로 실시할 필요가 없음
- 단점 : 컨슈머 장애가 발생했을 때 메시지가 손실되거나 메시지 중복이 발생할 수 있음 (리밸런싱으로 인한 문제가 생길 수 있다)
- 자동으로 커밋을 하는 방식은 오프셋 관리를 자동으로 하기 때문에 편리한 장점이 있는 반면, 리밸런싱으로 인한 문제가 생길 수 있다는 점을 염두해야 한다.자동 커밋의 주기를 줄인다고 해도, 중복이 발생하는 메시지의 개수를 줄일 뿐 중복 자체를 없앨 수는 없다. 그러면 이 문제를 해결하기 위해 고려해야 하는 것은 어떤 것이 있을까? 카프카는 이와 같이 메시지의 중복 수신이 발생할 수 있기 때문에 메시지 처리에 있어 멱등성을 생각해야 한다. 중복으로 메시지를 처리하더라도 결과에 변동이 되지 않도록 어플리케이션의 설계를 잘 해야 한다.
- 예를 들어, 5초마다 자동으로 커밋을 하게 어플리케이션을 설정했다고 하자. 매 5초마다 커밋이 발생할텐데 커밋이 발생하고 3초 뒤에 리밸런싱이 이루어진다면 어떻게 될까? 3초동안 처리한 메시지들에 대해서는 커밋이 이루어지지 않아 리밸런싱이 수행된 후 다른 컨슈머에 의해 다시 메시지를 읽고 중복으로 처리하게 될 수 있다.
- Manual Offset Commit (수동 커밋)
- 메시지 처리가 완벽하게 이루어지지 않으면 메시지를 읽지 않은 것으로 간주해야 할 경우 사용
- enable.auto.commit=false 설정
- commitSync() 메서드를 통해 오프셋을 수동으로 커밋할 수 있음
- 애플리케이션 안에서 언제라도 오프셋 커밋을 수행할 수 있음
- 카프카 클러스터에서 메시지 취득 후 메시지 처리가 완료한 시점에서 커밋
- 장점
- 해당 메시지 처리는 반드시 완료되어 있기 때문에 메시지 손실이 발생하지 않음
- 컨슈머 장애 발생 시 메시지 중복을 최소화 할 수 있음
- 단점
- 메시지 양에 따라 다르지만, 수동 오프셋 커밋이 자주 커밋 처리를 하므로 카프카 클러스터 부하가 높아진다는 점에는 주의가 필요
- 예를 들어, 컨슈머가 메시지를 가져와 DB 에 저장하는 상황이라 가정하자. 만약 자동 커밋을 사용한다면, DB 에 저장하는 로직이 실패할 경우에도 메시지의 오프셋은 커밋되었기 때문에 메시지의 손실이 발생할 수 있다. 이러한 경우를 방지하기 위해서, DB 에 저장되는 로직이 성공한 것을 확인한 후 메시지의 오프셋을 커밋해 메시지가 손실되지 않고 처리될 수 있도록 해야 한다.결국 어떤 커밋 방식을 사용한다 하더라도 메시지가 중복되는 것은 피할 수 없는 문제이다. 이는 카프카에서 기본적으로 Exactly-Once 방식이 아니라 At-Least-Once 방식을 채용하고 있기 때문인데, 중복은 발생할 수 있지만 손실은 발생하지 않는다는 원칙이다.
- 카프카에서도 Exactly-Once 를 사용할 수 있는 방식은 있지만 기본적으로는, 메시지의 중복을 처리하는 것은 결국 카프카를 사용하는 개발자의 몫이고 이 문제의 해결을 위해 많은 고민을 필요로 한다.
- 일반적으로는 위와 같이 메시지 처리의 결과에 따라 오프셋 커밋을 결정할 때 사용하는 것이 수동 커밋인데, 이 또한 자동 커밋 방식과 마찬가지로 메시지의 중복이 발생할 수 있다. 위와 같은 DB 저장 오류 시, 해당 메시지부터 다시 읽어와 처리를 수행하게 될텐데 해당 메시지 이후의 메시지들은 정상적으로 DB 에 저장되었다고 하자. 그러면 결국 오류가 발생한 메시지 외의 다른 메시지들은 중복으로 DB 에 저장될 것이다.
ZooKeeper
카프카의 브로커에 있어 분산 처리를 위한 관리 도구로 Apache ZooKeeper가 필요합니다. 주키퍼는 하둡 등 병렬 분산 처리용 OSS에 있어서 설정 관리, 이름 관리, 동기화를 위한 잠금 관리를 하는 구조로 자주 사용됩니다. 카프카에 있었어서는 분산 메시징의 메타 데이터(토픽과 파티션 등)를 관리하기 위한 구성 요소로 기능합니다. 주키퍼 클러스터는 주키퍼 앙상블이라고도 하며 구조상 3,5 처럼 홀수로 구성하는 것이 일반적입니다.
카프카 클러스터
카프카는 여러 대의 브로커 서버, 주키퍼 서버로 이루어진 클러스터링의 메시지 중계 기능과 메시지 송수신을 위한 라이브러리 그룹으로 구성됩니다.
Replication / 레플리카/리플리카 - 복제
Kafka 는 Fault Tolerant 를 위해 Replication 을 지원한다. Replication 이 무엇인지 간단하게 말하자면, 메시지를 복제해 관리하고 이를 통해 특정 브로커에 장애가 발생해도 다른 브로커가 해당 브로커의 역할을 대신할 수 있도록 하는 것이다.
카프카는 메시지를 중계함과 동시에 서버가 고장 났을 때 수신한 메시지를 잃지 않기 위해 복제(Replication) 구조를 갖추고 있습니다. 레플리카 중 하나는 Leader이며, 나머지는 Follower라고 불립니다. Follower는 그 이름대로 Leader로부터 메시지를 계속적으로 취득하여 복제를 유지하도록 동작합니다. 다만 프로듀서/컨슈머와의 데이터 교환은 Leader가 맡고 있습니다.
Leader Replica의 복제 상태를 유지하고 있는 레플리카는 In-Sync Replica(ISR)로 분류됩니다. 또한 복제 수와는 독립적으로 최소 ISR 수(min.insync.replicas) 설정이 가능합니다. 고장 등으로 인한 일시적인 동기 지연을 허용하여 전체 읽고 쓰기를 계속하는 것이 가능합니다.
복제 사용 시 오프셋 관리에는 LEO(Log End Offset) 이외에 High Watermark라는 개념이 있습니다. Hight Watermark는 복제가 완료된 오프셋이며, 그 성질에서 반드시 LEO와 동일하거나 오래된 오프셋을 나타냅니다. 컨슈머는 High Watermark까지 기록된 메시지를 취득할 수 있습니다.
- 리플리카: 파티션의 복제본
- 복제수(replication factor) 만큼 파티션의 복제본이 각 브로커에 생김
- 리더와 팔로워 구성
- 프로듀서와 컨슈머는 리더를 통해서만 메시지 처리
- 팔로워는 리더로부터 복제
- 장애 대응
- 리더가 속한 브로커 장애시 다른 팔로워가 리더가 됨
- 프로듀서와 컨슈머는 새로운 리더를 통해서 메시지를 다시 처리할 수 있게 됨
Ack
복제에 대한 중요한 구성 요소로 프로듀서의 메시지 송신 시 Ack 설정에 대해 알아보겠습니다. 브로커에서 프로듀서로 메시지가 송신된 것을 나타내는 Ack를 어느 타이밍에 송신할 것인지를 제어하는 것은 성능과 내장애성(브로커 서버 고장 시 데이터 분실 방지)에 큰 영향을 줍니다.
acks 는 간단하게 말해 확인의 의미이다. 프로듀서가 메시지를 보내고, 브로커에 잘 전달되었는 지 확인하기 위한 옵션으로 [1, 0, -1(all)] 값으로 설정할 수 있다.
- acks = 0 프로듀서가 메시지를 보내고, 브로커에게 메시지 전달이 정상적으로 되었는 지를 확인하지 않는다. 메시지를 보내기만 하기 때문에, 성능 면에서는 세 가지 옵션 중 가장 월등하지만 메시지 전달이 정상적으로 되고 있는 지를 보장하지 않는다. 이 옵션은 메시지 유실보다 메시지 처리량이 더 중요하게 생각될 때 적용할 수 있다.
- acks = 1 프로듀서가 메시지를 보내고, 앞선 포스팅에서 설명한 리더에게 메시지가 제대로 전달되었는 지를 확인한다. 리더는 메시지를 받아 로그 파일에 이를 추가하고, 다른 팔로워들이 메시지를 복제하는 것을 기다리지 않고 잘 전달되었다고 프로듀서에게 알린다. 이 옵션은 리더가 메시지를 받은 것을 보장하지만, 복제가 되었는 지를 확인하지 않기 때문에 리더가 메시지를 받고 바로 장애가 발생할 경우 메시지가 유실될 수 있다. 세 옵션 중, 지연 시간 / 처리량 / 안정성 면에서 가장 중도의 길을 걷는 옵션이다.
- acks = all (-1) 프로듀서가 메시지를 보내고, 리더와 ISR 팔로워들이 메시지를 모두 전달받았는 지를 확인한다. 리더는 모든 ISR 들이 메시지를 복제했음을 확인한 뒤 프로듀서에게 메시지의 전달이 잘 이루어졌다고 알린다. 이 옵션을 적용하면, 메시지의 전달에 있어 상대적으로 매우 긴 시간이 필요하지만, 다른 옵션들에 비해 월등한 안정성을 제공한다.
Ack 설정 설명
0 | 프로듀서는 메시지 송신 시 Ack를 기다리지 않고 다음 메시지를 송신함 |
1 | Leader Replica에 메시지가 전달되면 Ack를 반환 |
All (-1) | 모든 ISR의 수만큼 복제되면 Ack를 반환 |
프로듀서는 타임아웃 설정으로 Ack가 돌아오지 않고 타임아웃된 Send 처리를 ‘송신 실패’로 감지합니다. 참고로 Ack를 1 또는 all로 설정했을 경우 변환 타이밍이 의미하는 것은 각 복제에 ‘메시지가 전달’된 것으로 판단해 Ack를 반환하는 타이밍입니다. 이 타이밍에는 메시지가 디스크에 flush되는 것이 아니라 메모리(OS 버퍼)에 기록됩니다. 디스크에 flush하는 영속화 타이밍은 다른 속성에서 제어합니다.
시스템에 따라 어떤 옵션을 사용할 지는 차이가 있다. 안정성이 중요한 경우, acks=all 옵션을 사용할 것이고 처리량이 중요한 경우, acks=0 옵션을 사용할 수 있다. 둘 모두 적당히 필요할 경우 acks=1 을 사용할 수 있다. 옵션 중 어떤 것이 더 좋고 나쁨의 개념이 아니기 때문에 옵션에 따른 동작을 잘 이해하고 사용하는 것이 중요할 것 같다.
Replication Factor
Replication Factor 는 토픽의 파티션의 복제본을 몇 개를 생성할 지에 대한 설정이다. 카프카에서는 Replication Factor 를 임의로 지정해 토픽을 생성할 수 있는데, Factor 를 2 로 설정하면 복제본을 한 개 생성하고 3 으로 설정하면 두 개의 복제본을 생성한다는 의미이다. 이해를 위해 다음의 그림을 통해 설명을 덧붙이겠다.
위의 그림은 topic01 과 topic02 모두 replication factor 를 1로 설정한 경우이다. (여기서 Partition 은 고려하지 않겠다.) 이에 대해 topic01 는 replication.factor 를 2로, topic02 는 3으로 설정한 경우를 다음 그림에서 볼 수 있다.
이와 같이 Replication Factor 를 조정해 replication 의 수를 몇 개로 설정할 지 관리자가 조정할 수 있다. replication 수가 많을수록 브로커 장애 시 토픽에 저장된 데이터의 안전성이 보장되기 때문에 중요 데이터의 경우는 replication factor 를 크게 설정하는 것이 좋다. 물론 replication 이 많아지면 그만큼 데이터의 복제로 인한 성능 하락이 있을 수 있기 때문에 무조건 크게 설정하는 것이 권장되지는 않는다.
Leader & Follower
Rabbit MQ 의 경우 복제본이 2개 존재하는 경우 하나를 Master, 다른 하나를 Mirrored 라 표현하는데 이러한 용어는 어플리케이션마다 상이하다. Kafka 에서는 Leader / Follower 라는 용어를 사용하는데, 위 그림에서 Replication 을 표현했던 것에 Leader, Follower 를 연결해 보자.
topic01 은 두 개의 복제본을 가지고 있어 하나는 Leader / 다른 하나는 Follower 로 구성되고, topic02 는 하나의 Leader / 두 개의 Follower 로 구성된다. 카프카는 Leader 에게 특별한 기능을 부여했는데, 그 기능이란 다음과 같다.
Topic 으로 통하는 모든 데이터의 Read/Write 는 오직 Leader 를 통해서 이루어진다.
위와 같이 기능을 하기 위해, 리더는 항상 정상적으로 동작해야 한다. 하지만 어떻게 그렇게 할 수 있을까? 리더도 똑같은 브로커 중 하나일 뿐인데 장애가 발생하지 않으리란 보장이 있을까?
답을 간단하게 얘기하자면, 리더와 팔로워는 변경될 수 있다. 카프카는 리더가 장애가 발생하면 기존의 팔로워 중 하나가 리더가 될 수 있는 Failover 방식을 채용하고 있다. 자세한 것은 아래의 ISR 에서 이어서 설명하겠다.
ISR (In-Sync Replication)
ISR 이라는 용어는 다소 생소하지만 간단하게 얘기하면 Replication Group 이라 표현할 수 있다. 위에서 토픽 별로 Replication Factor 를 설정해 Replication 을 생성했는데, 각각의 토픽으로 묶인 Replication 들을 ISR 이라 칭한다.
위 그림에서 보는 것과 같이 하나의 ISR 에는 하나의 Leader 와 n 개의 Follower 가 존재한다. ISR 의 규칙은 다음과 같다.
ISR 내의 모든 Follower 들은 Leader 가 될 수 있다.
Broker Down Situation
위의 규칙으로 인해 리더가 장애가 발생할 경우 ISR 내의 팔로워 중 하나가 리더가 되는 것이다. 이 부분에 대해서는 추가적으로 설명이 필요하다. 우선은 장애가 발생했을 때 어떤 일들이 벌어지는 지 그림과 같이 이해해보자.
만약 위와 같이 broker1 이 down 되었다고 하자. topic01 의 리더와 topic02 의 팔로워가 다운되었기 때문에 다음과 같은 변화가 생긴다.
topic01 의 경우, ISR 내에 하나의 팔로워가 존재하고, 위에서 설명한 누구든 ISR 내의 follower 는 leader 가 될 수 있다는 조건을 충족하기에 기존의 팔로워가 새로운 리더가 된다. 이때, 일시적으로 리더가 존재하지 않기 때문에 Read/Write 의 Timeout 이 발생할 수 있지만, Retry 가 일어나면 새로운 리더에 Read/Write 할 수 있으므로 장애 상황은 아니다.
topic02 는 팔로워 하나가 다운되었고, 다운된 팔로워가 ISR 에서 제외된다. topic01 과 다르게 리더는 변하지 않았기 때문에 아무런 특이사항 없이 read/write 가 계속 이루어진다.
추가적으로 브로커 2까지 다운되었다고 가정해보자. topic01 의 경우 ISR 내의 더 이상의 팔로워가 없기 때문에 리더를 넘겨줄 수 없고, 리더가 없기 때문에 read/write 를 지속할 수 없다. 즉, topic01 의 경우는 read/write 가 불가능한 장애 상황이 된 것이다. topic02 의 경우는 ISR 내에 남은 팔로워가 있기 때문에 해당 팔로워가 리더를 이어받아 read/write 를 지속한다.
위의 예제를 통해, 브로커에 장애가 발생한 경우 리더가 어떻게 변경되고, ISR 이 변경되는지 설명했다. 추가적으로 이와 같은 동작을 위해 카프카가 내부적으로 어떤 동작들을 하는 지 알아보자.
먼저 우리가 토픽을 만들고 옵션으로 Replication Factor 를 설정하면, 설정에 맞게 위의 예제와 같이 ISR 이 구성된다. ISR 이 구성되면 리더와 팔로워는 각자 역할이 주어지고 그 역할을 수행하기 시작하는데, 그 역할은 다음과 같다.
Leader : 팔로워를 감시하고 팔로워들 중 자신보다 일정 기간 뒤쳐진 팔로워가 발생하면 해당 팔로워가 리더가 될 자격이 없다고 판단하고, 뒤쳐진 팔로워를 ISR 에서 제외한다.Follower : 리더와 동일한 내용을 유지하기 위해 일정 주기마다 리더로부터 데이터를 가져온다.
위에서 설명한 역할을 그림을 통해 살펴보자. 우선 리더의 입장에서, 리더는 ISR 내의 팔로워들을 감시하고, 뒤쳐진 팔로워를 ISR 에서 제외한다. 추가적으로, 브로커가 다운되는 경우에 팔로워는 리더로부터 데이터를 가져오지 못하고, 이러한 상황이 일정 시간동안 지속되면 리더는 해당 팔로워가 뒤쳐졌기 때문에 팔로워에게 리더를 넘길 수 없다 판단해 해당 팔로워를 ISR 에서 제외시키는 것이다.
반대로, 팔로워 입장에서 보면 ISR 내의 팔로워는 언제든 리더가 될 수 있어야 하기 때문에 리더와 동일한 데이터를 유지하는 것이 매우 중요하다. 따라서 팔로워는 리더를 계속 바라보며 컨슈머들이 카프카에서 데이터를 가져가는 것과 동일한 방법으로 주기적으로 데이터를 풀링한다. 매우 짧은 주기마다 새로운 데이터를 체크하며 리더와 동기화를 하게 된다.
결국 동일한 ISR 내에서 리더와 팔로워는 데이터 정합성을 위해 각자의 방식으로 서로를 계속 체크하며 Fault-Tolerance 한 메시지 시스템을 제공하는 것이다.
What is ISR for?
ISR 이 대체 왜 필요한 지를 살펴보자. ISR 은 카프카에서 Latency 와 Safety 간 Tradeoff 로서 작용한다. 프로듀서 입장에서 봤을 때, 메시지 손실이 발생하는 것이 시스템에 끔찍한 문제를 가져온다면 acks=all 옵션을 적용해 모든 ISR 들이 메시지를 복제한 것을 확인할 것이다. 근데 이 때, 하나의 복제본이 사라지거나 지연된다면 전체 파티션이 느려지거나 사용할 수 없는 문제로 이어질 수 있다. 결국 ISR 이 존재하는 목적은, Replica 들이 느려지거나 죽은 경우에도 Fault Tolerant 한 시스템을 유지하는 것이다.
ISR 은 이전에 설명했듯, 내부의 팔로워들이 지속적으로 리더의 메시지를 풀링해 동기화를 유지해야 하고 빠른 성능을 제공할 수 있어야 한다. 빠르게 응답이 불가능하다면 해당 팔로워는 ISR 에서 제외될 것이다. 근데 만약, 모든 팔로워들이 느려져서 ISR 내에 리더만 존재하는 경우가 생긴다면 어떻게 될까?
프로듀서는 메시지의 유실을 막기 위해 모든 ISR 이 메시지를 저장한 것을 확인하는데, ISR 내에 리더만 존재하기 때문에 acks=1 옵션과 동일하게 동작할 것이다. 결국 acks=all 옵션을 적용했음에도 메시지 유실에 대한 가능성이 충분히 존재하게 된다.
이를 막기 위한 방안으로, min.insync.replicas 라는 옵션이 존재한다. 이 옵션값이 2로 설정되면, ISR 내에 하나의 replica 만 존재할 경우 전달받는 메시지를 거절한다. 이것이 메시지 유실을 막기 위한 safety 를 위해 존재하는 ISR 의 존재 의의라 볼 수 있다.
Minimum In-Sync Replica
min.insyc.replicas 는 위에서 설명했듯 프로듀서가 정상적으로 메시지를 전달하기 위해 ISR 내의 replica 가 최소 몇 개 존재해야 하는 지를 의미한다. 이 값은 크게 설정할 경우, 그만큼 데이터 안정성이 보장된다고 할 수 있지만 반면에 가용성을 하락시킬 수 있다. 예를 들어 노드가 3개 존재할 때 min.insync.replicas 를 3으로 설정하면, 하나의 노드만 장애가 발생해도 어떤 메시지도 받을 수 없게 된다.
이 옵션은 메시지 처리량에 직접적으로 영향을 주지 않는다. 직접적으로 영향을 주는 옵션은 위에서 본 프로듀서의 acks 옵션이고, 이 옵션은 데이터의 유실을 막기 위한 옵션으로 생각하면 된다.
In-Sync Replica 설정
min.insync.replicas 설정은 서버 고장 시 ‘메시지를 잃지 않는 것’과 ‘메시징 시스템을 포함한 전체 시스템의 처리를 계속하는 것’사이의 균형을 조정합니다.
아래 예시 중 어느 것이 뛰어나다는 것이 아니라 시스템 요구 사항과 제약 조건에 의해 결정돼야 한다는 점에 주의해야 합니다.
In-Sync Replica와 Ack = all, 쓰기 계속성의 관계
브로커는 4대 레플리카 수는 3으로 브로커 1대가 고장나 레플리카를 하나 잃어버린 경우
설정 상황
설정 | 상황 |
min.insync.replicas=3 Ack=all인 경우 | 브로커 서버가 1대 고장난 경우 프로듀서는 비정상 상태로 간주하여잃어버린 레플리카가 ISR로 복귀할 때까지 데이터를 쓸 수 없음 |
min.insync.replicas=2 Ack=all인 경우 | - 브로커 서버가 1대 고장난 경우에도 Ack를 반환하고 처리하고 처리를 계속함 - 처리를 계속하는 점에 있어서는 위보다 나은 반면 - 나중에 추가된 파티션이 복제를 완료해 ISR로 승격될 떄까지 복제수가 2가 됨 - 복구 전에 2대가 고장난 경우는 처리 중인 메시지를 손실할 위험이 높아짐 |
min.insync.replicas는 프로듀서와 메시지를 보낼 때 송신처 파티션의 복제본 중 ISR에 속하는 복제본이 최소 몇 개나 필요한지를 설정하는 브로커와 토픽의 구성입니다. 브로커는 메시지를 수신한 직후에 특정 브로커에 장애가 발생했다고 해도 메시지를 분실하지 않기 위한 안정장치 역할을 합니다. 장애 허용 대수만큼의 브로커에 장애가 발생했을 때 카프카 클러스터에 영향 없이 서비스를 계속하기 위해서는 모든 파티션에서 ISR에 속하는 복제본의 수가 min.insync.replicas 수 이상이어야 합니다.
참고 :
https://www.tpointtech.com/apache-kafka-architecture
Apache Kafka - 높은 처리량과 실시간으로 대량의 데이터를 취급하는 카프카 | 아웃풋 트레이닝
[Apache kafka 조금 아는 척하기] 카프카란?
[Kafka] Kafka 의 Topic 과 Partition
ChatGPT (다른 자료와 크로스 체크를 항상 해야겠지만, 앞으로 학습에 ChatGPT를 적극 사용할 예정이다)
'개발 > back-end' 카테고리의 다른 글
JPA, Hibernate, Spring Data JPA 차이점 정리 (0) | 2025.03.03 |
---|---|
R2DBC (0) | 2024.06.17 |
[네트워크]스토리지(Storage) (0) | 2023.05.24 |