В моем приложении отслеживания статистики mmo для переписей / персонажей я получаю от пользователя партии входных данных, содержащие до 5-20 тыс. Документов, каждый из которых мне нужно объединить в базу данных. У меня есть конкретные критерии для поиска, чтобы решить, если документ из входных данных уже присутствует в коллекции и нуждается в обновлении или он полностью новый и должен быть вставлен в коллекцию.
Для правильной работы моего приложения также очень важно, чтобы я мог точно определить, сколько документов действительно было обновлено и / или добавлено непосредственно после обработки ввода.
Чтобы лучше объяснить, что я пытаюсь сделать, я приведу его в упрощенном примере, где я могу показать вам, как выглядит ввод и какие результаты желательны.
В качестве отправной точки для следующих входных случаев вот так выглядит коллекция:
collection = [
{ name: 'Jean', server: 'Alpha', level: 9 },
{ name: 'Anna', server: 'Beta', level: 17 },
{ name: 'Jean', server: 'Beta', level: 10 }
];
Исходя из этого, есть три основных случая ввода, которые мне нужно охватить.
Дело № 1
Когда я получаю ввод с совершенно новой комбинацией name
+ server
, в коллекцию должен быть добавлен новый документ
input = { name: 'Victor', server: 'Alpha', level: 22 };
должно стать:
collection = [
{ name: 'Jean', server: 'Alpha', level: 9 },
{ name: 'Anna', server: 'Beta', level: 17 },
{ name: 'Jean', server: 'Beta', level: 10 },
{ name: 'Victor', server: 'Alpha', level: 22 }
];
Дело № 2
Когда я получаю ввод с существующей комбинацией name
+ server
, но с выше level
, существующий документ должен быть обновлен
input = { name: 'Jean', server: 'Alpha', level: 10 };
должно стать
collection = [
{ name: 'Jean', server: 'Alpha', level: 10 },
{ name: 'Anna', server: 'Beta', level: 17 },
{ name: 'Jean', server: 'Beta', level: 10 }
];
Дело № 3
Когда я получаю ввод с существующей комбинацией name
+ server
, но с равным или меньшим level
, ничего не должно происходить, и коллекция должна оставаться как это было
input = { name: 'Jean', server: 'Alpha', level: 9 };
или
input = { name: 'Jean', server: 'Alpha', level: 8 };
должно остаться:
collection = [
{ name: 'Jean', server: 'Alpha', level: 9 },
{ name: 'Anna', server: 'Beta', level: 17 },
{ name: 'Jean', server: 'Beta', level: 10 }
];
На данный момент я в основном собираю всю коллекцию в массив, а затем с помощью Array.filter
выясняю, какие входные данные уже присутствуют в коллекции и обновляет их с помощью findOneAndUpdate
, а какие новые и вставьте их в коллекцию с помощью insertMany
:
Test.find({}, async (err, documents) => {
if (err) return console.log(err);
if (documents.length > 0) {
const changedInputs = inputs.filter(byChanged(documents));
const newInputs = inputs.filter(byNew(documents));
const insertResult = await Test.insertMany(newInputs);
const inserted = insertResult.length;
const updateResults = await Promise.all(compileUpdatePromises(changedInputs));
let updated = 0;
updateResults.forEach(updateResult => {
updated = updateResult === 'updated' ? updated + 1 : updated;
});
console.log('updated:', updated);
console.log('inserted:', inserted);
}
});
Ссылка на суть всего примера
Это работало нормально, когда в собрании было не так много документов, но теперь, когда он вырос до 50 000 документов, он становится безумно медленным и блокирует монго-соединение во время этого процесса, что также блокирует весь API для всех других вызовов.
Как только это приложение получает больше трафика, оно может быстро превратиться в коллекцию из миллиона документов, которые затем постоянно обновляются.
Есть ли какие-нибудь простые и более эффективные способы, чтобы mongodb делал всю эту тяжелую работу за меня вместо того, чтобы делать все сам?
Обновление 1:
С предложениями от simagix и почернение я очень близко подошел к решению. Вот как выглядит мой измененный код:
const bulkInput = inputs.map(input => ({
updateOne: {
filter: { name: input.name, server: input.server, level: { $lte: input.level } },
upsert: true,
update: { $set: { name: input.name, server: input.server, level: input.level } }
}
}));
Test.bulkWrite(bulkInput).then(result => {
console.log('inserted:', result.nUpserted, 'updated:', result.nModified);
});
Проблема теперь во втором примере Дело № 3 :
input = { name: 'Jean', server: 'Alpha', level: 8 };
Результат:
collection = [
{ name: 'Jean', server: 'Alpha', level: 9 },
{ name: 'Anna', server: 'Beta', level: 17 },
{ name: 'Jean', server: 'Beta', level: 10 },
{ name: 'Jean', server: 'Alpha', level: 8 }
];
Ссылка на обновленную суть
Обновление 2:
Просто нужно сделать составной индекс
testSchema.index({ name: 1, server: 1 });
к уникальному составному индексу
testSchema.index({ name: 1, server: 1 }, { unique: true });
Теперь мне нужно найти правильное решение для обработки исключения E11000 duplicate key error
, которое выдает для Пример № 3, пример 2 .
Ссылка на обновленную суть