프로젝트를 제작하다 보면 정말 많은 양의 데이터를 다루게 되는 경우가 있다. 또한 그 방대한 양의 데이터를 사용자에게 전달할 일이 필요 할 때가 있다. 그때 사용자가 요청한 모든 값을 가져오는 것은 매우 비효율적인 방법이다. 해당 데이터를 가져오는 과정에 서버에 과부하가 올 가능성이 커지기 때문이다.
그래서 과부하를 줄이기 위해 나온 방법이 바로 Pagination(페이지네이션)이라고 한다. 페이지네이션은 데이터를 원하는 단위로 정렬하게 되고 페이지 크기에 따라 나눠 전달한다.
Pagenation(페이지네이션)
데이터를 몇 번째 페이지인지, 해당 페이지에 몇 개의 데이터를 가져올 것인지, 정렬 기준을 토대로 정보를 전달하는 것이다.
Pageable
이것은 Spring에서 제공하는 Pagination을 위한 인터페이스이다.
가장 상위 인터페이스인 Pageable에서는 다양한 메소드가 있지만 우리가 입력한 값을 편하게 사용하기 위한 getPageNumber(), getPageSize(), getOffset() 등을 추상화 시켜놓았다. 그래서 우리가 처음에 repository 나 controller에 인자로 설정을 할때 값을 받아 읽을수 있는 Pageable 을 넣어놓는 것이다.
Pageable 의 상속 구조
Spring에서 제공하는 Pagenation을 위한 인터페이스의 상속 구조이다. 우리가 실제로 사용할 PageRequest는 위에서 보이는 것처럼 페이징과 관련된 다양한 인터페이스를 상속 받고 있다.
PageRequest
PageRequest는 인자로써 우리가 설정하는 몇 번째 페이지(page) 와 몇 개를 줄 것인가(size)그리고 이 데이터는 어떻게 정렬하여 전달할 것인가(sort)를 받는다.
인자가 없다면 기본값으로 작동한다.
AbstractPageRequest
그 위에서 작동하는 AbstractPageRequest는 이렇게 페이지와 사이즈를 받아 유효한 범위를 가지고 있는지 확인하고 오류 처리를 담당하고 있다.
그럼 이제 이것으로 JPA에서 어떻게 사용하게 되는지 알아보자
JPA에서의 Pageable 사용방법
pageable 은 JPA를 사용할때 자연스럽게 사용을 할 수 있다. JpaRepository의 상속 구조를 살펴보자
여기서 중요한 것은 PagingAndSortingRepository인데 이름만 봐도 페이징과 관련된 무엇과 정렬에 관련된 뭔가가 있을것 같다는 생각이 들 수 있다. 이렇게 JpaRepository를 사용하는 것으로 자연스럽게 페이징과 관련된 PagingAndSortingRepository 인터페이스를 사용하게 되는데 정확히 Pageable을 어떻게 사용하고 있을까?
해당 인터페이스를 보면 두번째 메소드에 인자로써 Pageable을 사용하고 있다는 것이다. 그럼 우리도 findAll 을 사용한다면 Pageable을 사용하게 되는 것이다.
여기서 두번째 메소드를 보면 파라미터로 Pageable을 받을수 있다는 것을 알수 있다. 때문에 JPA에서 Pageable을 사용할 수 있게 된다.
이렇게 Pageable 을 사용해서 JPA에서 간단하게 페이징을 할 수 있다
Page 와 Slice
페이지를 사용하는 것과 어떻게 사용할지는 다르다. 앞서 우리는 JPA 에서 Pageable을 어떻게 다루는지 알아보았다. 그럼 이제 pageable에서 페이지를 구성하는 것은 두가지의 방법이 있다.
1. Page
이때 모든 페이지를 전송하는 것 보다 원하는 페이지에 해당하는 몇 개의 데이터만 가져오면 전체의 데이터를 가져오는 것보다 훨씬 더 좋은 성능을 자랑하게 되는 것이다.
페이지를 가져오는 방식에서는 DB에 접근하는 쿼리문에 집중해보자
1번째 쿼리
select
??
from
??
where
??
offset ? rows
fetch first(limit) ? rows only
-------------------------------
2번째 쿼리
select
count(??)
from
??
where
??
보면 Page를 다루는 쿼리문에서는 두 개의 쿼리문이 실행되는 것을 알 수 있다.
여기서 count에 집중을 해야하는데 이것이 slice와의 가장 큰 차이이기 때문이다. 여기서 가장 큰 성능의 차이가 난다.
코드로 전환하면 어떻게 되는걸까?
이런식으로 Page형식으로 반환해준다. 인자는 Pageable 객체를 전달한다.
여기서 Pageable 은 쿼리 파라미터로 들어온 page, size, 그리고 order 까지 자동으로 인식한다. 그리고 그 값을 Pageable에 담는다.
그럼 이런식으로 pageable에 입력된 값을 꺼내오게 되고 그 값으로 실제로 구현할 PageRequest의 인자로 넣어주게 되면서 범위와 갯수 그리고 정렬까지 사용하게 되는것이다.
그리고 결과로 가져온 정보를 보면 Slice와의 차이점은 totalPages 와 totalElements 가 존재한다는 것이다. count를 통해 모든 데이터를 size별로 나누고 pages를 전해준것을 알 수 있다.
Page의 중요 포인트는 count 쿼리문이 한번 더 실행된다는 것인데 count는 전체 데이터의 갯수를 조회하기 때문에 효율적이라고 말하긴 어렵다.
또한 알아두어야 할 것이 있다. 만약 페이지가 마지막이거나 한 페이지밖에 없다면 count 쿼리를 실행하지 않는다는 것이다. 이로 인해 조금이라도 성능을 줄이고자 노력한 것이 보인다.
2. Slice
두번째로 페이지를 가져오는 것이 아닌 무한 스크롤을 구현 할 때 사용하는 Slice 방법이 있다.
이런식으로 한 페이지에서 밑으로 스크롤을 하면서 확인하는 방법인데 이는 핸드폰에서 굉장히 많이 사용된다.
select ??
from ??
limit ?,?
단 하나의 쿼리만 불러온다.
이는 slice를 이용하는 가장 큰 이유에도 속하는데 page와는 다르게 다음 데이터가 있는지 없는지만 확인하고 연산을 마친다. 때문에 데이터가 크면 클수록 훨씬 좋은 성능을 보이게 되는 것이다.
위에 사진은 Slice 형식으로 데이터를 가져왔을때 pageable의 현 상태를 보여주는 것이다.
물론 당연하게도 totalPages 와 totalElements은 나타나지 않는다.
그렇다면 어떻게 다음 데이터가 있는지 없는지 알 수 있을까? 방법은 limit + 1 에 있다. 내부적으로 slice를 사용하면 설정해놓은 limit 에 +1을 해서 데이터가 더 존재하는지 확인하는 한 후에 존재한다면 last항목에 false로 반환되며 뒤에 더 데이터가 있다는 뜻이 되고 true가 된다면 더 이상 데이터가 없다는 뜻이다.
1. JPA의 Repository 에 슬라이스타입으로 사용할 쿼리문을 만들어준다
2. 서비스에서 원하는 데이터를 가져온다.
3. 컨트롤러에서또한 반환값으로 Slice로 옳바르게 연결한다.
이렇게 Slice와 Page의 차이를 알아보았다.
두 개의 차이를 정리해보자
- page
- 조회 쿼리 이후 전체 데이터 개수를 한번 더 조회하는 카운트 쿼리가 실행된다.
- 게시판과 같이 총 데이터 개수가 필요할 때 사용하면 좋다.
- Slice
- slice는 limit(size)+1 된 값을 조회한다.
- slice는 카운트 쿼리가 나가지 않고 다음 slice가 존재하는지 여부만 확인하기 때문에 데이터 양이 많으면 slice를 사용하는 것이 성능 상 유리하다.
- 총 데이터 개수가 필요하지 않을 때, 다음 페이지 여부 확인(최근 모바일 리스트 생각해보면 됨, 다음 것을 미리 로딩, 무한 스크롤 등)에 사용된다.
뭐가 더 좋은 것은 딱히 이렇다 할 수 없다. 내가 구현하고자 하는 것을 알맞게 구성하는 것이 가장 좋은 방법이라고 할 수 있을 것이다.
'프로젝트 > 개인 프로젝트' 카테고리의 다른 글
Postman을 통한 테스트 자동화(Feat. 테스트 시나리오) (1) | 2023.10.13 |
---|---|
EC2의 서버와 로컬 서버의 응답 시간은 왜 차이가 나는 것일까? (0) | 2023.09.12 |
Pageable의 Page 와 Slice의 성능 테스트 (0) | 2023.09.11 |
Global Exception & Business Exception(예외 처리) (0) | 2023.09.08 |