← 블로그

페이징 성능 개선을 위해 No Offset 사용하기

#java

기존 페이징 방식인 offset 과 limit 를 이용한 방식은 서비스가 커짐에 따라서 장애를 유발할 수도 있다.

이유로는 초기엔 데이터가 적어서 문제가 없지만 데이터가 점점 많아지면 느려지기 때문인데 결국에는 offset 을 이용하면 offset + limit 만큼의 데이터를 읽어야 하기 때문이다.

일단 offset 을 이용하는 기존 페이징 쿼리는 다음과 같다.

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

이와 같은 형태는 페이지 번호가 뒤로 갈수록 앞에서 읽었던 행을 다시 읽어야 한다.

이 말은 offset이 10000이고 limit가 20이라면 10,020 행을 읽어야 한다는 것이고 그러고 나서 10,000 개의 행을 버리는 것이다.

그렇기 때문에 성능 상에 안좋다는 점인데 No Offset 방식은 시작 지점을 인덱스로 빠르게 찾아 첫 페이지부터 읽도록 하는 방식이다.

No Offset 을 이용하는 SQL 문은 다음과 같다.

SELECT *
FROM items
WHERE 조건문
AND id < 마지막 조회 ID
ORDER BY id desc
LIMIT 페이지 사이즈
  • 이전에 조회된 결과를 한번에 건너뛸 수 있게 마지막 조회 결과의 ID 를 조건문에 사용하는 방식을 이용한다.

NoOffset 예제 코드

public List<MemberDto> noOffset(Long lastMemberId, int limit){
    return queryFactory
        .select(new QMemberDto(
                member.username,
                member.age
        ))
        .from(member)
        .where(member.username.contains("member")
                .and(memberIdLt(lastMemberId)))
        .orderBy(member.id.desc())
        .limit(limit)
        .fetch();
}

private BooleanExpression memberIdLt(Long lastMemberId) {
    return lastMemberId != null ? member.id.lt(lastMemberId): null;
}

문제점

일반적으로 많이 사용하는 페이지 번호 선택식 페이징의 경우에는 이 방식을 사용할 수 없다.

페이지를 선택해서 이동할 경우 그 전 페이지의 마지막 Element를 알 수 없기 때문이다.

보통 No Offset을 통해 성능 향상을 기대해 볼 수 있는 방식은 무한 스크롤 방식의 페이징이다.