Health-Genie

무한스크롤 도입

j9972 2024. 3. 2. 11:31
728x90

기존에는 트레이너 프로필 조회하는 기능을 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

https://thalals.tistory.com/350