ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 무한스크롤 도입
    Health-Genie 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

Designed by Tistory.