데이터 셋이 수천만 건일 때, 해당 데이터를 모두 전송하는 것은 서버의 부하가 생기고, 클라이언트 또한 랜더링 시간이 증가되고, 과도한 스크롤로 사용자 경험이 안좋아진다.
이를 해결하기 위해서 데이터를 일정 개수로 잘라서 제공하면서 서버와 클라이언트 모두 윈-윈할 수 있다.
크게 offset 방식과 cursor 방식이 있다.
offset 방식은 offset과 limit를 사용하는 방식이다.
여기서 offset n(자연수)은 n만큼의 데이터를 건너뛰라는 의미이고, limit n(자연수)은 n만큼의 데이터를 가져오라는 의미이다.
따라서 select * from table limit 10 offset 10 쿼리가 있을 때, 20개의 데이터를 조회해서 앞의 10개의 데이터는 건너뛰고, 뒤의 10개의 데이터만 가져온다.
offset 방식의 장점은 다음과 같다.
offset 방식의 단점은 아래와 같다.
데이터 양이 많은 경우에 성능 저하가 발생한다.
offset 방식은 모든 데이터를 불러온 뒤, 해당 페이지 값이 아닌 데이터들은 모두 건너뛰어서 데이터를 전송한다.
따라서 limit가 100이고, 현재 페이지가 200페이지라면 총 20,000개의 데이터를 조회한 후에 19,900의 데이터를 건너뛰어서 100개의 데이터를 전송한다.
데이터의 삽입과 삭제가 자주 일어나는 경우 데이터가 누락되거나 중복될 수 있다.

총 8개의 데이터가 있고, 데이터를 3개씩 페이징해서 보고 있다고 가정하자.
사용자A가 1페이지를 보고 있다면 A,B,C 총 3개의 데이터를 보고 있을 것이다.
사용자A가 1페이지를 보고 있는 중에 사용자B가 데이터 C를 제거한 후에 사용자 A가 2페이지로 넘어간다고 해보자.
그 결과, 6개의 데이터를 가져와서 앞의 3개의 데이터를 건너뛰기 때문에, 사용자 A는 D,E,F가 아닌 E,F,G를 보게 되어 이전 페이지로 넘어가지 않는다면 데이터 D를 볼 수 없다.

마찬가지로 A,B,C 사이에 데이터 B-2가 추가가 된다면, 2페이지에서는 C,D,E를 보게 되어 데이터 C를 중복해서 보게 된다.
특정 포인터(커서)를 사용해서 필요로 하는 데이터가 있는 위치부터 시작해서 필요한 만큼 데이터를 전송하는 방식이다.
커서로는 순차적으로 증가한 id 또는 타임 스탬프, 인코딩된 커서, 복합 커서 등이 있다.
created_at 날짜를 기준으로 10개씩 커서 기반 페이지네이션을 한다고 가정해보자.
그렇다면 첫 데이터를 다음과 같이 가져오고, 클라이언트에게 반환할 것이다.
GET /table?limit=10
select * from table order by created_at desc limt 10
{
"tables": [...]
"cursor": "last_created_at"
}
이후 다음 페이지부터 다음과 같이 가져올 것이다.
GET /table?limit=10&cursor="last_create_at"
select * from table where created_at < 'last_created_at' order by created_at desc limt 10
{
"tables": [...]
"cursor": "last_created_at2"
}
cursor 방식의 장점으로는 다음과 같다.
불필요한 데이터를 조회하지 않고, 필요한 데이터만 반환할 수 있다.
일관된 결과를 제공한다.
이러한 장점 덕분에 SNS와 같이 데이터가 자주 변동되는 경우에 cursor 방식이 유용하다.
cursor 방식의 단점은 다음과 같다.
구현이 복잡하다.
커서 방식의 성능은 인덱스에 따라서 결정된다. 만약 인덱스가 없는 컬럼을 커서로 사용한다면 오히려 좋은 성능이 안 나올 수 있다.
장점으로 일관된 결과를 제공한다고 했다. 이는 커서가 순차적인 id 또는 타임스탬프일 경우 쉽게 구현할 수 있지만, 커서가 복합 커서 또는 필터와 같이 사용하는 경우 일관된 결과를 제공하기 위해 개발자가 고려해야 할 점이 많아진다.
offset 방식의 경우 다음과 같을 때 사용하면 좋다.
데이터의 삽입과 삭제가 자주 일어나지 않거나 정확한 순서 보장이 필요하지 않을 때: offset 방식은 데이터 중복과 누락이 발생할 수 있다. 하지만 이점이 그렇게 중요하지 않다면 적용할 만하다.
데이터 양이 많지 않을 때: 데이터 양이 많을 때 성능 저하가 발생하기 때문에 데이터 양이 적다면 적용할 만하다.
페이지 건너뛰기가 필요할 때: 사용자가 페이지 번호를 입력해서 특정 페이지로 바로 이동하는 경우 offset을 사용하는 것이 유리하다.
cursor 방식의 경우 다음과 같을 때 사용하면 좋다.
대규모 데이터일 때: offset 방식의 경우 대규모 데이터를 조회할 때 성능 저하가 발생하므로 대규모 데이터에선 커서 방식이 적합하다.
정확한 순서 보장이 필요할 때: 커서 방식은 페이지 간 중복이나 누락 없이 데이터를 처리할 수 있기 때문에 데이터 순서를 유지해야 할 때 cursor 방식이 더 적합하다.
offset과 cursor 방식의 장 단점을 학습하면서 프로젝트 진행 상황, 데이터 양를 기준으로 페이징 방식을 선택할 것 같다.
Offset vs Cursor-Based Pagination: Which is the Right Choice for Your Project?