Apache Kafka 도입기

KimYeonghun
IHFB  R&D 팀블로그
12 min readAug 6, 2021

평화롭게 코드를 작성하고 있던 어느 날, 학습관리팀 팀장님이 저를 찾아옵니다.

00 님, 오늘 자 피드가 전송 안 됐어요 ㅠㅠ

최근 며칠간 이런 일이 자주 발생하다 보니 근본적인 원인을 찾을 필요가 있었습니다. 원인을 찾고, 이슈를 해결하는 과정에서 Apache Kafka를 도입하게 되었습니다. 왜 다른 방법이 아니고, 왜 다른 서비스가 아니고 Kafka를 선택했는지, 도입하면서 겪었던 문제점 등 저희가 겪었던 과정들을 공유하고자 합니다.

이슈 원인

밀당영어에서 학생들이 학습하는 피드를 전송하는 과정은 다음과 같습니다.

1.매니저 페이지에서 본 서버로 피드 예약 요청2.본 서버에서 피드 예약을 실행3.예약한 시간이 도래하면 AWS Lambda 함수에서 예약된 피드를 찾고 본 서버로 피드 전송 요청5.피드 전송

이 과정에서 3.예약한 시간이 도래하면 AWS Lambda 함수에서 예약된 피드를 찾고 본 서버로 피드 전송 요청 단계에서 문제가 발생합니다. Lambda는 제한 시간이 정해져 있고, 최대로 설정할 수 있는 제한 시간은 15분입니다.

보통의 경우 이 제한 시간을 넘기지 않아야 정상이겠지만, 비효율적인 레거시 코드 + 이용자 수 급증 두 가지 요인으로 인해 제한 시간을 초과하게 되었고 이에 따른 대책이 필요했습니다.

해결방안

이슈를 해결하기 위해서 큐(queue) 시스템을 도입해서 피드 전송 부분을 큐로 밀어 넣고 순차적으로 처리하도록 하는 방법을 구상하고 있었습니다. Lambda 함수의 비효율적인 코드를 개선하는 등의 다른 방법도 생각해볼 수 있는데, 유저가 급증한다면 코드를 개선한다고 해도 같은 이슈가 발생할 가능성이 있다고 생각했고, 근본적으로 이 이슈를 해결하려면 큐 시스템을 도입해야 한다고 판단했습니다.

이 이슈와는 별개로 개발팀 내부에서 고민하고 있던 주제가 있습니다. 처음에 저희가 제공하던 서비스는 영어 단어 학습을 제공하는 서비스였기 때문에 서비스가 단순한 구조를 띄고 있었습니다. 하지만 최근 몇 년간 회사가 급성장하면서, 단어 학습뿐만 아니라, 문제 풀이, 지문 학습, 강의 시청 등의 학습 액티비티, 결제 시스템, 콘텐츠 입력 등의 다양한 요구사항을 반영하게 되었습니다. 이에 따라 각각의 개발자가 전체 서비스 구조를 이해하기 어려워졌고, 개발 조직을 도메인별로 나누어서 각각의 도메인에 집중할 수 있어야 한다고 판단했습니다.

개발 조직이 세분화되면, 각각 도메인별로 데이터가 파편화될 수 있었습니다. 데이터가 파편화된다는 것은 개발자 개개인이 특정한 기능 개발을 위해서 여러 데이터 시스템에 의존하여 개발을 해야 한다는 의미이고, 이는 개발 생산성 저하를 일으킬 수 있습니다. 따라서 사내의 모든 데이터를 하나로 흐르게 하는 데이터 파이프라인을 구축할 필요가 있었습니다.

정리하자면 큐 시스템 도입 데이터 파이프라인 구축 두 가지 이슈를 해결하기 위한 솔루션을 찾아야 했습니다.

Kafka vs RabbitMQ

후보로 선택된 솔루션은 Kafka와 RabbitMQ 두 가지입니다.

두 가지 솔루션 모두 기본적으로 도착한 메시지를 큐에 저장하고, 순차적으로 메시지를 소비하는 Pub/Sub 패턴을 가지는 메시지 브로커입니다.

