첫 페이지 조회 결과 cache 하기
기존)
BookRepositoryImpl
@Repository
@RequiredArgsConstructor
public class BookRepositoryImpl implements BookRepository {
private final JPAQueryFactory jpaQueryFactory;
private final SpringDataJpaBookRepository bookRepository;
...
@Override
public Page<BookSearchResponseDto.Response> findBooksBySimpleCategory(InquiryCategory category, String inquiryWord, Pageable pageable) {
JPQLQuery<BookSearchResponseDto.Response> query=jpaQueryFactory.select(
new QBookSearchResponseDto_Response(bookEntity.bookCode,bookEntity.bookName,bookEntity.bookAuthor,bookEntity.pubDt,bookEntity.bookState,bookEntity.bookImage)
)
.from(bookEntity)
.where(getSearchCategory(category,inquiryWord))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
;
//데이터 조회
List<BookSearchResponseDto.Response> content = query.fetch();
//총 갯수
long totalCount = query.fetchCount();
return new PageImpl<>(content,pageable,totalCount);
}
...
}
해당 코드로 매번 페이지 변경 시마다 매번 2 번의 쿼리가 나가게 된다.
1. 데이터 조회 쿼리
2. count 조회 쿼리
매번 데이터조회쿼리는 그렇다치지만 매번 count조회쿼리가 나가는 것이 불편했다.
첫 조회 시 totalCount를 알고 있다면 그 이외 페이징에선 굳이 알아야할 필요가 없어보였다.
개선) 첫 페이지 조회 결과 cache하기
1. 첫 조회 시 totalCount 구하기 -> totalCount는 클라이언트단에서 저장 / 데이터 및 count 조회쿼리 발생
2. 그 이후 페이지 변경 시, 첫 조회 시 구한 totalCount를 포함시켜 요청 / 데이터 조회 쿼리 발생
쉽게 말하면, 첫번째 페이지 조회 시에만 totalCount 조회하고 그 이후 페이징 버튼에선 조회하지 않도록 하는 것이다.
해당 방식 사용 이유는 실시간으로 데이터 삽입이 일어나지 않았으며, 검색 및 페이지버튼 이벤트가 모두 골고루 발생하였기에 사용하였다.
BookRepositoryImpl
@Repository
@RequiredArgsConstructor
public class BookRepositoryImpl implements BookRepository {
private final JPAQueryFactory jpaQueryFactory;
private final SpringDataJpaBookRepository bookRepository;
...
//클라이언트로부터 캐싱된 cacheCount 인입
@Override
public Page<BookSearchResponseDto.Response> findBooksBySimpleCategory(InquiryCategory category, String inquiryWord, Pageable pageable,Long cachedCount) {
JPQLQuery<BookSearchResponseDto.Response> query=jpaQueryFactory.select(
new QBookSearchResponseDto_Response(bookEntity.bookCode,bookEntity.bookName,bookEntity.bookAuthor,bookEntity.pubDt,bookEntity.bookState,bookEntity.bookImage)
)
.from(bookEntity)
.where(getSearchCategory(category,inquiryWord))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
;
//데이터 조회
List<BookSearchResponseDto.Response> content = query.fetch();
//총 갯수(cacheCount 인입 여부에 따른 count조회 수행)
long totalCount = cachedCount != null ? cachedCount : query.fetchCount();
return new PageImpl<>(content,pageable,totalCount);
}
...
}
React
Books.tsx
function Books(){
...
const [totalCount,setTotalCount] = useState(-1);
//현재 페이지
const currentPage = .. ;
//페이지 당 개수
const sizePerPage = .. ;
const {data,isLoading} = useQuery(
["inquiryBooksFetch",cateogry+"/"+inquiryWord+"/"+currentPage+"/"+sizePerPage], //쿼리키 , 쿼리키로 구분해서 data fetching
()=>inquiryBooksFetch(cateogry,inquiryWord,currentPage,sizePerPage,totalCount), //totalCount도 함께 요청
{
onSuccess(data) {
setTotalCount(data.data.totalCount); //응답받은 totalCount 저장
},
staleTime: 6000 *10
}
);
..
return (
<>
<h1>도서 조회 결과</h1>
<div>
{
isLoading
?
<p>isLoading</p>
:
data?.data?.bookList.map((book:IInquriyBooksReponse) =>{
return (
<div key={book.bookNo}>
...
</div>
)
})
}
</div>
<Pagination totalCount={totalCount} sizePerPage={sizePerPage} currentPage={currentPage} />
</>
);
}
api.ts
//도서 검색
export const inquiryBooksFetch = async (category:string, inquiryWord:string,currentPage:number,size:number,totalCount:number)=>{
const inquriyBooksUrl = totalCount>-1
? //초기값이 아닌 경우(=첫 조회가 아닌 경우)
`/book/inquiry/${category}/${inquiryWord}?page=${currentPage}&size=${size}&cachedCount=${totalCount}`
: //초기값인 경우(=첫 조회인 경우)
`/book/inquiry/${category}/${inquiryWord}?page=${currentPage}&size=${size}`;
return await PublicAPI.get(inquriyBooksUrl)
.then(response=>response.data);
}
첫 페이지 조회)
첫 페이지 조회 시엔 totalCount의 State값인 -1 로 api.ts 파일 내 fetch메소드로 들어오게 된다.
이 경우엔 서버요청 시 cachedCount값이 빠진 채 요청된다.
첫 페이지 요청에 대한 결과값으로 totalCount값을 전달받고 해당 값을 onSuccess 구문 내에서 state값에 세팅하게된다.
이후 페이지 조회)
그 이후 요청에선 0 이상의 totalCount를 받았을 것이니 분기처리하여 존재하는 cachedCount값을 포함시켜 요청하게 된다. 서버에선 cachedCount값이 들어왔으니 count조회 쿼리를 실행시키지 않는다.
실행시간
첫 페이지 이후 조회(10,000건 대상) | 개선 전 | 개선 후 |
소요 시간 | 43ms | 3ms |
참고)
https://jojoldu.tistory.com/531?category=637935
3-2. 페이징 성능 개선하기 - 첫 페이지 조회 결과 cache 하기
모든 코드는 Github에 있습니다. 지난 시간에 이어 count와 관련된 2번째 개선 방법은 첫 번째 쿼리의 결과를 Cache하기 인데요. 방법은 간단합니다. 처음 검색시 조회된 count 결과를 응답결과로 내려
jojoldu.tistory.com
'SpringBoot' 카테고리의 다른 글
Spring Annotation 활용기 (0) | 2024.05.31 |
---|---|
@OneToOne (2) | 2024.04.22 |
OpenFeign 사용 시 헤더 값 넘기기 (0) | 2024.03.27 |
DB-> Entity, Entity->DB 자동 변환 (@Convert) (0) | 2023.05.14 |
Lombok 어노테이션 (0) | 2022.12.06 |