์: LIMIT 10 OFFSET 30 (4๋ฒ์งธ ํ์ด์ง)
๋ฌธ์ ์ :
OFFSET 1000000 ๊ฐ์ ๊ฒฝ์ฐ ๋งค์ฐ ๋นํจ์จ์ )์:cursor=100 โ ID 100 ์ดํ์ 10๊ฐ๋ฅผ ๊ฐ์ ธ์๋ผ
ํน์ง:
๋ณดํต ์๋ ์ค ํ๋๋ฅผ cursor๋ก ์ฌ์ฉํจ:
size : ๋ช ๊ฐ ๊ฐ์ ธ์ฌ์งcursor : ์ด์ด์ ๊ฐ์ ธ์ฌ ์ง์ (null์ด๋ฉด ์ฒซ ํ์ด์ง)GET /posts?size=10
์๋ฒ ์๋ต:
{
"data": [...], // 10 items
"nextCursor": 150
}
GET /posts?size=10&cursor=150
์๋ฒ๋ DB์์:
WHERE id < 150
ORDER BY id DESC
LIMIT 10;
์ด๋ฐ ์์ผ๋ก ๊ฐ์ ธ์ด.
Post ์ํฐํฐ ๊ธฐ์ค
public interface PostRepositoryCustom {
Slice<Post> findByCursor(Long cursor, int size);
}
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepositoryCustom {
private final JPAQueryFactory query;
@Override
public Slice<Post> findByCursor(Long cursor, int size) {
QPost post = QPost.post;
List<Post> result = query
.selectFrom(post)
.where(
cursor != null ? post.id.lt(cursor) : null
)
.orderBy(post.id.desc())
.limit(size + 1) // ๋ค์ ํ์ด์ง ์กด์ฌ ์ฌ๋ถ ํ์ธ
.fetch();
boolean hasNext = result.size() > size;
if (hasNext) {
result.remove(size);
}
return new SliceImpl<>(result, PageRequest.of(0, size), hasNext);
}
}
cursor ๋ฐฉ์์์ limit(size + 1) ๋ก ๋ค์ ํ์ด์ง ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ๋จ.
๋ง์ฝ, ๋ค์ ํ์ด์ง๊ฐ ์์ผ๋ฉด result.size() > size
{
"posts": [...10๊ฐ...],
"nextCursor": 123,
"hasNext": true
}
์ ๊ธ์ด ์ถ๊ฐ๋์ด๋ ๊ฒฐ๊ณผ ์์๊ฐ ํ๋ค๋ฆฌ์ง ์์.
WHERE id < 150
์ ๋ ฌ + Range Scan๋ง ํ๋ฉด ๋์ด OFFSET 1000000 ๋ณด๋ค ์์ญ~์๋ฐฑ๋ฐฐ ๋น ๋ฆ
NextCursor๋ง ๋ณด๋ด๋ฉด ๋๋๊น.
| ๋ฐฉ์ | ์ฅ์ | ๋จ์ |
|---|---|---|
| Offset(page) | ๊ตฌํ ์ฌ์, ํ์ด์ง ๋ฒํธ ์ ๊ทผ ๊ฐ๋ฅ | ๋๋ฆผ, ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ์ทจ์ฝ |
| Cursor | ๋น ๋ฅด๊ณ ์์ ์ , SNS์ ์ต์ | ํ์ด์ง ๋ฒํธ ์ด๋ ๋ถ๊ฐ, ๋ณตํฉ์ ๋ ฌ ์ด๋ ค์ |