무한스크롤 도입
기존에는 트레이너 프로필 조회하는 기능을 offset을 통한 페이징 처리를 하였습니다.
하지만 데이터 양이 많을때 offset 방식의 문제점은 아래와 같이 존재 하였습니다
- 맨뒤의 페이지는 모든 데이터를 조회한 후에 마지막 페이지를 조회하기 때문에 뒤로 갈수록 조회가 느려집니다.
thalals님의 자료를 보면 특정 페이지의 데이터를 읽을 때와 맨 뒤의 페이지를 읽을 때 성능 차이를 알 수 있습니다.
offset 방식 [ 7만건의 데이터 중 ]
- 첫번째 페이지를 읽을 때 : 0.738
- 마지막 페이지를 읽을 때 : 2785.614 -> 0.984 [ 모든 데이터 조회 -> 특정 페이지 조회 ]
no offset 방식 [ 7만건의 데이터 중 ]
- 첫번째 페이지를 읽을 때 : 0.032
- 마지막 페이지를 읽을 때 : 0.044 -> 0.041
보시다 시피 no offset 같은 경우에는 첫번째 페이지와 마지막 페이지의 성능차이가 크게 없으며, 페이지가 늘어도 동일한 성능을 보장합니다.
그렇다면 no offset이 뭔가?
말 그대로 offset을 사용하지 않고 페이지네이션을 진행한다는 말입니다.
[ offset - 몇번째 부터 시작할것인가? ]
즉, 어디서 부터 시작할지를 offset을 사용하지 않고 판단한다는 것입니다.
어떻게 사용하는가?
offset을 대신해 탐색을 해준 위치로 이동시켜줄 무언가만 있으면 됩니다.
[ 아래 저의 코드에서는 lastIndex를 통해서 지정해주었습니다 ]
select * from table where idx > 0 limit 100;
이런식으로 간단하게 볼 수 있습니다.
코드 보기
기존에 offset을 통한 페이징 처리에서 현재 무한스크롤로의 변화를 보여드리겠습니다.
controller
[ 변경 전 ]
@GetMapping
public ResponseEntity<Result> getAllProfile(@RequestParam(required = false, defaultValue = "0") int page) {
List<ProfileResponseDto> response = profileService.getAllProfile(page, 10);
return ResponseEntity.ok(Result.of(response));
}
[ 변경 후 ]
@GetMapping
public ResponseEntity<Result> getAllProfile(@RequestParam(value = "lastIndex", required = false) Long lastIndex) {
List<ProfileResponseDto> response = profileService.getAllProfile(lastIndex);
return ResponseEntity.ok(Result.of(response));
}
controller 같은 경우는 parameter로 받는 데이터가 offset & limit 에서 lastIndex로의 변화에 집중하면 될거 같습니다.
service
[ 변경 전 ]
@Transactional(readOnly = true)
public List<ProfileResponseDto> getAllProfile(int page, int size) {
List<TrainerInfo> profiles = trainerQueryRepository.findAllProfiles(page, size);
return ProfileResponseDto.of(profiles);
}
[ 변경 후 ]
@Transactional(readOnly = true)
public List<ProfileResponseDto> getAllProfile(Long lastIndex) {
Long maxId = lastIndex;
if (maxId == null) {
maxId = trainerQueryRepository.findMaxId().orElse(0L);
}
List<TrainerInfo> profiles = trainerQueryRepository.findAllProfilesSortByLatest(maxId);
return ProfileResponseDto.of(profiles);
}
repository ( querydsl 작성 )
[ 변경 전 ]
public List<TrainerInfo> findAllProfiles(int page, int size) {
return query
.selectFrom(trainerInfo)
.orderBy(trainerInfo.id.desc())
.offset(page)
.limit(size)
.fetch();
}
[ 변경 후 ]
public List<TrainerInfo> findAllProfilesSortByLatest(Long lastIndex) {
return query
.selectFrom(trainerInfo)
.where(trainerInfo.id.loe(lastIndex))
.orderBy(trainerInfo.id.desc())
.limit(10)
.fetch();
}
public Optional<Long> findMaxId() {
return Optional.ofNullable(query
.select(trainerInfo.id.max())
.from(trainerInfo)
.fetchOne());
}
기존에 비해 만일 lastindex가 null이 나오는 경우를 막기위해서 findMaxId()를 추가적으로 넣어주었습니다.
참고
https://medium.com/@hee98.09.14/무한-스크롤-기능-구현하기-with-no-offset-a580bf2addc5
무한 스크롤 기능 구현하기 (with No Offset)
OFFSET 방식의 페이지 처리 문제점
medium.com