DataBase

MongoDB에서 효율적으로 페이징 처리하기(pagination)

tmkimm 2022. 11. 11. 17:00

MongoDB Pagination

MongoDB에서 페이징을 처리하는 방법은 여러 가지가 있습니다.

효율적으로 처리하기 위해 했던 고민들을 공유합니다.

 

개발 환경

  • Node.js(Express)
  • mongoDB(Mongoose)

 

 Skip, Limit

처음 적용했던 방식은 mongoDB의 skip과 limit 기능을 이용해서 데이터를 건너뛰고(skip), 한 페이지에 보여줘야 하는 수만큼 제한(skip)하는 것입니다.

현재 3 페이지고 한 페이지에 10개의 데이터를 보여준다면 21~30 번째 글을 보여주면 되므로 skip(20).limit(10) 가 됩니다.

db.students.find().skip(20) // 10개를 건너뛰고 21개부터 조회
db.students.find().limit(10) // 10개만 조회
db.students.find().skip(20).limit(10) // 20개를 건너뛰고 10개만 조회

 

 

Node.js에서 페이징을 적용하려면 아래와 같이 작성하면 됩니다.

const itemsPerPage = 10;
const page = 3;

const resuilt = await this.students.find({})
    .skip((page - 1) * itemsPerPage)
    .limit(itemsPerPage)

 

같은 방법으로 페이징이 아닌 무한 스크롤(Infinite Scroll)에도 적용할 수 있습니다.

시작할 순서(offset)와 조회 개수(limit)를 요청받아 skip과 limit에 그대로 사용하면 됩니다.

한 번에 20개의 데이터를 조회한다면

  • 최초 조회: offset 0, limit 20
  • 첫 번째 스크롤: offset 20, limit 20
  • 두 번째 스크롤: offset 40. limit 20
const resuilt = await this.students.find({})
    .skip(offset)
    .limit(limit)

 

Skip과 Limit의 단점

1. 성능 저하

사용하기는 정말 편하지만 find에 해당되는 데이터를 전부 조회한 후 skip과 limit을 적용하기 때문에 성능에 문제가 생길 수 있습니다. 필요한 데이터는 10건인데 매번 전체 데이터를 읽는 것은 비효율적입니다.

 

2. 데이터 불일치 가능성

사용자가 3번째 페이지를 읽다가 새로운 글이 등록됐을 경우 4번 페이지로 이동했을 때 같은 글이 중복되어 노출될 수 있습니다. skip & limit은 이 문제를 해결할 수 있는 방법이 없습니다.

 

skip과 limit 대신 다른 방법이 필요해 보입니다

 


 Object ID 비교(참고글)

MongoDB에서 document별로 부여되는 Object ID의 앞자리는 timestamp를 가지고 있습니다. 

이미지 출처 : https://koonsland.tistory.com/89

Object ID를 비교하면 어떤 글이 먼저 등록되었는지 알 수 있습니다.(timestamp가 클수록 최신 글입니다)

// NodeJS로 확인
if(첫번째로 등록된 글("632862fb37ad6700140c2f11") < 두번째로 등록된 글("63482c55d4143d48ee4d5796")) // true

// MongoDB에서 확인
{_id: {$gt: ObjectId('632862fb37ad6700140c2f11') }}

대부분 페이징 처리를 하는 경우 등록일을 오름차순이나 내림차순으로 정렬되어 있습니다.

즉 데이터가 정렬되어 있는 상태라면 ObjectID를 통해 페이징을 처리할 수 있습니다.

 

현재 페이지의 마지막 글보다 ObjectID가 큰 데이터를 조회하면 다음 페이지를 가져올 수 있습니다. 

const result = await this.students.find({ $gt: "1페이지의 마지막 Object ID" })
    .limit(limit)

이 방법을 사용하면 데이터 전체를 조회하지 않아도 되기 때문에 속도도 개선되고 데이터 중복도 피할 수 있습니다! 👍👍

 

주의 사항

이때 find 조건에 $or이 있을 경우 데이터가 섞일 수 있습니다. $and 연산자를 이용하여 데이터가 섞이지 않도록 주의해야 합니다.

 


여러 페이지 이동

한 페이지를 이동이 아닌 여러 페이지를 이동할 때는 어떻게 처리해야 할까요?

현재 페이지는 3 페이지고 이전 페이지가 1일 경우 Object ID 비교와 skip을 같이 사용하면 됩니다.

const itemsPerPage = 10; // 한 페이지에 표현할 글 수
let currentPage = 3;	 // 현재 페이지
let previousPage = 1;    // 이전 페이지
let pagesToSkip = currentPage - previousPage;    // 이동한 페이지

db.students
.find({’_id’: {’$gt’: last_id}})
.skip(itemsPerPage * pagesToSkip)
.limit(itemsPerPage)

 

저는 페이지가 앞에서 뒤로 이동할 수도 있으므로 pagesToSkip에 따라 $gt, $lt로 조회하도록 처리했습니다.

더 좋은 방법이 있다면 댓글로 알려주시면 감사하겠습니다. 🙏

 

 

 

 

참고 글

MongoDB Pagination, Fast & Consistent

Implementing pagination in mongodb