하지만 두 솔루션의 가장 큰 차이점은 Kafka는 이벤트 브로커라는 점입니다. RabbitMQ는 메시지를 소비하면 즉시 혹은 짧은 시간내에 메시지가 큐에서 제거됩니다. 하지만 Kafka와 같은 이벤트 브로커는 메시지가 제거 되지 않고 저장됩니다(정확히는 설정한 시간만큼 저장합니다). 이 특징으로 Kafka는 과거의 이벤트(메시지)부터 읽어 다시 Processing하는 것이 가능합니다.

위에서 언급한 바와 같이 저희 밀당영어에서의 발생한 문제는 비동기적으로 RDS에 INSERT가 일어나고 있었고, 이는 SELECT로 읽어온 예약 피드의 Sequence와는 일치하지 않게 Write가 발생합니다.

여기서 Lambda에 장애가 생긴다면 처리하고 있던 예약된 피드 데이터 중 어떤 부분이 처리되었는지 확인하여 정리하는 작업이 매우 오래 걸리는 문제가 있었고, 이러한 문제는 Pub/Sub 패턴의 메시지 브로커를 도입한다고 해도 Consumer의 처리 과정에서 장애가 발생한다면 같은 문제가 일어날 수 있습니다.

하지만 Kafka 처럼 이벤트(메시지)를 저장하고 있다면 장애가 발생한 이전 시점부터 다시 데이터를 처리하면 쉽게 장애를 극복해낼 수 있습니다. 따라서 저희는 보다 쉽게 장애를 극복할 수 있는 Kafka를 선택했습니다.

Kafka란?

Apache Kafka는 여러 대의 분산 서버에서 대량의 데이터를 처리하는 분산 메시징 시스템입니다. 메시지를 받고, 받은 메시지를 다른 시스템이나 장치에 보내기 위해 사용됩니다.

Kafka의 대표적인 기능은 5가지가 있습니다.

  • 데이터 허브 여러 시스템 사이에서 데이터를 상호 교환한다.
  • 로그 수집 BI 도구를 이용한 리포팅과 인공지능 분석을 위해 여러 서버에서 생성된 로그를 수집하고 축적할 곳에 연결한다.
  • 웹 활동 분석 실시간 대시보드와 이상 탐지/부정 검출 등 웹에서의 사용자 활동을 실시간으로 파악한다.
  • 사물인터넷 센서 등 다양한 디바이스에서 보낸 데이터를 수신해서 처리한 후 디바이스에 송신한다.
  • 이벤트 소싱 데이터에 대한 일련의 이벤트를 순차적으로 기록하고 CQRS 방식으로 대량의 이벤트를 유연하게 처리한다.

위에서도 언급한 것처럼, 저희는 5가지 기능 중에 피드 데이터를 순차적으로 기록하고 처리할 수 있도록 이벤트 소싱으로써의 역할, 다양한 데이터를 중앙화하는 데이터 허브로써의 역할을 기대하며 Kafka를 도입했습니다.

출처 — 사사키 도루, 이와사키 마사다케, 사루타 고스케, 쓰즈키 마사요시, 요시다 고요, 『실전 아파치 카프카』, 한빛미디어(2020), p30, p122–123

기존 구조 vs 개선된 구조

개선 전 구조도(현재)
개선 후 구조도(예상)

저희가 해결해야하는 문제는 크게 3가지입니다.

  1. Lambda의 시간제한 피하기
  2. Lambda의 Cron 주기인 5분 이내에 모든 이벤트 처리하기
  3. 장애 발생 시 데이터 누락 최대한 줄이기

위 그림은 기존의 구조도와 Kafka를 도입한 예상 설계도입니다. 설계도대로 진행한다면 기존에 발생했던 문제를 어떻게 해결하고 목표를 이룰 수 있을지 저희가 고민한 점을 하나하나씩 살펴봅시다.

