OpenRun Project - Redis를 사용해서 대용량 데이터의 조회 성능 향상 시키기(count query 개선)

Ango·2023년 8월 17일
0

Project

목록 보기
16/16

❓ 문제점

약 500만건의 데이터에 대해서 페이징 처리 된 페이지를 조회해오는데 너무 오랜 시간이 걸리는 문제가 발생했다.
전체 상품 조회에 대한 페이징 처리라 index도 많은 도움이 되지 않았다.

@Override
public Page<AllProductResponseDto> findAllDto(Pageable pageable) {
  List<AllProductResponseDto> content = queryFactory
          .select(Projections.constructor(AllProductResponseDto.class,
                  product.id,
                  product.productName,
                  product.price,
                  product.mallName,
                  product.category

          )).from(product)
					.orderby(product.id.desc())
          .offset(pageable.getOffset())					
          .limit(pageable.getPageSize())					
          .fetch();

  JPAQuery<Long> countQuery = queryFactory
          .select(product.count())
          .from(product);

  return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
}

그래서 발견한 문제점이 select 쿼리는 크게 시간이 소요되지는 않지만
count query가 대부분의 시간을 잡아먹고있었다.
한페이지 조회하는데 약 24초의 시간이 걸렸다.

🔑 시도 && 해결 방법

총 count의 수를 미리 cache에 저장해두고 페이지 조회시에 캐시에서 가져오는 방식을 생각했다. 해당 방식을 저장 할 수 있었던 이유는

  1. 총 상품개수는 페이지 조회에 비해 변하는 횟수가 월등히 적다.
  2. 상품 추가, 삭제가 일어날 때만 cache를 업데이트 해주면 정합성 문제도 해결 가능하다.

때문에 매일 12시에 스케줄러를 통해 총 상품 개수를 저장해두도록 하고 count 쿼리를 page 조회 매서드에서 삭제했다.

@Override
public Page<AllProductResponseDto> findAllDto(Pageable pageable,Long count) {
    List<AllProductResponseDto> content = queryFactory
            .select(Projections.constructor(AllProductResponseDto.class,
                    product.id,
                    product.productName,
                    product.price,
                    product.mallName,
                    product.category
            )).from(product)
            .orderBy(product.id.desc())
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();


    return new PageImpl<>(content, pageable, count);
}
public Page<AllProductResponseDto> getAllProducts(Pageable pageable) {
	  int pageNumber = pageable.getPageNumber();

    // 인덱싱 적용 고려중

    Long count = allProductRedisRepository.getProductCount().orElseGet(() -> {
        long countResult = productRepository.count();
        allProductRedisRepository.saveProductCount(countResult);
        return countResult;
    });

    Page<AllProductResponseDto> productsInDB = productRepository.findAllDto(pageable,count);

    return productsInDB;

}

###ㄴ 테스트 환경

인원 : 100명

시간 : 5초

루프 : 10번

100명 기준으로 거의 모든 요청이 Timeout이 발생해 에러가 발생했던 이전과 다르게 에러율을 0%로 낮췄고 처리량 또한 대폭 증가했다.

아직 Troughput이 여유가 있는 것으로 보여 인원을 최대한 증가시켜보았다.

추가 테스트 환경(최대 성능 기록)

인원 : 1500명

시간 : 5초

루프 : 10번


처리량은 602.7/sec 까지 증가했고

인원을 1500명 이상으로 세팅 시, EC2 CPU의 병목 현상으로 처리량이 반으로 줄어드는 결과가 나왔다.

💡 알게 된 점

단순히 page 처리를 했지만 워낙 데이터가 많다보니 조회 쿼리에서 많은 시간이 소요 되는지 알았는데 실제로 문제가 된것은
page 처리를 위해 총 데이터 개수를 구하는 count 쿼리가 문제가 되고 있었다.
이를 미리 REDIS(Elastic Global Cache)에 저장하고 조회해 오면서

100명에 대해서도 거으 100% 의 에러율을 보이던 이전과 다르게
1500명의 인원에 대해서 초당 600 이상의 처리량을 보이도록 성능을 향상 시킬 수 있었다.

profile
웹 벡엔드 개발자가 되어보자!

0개의 댓글

관련 채용 정보