Как эффективен способ условной обработки входных партий документов по 5-20 тыс. В коллекцию, содержащую до миллиона документов с помощью mongodb / mongoose? - PullRequest
0 голосов
/ 30 июня 2019

В моем приложении отслеживания статистики 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 .

Ссылка на обновленную суть

Ответы [ 2 ]

1 голос
/ 30 июня 2019

Из вашего упрощенного примера комбинация имя и сервер является уникальной.Вы можете создать уникальный индекс на {name: 1, server: 1}.Используйте функцию updateOne для обновления и установите флаг upsert в значение true, чтобы вставить документ, если документ не существует.Ниже приведены команды из оболочки Монго, чтобы показать вам, как она работает.

db.records.drop()

db.records.createIndex({name:1, server:1})

db.records.insertMany([     
    { name: 'Jean', server: 'Alpha', level: 9 },        
    { name: 'Anna', server: 'Beta', level: 17 },        
    { name: 'Jean', server: 'Beta', level: 10 }  ])

db.records.find({}, {_id: 0})

db.records.updateOne(
    { name: 'Victor', server: 'Alpha', level: {$lte: 22} },     
    {$set: {name: 'Victor', server: 'Alpha', level: 22 }},      
    {upsert: true})

db.records.find({}, {_id: 0})

db.records.updateOne(
    { name: 'Jean', server: 'Alpha', level: {$lte: 9} }, 
    {$set: {name: 'Jean', server: 'Alpha', level: 9}}, 
    {upsert: 1})

db.records.find({}, {_id: 0})

db.records.updateOne(
    { name: 'Jean', server: 'Alpha', level: {$lte: 10} }, 
    {$set: {name: 'Jean', server: 'Alpha', level: 10 }}, 
    {upsert: 1})

db.records.find({}, {_id: 0})
1 голос
/ 30 июня 2019

Сначала настройте составной индекс. https://docs.mongodb.com/manual/core/index-compound/

Доступен как на mongodb, так и на mongoose.

Во-вторых, пожалуйста, напишите правильные поисковые запросы. $ или (https://docs.mongodb.com/manual/reference/operator/query/or/), если поддерживается индексом O (k log n), где k - количество подходящих элементов.

В качестве альтернативы, попробуйте массовые операции. https://docs.mongodb.com/manual/reference/method/Bulk/.

Может возвращать количество успешных находок / обновлений. https://docs.mongodb.com/manual/reference/method/BulkWriteResult/. Добавьте дополнительное поле для поиска уровня: {$ lt: currlvl}, чтобы выполнять обновления только условно. Мне не очень понятно, как сочетать это с аппетитами.

Наконец, на вашем месте я бы хэшировал / конкатанировал сервер и имя и делал его идентификатором. Сделал бы жизнь намного проще.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...