Lambda

  • 기존 — Data Processing 시간 + DB Insert 대기 시간
  • 개선 — 단순 이벤트 발생
  • 기존의 Lambda가 데이터 가공 처리와 DB Insert 대기 시간 등 복합적인 원인으로 시간제한을 넘어 장애가 발생할 가능성이 존재했습니다. 개선된 구조에서 Lambda는 Cron Job으로 단순히 이벤트만 발생시키므로 15분이라는 시간제한은 상대적으로 매우 충분한 시간이 됩니다.

Consumer

  • 기존 — Lambda 장애 발생 → Data 누락
  • 개선 — Consumer 장애 발생 → Kafka에 저장된 이벤트 Reload
  • 기존에는 장애가 발생하면 데이터 누락이 발생했습니다. 하지만 개선된 구조에서는 Consumer가 장애가 발생해도 Kafka는 발생한 이벤트를 저장하고 있으므로 이전 이벤트들을 다시 읽어 처리하면 쉽게 Fail Over가 가능합니다.

Throughput

  • 기존 — Single Thread → Throughput 감소
  • 개선 — Consumer 스케일 아웃 → Throughput 향상
  • 기존 구조에서는 Lambda 인스턴스 1개로 처리하는데 Single Thread로 동작하고 있어 Throughput이 저조했습니다. 하지만 개선된 구조에서는 Consumer를 충분히 스케일 아웃하여 목표 Throughput을 달성하고 있습니다.

Single Thread로는 CPU를 충분히 활용할 수 없고, 서비스가 성장함에 따라 데이터양이 증가할 때 목표 Throughput을 달성하기 위해서는 Consumer를 자유롭게 스케일 아웃할 수 있어야 합니다. 빠르고 효율적으로 Consumer를 관리하기 위해서 저희는 Kubernetes를 사용하여 Pod으로 관리합니다.

Event-Driven한 구조에서 Kafka는 이벤트 브로커이자 허브 역할을 하고 있습니다. 만약 Kafka는 장애가 발생하지 않도록 고신뢰성과 고가용성을 보장해야 합니다. 이러한 특징 때문에 Kafka는 Cluster 구조를 취하고 있으며 각 브로커는 서로 통신 및 백업을 하며 장애에 대응하고 있습니다. 더불어, 저희 밀당영어에서는 MSK(Managed Streaming for Kafka)를 사용하여 더 안정적으로 Kafka를 운용하고 있습니다.

MSK vs EC2

왜 밀당영어에서는 EC2로 직접 구성하지 않았나요?

안정성 쉬운 구축 및 설정 이 두 가지가 가장 현실적인 이유였습니다. 데이터가 하나의 파이프라인으로 흐른다는 건, 그 하나의 파이프라인의 장애가 발생했을 때, 서비스 전체가 영향을 미칠 수 있다는 걸 의미합니다. 따라서 첫째로도 안정성 둘째로도 안정성을 최우선으로 고려하였기에 MSK로 결정하였습니다. Managed Service의 장점인 쉬운 구축 및 운용은 덤이구요. 😎

이외에도 두가지 옵션을 비교해보니 가격적인 부분에서 한 가지 중요한 점이 있습니다. 바로 Kafka Cluster에서 브로커 끼리 데이터를 복사하여 저장하고 있는데 이는 1개 브로커가 장애 발생시 바로 백업 브로커가 트래픽을 담당하게 됩니다. 여기서 복사하는 비용을 놓칠뻔 했는데 위 가격표를 보시면 MSK는 무려 Instance 비용이 2배이상 비쌉니다. 하지만 MSK는 Cluster 내에서만 발생하는 트래픽은 과금하지 않습니다. 반면에 EC2는 트래픽에 대해 요금이 발생한다고 보시면 됩니다.

MSK와 EC2를 비교한 요금표

처음 도입해보는 Kafka에서 직접 구성하여 안정성과 생산성을 보장하기까지 주어진 시간은 상대적으로 부족하고, 생산성이 나오지 않다고 판단했습니다. 이외에도 복합적으로 고려해본 결과 MSK의 가격은 합리적인 수준이었습니다. 또한 Zookeeper의 문제점이 제기되고 있고 추후 Zookeeper가 제거된 Kafka가 안정화가 된다면 추후에 고민해봐도 늦지 않다고 판단했습니다.

