MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Qual é a abordagem correta para atualizar muitos registros no MongoDB usando o Mongoose


A abordagem de criar um critério que consiste em todas as identificações de documentos e, em seguida, realizar a atualização pode causar problemas em potencial. Quando você itera uma lista de documentos enviando uma operação de atualização com cada documento, no Mongoose você corre o risco de explodir seu servidor, especialmente ao lidar com um grande conjunto de dados, porque você não está esperando que uma chamada assíncrona seja concluída antes de passar para a próxima iteração. Você estará essencialmente construindo uma "pilha" de operações não resolvidas até que isso cause um problema - Stackoverflow.

Por exemplo, suponha que você tenha uma matriz de IDs de documentos que deseja atualizar o documento correspondente no campo de status:
const processedIds = [
  "57a0a96bd1c6ef24376477cd",
  "57a052242acf5a06d4996537",
  "57a052242acf5a06d4996538"
];

onde você pode usar o updateMany() método
Model.updateMany(
  { _id: { $in: processedIds } }, 
  { $set: { status: "processed" } }, 
  callback
);

ou, alternativamente, para conjuntos de dados muito pequenos, você pode usar o forEach() método no array para iterar e atualizar sua coleção:
processedIds.forEach(function(id)){
  Model.update({ _id: id}, { $set: { status: "processed" } }, callback);
});

O acima é bom para pequenos conjuntos de dados. No entanto, isso se torna um problema quando você se depara com milhares ou milhões de documentos para atualizar, pois estará fazendo repetidas chamadas de servidor de código assíncrono dentro do loop.

Para superar isso, use algo como eachLimit e itere sobre a matriz executando uma operação de atualização do MongoDB para cada item sem nunca executar mais de x atualizações paralelas ao mesmo tempo.

A melhor abordagem seria usar a API em massa para isso, que é extremamente eficiente no processamento de atualizações em massa. A diferença no desempenho versus chamar a operação de atualização em cada um dos muitos documentos é que, em vez de enviar as solicitações de atualização para o servidor a cada iteração, a API em massa envia as solicitações uma vez a cada 1.000 solicitações (em lote).

Para versões do Mongoose >=4.3.0 que suporta MongoDB Server 3.2.x , você pode usar bulkWrite() para atualizações. O exemplo a seguir mostra como você pode fazer isso:
const bulkUpdateCallback = function(err, r){
  console.log(r.matchedCount);
  console.log(r.modifiedCount);
}

// Initialize the bulk operations array
const bulkUpdateOps = [], counter = 0;

processedIds.forEach(function (id) {
  bulkUpdateOps.push({
    updateOne: {
      filter: { _id: id },
      update: { $set: { status: "processed" } }
    }
  });
  counter++;

  if (counter % 500 == 0) {
    // Get the underlying collection via the Node.js driver collection object
    Model.collection.bulkWrite(bulkUpdateOps, { ordered: true, w: 1 }, bulkUpdateCallback);
    bulkUpdateOps = []; // re-initialize
  }
})

// Flush any remaining bulk ops
if (counter % 500 != 0) {
  Model.collection.bulkWrite(bulkOps, { ordered: true, w: 1 }, bulkUpdateCallback);
}

Para versões do Mongoose ~3.8.8 , ~3.8.22 , 4.x que suporta MongoDB Server >=2.6.x , você pode usar a API em massa da seguinte maneira
var bulk = Model.collection.initializeOrderedBulkOp(),
    counter = 0;

processedIds.forEach(function(id) {
    bulk.find({ "_id": id }).updateOne({ 
        "$set": { "status": "processed" }
    });

    counter++;
    if (counter % 500 == 0) {
        bulk.execute(function(err, r) {
           // do something with the result
           bulk = Model.collection.initializeOrderedBulkOp();
           counter = 0;
        });
    }
});

// Catch any docs in the queue under or over the 500's
if (counter > 0) {
    bulk.execute(function(err,result) {
       // do something with the result here
    });
}