인프런 커뮤니티 질문&답변

전병준님의 프로필 이미지
전병준

작성한 질문수

호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)

게시글 조회 5 - 페이징 처리 (QueryDSL)

Count 쿼리 발생에 대해 궁금합니다.

작성

·

472

·

수정됨

0

안녕하세요 호돌맨님 ! 질문이 있습니다 !!

Pageable 을 사용해서 페이징 구현 시 Full Scan 을 하기 위해 Count 쿼리가 발생하는걸 확인했는데, Pageable 에서는 페이징 처리를 위해 총 데이터의 개수를 파악해야 하기 때문이니까 Count 쿼리가 필요한거고 Querydsl 은 직접 limit 값, offset 값을 지정하기 때문에 Full Scan 할 필요가 없으니 Count 쿼리가 필요없는게 맞을까요 ?

답변 2

1

안녕하세요 전병준님! Count 쿼리에 대한 질문이 있군요.

일반적으로 페이징 처리를 위해 총 데이터의 개수 파악이 필요합니다. Pageable을 사용해서 페이징을 구현할 때, 총 데이터 개수를 파악하기 위해 Count 쿼리가 발생하게 됩니다. 따라서 Pageable을 사용하면 Count 쿼리가 필요한 것입니다.

하지만 Querydsl을 사용하면 직접 limit 값과 offset 값을 지정할 수 있기 때문에, Full Scan 할 필요 없이 데이터를 찾을 수 있습니다. 이 경우 Count 쿼리가 필요하지 않을 수 있습니다.

즉, Pageable을 사용할 때는 Count 쿼리가 필요하고, Querydsl을 사용할 때는 Count 쿼리가 필요하지 않을 수 있다는 것이 맞습니다. 페이지 처리 방식에 따라 Count 쿼리의 필요 여부가 달라집니다.

더 자세한 내용이나 궁금한 점이 있으시면 언제든지 물어보세요! 좋은 하루 되세요~요.

0

호돌맨님의 프로필 이미지
호돌맨
지식공유자

안녕하세요. 호돌맨입니다.

Full Scan 여부는 별로 상관 없어 보이는데요.
페이지네이션 처리를 하려면 전체 데이터 개수를 알아야합니다.
그렇기 때문에 QueryDSL을 이용해 데이터를 가져오실때 별도로 totalCount 쿼리를 통해
아이템, 전체 개수를 반환 해야합니다.

Pageable을 이용하면 이러한 과정을 좀 더 쉽게 해주는것 뿐입니다.

감사합니다.

전병준님의 프로필 이미지
전병준
질문자

Hibernate: 
    select
        post0_.id as id1_2_0_,
        user1_.user_id as user_id1_4_1_,
        post0_.create_at as create_a2_2_0_,
        post0_.modified_at as modified3_2_0_,
        post0_.content as content4_2_0_,
        post0_.is_deleted as is_delet5_2_0_,
        post0_.title as title6_2_0_,
        post0_.user_id as user_id7_2_0_,
        user1_.email as email2_4_1_,
        user1_.name as name3_4_1_,
        user1_.oauth_id as oauth_id4_4_1_,
        user1_.picture as picture5_4_1_,
        user1_.role as role6_4_1_ 
    from
        post post0_ 
    inner join
        users user1_ 
            on post0_.user_id=user1_.user_id 
    order by
        post0_.id desc limit ?,
        ?

저도 그렇게 생각하는데 Pageable 을 사용하지 않고 .fetct() 로 List<Post> 를 반환하니 count 쿼리가 따로 나가지 않아서요.. 그래서 찾아보니 fetch() 로 조회하면 count 쿼리가 따로 나가질 않으니 필요하다면 애플리케이션 로직에서 처리하던가, count 쿼리를 따로 호출해야한다고 하더라고요. 근데 count 쿼리가 나가질 않는데 어떻게 페이지네이션이 동작하는지 그걸 모르겠습니다..

호돌맨님의 프로필 이미지
호돌맨
지식공유자

Pageable로 호출한 부분 코드를 보여주실 수 있을까여?

전병준님의 프로필 이미지
전병준
질문자

// Controller
@GetMapping("/posts")
  public ResponseEntity<PostMultiReadResponse> readAll(@ModelAttribute PageSearch pageSearch) {
    PostMultiReadResponse response = postService.readAll(pageSearch);

    return ResponseEntity.ok(response);
  }

// Service
@Transactional
  public PostMultiReadResponse readAll(PageSearch pageSearch) {
    List<Post> offset = postRepository.readAll(pageSearch);
//    List<Post> noOffset = postRepository.readNoOffset(pageSearch);

    List<PostSingleReadResponse> response = offset.stream()
        .map(post -> PostSingleReadResponse.builder()
            .id(post.getId())
            .title(post.getTitle())
            .content(post.getContent())
            .createdDate(post.getCreateAt())
            .modifiedDate(post.getModifiedAt())
            .writer(WriterResponse.builder()
                .name(post.getUser().getName())
                .email(post.getUser().getEmail())
                .picture(post.getUser().getPicture())
                .build())
            .build())
        .collect(Collectors.toList());

    return PostMultiReadResponse
        .builder()
        .postsResponse(response)
        .build();
  }

// Repository
@Override
  public List<Post> readAll(PageSearch pageSearch) {
    return jpaQueryFactory.selectFrom(QPost.post)
        .join(QPost.post.user)
        .fetchJoin()
        .limit(pageSearch.getSize())
        .offset(pageSearch.getOffset())
        .orderBy(QPost.post.id.desc())
        .fetch();
  }

// Service Test
  @Test
  @DisplayName("게시글 전체 조회 API 페이징 성능을 테스트합니다.")
  void readAll() {
    // given
    PageSearch pageSearch = new PageSearch(1, 20);

    // when
    long startTime = System.currentTimeMillis();
    List<Post> response = postRepository.readAll(pageSearch);
    long endTime = System.currentTimeMillis();
    long pagingTime = endTime - startTime;
    System.out.println("반환 시간: " + pagingTime + "ms");

    // then
    assertThat(response).isNotNull();
  }

넵 차례대로 Controller, Service, Repository, Test 코드입니다.

테스트 코드 실행 결과는 아래와 같습니다.

Hibernate: 
    select
        post0_.id as id1_2_0_,
        user1_.user_id as user_id1_4_1_,
        post0_.create_at as create_a2_2_0_,
        post0_.modified_at as modified3_2_0_,
        post0_.content as content4_2_0_,
        post0_.is_deleted as is_delet5_2_0_,
        post0_.title as title6_2_0_,
        post0_.user_id as user_id7_2_0_,
        user1_.email as email2_4_1_,
        user1_.name as name3_4_1_,
        user1_.oauth_id as oauth_id4_4_1_,
        user1_.picture as picture5_4_1_,
        user1_.role as role6_4_1_ 
    from
        post post0_ 
    inner join
        users user1_ 
            on post0_.user_id=user1_.user_id 
    order by
        post0_.id desc limit ?,
        ?

반환 시간: 177ms
전병준님의 프로필 이미지
전병준

작성한 질문수

질문하기