커서 기반 페이지네이션
참조 : https://jojoldu.tistory.com/528?category=637935
대용량 트래픽 프로젝트에서 트위터를 모방한 애플리케이션을 구현하기 위해 cousor 기반으로 페이징을 시도했다.
페이지네이션
책의 페이지처럼 데이터를 묶음으로 분리
왜? offset 기반과 coursor 기반의 차이점
offset
은 조건에 해당하는 모든 데이터를 가져온 뒤 페이지사이즈를 기반으로 페이징을 한다.
예를 들어
SELECT *
FROM items
WHERE 조건문
ORDER BY id DESC
OFFSET 10000
LIMIT 20
이라 하면 10000부터 20개의 데이터를 가져오는 과정에서
1부터 10000까지의 10000개의 행을 버려야하는데도 불구하고 읽어와야 한다.
이러한 방식은 프론트 단에 명시적으로 모든 데이터의 개수와 페이지 총 개수를 전해줄 수 있지만
불필요한 시간이 늘어나고
사용자가 다음 페이지의 데이터를 보는 도중 게시물이 삭제된다면 페이지의 데이터가 하나씩 앞으로 이동하며 사용자가 보지 못하고 넘어가는 게시물이 생길 수 있다.
coursor
는 현재 보고 있는 데이터의 마지막 번호인 coursor 와 몇 개의 데이터를 불러올 것인지에 대한 size를 통해 호출할 수 있다.
SELECT *
FROM items
WHERE 조건문
AND id < 마지막조회ID # 직전 조회 결과의 마지막 id
ORDER BY id DESC
LIMIT 페이지사이즈
offset 과 달리 조회 시작 부분을 인덱스로 빠르게 찾아 읽도록 한다. (PK를 조건문으로 사용했기 때문에 빠르게 조회됨)
coursor 의 데이터가 인덱싱 되어 있다는 전제하에 db는 cursor 를 실시간 데이터를 효율적으로 다룰 수 있다.
데이터의 추가, 삭제에 안정적이게 된다.
다만 총 데이터의 개수를 알 수 없어 원하는 페이지로 이동하는 것이 불가능하다.
querydsl 세팅
QueryDslSupport
@Configuration
class QueryDslSupport(
@PersistenceContext
private var entityManager: EntityManager
) {
@Bean
fun queryFactory() = JPAQueryFactory(entityManager)
}
bubbleQueryDslRepositoryImpl
package com.sparta.bubbleclub.domain.bubble.repository
import com.querydsl.core.types.Projections
import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.jpa.impl.JPAQueryFactory
import com.sparta.bubbleclub.domain.bubble.dto.response.BubbleResponse
import com.sparta.bubbleclub.domain.bubble.entity.QBubble
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice
import org.springframework.data.domain.SliceImpl
import org.springframework.stereotype.Repository
@Repository
class BubbleQueryDslRepositoryImpl(
val queryFactory: JPAQueryFactory
) : BubbleQueryDslRepository {
private val bubble: QBubble = QBubble.bubble
// 커서기반 페이징, keyword like 조건 (keyword 인기 검색어 )
override fun getBubblesByKeyword(bubbleId: Long?, keyword: String?, pageable: Pageable): Slice<BubbleResponse> {
val pageSize = pageable.pageSize.toLong()
val result = queryFactory.select(
Projections.constructor(
BubbleResponse::class.java,
bubble.id,
bubble.content,
bubble.member.nickname,
bubble.createdAt
)
).from(bubble)
.innerJoin(bubble.member)
.where(
ltBubbleId(bubbleId),
bubble.content.like("$keyword%")
)
.orderBy(bubble.id.desc())
.limit(pageSize + 1)
.fetch()
return checkLastPage(pageable, result)
}
// id 보다 작은 bubbles
private fun ltBubbleId(bubbleId: Long?): BooleanExpression? {
if (bubbleId == null) {
return null
}
return bubble.id.lt(bubbleId)
}
// 마지막 페이지 확인
private fun checkLastPage(pageable: Pageable, result: MutableList<BubbleResponse>): Slice<BubbleResponse> {
var hasNext = false
if(result.size > pageable.pageSize ) {
result.removeAt(result.size-1)
hasNext = true
}
return SliceImpl<BubbleResponse>(result, pageable, hasNext)
}
}
val result = queryFactory.select(
Projections.constructor(
BubbleResponse::class.java,
bubble.id,
bubble.content,
bubble.member.nickname,
bubble.createdAt
)
).from(bubble)
.innerJoin(bubble.member)
.where(
ltBubbleId(bubbleId),
bubble.content.like("$keyword%")
)
.orderBy(bubble.id.desc())
.limit(pageSize + 1)
.fetch()
Projection 생성자
querydsl 반환값을 dto 로 받음
limit (pageSize + 1)
내가 조회한 데이터 이후에도 데이터가 있는지 프론트단에 알려주기 위해 지정한 size 보다 1만큼 큰 limit 을 지정한다.
1. 총 30개의 데이터에서 pageSize 를 10으로 지정해서
11~20 의 데이터를 보고싶다면
11~21 의 데이터, 총 11개의 데이터를 불러옴.
2. 총 30개의 데이터에서 pageSize 를 10으로 지정해서
21~30 의 데이터를 보고싶다면
21~30 의 데이터, 총 10개의 데이터를 불러옴.
// 마지막 페이지 확인
private fun checkLastPage(pageable: Pageable, result: MutableList<BubbleResponse>): Slice<BubbleResponse> {
var hasNext = false
if(result.size > pageable.pageSize ) {
result.removeAt(result.size-1)
hasNext = true
}
return SliceImpl<BubbleResponse>(result, pageable, hasNext)
}
result 의 size 가 우리가 pageable 에서 지정한 size 보다 크면/작으면 ( 뒤에 데이터가 더 남아있으면/없으면 )
result 의 마지막 인덱스 데이터를 제거한 뒤에 SliceImpl 의 인자로 다음 페이지가 있다는/없다는 true/false 값을 넘겨준다.
1. 11>10 true
21을 result 에서 제거
2. 10>10 false
X
'왕초보일지' 카테고리의 다른 글
240218 TIL | Redis 사용 (2) | 2024.02.18 |
---|---|
240216 TIL | (1) | 2024.02.16 |
240214 TIL | (0) | 2024.02.14 |
240213 TIL | 테스트 코드 (0) | 2024.02.13 |
240208 TIL | (0) | 2024.02.08 |