backend

성능 테스트와 개선을 위한 시도와 실패들

downfa11 2024. 12. 17. 14:01

병목 현상을 줄이기 위한 비동기 프로그래밍

@Async 어노테이션을 통해 데이터의 조회 과정을 비동기 방식으로 처리하도록 수정했다.


원래는 Netty를 이용한 Spring Webflux와 ReactiveRedis 같은 녀석들을 이용해서 Non-Blocking하게 작업할까도 생각했었지만 기획 단계에서 포기했다.
 
이미 진행중이던 다른 프로젝트에서 Webflux를 이용하기도 하고, 무엇보다 r2dbc에서 복잡한 연관관계의 데이터 모델을 구성하기 어렵기 때문이다.

데이터모델 설계 과정에서 드는 리소스를 감안하기보단, 탄탄한 rdbms를 지원하는 JPA의 이점을 살리는게 더 이득이라 판단했다.


조회 성능을 높히기 위한 Caching 정책

평소 Redis를 굉장히 좋아했음....ㅎ

리팩토링 과정에서 expireTime을 60초로 해놨던 것을 발견했다. 캐싱 테스트를 위해 짧게 설정했다가 성능 테스트할때까지 잊고 살았던 것이다!
허겁지겁 다시 고쳤다. (expireTime 60s → 600s)

추가로 이번 프로젝트에서는 Spring 어노테이션의 캐싱에서 Redis를 지원하는 걸 새로 알게 되어서 사용하게 되었다. 그리고 공부하는 과정에서 @Cacheable의 key를 세분화해서 캐싱 성능을 높힐 수 있다고 알게 되었다.

 

@Cacheable(value="getPosts",key="'getPosts'+ #categoryName +':'+ #lastboardId")

 
게시글이 작성 혹은 수정될때는 최신화되면서 위의 조회 로직들의 캐시 데이터가 전부 소용 없어지므로 캐시를 지워줘야 잘못된 데이터가 적용되는 오류가 뜨지 않는다.

@CacheEvict(value="getPosts", allEntries = true)

 

알고리즘 및 비즈니스 로직 개선

대용량 데이터에 대한 처리 능력을 올리고자, 검색이나 목록 조회, 필터 등에 게시글을 최신순으로 10개씩 끊어서 제공했다.
기존의 무식하게 전체 게시글 정보를 불러오던 방식과의 성능 차이를 비교해보니 다음과 같다.


Apache JMeter를 통해 200명의 사용자가 5 회/sec를 반복해서 요청하는 트래픽 상황에서 진행

그래서 불필요한 로직을 개선하고 중복된 DB 요청을 최대한 줄여서 QueryDSL로 마이그레이션한 뒤, 드디어 다시 성능 테스트를 진행했다.

 

throughput 23.3/sec → 160.4/sec
Average Latency 7568ms → 893ms

이전 코드보다 Throughput은 85.4%, Average Latency는 88.2 % 개선되었다.
이건 왜이렇게 성능 변화가 심하지.... 난 도대체 얼마나 한심한거지 아직도 모르겟다


그리고 어차피 웹소켓을 이용한 채팅 서버를 둬야해서 멀티모듈은 불가피한 상황이라, 접속자 트래픽이 몰릴 경우 Redis와 Spring Scheduling를 이용해서 대기열 서버를 두려고 준비했었다.

모듈간 IPC를 번거롭게 Async하게 Kafka를 쓸만큼의 리소스는 아니라고 판단. Http방식으로 통신하도록 설계했고 서버의 문제는 없지만... dedicated 서버에서 Spring Security 이슈로 잠시 닫은 상태이다.

정말 어처구니없는게... 아주 기본적인 문제 때문에 치명적인 결함이 생긴 셈이다. 평소 기본기에 충실하지 못한 탓에 생긴 일이라 생각하고 다시 초심으로 돌아가겠음


이후 배포 과정에서 AWS LoadBalancer를 이용해 애플리케이션의 부하를 분산하고자 계획 중이다. 이 부분은 비즈니스 로직이 어느 정도 완성된다면 다시 작성하겠다.