[Spring JPA] Slice를 활용해서 무한스크롤 기능 구현하기

이신영·2025년 7월 2일
0

Spring

목록 보기
19/19
post-thumbnail

최근 무한스크롤 기반의 일기 웹사이트 프로젝트를 진행하던 도중, 일기 전체, 내가 쓴 일기 목록, 열람 기록, 북마크 리스트 등 대부분의 리스트 API에 페이징이 필요했다.

매번 Page를 썼는데 Page에는 일기가 만약 수십만건이라하면 수십만건의 데이터 조회쿼리가 들어가기 때문에 비효율적이라고 생각해서 좀 더 무한 스크롤 기능에 어울리는 기능인 Slice를 써보았는데 효율적인 것 같아서 글을 적어보겠다.


Page vs Slice 차이

구분PageSlice
총 개수totalElements, totalPages 제공제공하지 않음
다음 페이지 여부hasNext()hasNext()
추가 정보totalElements, totalPages없음
쿼리 비용SELECT COUNT(*) 필요불필요
용도페이지 기반 UI, 전체 건수 필요할 때무한 스크롤

실제 적용 사례

기존 구조

내 프로젝트의 /api/diaries/public, /api/diaries/feed/recent, /api/diaries/me, /api/views/me API는 모두 Page 기반으로 구성되어 있었다.

하지만 피드나 일기 목록은 사용자가 스크롤로 내려가며 끝없이 내려보는 형태가 이상적이며, 전체 개수를 표시할 필요도 없었다.


Slice 적용 이유 정리

무한 스크롤에 최적화

  • 사용자는 “다음 페이지가 있는지”만 알면 되므로 Slice의 hasNext로 충분
  • “전체 몇 개 중 몇 번째”는 필요 없었음

성능 개선

  • Page는 total count를 구하기 위한 SELECT COUNT(*) 쿼리가 무조건 발생
  • Slice는 이를 생략해 쿼리 비용이 줄어듦
  • 피드/목록의 데이터가 많아질수록 이 차이는 커진다
  • 10만건 / 20Limit 이라 치면 단순 COUNT가 30ms로 가정할 때 총 5,000페이지를 요청해야 하니까 30ms * 5000회 = 150,000ms 이 발생하므로 2.5분의 순수한 오버헤드가 줄어든 셈이다.

Slice 작동 방식

  1. 프론트가 /api/diaries/feed/recent?page=0&size=10 요청
  2. 서버는 LIMIT 10 OFFSET 0 으로 10개 데이터를 가져오고, 다음 페이지 존재 여부 판단 후 hasNext = true/false 반환
  3. 프론트는 hasNext=true 면 다음 페이지(page=1) 요청, 아니면 호출 중단
  4. 사용자는 무한 스크롤 UX로 자연스럽게 이용 가능

Slice를 적용하는 방법

Slice를 쓰려면 리포지토리에서 메서드 반환 타입만 Page → Slice로 바꿔주면 끝이다.

public interface DiaryRepository extends JpaRepository<Diary, Long> {
    Slice<Diary> findAllByVisibleTrueOrderByCreatedAtDesc(Pageable pageable);
}

서비스/컨트롤러에서 기존 Page로 받던 부분을 Slice로 변경하면 된다.

@GetMapping("/api/diaries/feed/recent")
public ResponseEntity<Slice<VisibleDiarySummaryDto>> getRecentFeed(
        @PageableDefault(size = 10, sort = "createdAt", direction = DESC) Pageable pageable
) {
    Slice<VisibleDiarySummaryDto> feed = diaryService.getRecentFeed(pageable);
    return ResponseEntity.ok(feed);
}

이렇게 하면 무한 스크롤에 필요한 hasNext, content, size, number 등의 정보만 담긴 가벼운 응답으로 전환할 수 있다.


실제 예시 – 응답 예시

{
  "content": [
    {
      "id": 8,
      "title": "월요일 싫어",
      "content": "내일 출근하기 싫다...",
      "createdAt": "2025-07-02T09:26:57.496",
      "viewed": true,
      "totalReactionCount": 3,
      "commentCount": 2
    }
  ],
  "pageable": { ... },
  "last": false,
  "first": true,
  "hasNext": true,
  "size": 10,
  "number": 0
}

프론트는 hasNext만 보고 다음 페이지를 호출하면 되므로 구현과 유지가 단순해진다.


그럼 언제 Page를 쓰면 좋을까?

  • 전체 데이터 개수를 보여줘야 할 때
  • 페이지네이션 UI (1, 2, 3, 4...) 를 제공해야 할 때
  • 관리자 화면, 게시판 등의 특정 상황

그 외의 대부분의 피드/리스트에서는 Slice 기반 무한 스크롤이 유리하다.


정리

내 프로젝트 기준으로도 체감되는 효율 개선이 있었고, 불필요한 COUNT 쿼리를 없앰으로써 대량 데이터에서 성능 병목을 방지할 수 있다 !

구현하는 서비스가 무한 스크롤 기반이라면, Page 대신 Slice로 전환해 보길 추천한다 👍

profile
후회하지 않는 사람이 되자 🔥

0개의 댓글