아파치 카프카에 대해 매우 간략하게 알아보기
카프카는 왜 주목받기 시작했는가?
데이터를 송신(Source)하는 어플리케이션과 수신(Target)하는 어플리케이션이 많아지면서 이를 위한 데이터 라인이 점점 복잡해지고 이를 통해 효과적인 데이터 처리 시스템이 필요해지기 시작했다.
@그림 1 : 어플리케이션의 다양화로 인하여 복잡해진 데이터 라인
위 그림과 같이 데이터 라인이 많아지면 아래와 같은 문제점이 발생할 수 있다.
- 배포 단계가 복잡하고 어려워진다.
- 장애가 발생하면 원인 지점을 파악하는 것이 어려워진다.
- 어플리케이션마다 프로토콜이 다양해지면서, 처리 작업이 많이 추가된다.
@그림 2 : 아파치 카프카는 다양한 앱을 하나의 플랫폼에서 간편하게 구독, 처리할 수 있도록 하는 플랫폼이다 - 출처 : 데브원영
카프카는 링크드인(LinkedIn)에서 만든 오픈소스로, 링크드인에 적용된 카프카에 대해 자세히 알고싶다면 이곳(Kafka Ecosystem at LinkedIn)을 참조하면 된다.
@그림 3 : 아파치 카프카를 통해 데이터를 송수신하는 구조
결국 카프카의 가장 큰 목적은 ‘송신 어플리케이션과 수신 어플리케이션 간 커플링을 최대한 약하게’하는 것인데, 송신 어플리케이션은 카프카에 데이터를 전송하기만 하면되고, 수신 어플리케이션이 카프카로부터 데이터를 가져오는 형태가 된다. 예를 들어 쇼핑몰이 있다고 가정하면, 송신 어플리케이션에서는 상품을 클릭하거나 구매할 때 로그를 발생하고 타겟 어플리케이션에서 이러한 로그를 적재하고 처리할 수 있도록 만든 후 카프카를 이용해 효과적으로 데이터 라인을 구성할 수 있게 된다. 여기서 송신 어플리케이션의 경우 json, tsv, avro 등 데이터 포멧에 대한 제한 없이 대부분을 처리할 수 있도록 지원한다.
결국 카프카를 이용함으로써 얻는 장점은 아래와 같다.
- 고가용성(Fault Tolerant)을 지원하여, 갑작스런 서버 중단 또는 장애에도 데이터를 손실없이 복구할 수 있다.
- 낮은 지연(latency), 높은 처리량(throughput)을 통한 수많은 데이터의 빠른 처리가 가능하다.
AMQP(Advanced Message Queue Protocol)와 비교되기도 하는데, 자세한 내용은 포스팅하지 않고 하단에 참고자료로 첨부한다.
토픽(Topic)
카프카 내부에 존재하는 모든 데이터는 토픽이라는 큐 형태의 공간에 들어가게 된다. 카프카의 프로듀서가 데이터를 카프카에 송신하면 카프카의 컨슈머가 카프카로부터 원하는 데이터 형태에 맞는 토픽을 가져오는 방식이다.
@그림 4 : 카프카에서 데이터 처리에 사용되는 큐, 토픽
위의 그림과 같이 토픽은 카프카 내에서 여러개 생성할 수 있다. 토픽은 데이터베이스의 테이블 또는 파일시스템 폴더와 유사하다고 볼 수 있으며, 각 토픽은 ‘click_product_log’, ‘pay_product_log’등과 같은 이름을 가질 수 있다. 그렇기 때문에 해당 토픽이 하는 역할을 명확하게 표현해주는 이름을 붙여주면, 추후에 유지보수하는데 큰 도움이 된다.
@그림 5 : 토픽의 파티션
토픽에는 파티션이라는 추가 단위가 존재한다. 프로듀서가 카프카로 데이터를 보내면, 해당 데이터는 설정된 토픽 내에 있는 특정 파티션으로 들어가게 된다. 앞에서 설명한 것 처럼 토픽은 큐 형태로 존재하며, 데이터는 큐 안에 순서대로 쌓이게 된다. 컨슈머가 원하는 데이터를 포함하는 토픽과 파티션으로부터 데이터를 순서대로 가져오며, 데이터를 다 읽고 나면 새로운 데이터가 들어올 때 까지 대기하게 된다.
여기서 중요한 것은 데이터를 가져온다고 해서 파티션 내부에 있는 데이터가 삭제되지는 않는다는 것이다. 왜 카프카는 데이터를 바로 지우지 않는 것일까? 이유는 새로운 컨슈머가 토픽의 이전 데이터를 호출할 수도 있기 때문이다. 단 무조건 가능한 것은 아니며, 컨슈머의 그룹이 달라야 하며 특정 설정값에 대한 요건(auto.offset.reset = earliest)이 만족되어야 한다. 이러한 특징을 통해 얻을 수 있는 장점이 무엇일까? 먼저 위에서 설명한 것 처럼 서버에 갑작스런 이슈가 발생하였을 경우, 장애가 발생한 시점부터의 데이터를 복구하여 고가용성을 보장받을 수 있다. 또한 같은 데이터를 여러개의 서로 다른 플랫폼(예를 들어 한 쪽에서는 엘라스틱서치에서 처리를, 또 다른곳에서는 하둡에 데이터를 저장하고자 할 때)에 적재 할 수도 있다.
파티션 내의 데이터는 옵션에 따라 삭제되는 시점을 정할 수 있다.
- log.retention.ms : 최대 record(데이터) 보존 시간
- log.retention.byte : 최대 record(데이터) 보존 크기
위의 그림처럼 파티션이 2개 이상인 경우, 데이터는 어느 파티션으로 들어가게될까? 데이터마다 키가 존재하고, 이를 기반으로 다양한 옵션을 설정할 수 있다.
- 키가 null, 기본 파티션 사용 : 라운드 로빈(round robin) 기준으로 파티션에 할당
- 키가 존재, 기본 파티션 사용 : 키의 해시값을 이름으로 갖는 특정 파티션에 할당
그렇다면 파티션은 마음대로 늘일 수 있을까? 늘이는 것은 언제든지 가능하지만 반대로 줄이는 것은 불가능하다. 그렇기 때문에 파티션을 추가할 때에는 꼭 신중하게 진행하는 것이 좋다. 그렇다면 파티션은 언제 늘이는 것이 좋을까? 파티션을 늘이면 이를 구독할 수 있는 컨슈머의 갯수를 늘일 수 있기 때문에, 데이터 처리를 여러 컨슈머로 분산시킬 수 있다. 그렇기 때문에 컨슈머의 양이 증가되어야 하는 시점에 파티션을 동일하게 늘여주는 것이 좋다.
고가용성을 위한 Replication(복제)
카프카에는 브로커(broker)라는 개념이 존재하는데, 브로커는 카프카가 설치되어 있는 서버 단위를 의미하며 일반적으로 하나의 카프카 시스템에는 최소 3개의 브로커를 구성하는 것을 권장한다.
브로커는 크게 메시지 브로커와 이벤트 브로커로 구분할 수 있는데, 카프카에서 사용하는 브로커는 이벤트 브로커이다. 둘 간의 차이는 수집된 데이터의 즉각적인 삭제 여부이다. 메시지 브로커는 데이터를 보내고 처리하고 삭제하지만, 이벤트 브로커는 서비스에서 나온 이벤트를 큐에 저장해서 발생한 이벤트를 보관한다. 이를 통해 장애가 발생하더라도 해당 이벤트를 복구해서 다시 확인할 수 있거나, 많은 양의 실시간 스트림 데이터를 효과적으로 처리할 수 있는 등의 장점을 얻을 수 있다.
각 토픽마다 특정 파티션은 브로커에 하나씩 구성할 수 있으며, 이러한 파티션을 브로커마다 복제하는 것을 Replication이라고 부른다.
- Replication 1개인 토픽 -> n개의 브로커 중 1대에 토픽이 저장됨
- Replication 3개인 토픽 -> n개의 브로커 중 3대에 토픽이 저장됨.
- 단 위에서 언급한 것 처럼, 각 파티션은 브로커 마다 한개씩만 존재해야 하기 때문에 브로커의 수보다 파티션이 많아서는 안된다.
@그림 6 : 고가용성을 위한 ISR(In Sync Replica) 구조
그렇다면 하나의 파티션이 총 3개로 복제되었다면 각 파티션은 뭐라고 부를 수 있을까? 원본은 leader, 복제된 각 파티션은 follower라고 부른다. 즉 3개의 파티션중 원본 1개는 leader, 복제로부터 만들어진 2개는 follower가 된다. 그리고 이 모든 파티션을 합쳐서 ISR(In Sync Replica)라고 부른다(위 그림에서 노란색으로 칠해진 부분).
실제로 카프카 프로듀서가 데이터를 보내면, 이를 leader 파티션이 받아서 follower 파티션에게 전달하게 된다. 이 과정에서 ack라는 옵션을 사용하여 데이터 전달 과정에서 성능 또는 안정성 중 하나에 대한 중요도를 더욱 높일 수 있다.
ack | 특징 | 속도 | 데이터 유실 가능성 |
---|---|---|---|
0 | - leader에게 정상적으로 데이터가 전송되었는지 응답값 못받음 - follower에게 데이터가 제대로 복제되었는지 응답값 못받음 |
속도가 매우 빠름 | 데이터 유실 가능성 제일 높음 |
1 | - leader에게 정상적으로 데이터가 전송되었는지 응답값 받음 - follower에게 데이터가 제대로 복제되었는지 응답값 못받음 |
일반적인 속도 | Leader 파티션이 데이터를 받은 이후 브로커에 장애가 발생할 경우, 나머지 follower에 데이터가 전송되지 못해 데이터 유실가능성이 존재 |
all | - leader에게 정상적으로 데이터가 전송되었는지 응답값 받음 - follower에게 데이터가 제대로 복제되었는지 응답값 받음 |
가장 느린 속도 | 데이터의 정상적인 복제를 모두 확인하여 유실 가능성이 거의 없음 |
그렇다면 replication 갯수가 많으면 무조건 좋은것일까? 복제해야 하는 서버가 늘어난다는 것은 리소스의 증가 또한 비례한다는 것을 의미한다. 그렇기 때문에 카프카에 들어오는 데이터양과 저장시간(retention time)을 기준으로 총 복제 수를 정하는 것이 좋다. 일반적으로 3개 이상의 브로커를 사용할 때 replication을 3으로 두는 것을 권장한다.
프로듀서의 Partitioner란?
@그림 7 : 프로듀서에서 생성한 데이터의 파티션 위치를 결정하는 파티셔너
프로듀서의 파티셔너(Partitioner)는 레코드에 포함되어 있는 메시지 키 또는 메시지 값에 따라 해당 데이터를 토픽 내 어떠한 파티션으로 할당할지를 결정한다. 프로듀서 사용시점에 파티션을 특정하지 않았을 경우, 기본적으로 UniformStickyPartitioner으로 설정이 된다. 이 때 메시지 키의 설정 여부에 따라 동작 방식이 달라지게 된다.
메시지 키 여부 |
동작 방식 | 특징 |
---|---|---|
Y | - 파티셔너는 메시지 키를 이용하여 해시값 생성 - 해시값 기준으로 파티션 결정 |
순차적인 데이터 처리 가능 |
N | - 라운드 로빈형태로 파티션 결정 - 프로듀서가 배치 형태로 레코드를 최대한 많이 묶음화한 후, 이를 파티션으로 보냄 |
파티션마다 적절하게 레코드가 분배됨 |
결국 동일한 메시지 키는 동일한 해시값을 만들기 때문에, 항상 동일한 파티션에 데이터가 그룹화되어 할당되고 이를 통해 데이터를 할당된 순서대로 처리할 수 있게 된다. 예를 들어 날씨에 대한 로그를 분석하는 컨슈머의 경우 프로듀서가 메시지 키에 ‘서울’이라는 값을 넣게 되면, 해당 데이터가 동일한 파티션에만 순서대로 적재되기 때문에 컨슈머는 원활하게 데이터를 순서대로 처리할 수 있게 된다.
추가로 카프카에서는 사용자가 커스텀 파티셔너를 만들 수 있도록 Partitioner 인터페이스를 제공한다. 이를 이용하면 메시지 키, 메시지 값, 토픽 이름에 따라 파티션을 결정할 수 있는데, 예시로 VIP 고객의 데이터를 더 빠르게 처리하고 싶다면, 이러한 커스텀 파티셔너를 이용하여 처리할 컨슈머의 양을 고정적으로 증가시키는 방법을 적용할 수 있다.
컨슈머 랙(Lag)은 무엇이고 어떻게 확인할 수 있을까?
컨슈머 랙은 프로듀서가 파티션에 데이터를 넣어주는 속도가 컨슈머가 파티션으로부터 데이터를 가져가는 속도보다 빠른 경우에 발생한다. 일반적으로 컨슈머에서 성능 저하가 생기거나 비정상적인 동작이 생기는 경우 발생할 수 있는데, 각 파티션 별로 프로듀서가 넣은 데이터의 오프셋과 컨슈머가 가져가는 데이터의 오프셋 사이의 차이라고 이해하면 된다. 파티션 기준이기 때문에, 토픽 내에 존재하는 파티션의 수에 따라 컨슈머 랙 또한 여러 개 존재할 수 있다. 예를 들어 2개의 파티션과 1개의 컨슈머 그룹이 존재하는 경우, 발생 가능한 컨슈머 랙은 최대 2개가 된다. 이처럼 여러 개의 컨슈머 랙이 존재하는 경우, 이들 중 가장 높은 숫자의 랙은 records-lag-max로 부른다.
@그림 8 : 프로듀서와 컨슈머 사이의 데이터 처리량 차이에서 발생하는 컨슈머 랙
그렇다면 컨슈머 랙은 어떻게 모니터링할 수 있을까? 컨슈머 랙이 컨슈머를 통해 발생하기 때문에, 자연스럽게 컨슈머 단위로 이를 모니터링해야겠다는 생각이 들것이다. 하지만 이러한 방법은 컨슈머 상태에 매우 의존적이기 때문에, 굉장히 위험한 접근방법이다.
- 만약 컨슈머에 장애가 발생해서 더이상 동작하지 않게되면, 컨슈머에서는 발생하는 랙 정보를 더이상 보낼 수 없기 때문에 정보의 수집이 불가능해진다.
- 컨슈머가 추가로 개발될때마다 해당 컨슈머에서 컨슈머 랙을 수집하는 로직이 추가로 개발되어야 한다.
이러한 이유로 링크드인에서는 컨슈머 랙을 효과적으로 모니터링할 수 있는 별도의 어플리케이션인 Burrow를 제공한다. Burrow는 3가지 큰 특징을 가지고 있다.
- 여러개의 카프카 클러스터가 존재하더라도, 단일 Burrow 어플리케이션으로 모든 클러스터에서 발생하는 컨슈머 랙의 모니터링이 가능하다.
- Sliding window를 통해 컨슈머의 상태를 ERROR, WARNING, OK와 같이 간단하게 확인할 수 있다.
- WARNING: 데이터양이 일시적으로 증가하여 컨슈머 오프셋이 증가되는 경우
- ERROR: 데이터양이 계속해서 증가하지만 컨슈머에서 데이터를 더이상 수집하지 않는 경우
- HTTP API를 제공하여 멀티플랫폼에서 Burrow 적용이 가능하다.