몽구스 제한 / 오프셋 및 개수 쿼리


84

쿼리 성능에 대한 약간의 이상한 점 ... 총 문서 수를 수행하는 쿼리를 실행하고 제한 및 오프셋 가능한 결과 집합을 반환 할 수도 있습니다.

그래서 총 57 개의 문서가 있고 사용자는 10 개의 문서를 20으로 오프셋하기를 원합니다.

이 작업을 수행하는 두 가지 방법을 생각할 수 있습니다. 먼저 57 개 문서 (배열로 반환 됨)를 모두 쿼리 한 다음 array.slice를 사용하여 원하는 문서를 반환합니다. 두 번째 옵션은 mongo의 기본 'count'메소드를 사용하여 두 번째 쿼리를 실행 한 다음 mongo의 기본 $ limit 및 $ skip 집계자를 사용하여 두 번째 쿼리를 실행하는 것입니다.

어느 것이 더 잘 확장 될 것이라고 생각하십니까? 모든 것을 하나의 쿼리로 수행하거나 두 개의 개별 쿼리를 실행합니까?

편집하다:

// 1 query
var limit = 10;
var offset = 20;

Animals.find({}, function (err, animals) {
    if (err) {
        return next(err);
    }

    res.send({count: animals.length, animals: animals.slice(offset, limit + offset)});
});


// 2 queries
Animals.find({}, {limit:10, skip:20} function (err, animals) {            
    if (err) {
        return next(err);
    }

    Animals.count({}, function (err, count) {
        if (err) {
            return next(err);
        }

        res.send({count: count, animals: animals});
    });
});

나는 Mongoose에 대해 확신 count()하지 못하지만 PHP 의 기본 기능은 한 번의 제한 쿼리를 실행하고 건너 뛰고 카운트를 얻는 것이 아마도 여기에서 가장 성능이 좋은 솔루션을 제공해야한다고 말하지 않는 한 고려 limit하거나 skip고려 하지 않습니다 . 그러나 현재있는 항목을 계산하기 위해 두 개의 쿼리를 수행하지 않으면 57 개의 문서가 있다는 것을 어떻게 알 수 있습니까? 변하지 않는 고정 번호가 있습니까? 그렇지 않은 경우 건너 뛰기와 제한을 모두 수행 한 다음 카운트를 수행해야합니다.
Sammaye

죄송합니다. Mongo의 기본 계산 방법을 사용하는 것에 대해 이야기했습니다. db.collection.find(<query>).count();
leepowell

죄송합니다. 질문을 잘못 읽었습니다. 음, 실제로 어느 것이 더 좋을지 모르겠습니다. 결과 세트가 항상 57 문서처럼 정말 낮습니까? 그렇다면 클라이언트 측 슬라이스가 밀리 초보다 성능이 더 좋을 수 있습니다.
Sammaye

원래 질문에 예제를 추가했습니다. 데이터가 10,000+만큼 높아질 것이라고 생각하지 않지만 잠재적으로 그럴 수 있습니다.
leepowell

10k 레코드 에서 JS의 메모리 처리 가 MongoDB 의 기능 보다 성능이 떨어지는 것을 볼 있습니다 count(). count()MongoDB 의 기능은 상대적으로 느리지 만 더 큰 세트의 대부분의 클라이언트 측 변형만큼 빠르며 여기에서 계산하는 클라이언트 측보다 빠를 수 있습니다. 그러나 그 부분은 귀하의 테스트에 주관적입니다. 전에 10k 길이 배열을 쉽게 계산 했으므로 클라이언트 측이 더 빠를 수 있으므로 10k 요소에서 말하기가 매우 어렵습니다.
Sammaye

답변:


129

두 가지 쿼리를 사용하는 것이 좋습니다.

  1. db.collection.count()총 항목 수를 반환합니다. 이 값은 Mongo의 어딘가에 저장되며 계산되지 않습니다.

  2. db.collection.find().skip(20).limit(10)여기에서는 일부 필드별로 정렬을 사용할 수 있다고 가정하므로이 필드에 색인을 추가하는 것을 잊지 마십시오. 이 쿼리도 빠릅니다.

모든 항목을 쿼리하지 않고 건너 뛰고 가져가는 것보다 나중에 빅 데이터가 있으면 데이터 전송 및 처리에 문제가 생길 수 있다고 생각합니다.


1
내가 쓰고있는 것은 아무 의미없는 주석 일 뿐이지 만 .skip()명령이 컬렉션의 시작 부분으로 이동하여의 매개 변수에 지정된 값에 도달하기 때문에 CPU에 대한 명령이 무겁다 고 들었습니다 .skip(). 큰 컬렉션에 큰 영향을 미칠 수 있습니다! 하지만 .skip()어쨌든 사용 사이에 가장 무거운 것이 무엇인지 모르겠 거나 전체 컬렉션을 가져 와서 JS로 트림 ... 어떻게 생각하십니까?
Zachary Dahan 2015

