ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Redis :: 통계 데이터 배치처리
    Study 2022. 11. 22. 22:21

     

    1000만건의 상품 데이터가 있는 쇼핑몰 프로젝트를 진행중입니다.  


     

    문제 발생

    현재 프로젝트에선 AWS RDS MySQL 프리티어 버전을 사용하고 있습니다.  DB에서 네트워크 성능이 높지 않다보니 최적화는 필수였고, 상품 조회 시마다 발생하는 +1 Update는 문제가 될 확률이 아주 높았습니다.

     

    🤩 해결 방안

    • Cache Write Back 
    • 조회수를 캐시에 모아 일정 주기 배치 작업을 통해 DB에 반영
    • 싱글쓰레드인 Redis의 특성상 Atomic하게 Increment를 처리할 수 있다.
    • 조회 기능의 많은 I/O와 함께 발생하는 Update를 컨트롤할 수 있다.   

     

    CODE

     

    • 상품 조회가 발생할 때 countView() 가 함께 발생하도록 설계했습니다.
    // ProductService
    
    // 상세 조회
    @Cacheable(value = "product", key = "#id") // [product::1], [title : "" , ...]
    @Transactional(readOnly = true)
    public ProductDetailResponseDto findProduct(Long id) {
    
        log.info("Search Log Start....");
        
        ProductDetailResponseDto product = productRepository.detail(id);
        if (product == null ) throw new RequestException(NOT_FOUND_EXCEPTION);
        
        countView(id); // 조회수 추가
        
        return product;
    }
    
        // View ++
        public void countView(Long productId) {
        
        	log.info("View Increment");
            
            String key = "productView::" + productId;
             // key : [productView::1] (상품 번호),  value : [1] (누적 조회수)
             
            productRedisService.incrementView(key, productId);
        }
    }

     

    • 이미 View 캐시가 등록되어 있다면 increment() 메소드를 사용해 value +=1
    • 만약 데이터가 존재하지 않는다면 새로 등록 후 추가합니다.  
    // ProductRedisService
    
    public void incrementView(String key, Long productId) {
        ValueOperations<String, String> values = redisTemplate.opsForValue();
    
        if(values.get(key) == null) {
            setView(key, String.valueOf(productRepository.getView(productId)), Duration.ofMinutes(35));
            values.increment(key);
        }
        else values.increment(key);
    }
    
    // View Cache Setting
    public void setView(String key, String data, Duration duration) {
        ValueOperations<String, String> values = redisTemplate.opsForValue();
        values.set(key, data, duration);
    }

     

    • View Cache를 DB에 업데이트 시켜주는 메소드입니다.
    • @Scheduled 를 사용해 30분의 주기를 설정했습니다.
    • productRepository.addView() 는 Update Query 로직입니다.
    // ProductRedisService
    
    // View -> DB Update
    @Scheduled(cron = "0 0/30 * * * ?")
    @Transactional
    public void UpdateViewRDS() {
        Set<String> redisKeys = redisTemplate.keys("productView*");
        if (redisKeys == null) return;
    
        log.info("Starting View Update !");
    
        for (String data : redisKeys) {
            Long productId = Long.parseLong(data.split("::")[1]);
            int viewCnt = Integer.parseInt(redisTemplate.opsForValue().get(data));
    
            productRepository.addView(productId, viewCnt);
    
            redisTemplate.delete(data);
        }
        
        log.info("Success Update View !");
    }

     

     

     

    10초간 10,000건의 조회가 발생하는 상황으로 테스트를 진행해보겠습니다.

    ( Jmeter를 통한 부하테스트로 진행됩니다. )

    ( 상품 조회에는 Cache Aside 전략을 사용하고 있습니다. )  

     

    • Write Back 전략을 사용하지 않은 코드입니다.
    • 에러율 62.31%가 발생했습니다.

    Update 쿼리가 발생하는 코드

     

    • Write Back 전략을 사용한 코드입니다.
    • 동일 상황에 에러율이 0%입니다.
    • 상시 동작하는 Update 쿼리가 아주 큰 문제임을 알 수 있습니다.
    • 하지만 싱글쓰레드로 동작하는 Redis의 특성상 뒤에 들어오는 요청에 생기는 지연 개선이 필요해 보이네요..

    Write Back 전략을 사용한 코드

     

    + 배치 주기마다 발생하는 많은 양의 Update 요청도 컨트롤이 필요합니다.

    + 읽어주셔서 감사합니다.

    'Study' 카테고리의 다른 글

    Elastic Beanstalk 정리  (0) 2022.11.22
    Redis :: 테스트 환경 구축  (0) 2022.11.22
    Redis :: 다수의 명령어 파이프 라인으로 처리하기  (0) 2022.11.22
    Redis :: 랭킹보드 구현하기  (0) 2022.11.22
    스프링 기본 정보  (0) 2022.10.10

    댓글

Designed by Tistory.