페이지네이션(페이징)Offset-based Pagination (오프셋기반)실제 쿼리(mysql기반)offset 기반의 고려해볼 사항Cursor-based Pagenation (커서 기반)cursor 기반의 고려해볼 사항Offset vs CursorREF
페이지네이션(페이징)
- 서버와 클라이언트의 상황에서 보통 모든 데이터를 한번에 가져오지 않습니다.
- 보통은 필요한 갯수를 저장하고 상황에 맞춰 정렬기준이 조건에 추가됩니다.
- 정렬기준+갯수
- 이러한 조건을 맞춰서 데이터를 가져오는 것을 Pagenation, 페이징이라고 합니다.
- 페이지네이션을 처리하는 방법으로는 대표적으로 오프셋방식, 커서방식이 존재합니다.
Offset-based Pagination (오프셋기반)
실제 쿼리(mysql기반)
SELECT user_id FROM member ORDER BY created_at DESC LIMIT 20,40
- 좀 더 일반적인 변수를 사용한 쿼리문
- pagenum → 페이지(1,2..)
- takenum → 한번에 불러올 데이터(row) 수
SELECT user_id FROM member ORDER BY created_at DESC LIMIT + ($takenum*($pagenum-1)) + ',' + $takenum
- 매우 직관적이고 이해하기가 쉬우며 사용하기도 편리합니다. 보통 밑에 이동할 수 있는 페이지가 있고 페이지를 건너 뛸 수 있다면 offset 기반입니다.
offset 기반의 고려해볼 사항
- 사용자가 매우 많은 사이트에서 글을 쓰는 사람이 매우 많다면? 초당 평균 20개의 새로운 데이터가 들어온다 가정하고 한 페이지당 20개의 데이터를 보여준다면 ?
- 이를 오프셋 기반으로 구현을 한다면 1페이지의 20개의 글을 본 사람이 2페이지의 글을 보려고 눌렀을 때 방금 본 20개의 데이터를 그대로 보게되는 현상이 발생하게 됩니다.
- 즉 중복데이터를 출력하게 되며 이런 최악은 아니더라도 이런 상황은 빈번하게 나타나게 될 것입니다.
- 또한 정렬기준 조건에 따라서 row가 몇번 째 데이터인지 바뀌게 되는데, 당연히 DB는 모든 경우에 따른 rownum을 가지고 있지 않기 때문에 row의 개수가 많아질 수록 성능에 문제가 생기게 됩니다.
Cursor-based Pagenation (커서 기반)
- cursor를 포인터라고 생각하면 이해가 더 쉬울 것이라 생각합니다.
- curosr를 만들어 두고 포인터가 가리키는 것 다음부터 n개의 데이터를 주세요 하는 방법입니다.
- 오프셋 방식같은 경우는 “n개의 데이터를 page-1만큼 스킵하고 그 다음 n개의 데이터를 주세요" 이런 흐름으로 요청이 이루어집니다.
- 만약 cursor가 Integer로 된 id값이라면 id자체를 커서로 사용해도 상관은 없다.
- 하지만 포인터를 회원가입 시기로 지정한다면 어떻게 될까? 만들어진 순서대로 출력하고 싶을 경우
cursor 기반의 고려해볼 사항
- 상황 설명을 위해 LIMIT을 사용해서 현재 가정한 테이블의 상태는 아래라고 가정해 보겠습니다.
SELECT user_id, created_at FROM member ORDER BY created_at ASC LIMIT 4
user_id | created_at |
123 | 1 |
124 | 2 |
120 | 3 |
114 | 4 |
- 별다른 문제가 없어 보이지만 만약에 user_id가 “114”와 동시에 가입한 “115,116,117”이 있을 경우에?
- cursor를 created_at으로 설정할 경우 cursor>4인 리스트를 다음 페이지에 불러올 텐데 그럴 경우에 115,116,117의 데이터는 누락이 발생하게 됩니다.
커서 기반 페이지네이션을 위해서는 정렬 기준에 포함되는 필드 중 하나이상은 반드시 고유값을 가져야 한다!
- 다른 예시로 테이블을 한번 수정한 결과는 아래와 같습니다.(억지스럽지만 age는 중복값이 없다고 가정합니다.)
SELECT user_id, created_at FROM member WHERE (created_at > 4) or (created_at=4 and age> 20) ORDER BY created_at ASC LIMIT 4
user_id | created_at | age |
123 | 1 | 17 |
124 | 2 | 18 |
120 | 3 | 19 |
114 | 4 | 20 |
- 이렇게 쿼리를 날린다면 creaetdAt이 4인 115,116,117의 데이터는 누락되지 않습니다. 이제 문제가 모두 해결된 것처럼 보이지만 여기서도 문제점을 한번 더 생각해볼 수 있습니다.
- 이렇게 조건절에 필요사항이 달림에 따라서 클라이언트 측에서도 이를 이해하고 ORDER BY에 달려있는 필드들을 이해하고 이에 해당하는 값들을 요청시 마다 보내야 하는데
- 당연히 클라이언트 측은 이런걸 좋아하지 않고 원치 않습니다. 그렇기 때문에 WHERE절에 걸리는 조건들을 이용해서 고유한 값인 커서를 만들면 됩니다.
- 간단한 예시로 createdAt이 최대 6자고 age가 최대 3자라고 가정하면
- 두 가지를 합쳐서 4+17 ⇒ 000004017 이런식으로 가공해서 커서 값으로 사용을 하는 것이다. 커서를 기준으로 출력 후 그 다음 리스트는 이 커서 기준으로 다음 데이터를 가지고 오면 위의 문제점들이 해결이 됩니다.
Offset vs Cursor
- 위의 두개 기준으로만 보면 커서 방식이 더 좋아 보이지만 데이터의 입력이 매우 드물고 또는 중복되는 데이터가 나와도 크게 상관이 없는 경우, 데이터의 수 자체가 많지 않아서 성능 고려를 딱히 안해도 되는 경우에는 오프셋을 사용하는 것도 하나의 방법이 될 것 같습니다.
- 하지만 유저가 접속해서 사용하는 경우 커서기반 페이징 방식을 사용하는것이 좋습니다.