본문 바로가기

내일 배움 캠프

2024-01-26

오늘은 프로젝트 23일 차이다. 
오늘은 어제 진행하던 내용에서 N+1문제를 해결하기 위해 기존에 커서기반의 공연 전체조회 기능의 쿼리를 수정했다. 여기서 N+1문제가 발생할만한 이유는 goodsInfo에 goodsImage가 양방향 매핑으로 설정 돼 있는 동시에 Laze의 설정 때문에 발생하는 것으로 원인 추축이 된다. 
수정을 위해 기존의 쿼리에서는 이미지를 쿼리 처리가 끝난후에 가져온 이미지 중에서 서비스 로직을 통해 대표 이미지를 분리해서 넣었었다. 이번에 개선한 점은 이미지를 개선과 동시에 DTO를 생성하여 필요한 데이터만 쿼리로 바로 처리하게 뜸했다.
GoodsImage의 서브쿼리를 생성하고 서브쿼리에서 Dto에 필요한 객체를 매핑하여 goodsid, title, 이미지는 내부 서브 쿼리를 다시 생성하여 goods에서 goodsInfo의 id값이 goodsImage의 goodsInfo.id와 일치하는 이미지들 중 대표 이미지를 가져오게끔 추가했다.

따라서 주쿼리로 해당하는 goods를 가져오고 서브쿼리로 Dto 객체에 맞춰 만들어 준후 커서 Id의 조건에 따라서 원하는 size만큼 Dto객체를 보내주는 것이다. 

@Override
public List<GoodsGetQueryResponse> findAllByGoodsAndCategoryName(
    Long cursorId, int size, String categoryName) {

    QGoodsImage goodsImageSubQuery = new QGoodsImage("goodsImageSub");
    JPAQuery<GoodsGetQueryResponse> query = this.query
       .select(
          Projections.constructor(
             GoodsGetQueryResponse.class,
             goods.id,
             goods.title,
             JPAExpressions
                .select(goodsImageSubQuery.s3Key)
                .from(goodsImageSubQuery)
                .where(goodsImageSubQuery.goodsInfo.id.eq(goods.goodsInfo.id)
                   .and(goodsImageSubQuery.type.eq(ImageType.POSTER_IMG)))
                .limit(1)
          ))
       .from(goods)
       .leftJoin(goods.goodsInfo, goodsInfo)
       .leftJoin(goodsInfo.goodsCategory, goodsCategory)
       .where(goods.endDate.after(LocalDate.now()));

    if (categoryName != null) {
       query.where(goodsCategory.name.eq(categoryName));
    }

    if (cursorId != null) {
       query.where(goods.id.lt(cursorId));
    }

    return query
       .limit(size)
       .orderBy(goods.id.desc())
       .fetch();
}

 

이렇게 진행한 후 기존에 있던 N+1문제를 해결했고 테스트 결과는 놀라웠다.
개선한 코드 테스트 커서 9501번 Size 9500
2024-01-25T 22:14:42.667+09:00  INFO 30268 --- [io-8080-exec-10] c.s.t.global.aop.QueryExecutionAspect    : Execution of List com.sparta.ticketauction.domain.goods.repository.GoodsRepositor yCustomImpl.findAllByGoodsPageAndCategoryName(Long, int, String) took 424 ms
2024-01-25T 22:15:30.644+09:00  INFO 30268 --- [nio-8080-exec-3] c.s.t.global.aop.QueryExecutionAspect    : Execution of List com.sparta.ticketauction.domain.goods.repository.GoodsRepositor yCustomImpl.findAllByGoodsAndCategoryNameQuery(Long, int, String) took 155 ms

기존에 커서를 0으로 두고 size를 9500으로 설정하여 데이터를 조회했더니 9500개의 데이터를 조회하는 데 걸린 시간 차이가 거의 2.7배 빨라졌다. size를 20개씩 잘라서 조회할 때도 20ms정도 차이가 났다.

 

size20으로 고정하고 첫페이지를 조회한다고 했을때 커서 9501 

2024-01-25T 22:18:30.753+09:00  INFO 30268 --- [io-8080-exec-10] c.s.t.global.aop.QueryExecutionAspect    : Execution of List com.sparta.ticketauction.domain.goods.repository.GoodsRepositor yCustomImpl.findAllByGoodsPageAndCategoryName(Long, int, String) took 38 ms
2024-01-25T 22:19:10.553+09:00  INFO 30268 --- [nio-8080-exec-3] c.s.t.global.aop.QueryExecutionAspect    : Execution of List com.sparta.ticketauction.domain.goods.repository.GoodsRepositor yCustomImpl.findAllByGoodsAndCategoryNameQuery(Long, int, String) took 18 ms

'내일 배움 캠프' 카테고리의 다른 글

2024-01-30  (1) 2024.02.01
2024-01-29  (0) 2024.01.30
2024-01-25  (1) 2024.01.27
2024-01-24  (0) 2024.01.25
2024-01-23  (0) 2024.01.23