Skip to main content Link Menu Expand (external link) Document Search Copy Copied

created at 2023-05-01

MSA 버전과 이전 모노 버전에서의 변경된 점성능 문제점해결 과정을 설명하려 합니다.

일단 먼저 변경점을 아래와 같이 정리해보았어요.

1. 변경된 점

변경점기존변경
Tomcat -> Netty -> UndertowTomcat 을 사용하면서 max connection = 10000 을 통해 동시성 증대 및 timeout 설정Netty 장점 포스팅 Non-blocking 에 최적화됨. 하지만 Netty 의 설정들을 Spring boot 에서 변경하기가 까다로움. yaml 파일로 configuration 할 수 있는 메소드들이 별로 없기때문. 그래서 Netty based Undertow 를 사용.
서비스 별 RDB 분할RDS 1대를 유저 서비스/채팅 서비스/고객 서비스가 사용함AWS RDS 1대를 유저 서비스가 사용하고, 채팅과 고객서비스는 각각 local RDB 를 사용함
이벤트 트랜젝션 캐싱 관리이벤트 트랜젝션 개념이 없었음이벤트를 하나의 트랜젝션으로 일관되도록 관리하는 테이블을 추가함. update:O, event-sourcing:X. Saga의 Orchestration 패턴! 해당 부분을 RDB로 처리하기엔 상당한 리소스가 소모됨. 따라서 Redis 로 관리.
메세지 큐 상세 설정MQ 사용을 했지만, 응답과 롤백을 받지 않았음Kafka 설정 및 [유저 관리 요청, 유저 관리 응답, 채팅유저 보상 이벤트, 고객유저 보상 이벤트] 총 4가지 토픽과 파티션 추가
Spring-cloud configuration 추가각각의 configuration 파일을 따로 관리하였음config-server 와 rabbitmq 를 통해 git에서 설정 파일을 클론하고 이를 모든 서버가 사용할 수 있도록 함. 또한 모두 모듈화하여 통합 gradle 관리 설정
Spring-cloud eureka, gateway 추가nginx 로 모든 서비스들의 endpoint 를 직접 엮어주었음eureka 서버에 등록하고 gateway 는 이를 자체적으로 load-balancing 하도록 설정하였음
Spring-security, JWT 인증/인가 추가인증과 인가를 세션으로 관리함세션 관리 + JWT 토큰의 payload 에 저장된 permission 정보, 유저ID 를 통해 인증과 인가를 구현함. 특히 Reactor 설정을 따로 하였음

2. 성능비교 및 문제점

img

생각보다 상당히 낮은 성능 수치를 보여줍니다. 현재 생각하는 문제점은 유저 서비스에서 Kafka까지 메세지가 전송되는 비율이 상당히 낮다는 점입니다. 아래의 그림을 보면 이해가 될거에요(아래의 그림은 동시에 100개의 API 요청을 보냈을 때 Kafka 토픽 내 새로운 record 개수입니다).

img img

보시다시피 유저 서비스에서 Kafka 에 메세지를 전송하는 rate가 초당 평균적으로 10개 인것을 확인할 수 있습니다. 고객 서비스나 채팅 서비스에서 이를 소비하는 부분에서는 문제가 없죠. 애초에 적은 수의 이벤트를 수신받았으니까요!

그래서 이 부분을 몇 가지 체크리스트를 가지고 개선하려고 합니다(적다보니 많네요ㅎㅎ…).

3. 성능 이슈 해결 과정

문제점 가설설명문제였나?
Undertow 의 적은 parellel thread이 부분에서는 문제가 되지 않는다고 판단합니다. 이유는 8개의 parellel 스레드가 실제로 vCPU 8 코어에 각각 배정되었기에, 빠르고 Context Switch 가 작으며 효율적으로 동작하기 때문입니다.
Spring Security 의 토큰 확인 절차에서 발생할 수 있는 딜레이 문제이 부분은 필연적으로 발생하는 부분이고 Header 체크만 하면 되는 기능이라 매우 적은시간이 소모됩니다. 따라서 이 부분은 문제가 되지 않는다고 판단하였습니다.
이벤트 트랜젝션을 관리하는 Redis 저장 성능 문제실측 결과, 인상적이였습니다. 동시에 100개의 요청 시, 10ms 소요. 따라서 현재로써는 성능에 크게 영향을 주지 않는다는 것을 확인하였습니다.
CPU/Memory 부족18개 컨테이너의 CPU 점유 목록 img img 도커가 많은 CPU 를 소모하고 있습니다. 그 중 특히 유저 서비스 컨테이너에 CPU usage : 821% 로 많은 과부하가 걸리고 있는 것을 확인할 수 있어요. 이는 직접적인 개선점이 아닌 간접적인 개선점이겠죠? 또한, 실제 테스트가 끝난 이후에도 계속 CPU를 잡아먹고 있습니다… 왜 이런걸까요. 뭔가 큰일이 났습니다. Kafka 메세지에서 발생한 오류처리를 계속 수행하기 때문일까요? offset으로 보면 문제가 없는데 말이죠.
(Problem)
요청이 쌓임에 따라 CPU 자원이 부족함
(Solution)
작성중
(Result)
작성중
적은 Kafka Producer 스레드 개수적은 Kafka Producer 스레드 개수로 인한 병목현상이 발견되었습니다!
img 기존에 저는 싱글 스레드로 Kafka에 메세지를 전송하고 있었습니다. 이 과정에서 저는 병목현상 가능성을 발견했고 직접 실측한 결과, 요청이 쌓임에 따라 전송딜레이가 증가함을 확인하였습니다! 즉, 병목이 발생했다라는 것이죠. 여기서 우리가 필요한 부분은 충분한 CPU 리소스라고 판단되어 Partition 별 전송 스레드를 설정하였습니다. 결과로 위의 그래프에서 확인가능하듯, 요청에 대한 평균 응답 딜레이가 약 8초->4초로 감소되었습니다.
참고자료
When not using Transactions, by default, the DefaultKafkaProducerFactory creates a singleton producer used by all clients, as recommended in the KafkaProducer javadocs. However, if you call flush() on the template, this can cause delays(이게 문제였다는 것을 우리가 확인했죠?) for other threads using the same producer. Starting with version 2.3, the DefaultKafkaProducerFactory has a new property producerPerThread. When set to true, the factory will create (and cache) a separate producer for each thread, to avoid this issue.

(Problem)
요청이 쌓임에 따라 전송딜레이가 증가함
(Solution)
Partition 별 전송 스레드를 설정
(Result)
요청에 대한 평균 응답 딜레이가 약 8.6초 -> 4.1초로 감소
linger.ms 와 batch_size 문제linger.ms 는 이벤트 전송 대기 시간, batch_size는 이벤트를 묶어서 보내는 크기 입니다. 기존에는 이벤트 전송 전까지 50ms 기다리고 최대한 50개를 묶어서 보내도록 설정했어요. 하지만, 보다시피 평균적으로 10개만 보내는 것을 이미 우리가 확인했죠. 즉, 이벤트를 전송하는 것에 대한 네트워크 로드는 낮다, 때문에 문제가 되지 않는다. 라도 판단됩니다.