우연한 기회에 카프카 스터디를 하게 되었습니다. 업무에서 kinesis를 사용하고 있기는 하지만 항상 kafka에 대한 갈증이 있었고 이번 기회를 통해 kafka에 대해 구조적으로 파악하고 왜 kafka가 실시간 메세징 시스템을 지배하게 되었는지 파악할 수 있게 되기를 소망합니다.
고승범님의 실전 카프카 개발부터 운영까지
라는 책이 선정되어서 3월 8일에 읽기 시작했는데, 보통은 건질 것?이 없다고 생각하는 1장 개요 부분도 느낀 점이 꽤 많았습니다. 이 책의 1장 내용과 따로 찾아본 내용을 기록합니다. 개인적인 생각 및 해설이 강하게 묻어있기 때문에 신문 사설같은 느낌으로 읽어주시면 감사하겠습니다 :)
잘란도의 카프카
처음은 유럽의 패션 플랫폼 잘란도의 카프카 도입배경에 대해 설명합니다. 잘란도의 사례를 통해 일반적인 회사에서 카프카를 도입하는 배경을 의식의 흐름처럼 이해할 수 있습니다.
책에서는 이렇게 설명합니다.
데이터의 변화가 스트림으로 컨슈머 측에 전달되는 이벤트 드리븐 시스템으로의 전환을 결정했습니다. 이벤트 드리븐 시스템 도입을 통해 데이터를 소비하는 컨슈머들은 자신의 요구사항에 따라 데이터를 처리하거나 구독할 수 있게 됐습니다.
일단, 이벤트 드리븐이라는 말 자체는 IT 업계에서 어제오늘 나오는 말은 아닙니다. 이벤트 드리븐 뿐만 아니라 데이터 드리븐, 도메인 드리븐 등등 Driven이라는 단어가 가지는 주도한다는 뉘앙스가 사람들을 매료시키는 것이 아닌가 생각합니다.
이벤트 드리븐은 말 그대로 시스템에서 발생하는 특정 행위들을 기반으로 의사결정을 한다는 뜻입니다. 그러려면 당연히 원하는 곳에서 발생하는 이벤트들을 빠르게 캐치해야 합니다.
쇼핑몰을 생각해봅시다. 쇼핑몰에서 발생하는 이벤트로는 사용자가 일으키는 주문완료, 접속, 찜, 장바구니 등을 생각해볼 수 있겠습니다. 접속은 해당사항이 없겠지만 주문 여부, 찜 등 대부분의 이벤트들은 그 결과를 쇼핑몰 RDB에 저장하는 것으로 이벤트 기록을 대신합니다. 만약 a라는 유저가 장바구니에 b라는 상품을 넣었는지 아닌지를 확인하려면 장바구니 DB에 아래와 같은 query를 실행시킬 것입니다.
1 |
|
사용자의 상태를 확인하는 방법으로 틀린 것은 아니지만, 이벤트를 즉시 캐치한다는 관점에선 어긋납니다. 이벤트가 발생하고 그 결과가 DB에 저장되고 나서야 우리는 이런 이벤트가 발생했다는 것을 알 수 있기 때문입니다.
기존 RDB 시스템에 익숙하다면 위와 같은 생각에서 자연스럽게 REST API + DB CRUD 방식의 이벤트 캐치 방법을 고려해볼 수 있습니다. API가 DB의 상태를 변경하고(CRUD), DB가 업데이트되면 필요한 곳으로 outbound 이벤트를 발생시키는 구조입니다.
그런데 기존 방식과는 비교도 되지 않은 정도로 많은 이벤트가 발생하다보니 문제가 발생합니다. 특정 유저가 특정 상품을 클릭했다, 혹은 노출되었다와 같은 이벤트는 1명의 사용자가 얼마나 발생시킬까요? 이런 것들은 RDB 시스템에서는 기록하지 않던 데이터입니다. 이런 데이터들을 REST API + CRUD 방식으로 처리한다면 한계가 드러나기 시작합니다.
-
해당 데이터를 여러 명이 사용하고자 한다면? n명에게 outbound 이벤트가 지연없이 전달될 수 있는가?
-
데이터 사용자들의 요구가 전부 다르다면? 이벤트를 전송하는 outbound 측에서 사용자마다 전부 다르게 전송을 구현해야 하는가?
-
정확한 순서와 유실없는 outbound 이벤트를 보장할 수 있는가?
이벤트 캐치에서 가장 중요한 것은 고객이 발생시켜 input으로 들어오는 이벤트와 output으로 나와 개발자가 확인하는 이벤트 간의 정합성
입니다. 그러나 못지 않게 위의 한계점들도 개선되어야 합니다. 그렇지 않으면 대량으로 발생하는 데이터들을 사용하기가 어렵습니다.
이러한 기존의 한계들을 보완해 나온 데이터 스트리밍 플랫폼이 카프카입니다. 잘란도 또한 카프카를 채택, 사용했습니다. 카프카는 어떤 장점이 있고 그래서 어떻게 한계점들을 보완했는지 살펴봅니다.
카프카의 장점
적어도 한번 전송
한 마디로 표현하면 유실보다는 중복이 낫다
입니다. 데이터를 생산하는 producer와 데이터를 사용하려고 하는 consumer 사이에는 여러 네트워크 환경이 걸쳐져 있습니다. 이런 환경에서 어디서 유실이 났으며 정확히 여기에 유실이 난 데이터를 다시 재전송하는 시스템을 구축하는 것은 너무 비효율적입니다.
네트워크의 4 hand shake를 생각해보면 좀 더 이해가 빠를 것 같습니다. 서버는 클라이언트에 종료 플래그 FIN을 전송하고, 클라이언트는 연결 종료 확인의 ACK 플래그를 서버에 전송함으로써 클라이언트와 서버 간의 세션이 종료됩니다. 그런데 서버가 클라이언트에 전송하는 FIN 플래그보다 늦게 도착하는 데이터가 있을 수 있기 때문에, 클라이언트는 time wait을 두어 늦게 도착하는 패킷의 유실을 방지합니다.
그러나 우리의 카프카는 이걸 기다릴 시간이 없습니다. 그렇기 때문에 카프카는 적어도 한번 전송
방식을 채택했습니다. 복잡한 트랜잭션 시스템을 구축하느니, 중복은 각자의 백엔드 시스템에서 알아서 걸러내더라도 유실에 대한 걱정을 없애겠다는 것입니다.
멱등성(idempotent)과도 관련이 깊은데, 이 내용은 좀 더 나중에 다뤄보도록 하겠습니다.
백프래셔 핸들링
데이터 스트림에서 전송한다(push)는 개념이 아닌 컨슈머가 알아서 데이터를 가져간다(pull) 방식을 사용합니다. pull 방식을 사용함으로써 스트림의 데이터 전송속도에 대한 부담이 줄어들 뿐만 아니라 각자의 요구사항들을 각자 알아서 구현할 수 있다는 피드백 측면의 장점도 가져갈 수 있습니다. 속도와 편의성을 중시한 방식입니다.
파티셔닝
토픽을 여러개로 나눠서 데이터를 처리할 수 있습니다. 각 토픽은 독립적이기 때문에 수평적으로 데이터를 가져갈 수 있고 확장 또한 용이합니다.
비동기 방식
producer와 consumer 간에 비동기 방식을 채택했습니다. 앞서 설명했던 REST API + DB CRUD 방식의 한계를 정면으로 들이받은 개선점이라고 볼 수 있겠습니다. producer가 데이터를 언제 생산해서 언제 스트림에 넘겨주든 consumer는 계속 데이터는 pulling해갈 뿐입니다. 둘의 이벤트 시점과 순서가 일치하지 않고 맞출 필요도 없습니다. 비동기 방식을 사용함으로써 애플리케이션 ~ 최종 데이터 사용자(consumer) 사이의 병목 / 지연 현상이 줄어들었습니다.
강력한 커뮤니티
카프카는 오픈 소스 프로젝트로 이미 유명하기 때문에 사용법, 장애 대응, 아키텍처에 대한 내용들을 찾기가 쉽습니다. 또한 많은 회사에서 kafka를 쓰고 있기 때문에 구인구직에도 좋은 가점요소가 될 수 있습니다.
트위터의 카프카
트위터도 카프카를 사용하고 있는 플랫폼입니다. 트위터의 사용자 규모를 생각했을 때 엄청난 양의 데이터를 처리할 것이라는 걸 어렵지 않게 추측할 수 있습니다. 그러나 트위터는 카프카로 유턴한 케이스입니다.
카프카라고 처음부터 다 좋았겠습니까. 트위터는 문제가 많던 초기 버전을 유지하지 않고 이벤트 버스 시스템을 구축해 사용했습니다.
이 방식도 DB를 끼는 방식보다는 훨씬 빠릅니다. 이벤트 버스 시스템도 producer와 consumer 간의 결합이 강하지는 않기 때문에 확장이나 분산 처리에도 용이한 메시징 아키텍처입니다.
그러나 event bus 방식이 갖는 단점은 명확합니다.
- event bus는 모든 consumer에게 데이터를 전송하다.
- 유실에 대한 대처가 부족하다.
책에서는 kafka와 event bus의 차이점으로 fsync()를 처리하는 방법의 차이를 언급합니다. event bus는 fsync를 실행할 때 블로킹을 한다고 합니다. OS 단계에서 다른 process에 대한 blocking을 말하는 것 같습니다.
그에 비해 kafka는 제로 카피를 사용한다고 합니다. 제로 카피에 대해서도 찾아봤는데, 제로 카피의 개념은 아래 그림의 sendfile() API로 간단하게 정리할 수 있을 것 같습니다.
제로 카피를 사용하지 않으면 데이터를 읽고 쓰는 디스크, 즉 하드웨어가 추가로 계속 필요할 수 밖에 없습니다. kafka는 속도 면에서도, 하드웨어 면에서도 이점을 갖게 된 것입니다.
그런데 여기서 이해가 잘 가지 않는 것이 있습니다. fsync API는 하드 디스크에 file의 변경사항을 저장하는 API call입니다. 그런데 스트림 방식으로 데이터를 전송하는 kafka에서 fsync와 접점이 어디인지 잘 모르겠습니다. kafka의 디스크 어딘가에 메시지들을 파일로 저장하는 것이 아닌가 추측합니다.
카프카의 주요 특징
장점이라고도 볼 수 있겠지만, 특징이라는 제목답게 특장점에 더 가까운 내용들이 언급됩니다. 다는 아니고 몇가지만 간단하게 소개하겠습니다.
높은 처리량과 지연시간
책에서는 전통적인 메시징 큐 시스템의 강자 RabbitMQ / Pulsar(사실 뭔지 모르겠습니다) / kafka의 성능차이를 비교하고 있습니다.
처리량(throughput)에서는 kafka가 압도적인 성능을 보이고, 지연 시간(Latency)는 RabbitMQ가 가장 짧습니다. 2가지 비교사항을 함께 비교하면 kafka의 성능이 단연 압도적입니다.
높은 확장성
kafka는 설계 단계부터 높은 확장성을 염두에 두고 설계되었습니다.
고가용성
고가용성, 즉 오랫동안 이상없이 사용이 가능하다는 의미입니다. 개발해놓은 프로그램이 당연히 처리량이나 외부 환경에 변화가 없으면 잘 작동해야 하는것 아니야? 라고 생각할 수 있지만, 고가용성을 유지하는 것은 생각보다 쉽지 않습니다. 장애는 오만가지 포인트에서 발생할 수 있기 때문입니다.
replication을 두어 장애에 대응하는 방법이 대표적이며, 이 또한 후에 자세히 다룰 것입니다.
내구성
메세지 생산자(producer)에 의해 kafka로 전송되는 모든 메세지는 kafka의 로컬 디스크에 저장됩니다. sqs와 같은 전통적인 메세징 큐 방식에서는 consumer가 메세지를 가져가면 큐에서 메세지는 바로 삭제되지만 kafka는 일정 기간 메시지를 보관하기 때문에 나중에 복원이 용이합니다. 이 또한 n개의 replication에 분산 저장되기 때문에 고가용성과도 연결됩니다.
카프카의 성장
2011년 오픈소스로 출시된 이후로 kafka는 지속적인 버전 업그레이드, 그에 따른 새로운 서비스들도 출시되었습니다.
replication (v 0.8)
카프카 클러스터에 장애가 발생해도 replica로 떠있는 다른 클러스터로 데이터를 유실 없이 받을 수 있는 가능이 추가되었습니다.
schema registry (v 0.8.2)
kafka는 여러 비정형 데이터들을 다룹니다. 그러다보니 producer와 consumer 간에 데이터 형태를 맞추기 위해 중간에 파싱이 들어가야 하는 문제가 발생했습니다.
이런 문제를 해결하고자 데이터 스키마를 미리 정의해 여기에 맞는 데이터만 주고받을 수 있도록 하는 schema registry 기능이 추가되었습니다. 이로 인해 RDBMS의 schema on write와 유사한 형태로 데이터 정합성을 맞출 수 있게 되었습니다.
kafka connect (v 0.9)
kafka로 데이터를 전송하는 주체와 데이터를 저장하는 데이터베이스의 종류는 아주 다양합니다. RDB, MongoDB, S3, HDFS 등 많은 DB나 프로토콜에 대응하는 시스템을 하나하나 유지보수하는 일은 쉬운 일이 아닙니다.
이에 별도 코드 작성 없이 다양한 DB 및 프로토콜과 kafka를 연동할 수 있는 kafka connect가 개발되었습니다.
정리
kafka는 위에 언급한 특장점 뿐만 아니라 kafka streams, ksql 지원 등 다양한 기능들을 기반으로
- 메시지 pub/sub 시스템
- 배치 처리
- 실시간 데이터 처리 및 분석
등 여러 데이터 환경에 대응하고 통합하는 역할을 할 수 있는 이벤트 기반 데이터 플랫폼입니다.