2
@Stuffix 사용에 대한 동일한 우려를 들었습니다 .skip(). 이 답변 은 그것에 대해 수정하고 날짜 필드에 필터를 사용하도록 권장합니다. .skip()& .take()메소드 와 함께 이것을 사용할 수 있습니다 . 이것은 좋은 생각 인 것 같습니다. 그러나 총 문서 수를 얻는 방법에 대한이 OP의 질문에 문제가 있습니다. 의 성능 영향과 싸우기 위해 필터를 사용하는 경우 .skip()어떻게 정확한 개수를 얻을 수 있습니까? db에 저장된 개수는 필터링 된 데이터 세트를 반영하지 않습니다.
Michael Leanos

안녕하세요 @MichaelLeanos, 동일한 문제에 직면 해 있습니다. 즉, 총 문서 수를 얻는 방법입니다. 필터를 사용하면 정확한 개수를 어떻게 구할 수 있습니까? 이것에 대한 해결책을 얻었습니까?
virsha

@virsha, cursor.count()필터링 된 문서 개수를 반환 하는 데 사용 합니다 (쿼리를 실행하지 않고 일치하는 문서 개수를 반환합니다). 필터 및 주문 속성이 인덱싱되고 모든 것이 정상인지 확인하십시오.
user854301

@virsha 사용은 cursor.count()@ user854301이 지적한대로 작동해야합니다. 그러나 결국 내가 한 일은 /api/my-colllection/statsMongoose의 db.collection.stats 기능을 사용하여 내 컬렉션에 대한 다양한 통계를 반환하는 데 사용한 API ( )에 엔드 포인트를 추가하는 것 입니다. 나는 이것이 내 프런트 엔드에만 필요했기 때문에 서버 측 페이지 매김과 관계없이 해당 정보를 반환하도록 엔드 포인트에 쿼리했습니다.
Michael Leanos

19

2 개의 별도 쿼리를 사용하는 대신 aggregate()단일 쿼리에서 사용할 수 있습니다 .

집계 "$ facet" 는 더 빨리 가져올 수 있으며 총 개수데이터 건너 뛰기 및 제한

    db.collection.aggregate([

      //{$sort: {...}}

      //{$match:{...}}

      {$facet:{

        "stage1" : [ {"$group": {_id:null, count:{$sum:1}}} ],

        "stage2" : [ { "$skip": 0}, {"$limit": 2} ]
  
      }},
     
     {$unwind: "$stage1"},
  
      //output projection
     {$project:{
        count: "$stage1.count",
        data: "$stage2"
     }}

 ]);

다음과 같이 출력 :-

[{
     count: 50,
     data: [
        {...},
        {...}
      ]
 }]

또한 https://docs.mongodb.com/manual/reference/operator/aggregation/facet/를 살펴보십시오.


2

이 문제를 직접 해결 한 후 user854301의 답변을 기반으로 작성하고 싶습니다.

Mongoose ^ 4.13.8 toConstructor()필터가 적용될 때 쿼리를 여러 번 작성하지 않도록 하는 함수를 사용할 수있었습니다 . 이 기능은 이전 버전에서도 사용할 수 있다는 것을 알고 있지만이를 확인하려면 Mongoose 문서를 확인해야합니다.

다음은 Bluebird promise를 사용합니다.

let schema = Query.find({ name: 'bloggs', age: { $gt: 30 } });

// save the query as a 'template'
let query = schema.toConstructor();

return Promise.join(
    schema.count().exec(),
    query().limit(limit).skip(skip).exec(),

    function (total, data) {
        return { data: data, total: total }
    }
);

이제 개수 쿼리는 일치하는 총 레코드를 반환하고 반환 된 데이터는 총 레코드의 하위 집합이됩니다.

쿼리를 구성하는 query () 주변 의 ()에 유의하십시오 .



0
db.collection_name.aggregate([
    { '$match'    : { } },
    { '$sort'     : { '_id' : -1 } },
    { '$facet'    : {
        metadata: [ { $count: "total" } ],
        data: [ { $skip: 1 }, { $limit: 10 },{ '$project' : {"_id":0} } ] // add projection here wish you re-shape the docs
    } }
] )

총 개수를 찾기 위해 두 개의 쿼리를 사용하는 대신 일치하는 레코드를 건너 뜁니다.
$ facet은 가장 좋고 최적화 된 방법입니다.

  1. 기록과 일치
  2. total_count 찾기
  3. 기록을 건너 뛰다
  4. 또한 쿼리의 필요에 따라 데이터를 재구성 할 수도 있습니다.

1
다른 사람들이 배울 수 있도록 답변에 설명을 추가하십시오
Nico Haase
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.