webflux + reactive redis cache
redis는 캐시로 사용할 수 있음. AOP에서 redis에 원하는 데이터가 있으면 바로 get, 없으면 set하는 로직.
기존 mvc 모델에서는 캐시 어노테이션과 AOP로 캐시를 쉽게 구현할 수 있음. 그런데 webflux 모델에서는 캐시를 구현할 수 가 없었다.
mvc 모델
@RedisCacheable(DEAL)
public Deal getDeal(final long dealNo, ...) {
// 실제 데이터를 리턴
}
webflux 모델
webflux는 데이터를 get하면 Mono/Flux 체인이 리턴된다.
Mono/Flux는 Reactive Streams에서 데이터를 제공하는 발행자 역할을 하는 Publisher의 구현체이다. 구독자가 subscribe()하기 전까지는 실데이터를 얻을 수 없다.
webflux로는 기존 mvc 코드로 AOP 내에서 캐시 구현을 할 수가 없어서 다른 방법이 필요하다.
@RedisCacheable(DEAL)
public Mono<Deal> getDeal(final long dealNo, ...) {
// Mono 체인을 리턴
}
Reactor Addons 라이브러리를 사용하기
Reacotr Addons 라이브러리를 사용하면 기존 mvc 모델에서처럼 annotation + AOP 방식으로 캐시를 구현할 수 있다.
implementation("io.projectreactor.addons:reactor-extra:3.3.0.RELEASE")
Reactor Addons의 CacheMono, CacheFlux는 아래와 같은 구조로 되어있다.
해당 클래스를 AOP에서 적절히 적용하여 webflux에서 reactive redis 캐시를 구현할 수 있다.
- AOP에 적용한 코드 예시(AOP + redis)
public class ReactiveCacheAspect { private final ReactiveRedisTemplate reactiveRedisTemplate; @Around("@annotation(com.example.reactiverediscache.redis.RedisCacheable)") public <T> Mono cache(ProceedingJoinPoint joinPoint) { final String cacheKey = (String) joinPoint.getArgs()[0]; return CacheMono .lookup(k -> { Mono<T> cacheValue = reactiveRedisTemplate.opsForValue().get(cacheKey); return cacheValue.map(Signal::next); }, cacheKey) .onCacheMissResume(() -> Mono.defer(() -> { try { return (Mono<T>)joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } return null; })) .andWriteWith((k, signal) -> Mono.fromRunnable(() -> { if (!signal.isOnError()) { reactiveRedisTemplate.opsForValue().set(cacheKey, signal.get()).subscribe(); } })); } }
정말 성능이 좋아질까(Webflux vs MVC)
테스트 상황) for loop 3번 돌며 redis에 데이터 set
- mvc + 일반 redis
vuser | RunTime | TPS | Peak TPS | Executed Test | Successful Tests | ||
mvc + 일반 redis |
30 (Process:2, Threads: 15) |
3분 | 1,196.4 | 1,464.5 | 210,758 | 210,758 | |
50 (Process:2, Threads: 25) |
3분 | 1,416.6 | 1,846.0 | 246,730 | 246,730 | ||
70 (Process:2, Threads: 35) |
3분 | 1,554.4 | 1,971.5 | 273,891 | 273,891 |
- webflux + reactive redis
vuserRun timeTPSPeak TPSExecuted TestsSuccessful Tests
vuser | Run Time | TPS | Peak TPS | Executed Test | Successful Tests | ||
webflux + reactive redis |
30 (Process:2, Threads: 15) |
3분 | 2,316.3 | 3,541.0 | 408,263 | 408,263 | |
50 (Process:2, Threads: 25) |
3분 | 2,579.1 | 4,158.5 | 449,646 | 449,646 | ||
70 (Process:2, Threads: 35) |
3분 | 2,675.1 | 3,546.5 | 466,313 | 466,313 |
but, cache 구현에서는 기존 redis cache와 비교했을 때 webflux + reactive redis의 TPS가 높아지지는 않았음. 비슷하거나 오히려 약간 더 낮았다.
결론
리액티스 프로그래밍을 사용한다고 해서 기본 방식보다 성능이 월등히 높아지는 것은 아니다.
오히려 발행/구독 형식으로 되어 있기 때문에 단일 작업에서는 성능이 약간 느려질 수 있다. 대신 동시 호출이 많은 작업에 webflux를 사용하면 좋을 것 같다.
하지만 더 적은 쓰레드와 더 적은 하드웨어 리소스(CPU, 메모리 사용률)로 동시성이 높아진다.
동시성을 높이는 방식으로 반응을 즉각적으로 하기 때문에 적은 리소스로도 높은 반응을 보장할 수 있다는 것이지 기존 서버의 처리 속도를 올려주는 형태는 아니다.
하지만 동시성이 높다는 것은 같은 서버로 더 많은 처리를 할 수 있기 때문에 결론적으로 성능이 높아진 것 같은 효과를 누릴 수 있다.
'개발 > Spring' 카테고리의 다른 글
Spring Boot 배포 WAR 에서 JAR 로 변경하기 (Spring Boot Embedded Tomcat 사용하기) (2) | 2022.04.13 |
---|---|
QueryDSL + multi data source 연동하기 (0) | 2021.09.13 |
Spring-Cloud-Data-Flow(SCDF)구축해보기 (4) | 2020.11.10 |