brunch

You can make anything
by writing

C.S.Lewis

by 에디의 기술블로그 Feb 25. 2019

Redis & Spring API 성능 테스트

이번 주말에는 Redis & 스프링 기반 Rest API 의 성능 테스트를 진행하였다. 단, 해당 성능 테스트 결과는 객관적인 결과는 아니다. 필자가 마음대로, 하고싶은대로 했다. 혹시, 이 글에서 잘못된 내용, 말도 안되는 내용이 있다면 꼭 피드백을 해주길 바란다. 


서버 하드웨어 사양, 네트워크 환경에 따라서 테스트 결과는 다를 수 있다. 


JMeter 성능 테스트


이번 성능 테스트에서는 Jmeter 를 사용하였다.  

https://jmeter.apache.org/

부하 테스트 조건은 아래와 같다. 


Number of Threads : 쓰레드를 동시에 몇개 생성할지에 대한 설정. 10으로 설정하면, 10명이서 동시에 접속하는 상황이다.

Ramp-Up Period : 주기를 의미한다. 10으로 설정하면 10초에 한번 작동하게 된다. 필자는 1로 설정했기 때문에 큰 의미는 없다. 

Loop Count : 쓰레드의 반복 회수를 의미한다. 100이면 100번 반복한다. 


10명의 사용자의 요청을, 100번 반복으로 총 1000번의 Http Request 호출 테스트를 진행하였다. 



비즈니스 로직


컬러 정보 리스트를 제공하는 API 가 있다고 가정하자.  

API 를 호출하면 컬러(색상)의 이름, RGB 데이터를 Json 형태로 리턴해주는 아주 심플한 API 이다. 컬러(색상) 데이터는 약 100여개 가 저장되어 있고, 모든 컬러(색상) 정보는 각각의 Key 로 Cache 저장소에 저장되어 있다. Rest API 는 캐시 스토리지에 이미 저장 되어있는 컬러(색상) 정보를 사용자에게 빠르게 응답하도록 한다.



Case 1 - Spring Cache


Case1 에서는 스프링 캐시를 사용해볼것이다.  먼저, Lombok 을 사용하기 위해서 IDE 를 설정한다. 필자는 인텔리J를 사용한다. 

Gradle 환경에서 디펜던시를 추가한다. 필자는 스프링 부트 2.1.3.RELEASE 버전을 사용할 것이다.

starter-data-redis 와 starter-web 을 디펜던시 추가한다. 

사실은...Case1 에서는 starter-data-redis 를 사용하지 않아도 된다. 필자가 해당 스타터를 추가한 이유는 스프링 부트 환경에서 starter-data-redis 를 추가하면, Redis 커넥션 연동에 대한 Auto-Configuration 을 해주기 때문에 심플하게 Redis 를 연동할 수 있기 때문이다. Case1에서는 spring-data-common 의 주요 기능을 전혀 사용하지 않을 것이다. 오해하지 않기를 바란다. 


아래와 같이 Properties 설정만 해주면 Redis 연동이 잘 될 것이다.

자, 이제 컬러 정보를 정의하는 클래스를 만들자.

그리고, 스프링 캐시 추상화의 @Cacheable 어노테이션을 사용해서 간단하게 캐시 메서드를 구현한다. Cache Key 설계는,  colors::v1::BLUE 으로 설계하였다. 

해당 코드에서 사용한 Color(색상) 정보를 조회하는, javafx.scene.paint.Color 패키지는 필자도 처음 보는 패키지인데, 이 글에서 중요한 내용은 아니다.  @Cacheable 어노테이션을 붙이고 만든 findByName 메서드는 처음(최초1회) 호출할 경우에만 내부 로직을 수행하고, 그 이후에는 메서드 내부를 실행하지 않고, Redis 캐시 데이터를 바로 리턴할 것이다. 


필자는, 해당 코드에서 초기 테스트 데이터를 애플리케이션이 실행될 때 저장하도록 구현하였다. 

코드가 깔끔하지 않지만, 이해해주길 바란다. 저장된 데이터는 아래와 같이 Redis 콘솔에서 조회해서 확인할 수 있다. 

직렬화가 되어서 저장되어 있는데,  참고로 필자가 이 글에서 Redis 에 넣는 데이터는 저장 방법이 전부 다르다.

colors::v1::name     - @Cacheable 어노테이션을 사용하여 저장하는 직렬화 된 데이터

