Каков правильный подход к обновлению многих записей в MongoDB с использованием Mongoose

Я извлекаю некоторые записи из MongoDB, используя Mongoose, импортируя их в другую систему, а затем я хотел бы установить статус (атрибут документа) для всех этих документов на processed.

Я мог бы найти это решение: Обновление нескольких документов по установленному идентификатору. Mongoose

Мне было интересно, правильно ли это подход, чтобы создать критерий, состоящий из всех идентификаторов документов, а затем выполнить обновление. Пожалуйста, учтите также, что это будет много документов.

(Каков предел запроса на обновление? Не удалось найти его где-нибудь. Официальная документация: http://mongoosejs.com/docs/2.7.x/docs/updating-documents.html)

Ответ 1

Подход к построению критерия, состоящего из всех идентификаторов документов и последующему обновлению, неизбежно вызовет потенциальные проблемы. Когда вы выполняете итерацию списка документов, отправляющих операцию обновления с каждым документом, в Mongoose вы рискуете взорвать свой сервер, особенно при работе с большим набором данных, поскольку вы не ожидаете завершения асинхронного вызова, прежде чем переходить к следующему итерация. По сути, вы будете создавать "стек" неразрешенных операций, пока это не вызовет проблему - Stackoverflow.

Возьмем, к примеру, предположим, что у вас есть массив идентификаторов документов, которые вы хотите обновить соответствующий документ в поле состояния:

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

где вы можете использовать метод updateMany() method

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

или, наоборот, для действительно небольших наборов данных вы можете использовать метод forEach() в массиве, чтобы выполнить итерацию и обновить свою коллекцию:

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

Вышесказанное подходит для небольших наборов данных. Однако это становится проблемой, когда вы сталкиваетесь с тысячами или миллионами документов для обновления, так как вы будете делать повторные вызовы сервера асинхронного кода в цикле.

Чтобы преодолеть это, используйте что-то вроде async eachLimit и итерируйте по массиву, выполняя операцию обновления MongoDB для каждого элемента, не выполняя одновременно более x параллельных обновлений.


Лучшим подходом было бы использовать для этого пакетный API, который чрезвычайно эффективен при массовой обработке обновлений. Разница в производительности по сравнению с вызовом операции обновления для каждого из множества документов заключается в том, что вместо отправки запросов на обновление на сервер с каждой итерацией массовый API отправляет запросы один раз в каждую 1000 запросов (в пакетном режиме).

Для версий Mongoose >=4.3.0, которые поддерживают MongoDB Server 3.2.x, вы можете использовать bulkWrite() для обновлений. В следующем примере показано, как вы можете это сделать:

var bulkUpdateCallback = function(err, r){
    console.log(r.matchedCount);
    console.log(r.modifiedCount);
}
// Initialise the bulk operations array
var 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 native node.js driver collection object
        Model.collection.bulkWrite(bulkOps, { "ordered": true, w: 1 }, bulkUpdateCallback);
        bulkUpdateOps = []; // re-initialize
    }
})

if (counter % 500 != 0) { Model.collection.bulkWrite(bulkOps, { "ordered": true, w: 1 }, bulkUpdateCallback); }

Для версий Mongoose ~3.8.8, ~3.8.22, 4.x, которые поддерживают Сервер MongoDB >=2.6.x, вы можете использовать Bulk API следующим образом

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

Ответ 2

Вы можете использовать опцию {multi: true} в своем запросе обновления для массового обновления.

Пример:

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

Вышеприведенный код в mongoose эквивалентен приведенному ниже коду в mongodb:

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