갓생사는 김초원의 개발 블로그
chocho_log
갓생사는 김초원의 개발 블로그
전체 방문자
오늘
어제
  • 분류 전체보기 (76)
    • 개발 (22)
      • Spring (4)
      • Java (3)
      • Database (2)
      • Elasticsearch (3)
      • ETC (3)
      • JPA (3)
      • 이슈 (1)
    • 코딩 테스트 (43)
      • 프로그래머스 (23)
      • 백준 (12)
      • TIP (8)
    • 자료구조 (2)
    • 알고리즘 (4)
    • 잡생각 (0)
    • 경험 (3)
      • AWS re:Invent 2024 (3)

블로그 메뉴

    공지사항

    인기 글

    태그

    • Spring Boot Embedded Tomcat
    • jpa
    • 디자인패턴 #SOLID 원칙
    • war
    • Lazy Loading
    • jar
    • querydsl
    • 지연로딩

    최근 댓글

    최근 글

    갓생사는 김초원의 개발 블로그

    chocho_log

    webflux + reactive redis cache 적용하기
    개발/Spring

    webflux + reactive redis cache 적용하기

    2021. 8. 23. 02:13

    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
      '개발/Spring' 카테고리의 다른 글
      • Spring Boot 배포 WAR 에서 JAR 로 변경하기 (Spring Boot Embedded Tomcat 사용하기)
      • QueryDSL + multi data source 연동하기
      • Spring-Cloud-Data-Flow(SCDF)구축해보기
      갓생사는 김초원의 개발 블로그
      갓생사는 김초원의 개발 블로그
      갓생사는 김초원의 개발 블로그 github: https://github.com/kimchowon

      티스토리툴바