-
Notifications
You must be signed in to change notification settings - Fork 0
No OffSet을 사용한 페이징 처리
Jay_ edited this page Apr 12, 2022
·
3 revisions
우리 프로젝트는 메인 기능인 모아보기, 디모, 마이페이지 등의 대부분의 조회페이지에서 사용자의 사용성을 위해서 무한 스크롤 페이징을 사용하게 되었다.
그 중, 쿼리의 효율성을 위해서 사용한 QueryDsl 내의 페이징 옵션인 limit, Offset을 사용한 페이징처리를 진행하였다.
@Override
public List<ArtworkMain> findAllArtWork(Long lastArtworkId, String category, Pageable paging,int sortSign) {
return queryFactory
.select(Projections.constructor(ArtworkMain.class,
artWorks.id,
account.id,
account.nickname,
account.profileImg,
artWorks.thumbnail,
artWorks.view,
artWorkLikes.count(),
artWorks.category,
artWorks.created
))
.from(artWorks)
.join(account).on(account.id.eq(artWorks.account.id))
.leftJoin(artWorkLikes).on(artWorkLikes.artWorks.eq(artWorks))
.offset(paging.getOffset())
.limit(paging.getPageSize())
.where(isCategory(category),
artWorks.scope.isTrue())
.groupBy(artWorks.id)
.orderBy(isArtWorkSort(sortSign))
.fetch();
}
초반에는 더미데이터를 많이 넣지 않아서, 페이징 처리가 원활하게 이루어지는 줄 알았으나, 더미 데이터를 DB에 수만건 이상 넣어봤을 때 성능의 이슈가 있었다.
알고보니, offset 방식의 가장 치명적인 단점이 앞에서 읽은 데이터를 다시 읽어오는 풀스캔이 일어난다는 것이다. 페이징 처리가 10개씩 이루어진다고 하면, offset방식은 1번부터 10번까지를 가져오고 그 뒤의 데이터는 다시 1부터 10까지 스캔 후, 11번부터 20번까지의 10개를 다시 가져오는 식으로 진행된다.
극단적으로, 1억개의 DB데이터를 조회해야한다고 할 때 마지막 10개의 페이징 처리는 그 전 필요없는 99,999,990번의 데이터 스캔 후 그 데이터를 버린 후 10개를 가져오는 것이다.
이렇게 될 때, 당연히 페이징이 진행될 수록 점점 로딩 속도가 느려질 수 밖에 없고, 이를 개선하기 위한 다른 옵션을 알아보게 되었다. 그 중 가장 적합하다고 판단한 방법이 No Offset 방식이었다.
No Offset의 경우, limit로 페이징할 페이지 개수를 지정하고, 조회 시작 부분을 인덱스로 빠르게 찾아서 매번 첫 페이지만 읽어오는 방식을 차용하고 있다.
페이징 처리를 진행하는 Where절의 id < 마지막 조회 id와 같이 직전 조회 결과의 마지막 id를 입력으로 받아서 매번 이전 페이지 전체를 건너 뛸 수 있고, 아무리 페이지가 뒤로 가더라도, 처음 페이지를 읽은 것과 같은 동일한 성능을 가지게 된다.
@Override
public List<ArtworkMain> findAllArtWork(Long lastArtworkId, String category) {
return queryFactory
.select(Projections.constructor(ArtworkMain.class,
artWorks.id,
account.id,
account.nickname,
account.profileImg,
artWorks.thumbnail,
artWorks.view,
artWorkLikes.count(),
artWorks.category,
artWorks.created
))
.from(artWorks)
.join(account).on(account.id.eq(artWorks.account.id))
.leftJoin(artWorkLikes).on(artWorkLikes.artWorks.eq(artWorks))
.limit(10)
.where(isLastArtworkId(lastArtworkId),
isCategory(category),
artWorks.scope.isTrue())
.groupBy(artWorks.id)
.orderBy(artWorks.created.desc())
.fetch();
}