Care este abordarea corectă pentru a actualiza mai multe înregistrări în MongoDB folosind Mongoose (Programare, Node.Js, Mongodb, Mongoose)

Ondrej Tokar a intrebat.

Extrag câteva înregistrări din MongoDB folosind Mongoose, le import în alt sistem și apoi aș dori să setez starea (atributul documentului) pentru toate aceste documente la processed.

Am putut găsi această soluție: Actualizarea mai multor documente după setul de id. Mongoose

Mă întrebam dacă aceasta este abordarea corectă, să construiesc un criteriu format din toate id-urile documentelor și apoi să efectuez actualizarea. Vă rog să luați în considerare și un fapt că vor fi multe documente.

(Care este limita interogării de actualizare? Nu am găsit-o nicăieri. Documentația oficială: http://mongoosejs.com/docs/2.7.x/docs/updating-documents.html)

Comentarii

3 răspunsuri
chridam

Abordarea de a construi un criteriu format din toate id-urile documentelor și apoi de a efectua actualizarea este menită să cauzeze potențiale probleme. Când iterați o listă de documente trimițând o operațiune de actualizare cu fiecare document, în Mongoose riscați să vă aruncați serverul în aer, mai ales când aveți de-a face cu un set de date mare, deoarece nu așteptați finalizarea unui apel asincron înainte de a trece la următoarea iterație. În esență, veți construi o „stivă” de operațiuni nerezolvate până când acest lucru va cauza o problemă – Stackoverflow.

Să presupunem, de exemplu, că aveți o matrice de id-uri de documente pe care doriți să actualizați documentul corespunzător în câmpul de stare:

const processedIds = [
  "57a0a96bd1c6ef24376477cd",
  "57a052242acf5a06d4996537",
  "57a052242acf5a06d4996538"
];

unde puteți utiliza updateMany() metoda

Model.updateMany(
  { _id: { $in: processedIds } }, 
  { $set: { status: "processed" } }, 
  callback
);

sau, alternativ, pentru seturi de date foarte mici, ați putea utiliza metoda forEach() pe matrice pentru a o itera și a actualiza colecția:

processedIds.forEach(function(id)){
  Model.update({ _id: id}, { $set: { status: "processed" } }, callback);
});

Metoda de mai sus este adecvată pentru seturi de date mici. Cu toate acestea, acest lucru devine o problemă atunci când vă confruntați cu mii sau milioane de documente de actualizat, deoarece veți efectua apeluri repetate ale serverului de cod asincron în cadrul buclei.

Pentru a depăși acest lucru, utilizați ceva de genul async’s eachLimit și iterați peste matrice efectuând o operațiune de actualizare MongoDB pentru fiecare element, fără a efectua niciodată mai mult de x actualizări paralele în același timp.


Cea mai bună abordare ar fi să folosiți pentru aceasta API-ul bulk, care este extrem de eficient în procesarea actualizărilor în masă. Diferența de performanță față de apelarea operației de actualizare pentru fiecare dintre numeroasele documente este că, în loc să trimită cererile de actualizare către server la fiecare iterație, API-ul de tip „bulk” trimite cererile o dată la fiecare 1000 de cereri (grupate).

Pentru versiunile Mongoose >=4.3.0 care acceptă MongoDB Server 3.2.x, , puteți utiliza bulkWrite() pentru actualizări. Exemplul următor arată cum puteți proceda în acest sens:

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

Pentru versiunile Mongoose ~3.8.8, , ~3.8.22, , 4.x care acceptă MongoDB Server >=2.6.x, , ați putea utiliza Bulk API după cum urmează

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

Comentarii

  • Vreți să-mi spuneți cum este bulkWrite diferit de insertMany? –  > Por Ondrej Tokar.
  • Sau cum este collection.insert diferit de collection.bulkWrite? Se pare că nu pot găsi nicio documentație oficială despre aceste lucruri 🙁 Referință: unknownerror.org/opensource/Automattic/mongoose/q/stackoverflow/… -…  > Por Ondrej Tokar.
  • insertMany() este noua modalitate de a face scrieri în masă cu driverele mongodb în Mongoose 4.4 și mai sus, în timp ce bulkWrite() va fi susținut la un moment dat în viitor #3998. În principiu, insertMany utilizează Model.collection.insertMany() sub capotă. Principala diferență pe care o pot identifica este că bulkWrite() metoda oferă posibilitatea de a efectua operații de inserare, actualizare și eliminare în masă, iar insertMany() suportă doar operațiunile de inserare în masă. Referință document. –  > Por chridam.
  • Acest lucru funcționează foarte bine. Singurul lucru pe care l-am adăugat a fost un callback final către funcția de apelare în interiorul bulkUpdateCallback atunci când calculează că toate rândurile au fost procesate. În caz contrar, este dificil să încorporați acele contoare în modelul altfel iterativ, deoarece vor ajunge prea târziu pentru a fi returnate. Mi se pare interesant modul în care modelul asincron creează, în general, provocări cu procesele de tip bulk/iterativ – aceasta este o soluție hibridă excelentă. –  > Por scipilot.
  • Această abordare este foarte utilă… Am petrecut zile întregi căutând soluția potrivită… aceasta a funcționat pentru mine… mulțumesc… 🙂 –  > Por John.
Avinash

Puteți folosi {multi: true} în interogarea de actualizare pentru actualizarea în masă.

Exemplu:

employees.update({ _id: { $gt: 3 } },{$inc: { sortOrder: -1 }},{'multi':true});

Codul de mai sus în mongoose este echivalent cu codul de mai jos în mongodb:

db.employees.updateMany({ _id: { $gt: 3 } },{$inc: { sortOrder: -1 }});

Abhishek Singh

pentru a actualiza mai multe înregistrări, $in este cea mai bună opțiune din câte știu eu.

db.collectionName.updateMany(
{
    _id:
        {
            $in:
                [
                    ObjectId("your object id"),
                    ObjectId("your object id")

                ]
        }
},
{
    $inc: { quantity: 100 }

})

Vreau să mai adaug un punct, puteți folosi $in pentru a prelua mai multe documente

db.collectionName.find(
        {
            _id:
                {
                    $in:
                        [
                            ObjectId("your object id"),
                            ObjectId("your object id")

                        ]
                }
        })

Comentarii

  • Este vorba de MongoDB, nu de mongoose, iar $in a fost deja discutat în răspunsul de sus. Ce anume adaugă acest răspuns la răspunsurile deja existente? –  > Por Dan Dăscălescu.