colors::v2 :: name   - Spring Data Redis 에서 저장하는 Hash 타입 데이터

colors::v3::name     - RedisTemplate 에서 저장하는 Json 직렬화 데이터


필자의 생각으로는, 해당 성능 검토에서 데이터 타입으로 인해서 변수가 크지 않을 것이라 생각했다. 혹시, 필자의 가설이 잘못되었다면 의견을 주길 바란다. 


아무튼, 데이터를 조회하는 서비스 클래스를 만들어보자. 일단, 컬러 정보 이름 리스트를 하드코딩 했다... 

서비스 클래스에서 데이터를 조회하는 findAllByLoop 라는 이름의 메서드를 만들자. 아주 촌스러운 이름이다. 이름에서 알수 있듯이 그냥 For 문 돌린다는 얘기다. 

for 문을 돌리면서, @Cacheable 어노테이션으로 구현한 메서드를 호출한 다음에 List 자료구조에 저장한다. Redis 에 100개의 데이터가 있다면 100번 조회하는 로직이다. (아마 성능은 정말 안좋을 것이다.) 하지만, @Cacheable 를 사용한다면 어쩔수가 없을 것 같다. @Cacheable 는 보통 단일 Key만 지원하도록 구현하는 경우가 대부분일 것이다.

혹시, @Cacheable 를 사용하면서 Multi Key 기반으로 조회할 수 있는 방법을 아는 개발자는 피드백을 해주길 바란다. 

Controller 를 아래와 같이 작성하고, 

애플리케이션을 실행하면...

요런 결과를 볼 수 있다. 이제, 부하테스트를 해보자. 큰 기대는 하지 말자. 성능이 안좋게 나올 것이다. 


JMeter 에서 Thread Group에 Sampler 에서 Http Request 를 추가하자. 

JMeter 에 대한 자세한 설명은 생략한다. 결과는, 아래와 같다. 

Response Time Over TIme : 150ms

Transations per Second : 65 tps

현재 Redis 서버는 Rest API 서버와 동일 네트워크에 있기 때문에 Redis 로의 네트워크 비용은 매우 낮은 상황이다. 실제 IDC 운영 상황에서도 내부 네트워크 대역에서 통신하기 때문에, 네트워크 비용은 실서비스 환경에서도 매우 낮을 것으로 판단된다.

(참고로 이번 테스트를 진행하기 전에 필자가 Redis 서버를 외부 호스팅 서버에 구축하고 테스트를 해봤었는데, 결과는 지금 나온 결과보다 훨씬 안좋게 나왔다.) 


자 이제...  for 문 돌아가는 로직을 좀 개선해보자. Stream API 를 사용해서 변경해봤다. 

수정해서 호출해보니, 체감 결과는 나름(?) 응답하였지만 실제로 결과는........

부푼 마음으로 부하테스트를 돌려봤는데, 예상 밖의 결과가 나왔다. 

Response Time Over TIme : 200ms +++++

Transations per Second : 15 tps ~ 20 tps 

첫 호출은 빠른 느낌이었지만, 동시 요청자가 많아지면서 블록킹 현상이 발생하였고, 성능이 매우 안좋았다. 응답이 200ms 를 훌쩍 넘어가버렸다. 필자의 코드에서 아마 잘못 구현된 내용이 있을 것이다. 이 글을 읽는 개발자분들은 허접한 필자의 코드를 보고 코드 리뷰&피드백 해주길 바란다. 필자가 CompletableFuture 를 사용해서도 구현을 해봤지만, 코딩하기 쉽지 않아서 이번 글에서는 생략하도록 하겠다. 코드는 아래 필자의 github 코드를 참고하길 바란다.

https://github.com/sieunkr/spring-redis/tree/master/spring-cache-redis



Case 2 - Spring Data Redis


Case2 에서는, Spring Data Redis 를 사용해보자. 프로퍼티 설정은 아래와 같이 변경된다. spring.cache.type=redis 를 제거하고, spring.data.redis.repositories.enabled를 추가한다.

Color 클래스에는 @RedisHash 어노테이션을 적용한다. 

그리고 Spring Data Redis 의 Repository 를 사용하기 위해서, 

아래와 같이 ColorRepository 를 정의하고, Bean생성을 위해서 @EnableRedisRepository 어노테이션을 추가한다.

Service, Controller 를 구현하고...

데이터 초기 저장은 아래와 같이.... 급하게 저장하고,

