MongoDB-페이징


81

MongoDB를 사용할 때 페이지보기와 같은 특별한 패턴이 있습니까? 이전 게시물로 뒤로 이동할 수있는 최신 게시물 10 개를 나열하는 블로그를 말합니다.

아니면 blogpost.publishdate의 색인으로 문제를 해결하고 결과를 건너 뛰고 제한합니까?


1
이 척도를 만드는 올바른 방법이 무엇인지에 대해 약간의 의견 차이가있는 것 같으므로 나는 이것을 매달아 두겠습니다.
Roger Johansson

답변:


98

성능이 문제가되거나 대규모 컬렉션이있는 경우 skip + limit를 사용하는 것은 페이징을 수행하는 좋은 방법이 아닙니다. 페이지 번호를 늘리면 점점 느려집니다. skip을 사용하려면 서버가 0에서 오프셋 (건너 뛰기) 값까지 모든 문서 (또는 색인 값)를 통과해야합니다.

마지막 페이지의 범위 값을 전달하는 범위 쿼리 (+ 제한)를 사용하는 것이 훨씬 좋습니다. 예를 들어 "publishdate"로 정렬하는 경우 쿼리 기준으로 마지막 "publishdate"값을 전달하여 데이터의 다음 페이지를 가져 오면됩니다.


4
mongodb에서 모든 문서를 반복한다는 것을 확인하는 일부 문서를 보는 것이 좋을 것입니다.
Andrew Orsich

5
여기 있습니다 : 문서 건너 뛰기 정보를 업데이트해야하는 다른 위치가 있으면 알려주세요.
Scott Hernandez

2
@ScottHernandez : 여러 페이지에 대한 링크 (예 : 페이지 : First, 2, 3, 4, 5, Last)를 사용하여 페이징하고 모든 필드를 정렬합니다. 내 필드 중 하나만 고유하고 인덱싱됩니다. 범위 쿼리가이 사용 사례에서 작동합니까? 안타깝지만 가능한지 확인하고 싶었습니다. 감사.
user183037


8
동일한 publishdate 값을 가진 여러 문서가있는 경우 작동하지 않는 것처럼 보입니다.
d512

12
  1. 범위 기반 페이징은 여러 가지 방법으로 항목을 정렬해야하는 경우 구현하기 어렵습니다.
  2. 정렬 매개 변수의 필드 값이 고유하지 않으면 범위 기반 페이징이 부적절 해집니다.

가능한 해결책 : desgin을 단순화하고 id 또는 고유 한 값으로 만 정렬 할 수 있는지 생각해보십시오.

그리고 가능하다면 범위 기반 페이징을 사용할 수 있습니다.

일반적인 방법은 위에서 설명한 페이징을 구현하기 위해 sort (), skip () 및 limit ()을 사용하는 것입니다.


Python 코드 예제가 포함 된 좋은 기사는 여기에서 찾을 수 있습니다. codementor.io/arpitbhayani/…
Gianfranco P.

1
감사합니다-훌륭한 답변입니다! 사람들이 필터를 사용하여 페이지 매김을 제안 할 때 짜증이납니다. 예를 들어 { _id: { $gt: ... } }... 사용자 지정 순서를 사용하면 작동하지 않습니다 .sort(...).
Nick Grealy

1
@NickGrealy 나는 튜토리얼을 따라이 작업을 수행했으며 이제 페이징이 작동하는 것처럼 보이지만 mongo ID를 사용하고 있지만 새 데이터가 db에 삽입되면 문서가 누락되는 상황에 처해 있습니다. 시작 페이지에 A로 시작하는 레코드가 포함되어 있지만 ID가 AA로 시작하는 레코드보다 높은 경우 컬렉션이 알파벳순으로 정렬됩니다. 그 이후에 삽입 되었기 때문에 AA 레코드는 페이징에 의해 반환되지 않습니다. 건너 뛰기 및 제한이 적합합니까? 검색 할 문서가 6 천만 개에 달합니다.
berimbolo

@berimbolo-이것은 대화의 가치가 있습니다-여기 댓글에서 답을 얻지 못할 것입니다. 질문 : 어떤 행동을 기대합니까? 레코드가 항상 생성되고 삭제되는 라이브 시스템으로 작업하고 있습니다. 새로운 페이지가로드 될 때마다 데이터의 라이브 스냅 샷을 다시 요청하는 경우 기본 데이터가 변경 될 것으로 예상해야합니다. 행동은 무엇입니까? "특정 시점"데이터 스냅 샷으로 작업하는 경우 "고정 페이지"가 ​​있지만 "오래된"데이터도 있습니다. 설명하는 문제가 얼마나 크고 사람들이 문제를 얼마나 자주 접합니까?
Nick Grealy 2019

1
확실히 대화의 가치가 있습니다. 제 문제는 번호판의 알파벳 순서로 일회성 파일을 검색하고 15 분마다 변경된 (제거 또는 추가 된) 번호판에 업데이트를 적용한다는 것입니다. 문제는 새 번호판이 추가되고 시작된다는 것입니다. 예를 들어 A를 사용하고 페이지 크기가 페이지의 마지막이므로 다음이 요청되면 레코드가 반환되지 않습니다 (가정 및 인위적 예이지만 내 문제를 설명하는 것). 세트. 이제 전체 번호판을 사용하여 쿼리의 더 많은 부분을 운전하려고합니다.
berimbolo

5

