무한 스크롤을 구현하면서 관련 정보를 찾아보다가 페이징 처리시 오프셋 방식을 사용하면 성능저하 이슈가 생길 수 있다는 정보를 접하게 됐다. 그래서 두 가지의 다른 페이지네이션 방식인 오프셋 기반 페이지네이션과 커서 기반 페이지네이션에 대해 알아보기로 했다.
페이지네이션은 한정된 네트워크 자원을 효율적으로 활용하기 위해 특정한 정렬 기준에 따라 데이터를 분할하여 가져오는 것이다. 우리가 매일 방문하는 거의 모든 사이트에서 페이지네이션이 사용되고 있다. 이러한 페이지네이션의 사용법에는 오프셋 기반 페이지네이션(Offset-based pagination)과 커서 기반 페이지네이션(Cursor-based Pagination) 두 가지 다른 방식이 존재한다.
오프셋 기반 페이지 매김은 클라이언트가 특정 리밋(결과 수)과 오프셋(건너뛸 갯수)이 있는 매개변수를 요청하는 매우 유명한 기술이다. 오프셋 기반 페이지 매김은 사용하기 쉽고 정적 데이터에 선호된다.
커서 기반 페이지 매김의 핵심 매개변수는 커서(Cursor)다. 클라이언트는 응답과 함께 커서라 명명된 변수를 받는다. 커서는 요청과 함께 보내야 하는 특정 항목을 가리키는 포인터다. 그런 다음 서버는 커서를 사용하여 다른 항목 집합을 찾는다. 커서 기반 페이지 매김은 더 복잡하지만 실시간 데이터 세트를 처리할 때 선호된다.
먼저 커서 기반 페이지 매김은 가장 효과적인 페이지 매김 방법 중 하나로 간주되고 있다. 오프셋 페이지 매김과 비교하여 커서 기반 페이지 매김의 장점은 다음과 같다.
건너뛰는 데이터가 없다
: 인덱스처럼 작동하는 오프셋 매개변수를 보내는 대신 커서 페이지 매김은 조회가 중단된 마지막 페이지의 위치를 표시하기 위해 데이터베이스에 있는 커서 매개변수를 보내기 때문이다. 커서는 특정 레코드에 대한 포인터처럼 작동한다.
뛰어난 실시간 데이터 기능
: 커서 페이지 매김의 가장 중요한 장점 중 하나는 실시간 데이터를 매우 효율적으로 관리할 수 있다는 것이다. 그 이유는 커서에 정적 데이터가 필요하지 않기 때문이다. 따라서 각 페이지의 로드 절차에 영향을 주지 않고 새 행이나 항목을 쉽게 제거하거나 추가할 수 있다. 시간이 지남에 따라 실시간 응용 프로그램의 증가로 인해 커서 기반 페이지 매김이 많은 인기를 얻었다. 오프셋 페이지 매김은 정적 데이터를 필요로 하고 그렇지 않은 경우 항목이 두 번 반환되거나 모두 건너뛸 수 있다.
대용량 데이터 세트의 효과적인 관리
: 이것을 더 잘 이해하려면 먼저 오프셋에 대해 알아야 한다. 오프셋은 데이터베이스가 레코드를 선택하기 전에 건너뛰어야 하는 갯수이다. 이것은 요청된 다음 데이터 세트를 반환할 뿐만 아니라 그 앞에 나타나는 다른 모든 이전 데이터도 스캔한다는 것을 의미한다. 이것은 오프셋 수가 증가할 때 중요한 문제로 페이지를 로드하고 나가는 데 많은 시간이 필요해진다.
하지만 커서 기반 페이지 매김에도 단점이 존재한다. 오프셋 기반 페이지 매김과 비교하여 커서 기반 페이지 매김의 단점은 다음과 같다.
제한된 정렬 기능
: 이름과 성을 기준으로 사용자 테이블을 정렬하는 작업이 있다고 가정해 보자. 커서는 정렬을 위해 중복된 값이 존재하면 안되고, 고유한 순차 열을 구현해야 하므로 문제가 될 수 있다. 대부분의 커서 구현은 순차적이고 고유한 타임스탬프 열을 기반으로 한다. 따라서 요구 사항에 복잡한 정렬이 필요하다면 커서 기반 페이징은 최선의 선택이 아니다. 이렇게 구현하기 위한 프로세스가 오프셋보다 훨씬 느려질 수 있기 떄문이다.
구현하기 약간 까다로운 것으로 간주
: 커서를 구현하는 것은 그리 어렵지 않을 수 있지만 오프셋은 일반적으로 구현하기가 쉽다. 따라서 페이지 매김을 빠르게 구현하려는 경우 오프셋을 선택하는 것이 올바른 방법이다. 이는 데이터가 정적인 경우 특히 유용하다.
실시간 애플리케이션에 가장 널리 사용되는 페이지 매김은 커서 페이지 매김이다. 이는 데이터베이스에 추가되는 새 데이터의 빈도가 증가했기 때문이다. 커서 접근 방식은 중복된 항목을 제거하고 항목을 건너뛰지 않으므로 권장된다. 포인터는 데이터의 위치와 다음에 가져와야 하는 항목을 추적한다.
하지만 커서와 오프셋이 모두 장단점이 되는 다양한 상황이 있고, 사용법은 주로 처리되는 데이터에 따라 달라진다. 따라서 올바른 접근 방식을 선택하는 결정은 전적으로 사용 사례와 페이지 매김이 제품에 미치는 영향에 달려 있다.
공부하다 보니 오프셋 방식보다 커서 기반 방식이 확실히 여러 부분에서 이점을 가진 것 같아 블로그 페이지의 페이징 처리를 커서 기반 방식으로 변경하면 어떨지 제안했다. 그런데 굳이 커서 기반방식을 사용하지 않아도 되는 경우라고 생각해서 오프셋 방식으로 처리했다는 이야기를 해주셨다.
🙍♂️: 블로그 페이지는 개별 사용자의 데이터만 가지고 있으니까 그렇게 많은 데이터를 불러올 일이 없고, 사용자가 보는 중간에 데이터가 삭제되거나 변경되는 일도 잦지 않기 때문에 굳이 필요가 없을 것 같아요.
나: 오프셋 방식이 가지고 있는 확실한 단점이 있고, 이걸 커서 기반 방식이 커버하고 있다면 둘 중에 커서 기반 방식을 기본적으로 사용하는 게 낫지 않을까요?
오프셋으로 처리한 기준이 있다는 것은 이해했으나 나는 저러한 의견을 가지고 있어 다시 여쭤 보았고 일단 좀 더 고민해보기로 하며 보류하게 되었다.
그리고나서 공부한 내용을 바탕으로 실제 우리 프로젝트 상황에 대입해 보았다.
1) 먼저 내가 고민한 부분은 커서 기반 방식이 많은 데이터를 다루는 경우에만 필요한지였다.
확실히 커서 기반 방식은 대량의 데이터를 다루는 서비스에 더 효율적이다. 오프셋 방식은 요청된 데이터를 바로 조회하는 게 아니라 그 앞에 있는 이전 데이터도 모두 스캔해야 한다. offset이 작은 수라면 큰 문제가 되지 않으나, 데이터의 수가 많아 offset값 역시 커질 경우 비효율성이 극대화될 것이기 때문이다.
내 디벨로픽 페이지는 블로그 기능을 제공하기 때문에 사용자가 많아지면서 다수의 글을 작성한다면 대량의 데이터를 축적할 특성을 가지고 있다고 생각했다.
2) 다음은 누락되거나 중복되는 데이터와 관련된 문제였다.
커서 기반 방식은 실시간으로 잦은 생성과 삭제가 반복되는 서비스를 다룰 때 유용하다. 예를 들어 오프셋 방식의 경우, 해변에 관한 사진 게시물 1-5번까지 1페이지에 있고, 6-10번까지 2페이지에 있는 상황에서 실시간으로 새로운 게시물 3개가 추가된다면, 사용자는 2페이지에서 1페이지에서 봤던 8,9,10번 글을 중복해서 보게 될 것이다. 이는 오프셋 방식이 내가 어떤 데이터를 보고 있는지에 관계없이 몇 개의 row를 건너뛰는 것에 집중하기 때문이다.
반면 커서 기반 방식은 마지막으로 조회한 데이터를 기준으로 이를 커서를 통해 알려주고 다음 게시물을 불러오기 때문에 중복되는 데이터가 발생하지 않는다. 단순히 5~10번을 가져와! 가 아니라, 마지막 데이터가 8번을 가리키고 있다면 그 8번으로부터 n개의 데이터를 가져오기 때문에 이런 서비스 환경에 적합하다고 할 수 있다.
이 역시 사용자의 증가를 가정한다면, 결국 디벨로픽 페이지 내에 실시간으로 블로그 데이터가 생성, 수정, 삭제 될 것이기 때문에 적합하다고 볼 수 있을 것 같다.
결론적으로는 현재 상황과 서비스 초기에 필요한 건 아니지만, 우리가 기획한 디벨로픽(블로그) 페이지의 특성과 장기적인 목표(서비스 사용자가 많아지고 실시간 업데이트가 이루어진다면!)의 관점에서 바라본다면 커서 기반 방식을 적용하는 게 맞다는 생각이 들었다.
하지만 우선 순위를 정해 프로젝트를 진행해야 하고, 백엔드 팀원 분과 조율 역시 필요하기 때문에 이런 부분을 공부했다는 데 의의를 두고 추후에 적용하는 것으로 마무리 했다.
References