페이지네이션이란
서버에서 데이터를 가져올 때 모든 데이터를 한번에 가져오지 않음.
서버 입장에서도 클라이언트 입장에서도 ‘특정한 정렬 기준에 따라 지정된 개수’의 데이터를 가져오는 것이 필요. 이를 ‘페이지네이션’이라고 표현함
페이지네이션은 두가지 방식으로 처리가 가능
1.
Offset-based Pagination
•
DB의 offset 쿼리를 사용하여 페이지 단위로 구분하여 요청/응답
2.
Cursor-based Pagination
•
클라이언트가 가져간 마지막 row의 순서상 다음 row들의 지정된 개수 요청/응답
1. Offset-based Pagination
Limit 쿼리에 콤마를 붙여 건너 뛸 row 숫자를 지정
SELECT ID FROM 'employees' ORDER BY id DESC LIMIT 20, 40
JavaScript
복사
예를 들어 한 페이지에 N 게시글이 보여지는 A 페이지의 게시글 목록에 대한 쿼리 스트링은
String offsetQuery = "SELECT id FROM employees ORDER BY id Desc LIMIT '(?[N값]*(?[A값]-1))',?[N값]";
JavaScript
복사
하지만 여기엔 두가지 문제가 발생
[문제1] 중복 데이터 노출
1페이지에서 20개의 row를 불러와서 유저에게 1페이지를 띄어줌. 고객이 1페이지를 보는 사이, 다른 이용자가 5개의 게시글을 새로 올림.
유저는 1페이지 게시글들을 둘러 보다 2페이지를 누를 경우 유저는 이미 1페이지에서 보았던 글 20개중 마지막 5개를 다시 2페이지에서 보게 됨
[문제2] OFFSET 쿼리의 퍼포먼스 이슈
대부분의 RDBMS에서 offset 쿼리의 퍼포먼스 이슈가 발생함.
정렬 기준에 대해 해당 row가 몇 번째의 순서를 갖는지 알지 못함 따라서 offset 값을 지정하여 쿼리를 한다고 했을 때 임시로 해당 쿼리의 모든 값들을 전부 만들어 놓은 후 지정된 개수만 순회하여 자르는 방식을 사용하게 됨.
offset이 작은수라면 크게 문제가 되지 않지만 row 수가 아주 많고 offset 값이 올라가질수록 퍼포먼스는 이에 비례하여 떨어지게 됨
그렇다면 오프셋 기반 페이지네이션은 언제 사용하는게 적합할까?
•
데이터의 변화가 거의 없다시피하여 중복 데이터가 노출될 염려가 없는 경우 (개인 블로그 등)
•
일반 유저에게 노출되는 리스트가 아니라 중복 데이터가 노출되어도 크게 문제 되지 않는 경우
•
검색 엔진이 인덱싱 할 이유도 유저가 마지막 페이지를 갈 이유도, 오래 된 데이터의 링크가 공유 될 이유도 없는 경우
•
애초에 row 수가 그렇게 많지 않아 특별히 퍼포먼스 걱정이 필요 없는 경우
2. Cursor-based Pagination
오프셋 기반 페이지네이션은 우리가 원하는 데이터가 몇 번째에 있다는 데에 집중하고 있다면,
커서 기반 페이지네이션은 우리가 원하는 데이터가 어떤 데이터의 다음에 있다는 데에 집중함.
n개의 row를 스킵한 다음 10개를 주세요가 아닌 이 row 다음꺼부터 10개 주세요를 요청하는 식
SELECT id, name FROM 'employees' where id < 996 ORDER BY id DESC
JavaScript
복사
여기서 cursor가 employees 테이블의 id이고 그 값은 996.
만약 부서번호가 낮은 사원들부터 보고싶다면
SELECT id, name, deptNo FROM employees WHERE deptNo > 10 ORDER BY deptNo ASC
JavaScript
복사
cursor가 deptNo이며, 그 값은 10.
여기에는 한가지 문제가 있음. id는 고유값이라서 절대 겹칠 일이 없었지만 부서번호는 아님.
위의 쿼리라면 deptNo가 10인 사원들은 모두 skip되고 한참 밑에 있는 사원들이 나오는 문제가 발생.
따라서 커서 기반 페이지네이션을 위해서는 반드시 정렬 기준이 되는 필드 중 적어도 하나는 고유값이여야 함
SELECT id, name, deptNo
FROM employees
WHERE (deptNo > 10) OR (deptNo = 10 AND id>446)
ORDER BY deptNo ASC, id ASC
JavaScript
복사
이 쿼리에서 문제점 발생
[문제1] 인덱스 타지 않는 쿼리
대부분의 RDBMS에서는 WHERE에 OR를 사용하면 인덱싱을 제대로 못 태움
→ 인덱스를 타지 않는 쿼리에 대해서는 추가로 글 작성 해야겠음
[문제2] 클라이언트가 매 페이지 요청 시 ORDER BY에 걸려 있는 deptNo와 id의 필드를 모두 알아야하고 이 값들을 전부 보내야 함
→ 몇 개의 필드를 ORDER BY 조건절로 사용하든, 항상 같은 방향으로 특정 값을 부여하고, 이를 cursor로 사용
select id, name, deptNo,
CONCAT(LPAD(POW(10, 1O)-deptNo, 10 , '0'), LPAD(POW(10,10)-id, 10, '0)) as cursor
FROM employees
ORDER BY deptNo DESC, id DESC
SQL
복사
CONCAT: MySQL 내장함수, 문자열 합침 (숫자인 경우 문자열 자동변환)
LPAD: MySQL 내장함수, 문자열 / 숫자를 지정된 길이의 문자열로 채움 (왼쪽)
select id, name, deptNo,
CONCAT(LPAD(POW(10, 1O)-deptNo, 10 , '0'), LPAD(POW(10,10)-id, 10, '0)) as cursor
FROM employees
WHERE CONCAT(LPAD(POW(10, 1O)-deptNo, 10 , '0'), LPAD(POW(10,10)-id, 10, '0)) < '9999000099000054'
ORDER BY deptNo DESC, id DESC
SQL
복사