[ Spring ] 페이징 성능 개선

5tr1ker·2024년 9월 15일
0

Spring

목록 보기
16/16
post-thumbnail

커버링 인덱스

커버링 인덱스는 쿼리를 충족시키는 데 필요한 모든 데이터를 갖고 있는 인덱스를 이야기합니다. 즉 select, where, order by, limit, group by 등에서 사용되는 모든 컬럼이 인덱스 컬럼이 되는 것을 말합니다.

SELECT *
FROM items
WHERE 조건문
ORDER BY id DESC
OFFSET 페이지번호
LIMIT 페이지사이즈

위와 같은 일반적인 페이징 쿼리를 아래처럼 표현한 코드를 말합니다.

SELECT  *
FROM  items as i
JOIN (SELECT id
        FROM items
        WHERE 조건문
        ORDER BY id DESC
        OFFSET 페이지번호
        LIMIT 페이지사이즈) as temp on temp.id = i.id

select 를 비롯해 order by, where 등 쿼리 내 모든 항목이 인덱스 컬럼으로만 이뤄져있어, 인덱스 내부에서 빠르게 쿼리가 완성되게 하는 방식입니다.

보통 select에서 모든 row의 데이터를 가져오는 과정에서 속도가 느려지는 반면, 커버링 인덱스는 인덱스 컬럼만 조회하기 때문에 데이터 블록에 접근하지 않아도 됩니다.
이는 order by, where 절에서 인덱스가 아닌 데이터를 조건을 거는 경우에도 데이터 블록에 접근하기 때문에 성능이 저하될 수 있습니다.

즉, 데이터 블록에 접근하게 될 경우 성능이 저하되게 됩니다. 따라서 커버링 인덱스는 페이징에서는 인덱스만 사용하여 필요한 데이터들의 인덱스 값만 가져와 이후 데이터 블록에 접근하는 방식이 사용됩니다.

No Offset

No Offset은 일반적인 쿼리와는 다르게 offset을 사용하지 않는 것을 말합니다.

SELECT *
FROM items
WHERE 조건문
OFFSET 페이지번호
LIMIT 페이지사이즈

위는 일반적으로 사용하는 쿼리인데 offset 10000, limit 20 이라면 총 10020 개의 행을 읽고 앞에 10000개의 데이터는 버리고 뒤에 20개의 데이터만 가져옵니다.

이는 뒤로 갈수록 사용하지 않지만 조회되는 데이터가 많아 느려지게 됩니다.
이를 개선하여 No Offset 방식은 조회 시작 부분을 인덱스로 찾아 첫 페이지 이후로 읽게 하는 방법 입니다.

SELECT *
FROM items
WHERE 조건문
AND id < 마지막조회ID # 직전 조회 결과의 마지막 id
LIMIT 페이지사이즈

이전에 조회된 결과를 한번에 건너뛸 수 있으므로 이전 페이지는 건너뛸 수 있습니다.

이는 무한 스크롤 방식에서 유용하게 사용될 수 있으나 일반 게시판처럼 특정 페이지 번호로 이동하는 기능에서는 다소 유용하지 않습니다.

장단점

커버링 인덱스

무한 스크롤이 아닌 특정 페이지로 이동할 수 있습니다. 하지만 인덱스가 많아져 크기가 커질 수 있다는 단점과 데이터의 양이 많아지고 뒤로 갈수록 성능이 떨어진다는 단점을 가지고 있습니다.

NoOffset

NoOffset 방식은 커버링 인덱스보다 더 빠른 성능을 낼 수 있습니다. 하지만 무한 스크롤 방식인 순차적 접근만 가능하며, 특정 페이지로 이동할 수는 없다는 단점을 가지고 있습니다.

실제 개선

  • 개선 이전의 코드
public Object findByemail(String email, boolean existReview, Pageable pageable) {
			query.offset(pageable.getOffset())
			.limit(pageable.getPageSize());

위의 코드를 보면 offset과 limit를 사용하면 한 번의 API 호출 시 MTT가 1,000 을 넘어선 1,038ms 정도 걸리는 것을 볼 수 있습니다.

커버링 인덱스를 활용해 1,038ms 에서 98ms 로 단축된 것을 확인할 수 있습니다.

 JPAQuery<Long> query = jpaQueryFactory.select(order.orderId)
                .from(order)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();
                
                public Object findByemail(String email, boolean existReview, Pageable pageable) {
			query.where(order.orderId.in(indexKey));

참고

참고 블로그 1 : https://jojoldu.tistory.com/528
참고 블로그 2 : https://jojoldu.tistory.com/529

profile
https://github.com/5tr1ker

0개의 댓글