참고. Kafka without Zookeeper — https://www.confluent.io/blog/kafka-without-zookeeper-a-sneak-peek/

결론

Lambda 실행 시간

  • 가끔 15분이 넘어가면서 장애 발생 → 1회 실행 시 평균 10초 이내

모든 데이터 처리까지 소요 시간

  • 가끔 15분이 넘어가면서 장애 발생 → 평균 5분 이내

최종적으로 도입 후 변화된 결과입니다. 가장 문제가 되었던 Lambda가 평균 10초 이내로 이제는 시간제한 걱정이 없습니다. 또한 Lambda의 스케줄링이 5분마다 작동하고 있는데, Lambda가 생성한 모든 이벤트를 5분 이내에 모두 소비하고 있습니다. 그리고 지금까지 굉장히 안정적으로 잘 동작하고 있습니다!

여러분! 드디어 예약 피드 발송 문제에서 해방되었습니다!!

마치며

프로젝트를 진행하면서 맨땅에서 Kafka 도입부터 피드 예약 시스템 개편까지 정말 2달 동안 정신없이 달렸습니다. 물론 알아요, 저 문제 하나만을 해결하기 위해서 더 간단하고 생산 효율적인 옵션이 있다는걸… 😂 하지만 이 문제에 Kafka를 도입한 이유는 앞에서도 언급했던 것 처럼, 데이터 중앙화를 위한 데이터 허브로써의 의미가 크다고 할 수 있습니다.

다음 포스팅에는 이 프로젝트를 진행하면서 정말 고생 많이한 부분인 메시지의 중복과 누락 그리고 이 문제에 대해 Kafka를 이용해 어떻게 해결하는지 밀당영어에서 도입한 방법을 소개하겠습니다.

부록 — Conduktor

Kafka를 운용하면서 정말 큰 도움을 받은 툴 하나를 소개합니다. Kafka 운용 시 중요한 부분 중 하나는 Lag이 얼마나 쌓이는지 항상 모니터링하는 것입니다. Lag이란 Kafka에 미처리된 이벤트가 얼마나 남았는지에 대한 메트릭인데 테스트 과정에서 적정한 Consumer 개수와 Partition 개수를 정할 때 Lag을 보면서 설계해야 합니다.

출처 : https://www.conduktor.io/features/

Conduktor는 Kafka Desktop Client입니다. 실시간으로 브로커 개수, 토픽 개수, 컨슈머 개수 등등을 모니터링할 수 있어서, 저는 무엇보다도 앞서 언급한 Lag을 관리하는 데 큰 도움을 받았습니다. 이 툴을 찾기 전에는 Lag 개수 확인을 위해서 콘솔 창에서 명령어를 입력해가면서 Lag을 확인했어야 했는데, 이 툴을 이용하면 바로 확인할 수 있어 너무 편리했습니다(GUI가 편한 걸 보니, 저는 진성 개발자는 아닌 것 같습니다..🥲). 말씀드린 내용 이외에도 카프카 운용하는데 필요한 많은 기능을 제공하고 있으니 카프카를 운용하고 있거나 운용하실 계획이면 써보시는 걸 추천합니다!

백엔드 팀에서 동료를 찾고 있습니다!

밀당 영어가 최근 급성장하여 에듀테크 유니콘이 되기 위해 새로운 시스템을 개발하고 있습니다. 데이터 파이프라인 구축부터 인프라 개선 등 기존 레거시에 존재하는 다양한 문제를 해결, 개선하고 있습니다. 새로운 도전을 즐기시는 분들과 함께 하면 교육의 혁신을 더욱 빠르고 멋지게 이룰 수 있을 것 같습니다!

밀당과 함께할 동료를 찾습니다.

이 포스트는 고준호 님과 공동으로 작성하였습니다.

--

--