Consulta de límite / compensación y recuento de mangosta

84

Un poco extraño en el rendimiento de la consulta ... Necesito ejecutar una consulta que haga un recuento total de documentos, y también puedo devolver un conjunto de resultados que puede ser limitado y compensado.

Entonces, tengo 57 documentos en total y el usuario quiere 10 documentos compensados ​​por 20.

Puedo pensar en 2 formas de hacer esto, primero es consultar los 57 documentos (devueltos como una matriz), luego usar array.slice devuelve los documentos que desean. La segunda opción es ejecutar 2 consultas, la primera usando el método nativo 'count' de mongo, luego ejecutar una segunda consulta usando los agregadores nativos $ limit y $ skip de mongo.

¿Cuál crees que escalaría mejor? ¿Hacerlo todo en una consulta o ejecutar dos independientes?

Editar:

// 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});
    });
});
leepowell
fuente
Estoy seguro de Mangosta sin embargo el defecto count()función en PHP no toma limito skipen cuenta a menos que se lo que sólo se ejecuta una consulta de límite y salte y luego conseguir el conteo debe dar la solución más probable performant aquí. Sin embargo, ¿cómo sabrá que hay 57 documentos si no hace dos consultas para contar lo que hay actualmente? ¿Tiene un número estático que nunca cambia? Si no es así, deberá realizar tanto el salto como el límite y luego el conteo.
Sammaye
Lo siento, estaba hablando de usar el método de conteo nativo de Mongodb.collection.find(<query>).count();
leepowell
Lo siento, fui yo, leí mal tu pregunta. Hmmm, en realidad, no estoy seguro de cuál sería mejor, ¿su conjunto de resultados siempre será realmente bajo, como 57 documentos? Si es así, el segmento del lado del cliente podría tener un milisegundo más de rendimiento.
Sammaye
Agregué un ejemplo a la pregunta original, no creo que los datos lleguen nunca a más de 10,000, pero potencialmente podrían hacerlo.
leepowell
Con 10k registros, podría ver que el manejo de la memoria de JS tiene menos rendimiento que la count()función de MongoDB. La count()función en MongoDB es relativamente lenta, pero sigue siendo bastante tan rápida como la mayoría de las variaciones del lado del cliente en conjuntos más grandes y podría ser más rápida que el conteo del lado del cliente aquí. Pero esa parte es subjetiva para sus propias pruebas. Tenga en cuenta que antes he contado matrices de 10k de longitud fácilmente, por lo que podría ser más rápido en el lado del cliente, es muy difícil decirlo en 10k elementos.
Sammaye

Respuestas:

129

Le sugiero que utilice 2 consultas:

  1. db.collection.count()devolverá el número total de artículos. Este valor se almacena en algún lugar de Mongo y no se calcula.

  2. db.collection.find().skip(20).limit(10)aquí supongo que podría usar una clasificación por algún campo, así que no olvide agregar un índice en este campo. Esta consulta también será rápida.

Creo que no debe consultar todos los elementos y luego realizar la omisión y la toma, porque más adelante, cuando tenga big data, tendrá problemas con la transferencia y el procesamiento de datos.

usuario854301
fuente
1
Lo que estoy escribiendo es solo un comentario sin ninguna pretensión, pero escuché que la .skip()instrucción es pesada para la CPU porque va al comienzo de la colección y llega al valor especificado en el parámetro de .skip(). ¡Puede tener un impacto real en una gran colección! Pero no sé cuál es el más pesado entre uso de .skip()todos modos o obtener toda la colección y recortar con JS ... ¿Qué les parece?
Zachary Dahan
2
@Stuffix He escuchado las mismas preocupaciones sobre el uso .skip(). Esta respuesta lo retoca y aconseja usar un filtro en un campo de fecha. Se podría usar esto con los métodos .skip()& .take(). Esto parece una buena idea. Sin embargo, tengo problemas con la pregunta de este OP sobre cómo obtener un recuento del total de documentos. Si se utiliza un filtro para combatir las implicaciones de rendimiento de .skip(), ¿cómo podemos tener un recuento preciso? El recuento almacenado en la base de datos no reflejará nuestro conjunto de datos filtrados.
Michael Leanos
Hola @MichaelLeanos, me estoy enfrentando al mismo problema: es decir, cómo obtener un recuento del total de documentos. Si se utiliza un filtro, ¿cómo podemos tener un recuento preciso? ¿Obtuviste la solución para esto?
virsha
@virsha, use cursor.count()para devolver el número de documentos filtrados (no ejecutará la consulta, le devolverá el número de documentos coincidentes). Asegúrese de que las propiedades de filtrado y orden estén indexadas y todo estará bien.
user854301
@virsha El uso de cursor.count()debería funcionar como señaló @ user854301. Sin embargo, lo que terminé haciendo fue agregar un punto final a mi API ( /api/my-colllection/stats) que usé para devolver varias estadísticas en mis colecciones usando la función db.collection.stats de Mongoose . Como realmente solo necesitaba esto para mi interfaz, solo consulté el punto final para devolver esa información independientemente de la paginación del lado del servidor.
Michael Leanos
19

En lugar de usar 2 consultas separadas, puede usar aggregate()en una sola consulta:

El agregado "$ facet" se puede recuperar más rápidamente, el recuento total y los datos con omisión y límite

    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"
     }}

 ]);

salida de la siguiente manera: -

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

Además, eche un vistazo a https://docs.mongodb.com/manual/reference/operator/aggregation/facet/

Dhinesh: Sí
fuente
2

Después de tener que abordar este problema yo mismo, me gustaría aprovechar la respuesta del usuario854301.

Mongoose ^ 4.13.8 Pude usar una función llamada toConstructor()que me permitió evitar generar la consulta varias veces cuando se aplican filtros. Sé que esta función también está disponible en versiones anteriores, pero tendrás que consultar los documentos de Mongoose para confirmarlo.

Lo siguiente usa las promesas de Bluebird:

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 }
    }
);

Ahora, la consulta de recuento devolverá el total de registros que coincidió y los datos devueltos serán un subconjunto del total de registros.

Tenga en cuenta el () alrededor de query () que construye la consulta.

oli_taz
fuente
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
    } }
] )

En lugar de utilizar dos consultas para encontrar el recuento total y omitir el registro coincidente.
$ facet es la mejor y más optimizada forma.

  1. Coincidir con el récord
  2. Encontrar total_count
  3. omitir el registro
  4. Y también podemos remodelar los datos según nuestras necesidades en la consulta.
SANJEEV RAVI
fuente
1
Agregue alguna explicación a su respuesta para que otros puedan aprender de ella
Nico Haase