이것은 내 컬렉션이 너무 커져 단일 쿼리로 반환 할 수 없을 때 사용한 솔루션입니다. _id필드 의 고유 한 순서를 활용 하고 지정된 배치 크기로 컬렉션을 반복 할 수 있습니다.

다음은 npm 모듈 인 mongoose-paging입니다 . 전체 코드는 다음과 같습니다.

function promiseWhile(condition, action) {
  return new Promise(function(resolve, reject) {
    process.nextTick(function loop() {
      if(!condition()) {
        resolve();
      } else {
        action().then(loop).catch(reject);
      }
    });
  });
}

function findPaged(query, fields, options, iterator, cb) {
  var Model  = this,
    step     = options.step,
    cursor   = null,
    length   = null;

  promiseWhile(function() {
    return ( length===null || length > 0 );
  }, function() {
    return new Promise(function(resolve, reject) {

        if(cursor) query['_id'] = { $gt: cursor };

        Model.find(query, fields, options).sort({_id: 1}).limit(step).exec(function(err, items) {
          if(err) {
            reject(err);
          } else {
            length  = items.length;
            if(length > 0) {
              cursor  = items[length - 1]._id;
              iterator(items, function(err) {
                if(err) {
                  reject(err);
                } else {
                  resolve();
                }
              });
            } else {
              resolve();
            }
          }
        });
      });
  }).then(cb).catch(cb);

}

module.exports = function(schema) {
  schema.statics.findPaged = findPaged;
};

다음과 같이 모델에 연결하십시오.

MySchema.plugin(findPaged);

그런 다음 다음과 같이 쿼리하십시오.

MyModel.findPaged(
  // mongoose query object, leave blank for all
  {source: 'email'},
  // fields to return, leave blank for all
  ['subject', 'message'],
  // number of results per page
  {step: 100},
  // iterator to call on each set of results
  function(results, cb) {
    console.log(results);
    // this is called repeatedly while until there are no more results.
    // results is an array of maximum length 100 containing the
    // results of your query

    // if all goes well
    cb();

    // if your async stuff has an error
    cb(err);
  },
  // function to call when finished looping
  function(err) {
    throw err;
    // this is called once there are no more results (err is null),
    // or if there is an error (then err is set)
  }
);

이 답변에 더 많은 찬성표가없는 이유를 모르겠습니다. 이 스킵 / 제한보다 PAGINATE에 대한보다 효율적인 방법입니다
nxmohamad

나는 또한이 패키지로 왔지만 건너 뛰기 / 제한과 비교 한 성능 및 @Scott Hernandez가 제공 한 대답은 어떻습니까?
Tanckom

5
이 답변은 다른 필드에서 정렬하는 데 어떻게 작동합니까?
Nick Grealy 2018

1

범위 기반 페이징은 가능하지만 쿼리를 최소화 / 최대화하는 방법에 대해 현명해야합니다.

여유가 있다면 임시 파일이나 컬렉션에 쿼리 결과를 캐싱해야합니다. MongoDB의 TTL 컬렉션 덕분에 결과를 두 컬렉션에 삽입 할 수 있습니다.

  1. 검색 + 사용자 + 매개 변수 쿼리 (TTL 상관 없음)
  2. 쿼리 결과 (TTL 상관 없음 + 청소 간격 + 1)

두 가지를 모두 사용하면 TTL이 현재 시간에 가까워도 부분적인 결과를 얻을 수 없습니다. 결과를 저장할 때 간단한 카운터를 사용하여 해당 지점에서 매우 간단한 범위 쿼리를 수행 할 수 있습니다.


1

여기에서 검색리스트의 예 User에서 문서 순서 CreatedDate( pageIndex공식 C # 1 드라이버를 사용하여 제로가된다).

public void List<User> GetUsers() 
{
  var connectionString = "<a connection string>";
  var client = new MongoClient(connectionString);
  var server = client.GetServer();
  var database = server.GetDatabase("<a database name>");

  var sortBy = SortBy<User>.Descending(u => u.CreatedDate);
  var collection = database.GetCollection<User>("Users");
  var cursor = collection.FindAll();
  cursor.SetSortOrder(sortBy);

  cursor.Skip = pageIndex * pageSize;
  cursor.Limit = pageSize;
  return cursor.ToList();
}

모든 정렬 및 페이징 작업은 서버 측에서 수행됩니다. 이것은 C #의 예이지만 다른 언어 포트에도 동일하게 적용될 수 있다고 생각합니다.

http://docs.mongodb.org/ecosystem/tutorial/use-csharp-driver/#modifying-a-cursor-before-enumerating-it을 참조 하십시오 .


0
    // file:ad-hoc.js
    // an example of using the less binary as pager in the bash shell
    //
    // call on the shell by:
    // mongo localhost:27017/mydb ad-hoc.js | less
    //
    // note ad-hoc.js must be in your current directory
    // replace the 27017 wit the port of your mongodb instance
    // replace the mydb with the name of the db you want to query
    //
    // create the connection obj
    conn = new Mongo();

    // set the db of the connection
    // replace the mydb with the name of the db you want to query
    db = conn.getDB("mydb");

    // replace the products with the name of the collection
    // populate my the products collection
    // this is just for demo purposes - you will probably have your data already
    for (var i=0;i<1000;i++ ) {
    db.products.insert(
        [
            { _id: i, item: "lamp", qty: 50, type: "desk" },
        ],
        { ordered: true }
    )
    }


    // replace the products with the name of the collection
    cursor = db.products.find();

    // print the collection contents
    while ( cursor.hasNext() ) {
        printjson( cursor.next() );
    }
    // eof file: ad-hoc.js
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.