Redis 콘솔에서 확인하면 저장된 데이터는 Hash 타입으로 저장될 것이다. 


자!!! 이제 준비는 끝났다. 성능테스트를 해보자. 

Response Time Over TIme : 170ms

Transations per Second : 60 tps +


Case1 보다 더 안좋은 성능 결과가 나왔다. Spring Data Redis 사용할 때 조심하자. Spring Data Common 의 기능은 사용하기 편하지만, 잘못 사용하면 성능 이슈가 발생할 수 있다. 


어쨋든, Case1, Case2 모두 각각의 Key 로 데이터를 조회하는 방법인데, 100개의 데이터를 조회할때 100번의 Call 을 하는 방법이다. 매우 비효율적이기 때문에 이런 방법으로 개발을 하면 안된다. 하지만, 필자의 회사 코드는 이렇게... ㅠㅠ 잠시 반성좀 하고, 한번 의 호출로, 모든 데이터를 조회할 수 있도록 검토해보자. 


https://github.com/sieunkr/spring-redis/tree/master/spring-data-redis



Case 3 - Spring RedisTemplate(mget)


Case1, Case2 위에서 검토했던 케이스는 데이터를 조회할 때 Key 개별적으로 데이터를 요청하기 때문에 100개의 데이터가 필요하다면, 100번의 요청을 하게 될것이다. 한번의 콜(Call), 즉,  Multi Key를 전달해서 모든 데이터를 한큐에 조회 하면 성능은 좋아질 것이다. 자.. Spring Cache 와 spring-data-redis 에서는 필자가 방법을 아직 찾지 못했기 때문에, RedisTemplate 를 사용해보겠다. 참고로, Redis에 저장된 데이터는 Json직렬화로 저장하였다. (저장 방법은 생략하겠다...)

필자의 코드는 리팩토링이 필요하지만,

어쩃든 지금 Case3의 중요한 점은, 단 한번의 호출로 모든 데이터를 조회한다는 점이다. Redis 콘솔에서 사용하는 Mget 명령어와 같다고 생각하면 된다. 

코드에 대한 설명은 다 생략하겠다.


성능 테스트 결과는 아래와 같다. 

Response Time Over TIme : 50ms

Transations per Second : 180 tps +

응답 시간은 평균 50ms 정도가 나왔고, TPS 도 180TPS 까지 올라가는 것을 확인하였다. 


https://github.com/sieunkr/spring-redis/tree/master/spring-redis


Case1,Case2 와 비교하면 훨씬 좋은 성능이지만, 그래도 이정도 성능으로 만족할 수는 없다. 


좀 더 욕심을 내서 Reactive 프로그래밍으로 구현해보자



Case 4 - Spring Reactive Redis (mget)


이제 마지막 테스트 케이스이다. 이번 마지막 케이스는, 스프링 리액티브 프로그래밍으로 구현하겠다. 


일단, 디펜던시는 Reactive 관련 모듈을 추가해야 한다. 

spring-boot-starter-data-redis-reactive

spring-boot-starter-webflux

ReactiveRedisOperations 빈을 정의해야 한다.  

Jackson2JsonRedisSerializer를 사용해서, Json직렬화 방법을 사용할 것이다. 애플리케이션에서 초기 데이터는 아래와 같이 @PostConstruct 로 저장하였다. 

Controller 에서 바로  ReactiveRedisOperations 를 호출해보자. Mono<List<Color>> 로 리턴한다. 

결과는 아래와 같다. 


JMeter 로 성능테스트를 해보자. 


부하 테스트 결과는!!!! 


Response Time Over TIme : 25ms

Transations per Second : 400 tps +

모든 테스트 케이스 중에서 가장 결과가 좋다. 

응답 속도는 20ms 평균이고, TPS 도 400TPS 까지 치고 오르는 것을 확인할 수 있다. 오...역시 Reactive@


https://github.com/sieunkr/spring-redis/tree/master/spring-data-redis-reactive



마무리


이번 글에서는 Redis 를 연동한 Spring API 의 성능 테스트를 진행하였다. 모든 Case 중에서 Spring Reactive Redis 로 구현한 결과가 가장 훌륭하였다. 하지만, 이 결과를 객관적으로 판단하기에는 무리가 있다. 실서비스 환경이 아니고, 필자의 개인 서버 환경이라는 점을 참고하길 바란다. 끝!

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari