본문 바로가기

Spring/Ticketing 프로젝트

공연 조회 API에 캐싱을 적용하고 성능 테스트하기

반응형

개요

  • 이번에는 저번 글에서 병목지점이 Connection Pool의 커넥션 갯수 부족일 것이라 가설을 세웠었는데, 이를 해결하기 위해 API에 캐시를 도입하였습니다.
  • 캐시는 이 글에서 이미 개발을 했었는데요, 총 3개의 CacheManager(Local, Global, Composite)을 빈으로 등록했었는데요, 저는 지금은 Spring 인스턴스가 하나이기 때문에 Local Cache만을 적용하여 테스트 하였습니다.
@Caching(
    cacheable = [
        Cacheable(
            cacheManager = CacheConfig.LOCAL_CACHE_MANAGER_NAME,
            cacheNames = [CacheType.PRODUCT_CACHE_NAME],
            key = "'performance_' + (#cursorInfoDto.cursor ?: 'first_page') + '_' + #cursorInfoDto.limit"
        )
    ]
)
fun search(
    cursorInfoDto: CursorInfoDto
): List<PerformanceSummarySearchResult> {
	// ...
}
  • 테스트 스크립트는 이전과 동일합니다.
현재 보고 계신 글은 공연 조회 API 최적화 시리즈 입니다!

1편 :공연 정보 조회 API 쿼리 분석하고 개선하기
2편 :공연 조회 API 성능 측정 및 개선 사안 찾아보기
3편 :공연 조회 API에 캐싱을 적용하고 성능 테스트하기
4편 :아키텍처 최적화, 로그 방식 변경을 통한 공연 정보 조회 API 최적화 하기

 

 

테스트 결과

  • 저번 글에는 모든 지표를 다 보여드렸지만, 가시성이 너무 떨어질 거같아 이번 글 부터는 이상이 있는 것으로 보이는 지표만 보여드리도록 하겠습니다.

K6 성능 테스트 결과

k6 전체 지표
응답 시간

 

  • 먼저 k6 성능 테스트 결과입니다. 최대 RPS가 842(약 87.11% 개선)로 늘어났을 뿐만 아니라, 응답시간 또한 p95 사용자 기준 970ms 정도로  약 34.01% 정도가 개선되었습니다.
  • 다만 오류(502 Bad Gateway)율이 11%로 상당히 높게 측정됨을 알 수 있습니다.

 

JVM 지표

CPU Usage
Thread Pool

  • 다음으로 CPU 사용률과 Thread Pool의 갯수인데요, Process CPU의 사용률이 줄었지만 System의 CPU 사용량은 여전히 100%를 달성하고 있습니다. 또 VUS에 맞게 Thread Pool의 갯수도 늘려주어야 할 것으로 보입니다.

Spring Cache Info

  • 그 다음으로 캐시 지표인데요, Local Cache가 정상적으로 사용되고 있고, LocalCache의 TTL이 10분이고 테스트 시간이 약 30분이기 때문에 Cache miss도 기대한 2~3회만큼 나타남을 알 수 있었습니다.

 

Spring 서버 지표

CPU Usage(top)
Cpu, Memory Usage

  • 마지막으론 Spring 서버의 CPU, Memory 지표인데요, User Process의 CPU 사용량이 줄어든 만큼, System이 사용하는 CPU의 사용량이 많아진 것 같습니다. 또 NGINX의 CPU 처리량이 굉장히 높았던 것으로 보입니다.

어떤 것을 개선해야 할까?

  • 저번 성능 테스트에선 보이지 않던 NGINX의 502 Bad Gateway 문제가 발생했는데요, NGINX와 Proxy간의 타임 아웃( proxy_read_timeout )은 약 60초로 설정되어 있기 때문에, 1초만에 응답이 왔다는 것은 Spring 서버가 응답을 못했다기 보다는 NGINX의 worker connection이 부족해서 연결을 못한게 아닐까하는 의심이 듭니다.(실제로 로그를 살펴보니 Spring 서버는 모두 2xx 응답을 했습니다)
  • 또 System과 NGINX의 CPU 사용량이 굉장히 높았는데, 이는 이전에도 언급했듯 로그의 작성 방식으로 인해 과한 Disk I/O가 발생한 게 아닐까하는 예상이 듭니다.
  • 마지막으로 Spring 서버의 Thread Pool 갯수를 더 늘려주어야 할 것으로 보입니다. 현재는 VUS가 300이기 때문에 상관 없을 것 같지만, 나중에 VUS를 늘렸을 때 병목지점이 될만한 요소인 것은 분명해 보입니다.
  • 정리하자면 저는 위 문제를 해결하기 위해 아키텍쳐의 구조를 변경할 것인데요, 첫번째로 Spring 서버로 요청을 보낼때 NGINX를 거치지 않고 바로 Spring 서버로 요청을 보내는 것입니다.

  • 물론 스프링 서버가 2개 이상이라면 별도의 로드밸런서가 필요한 것은 맞지만, 지금은 Spring 서버가 한대이기 때문에 더 효율적인 처리가 가능할 것이라고 생각됩니다. 나중에 필요하면 Oracle에서 로드 밸런서를 제공해준다 하니 이것을 써봐도 좋을 것 같네요.
  • 그다음으론 로깅 방식의 변경인데요, 지금 로깅 방식은 다음과 같은 구조로 되어 있습니다.

  • 현재 Spring과 NGINX 서버는 logs 디렉터리에 로그를 남기고, 이를 filebeat가 logstash로 보내는 방식인데요, 이 방식은 과한 Disk IO를 일으키고 /logs 디렉터리와 ElasticSearch 두곳에 데이터가 중복되는 문제가 있습니다.
  • 이를 해결하기 위해 logs 디렉터리에 로그를 쓰지 않고, 중간에 미들웨어를 두어 쓰기 비용을 줄이고자 합니다.

  • 이렇게 중간에 Middleware를 두어 로그를 중복 저장하지 않고 스프링 서버의 부하또한 줄이는 것을 목표로 하였습니다. Middleware는 크게 Redis, RabbitMQ, Kafka를 주로 사용하는 것으로 보이는데요, 저는 세 가지의 기준을 두고 미들웨어를 선정하였습니다.
    1. 로그를 쓰는 인스턴스(Producer)의 부하가 적은가?(비동기 방식으로 쓰기가 가능한가?)
    2. 로그 데이터의 유실이 발생하지 않는가?
    3. 현재 시스템의 규모에 적합한가? 적은 비용으로도 운영이 가능한가?

  • 3가지의 Message Queue를 비교하는 글은 구글링하면 쉽게 정보를 찾을 수 있었는데요, 요약하면 다음과 같습니다.
    1. Redis : 영속성 전략을 지원하긴 하지만 다른 두 솔루션에 비해 로그 유실성이 큼
    2. Kafka: 고성능, 영속성을 보장, replication을 통해 무한 확장이 가능하지만 비용이 비쌈
    3. RabbitMQ: Kafka보다 성능이 떨어지나 비용이 적고 초기 설정이 쉬움, 설정을 통해 메시지의 영속성도 지원

  • 제가 kafka와 Redis를 사용한 적은 없기 때문에 정확한 정보가 아닐수는 있지만, 제 생각에는 RabbitMQ를 사용하는 것이 전반적인 기준을 보았을 때 현재 프로젝트에 가장 적합하다고 판단하였습니다.
  • 따라서 다음 글에는 로그 시스템 개선, 프록시 서버 제거 등의 개선을 통해 다시 성능 테스트를 해보도록 하겠습니다.

 

반응형