-
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