Kafka? RabbitMQ? 어떤 것을 쓸까?
프로젝트를 시작할 때 MSA 아키텍쳐로 프로젝트를 진행해보자고 의사결정이 되었다.
MSA 아키텍쳐는 서비스 간 의존성을 낮춘 만큼 서비스 간 이벤트 기반 메세지 전송 방식이 존재할 수밖에 없었다.
기술 스택을 확립해 가는 과정에서 우리는 RabbitMQ와 Kafka 중 하나를 선택해서 개발을 진행하자고 결정이 되었고 장, 단점을 하나씩 정리해보았다.
RabbitMQ
- 프로듀서와 컨슈머 간 메세지 전달을 보장.
- 안정성 있는 전달을 토대로 관리적인 측면이나 다른 기능을 구현하는 것이 가능하다.
- 상대적으로 가볍다.
- 컨슈머에게로 메세지 전달이 되는 순간 (메세지 큐에서 메세지가 빠지는 순간) 삭제된다.
- 컨슈머가 성공적으로 메세지를 전달받았지만 예기치 못한 문제로 인해 로직 처리 시에 이슈가 발생한다면 메세지의 유실이 있을 수 있다.
- 재처리 과정에서 문제가 발생할 수 있다고 판단
Kafka
- RabbitMQ에서 발생할 수 있는 메세지 유실이 동일하게 발생할 수 있지만, RabbitMQ와 달리 컨슈머가 메세지를 처리할 때 바로 삭제하는 것이 아닌 이벤트 스트리머에 유지하는 것이 가능하다.
- 로직을 처리하고 메세지를 처리했다는 응답과 함께 Offset을 커밋함으로 써 예기치 못한 이슈를 예방할 수 있다고 판단
- 메세지를 파일 시스템에 저장함으로 영속성을 보장한다.
- 상대적으로 무겁다.
- 토픽에 대한 처리를 분산 시킴으로 써 성능적인 측면에서 강점을 갖는다.
우리는 MSA 아키텍처에서는 분산된 서비스다 보니 데이터의 처리가 가장 중요시되어야 한다고 생각했고, 이는 예기치 못한 이슈가 발생하더라도 RabbitMQ에 비해 Kafka를 이용해 보다 안정적으로 메세지 처리할 수 있을 것이라 예상했다.
그 결과 Kafka를 도입하기로 선택했다.
계획대로 되고 있어, OK 계획대로 되고 있어!
각자의 로컬에서 서비스 API 개발을 진행하면서 비동기적으로 처리할 수 있는 부분에 Kafka를 도입했다.
기존 Kafka 도입을 결정하면서 강점으로 생각한 부분과 실제 메세지 처리하는 부분을 성공적으로 구현함으로 써 문제없이 잘 처리되었고 우리가 최초에 계획한 대로 잘 흘러가고 있음을 체감하고 있었다.
특히, Kafka와 RabbitMQ를 알아보면서 “소규모 프로젝트에서는 권장되지 않고, 상대적으로 무거울 수도 있다” 라는 평에 비해 잘 처리되고 있고, 속도도 괜찮은 것을 보며 “지레 겁먹은 거였구나!”하고 있었다. 다가올 미래도 모른 채…
결국 로컬에서 개발할 때 별다른 성능적인 이슈나 데이터의 전송 과정에서도 문제없이 잘 구현되었다고 판단하여
오라클 클라우드에 Kafka를 도커로 구축하고, 개발한 서비스를 클라우드 서버에 배포를 진행했다.
살짝 클라우드가 느려지고 렉이 걸린다는 느낌을 받았지만, 프로젝트 빌드도 잘되고 배포도 잘됨을 확인했다.
문제는 이후에 발생했다.
이거 API 호출 속도가 비정상적인데? 클라우드도 느린데?
성공적으로 배포 후 빌드까지 하고 배포한 서비스들이 Kafka와 잘 연동된 것도 확인했고, 배포 직후에는 큰 문제없이 API 호출도 되고 있었다.
그러다가 최근에 클라우드가 느려지는 문제가 발생하고 있었고, 배포한 서비스 API를 호출하는데 응답 시간이 말도 안 되는 수준으로 느려졌다.
“ㅎㅎ” → “응..?” → “흠…” → “X 됐다.. 왜지… 왜 이렇게 느려??”
기존 로컬에서 테스트할 때도 모두 1초 내외로 전송되고 처리되는 로직들이 클라우드에 배포하고 Kafka를 연동했다는 이유로 갑자기 속도가 어마어마하게 느려졌다.
“무슨 간단한 닉네임 검증 로직이 53초나 걸려..?” 무엇인가 잘못되었다.
다른 서비스를 배포하고 Kafka를 연동하기 전까진 약간의 딜레이가 있더라도 로컬과 비슷하게 전송되고 있었다. 근데 이후에 이렇게 느려졌다? 이건 정황상 Kafka에서 문제가 발생한 것이라고 의심했다.
free -m
명령어를 이용해 메모리 사용률을 확인해보니 이미 스왑 한 메모리도 상당 부분 사용되고 있었고, 메모리 스왑을 한다고 해서 스왑 된 메모리만큼의 성능을 내지 못한다는 것도 기존에 확인해서 알고 있었다.
이때 갑자기 우리가 로컬에다 Kafka를 설치하고 이를 이용해 개발했다는 것이 떠올랐다.
우리가 제일 크게 간과했던 사실은 클라우드의 컴퓨터 사양과 로컬에서 개발하는 컴퓨터 사양이 당연히 어마 무시한 차이가 난다는 것을 생각하지 못했다는 점이다..
오라클 클라우드의 프리티어는 1GB 메모리를 지원하고 있었고, Kafka의 권장사양과 최소사양을 검색해 본 결과 Kafka의 최소 사양은 못해도 2GB의 메모리는 가져야 어느 정도 사용할 수 있다고 사람들이 의견을 남겼다.
프로젝트를 진행하면서 사용할 Jenkins, DB, 배포된 서비스들을 한 번에 클라우드에서 운영한다 해도 1GB 메모리가 현저히 부족함을 느끼고 있었는데 그보다 더한 Kafka를 사양도 안 되는 클라우드에 도입했으니 느려질 수밖에…
임시적인 해결책으로 사람들이 메모리 스왑을 통해 해결할 수 있다고 했지만, 이미 Jenkins를 사용하는 시점에서 느리다고 판단하여 메모리 스왑을 한 우리로써는 더 이상의 스왑을 할 수는 없었다.
CPU 사용률 또한 확인해보니 배포한 서비스 프로세스가 거의 100%에 육박하게 잡아먹고 있었다.
이 문제도 정황상 Kafka가 문제일 것이라고 판단했다.
이를 해결하기 위해서는 “해당 프로세스의 스레드 덤프를 뜨고, 스레드를 찾아 어떤 스레드가 점유율을 가지고 있고 어느 부분에서 문제를 발생시키는지 확인할 수 있다.”는 것을 알게 되어 스레드 덤프를 뜨게 되었다.
스레드 덤프 ?
스레드 덤프란 프로세스에 속한 모든 프로세스의 상태를 스냅샷처럼 기록한 것으로 각 스레드들을 추적하는 것이 가능하다.
블로킹과 같은 상태를 보여주며 이외에도 RUNNABLE, WAITING, TIME_WAITING과 같이 스레드의 상태를 확인할 수 있다. 이를 통해 JVM의 성능 최적화나 문제를 해결할 수 있다고 한다.
스레드 덤프 뜨기
위에서 99%의 점유율을 가지고 있는 프로세스가 문제이기 때문에 해당 프로세스의 스레드 덤프를 뜨기로 했다. 현재 오라클 클라우드에는 Java 11이 설치되어 있었기 때문에 jstack을 이용해 스레드 덤프를 떴다.
$ top # 프로세스 점유율 확인 PID확인
$ jstack 3135268
이렇게 명령어를 실행하면 아래와 같이 “Full Thread Dump OpeJDK … “이러면서 엄청난 양의 스레드 덤프 파일이 열린다.
여기서 어떤 스레드가 문제인지 확인하기 위해서 아래의 명령어를 실행하면 CPU 점유율과 LWP(Light Weight Process)를 알려준다.
$ ps -mo pcpu,lwp -p 3135268
여기서 나온 LWP의 고유번호를 16진수로 변환하면 위의 목록에 있는 스레드와 매핑되는 것이 있을 것이다.
이제 이 고유번호를 이용해 스레드를 찾고 해당 스레드가 어떤 문제를 발생하는지 확인하면 된다.
스레드 덤프를 간단하게 분석해주는 FastThread 사이트를 이용해 방금 뜬 스레드 덤프를 분석했고 그 결과 위에 나온 “3135433 고유번호"를 가진 스레드는 우리의 예상대로 Kafka에서 발생한 스레드였다.
Kafka Listener 부분에서 CPU를 점유하고 있던 것이었는데 찾아보니 요청이 없는 상태에서 Consumer가 지속적으로 Broker에 메세지 Polling 요청을 보내서 발생한 문제일 가능성이 있고, 이는 불충분한 Configuration으로 인해 발생하는 것이라고 유추할 수 있다고 되어있다.
위의 문제 원인 파악(StackOverflow) : https://stackoverflow.com/questions/32895778/kafka-consumer-100-cpu-usage
그렇게 많은 삽질을 통해 “정황상 Kafka에서 문제가 발생한 것 같다”에서 “Kafka에서 문제가 발생한 것이 맞다"라는 확신이 되었다.
서비스에 대한 부분은 환경 설정 변경을 통해 해결할 수도 있었겠지만, 이미 클라우드의 리소스를 많이 소비한 시점에서 Kafka까지 함께 가져가기에는 API 호출의 지연이나 클라우드 시스템 성능 저하가 더욱 발생할 것이라 판단했다.
따라서, Kafka에서 RabbitMQ로 메시지 소싱 부분을 전체 변경하기로 결정했고, 변경한 결과 API 속도와 CPU 점유율 및 클라우드 속도도 정상으로 돌아왔다.
스레드 덤프 분석하기 : https://d2.naver.com/helloworld/10963
목적도 중요하지만 조금 더 꼼꼼하게!
사실 긴 글에서의 결론은 결국 kafka가 클라우드에서 많은 CPU와 메모리를 점유하는 문제가 발생했고, 실제로 CPU 100%를 근접하게 먹는 서비스 프로세스의 스레드 덤프를 떠보니 Kafka Listener 부분에서 지속적으로 문제를 발생시키고 있다는 것이었다.
그리고 이에 대한 해결책으로 환경설정 정보 전파를 위해 미리 구축해놓은 RabbitMQ를 이용해 메세지 소싱까지 진행하도록 변경했다는 점이다.
“사이드 프로젝트 같은 소규모 서비스에서는 대용량의 처리가 필요한 경우가 없기 때문에 최적화된 이벤트 메세지 소싱 방식이 RabbitMQ를 이용하는 것임을 알고는 있었지만, 기술 스택을 선택하는 과정에서 우리가 가장 중요시하게 생각한 부분이 Kafka를 이용해 해결할 수 있다고 판단해 실제로 도입"한 부분에 대해서는 아직까지도 잘못된 선택이라고 생각하지 않는다.
어떤 기술 스택을 사용하던지 “왜?”라는 물음을 던지고 그로 인해 추론된 결과가 목적에 부합한다면 도입하는 것이 맞다고 생각되기 때문이다.
하지만, 이로 인해 나타날 사이드 이펙트나 발생할 수 있는 문제들에 대해서는 주의 깊게 판단하고 다양한 기준을 고려해 결정하는 과정이 필요하다는 것을 이번 기회를 통해 느끼게 되었다.
비록 많은 삽질을 겪었지만 좋은 경험을 한 것 같다.
'회고 > 2022년' 카테고리의 다른 글
자비스 앤 빌런즈 (삼쩜삼) 개발자 공채 챌린지 후기 (0) | 2022.11.04 |
---|---|
2022 상반기 회고 (0) | 2022.08.01 |
2022. 03.17 퇴사를 하다. (0) | 2022.04.28 |