Кучи JavaScript не хватает памяти - ошибка при вставке в mongodb - PullRequest
0 голосов
/ 30 декабря 2018

Я хочу вставить 1500000 документов в MongoDB.Во-первых, я запрашиваю базу данных и получаю список из 15000 преподавателей, и для каждого преподавателя я хочу добавить по 100 курсов для каждого из них.

Я запускаю два цикла: во-первых, он проходит по всем инструкторам, а во-вторых, в каждой итерации он вставляет 100 документов для этого идентификатора, как показано в коде ниже:

const instructors = await Instructor.find();
//const insrtuctor contains 15000 instructor
instructors.forEach((insructor) => {
    for(let i=0; i<=10; i++) {
        const course = new Course({
            title: faker.lorem.sentence(),
            description: faker.lorem.paragraph(),
            author: insructor._id,
            prise: Math.floor(Math.random()*11),
            isPublished: 'true',
            tags: ["java", "Nodejs", "javascript"]
        });
        course.save().then(result => {
            console.log(result._id);
            Instructor.findByIdAndUpdate(insructor._id, { $push: { courses: course._id } })
            .then(insructor => {
                console.log(`Instructor Id : ${insructor._id} add Course : ${i} `);
            }).catch(err => next(err));
            console.log(`Instructor id: ${ insructor._id } add Course: ${i}`)
        }).catch(err => console.log(err));
    }
});

Вот мой *Файл 1006 *, в который я положил что-то, что нашел в интернете:

{
    "scripts": {
        "start": "nodemon app.js",
        "fix-memory-limit": "cross-env LIMIT=2048 increase-memory-limit"
    },
    "devDependencies": {
        "cross-env": "^5.2.0",
        "faker": "^4.1.0",
        "increase-memory-limit": "^1.0.6",
    }
}

Это определение модели моего курса

const mongoose = require('mongoose');

const Course = mongoose.model('courses', new mongoose.Schema({

title: {
    type: String,
    required: true,
    minlength: 3
},
author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'instructor'
},
description: {
    type: String,
    required: true,
    minlength: 5
},
ratings: [{
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'users',
        required: true,
        unique: true
    },
    rating: {
        type: Number,
        required: true,
        min: 0,
        max: 5
    },
    description: {
        type: String,
        required: true,
        minlength: 5
    }
}],
tags: [String],
rating: {
    type: Number,
    min: 0,
    default: 0
},
ratedBy: {
    type: Number,
    min: 0,
    default: 0
},
prise: {
    type: Number,
    required: function() { this.isPublished },
    min: 0
},
isPublished: {
    type: Boolean,
    default: false
}
}));

module.exports = Course;

Ответы [ 2 ]

0 голосов
/ 30 декабря 2018

Для большой количество данных Вы должны использовать курсоры .

Идея означает обработать документ как можно скорее по мере получения один из дБ.

Как будто вы просите дБ дать инструкторам и дБ отправляет обратно небольшими партиями , и вы работаете с этой партией и обрабатываете их до достижения конец всех партий.

В противном случае await Instructor.find() будет загружать все данные в память и заполнение , что экземпляров методами мангусты, которые вам не нужны.

Даже await Instructor.find().lean() не даст преимущества памяти.

Курсор - это функция mongodb , когда вы делаете find в коллекции.

С помощью mongoose вы можете использовать: Instructor.collection.find({})

Часы это видео .

Ниже я написал решение для пакетной обработки данных с использованием курсора.

Добавьте это где-то внутри модуля:

const createCourseForInstructor = (instructor) => {
  const data = {
    title: faker.lorem.sentence(),
    description: faker.lorem.paragraph(),
    author: instructor._id,
    prise: Math.floor(Math.random()*11), // typo: "prise", must be: "price"
    isPublished: 'true',
    tags: ["java", "Nodejs", "javascript"]
  };
  return Course.create(data);
}

const assignCourseToInstructor = (course, instructor) => {
  const where = {_id: instructor._id};
  const operation = {$push: {courses: course._id}};
  return Instructor.collection.updateOne(where, operation, {upsert: false});
}

const processInstructor = async (instructor) => {
  let courseIds = [];
  for(let i = 0; i < 100; i++) {
    try {
      const course = await createCourseForInstructor(instructor)
      await assignCourseToInstructor(course, instructor);
      courseIds.push(course._id);
    } 
    catch (error) {
      console.error(error.message);
    }
  }
  console.log(
    'Created ', courseIds.length, 'courses for', 
    'Instructor:', instructor._id, 
    'Course ids:', courseIds
  );
};

и в своем асинхронном блоке замените цикл на:

const cursor = await Instructor.collection.find({}).batchSize(1000);

while(await cursor.hasNext()) {
  const instructor = await cursor.next();
  await processInstructor(instructor);
}

PS Я использую нативные collection.find и collection.updateOne для производительности до избегайте использования мангуста дополнительной кучи дляметоды и поля мангуста на экземплярах модели .

БОНУС:

Четный если с this курсор решение Ваш код выйдет из памяти снова снова , выполнит Ваш код как в этом примере (определить размер в мегабайтах в зависимости от оперативной памяти сервера):

nodemon --expose-gc --max_old_space_size=10240 app.js
0 голосов
/ 30 декабря 2018

Причина в том, что вы не ожидаете обещаний, возвращаемых save, и сразу же продолжаете следующие итерации циклов for и forEach.Это означает, что вы запускаете огромное количество (ожидающих) save операций, которые действительно увеличат использование памяти библиотекой mongodb.

Было бы лучше дождаться save (и прикованных цепочек).findByIdAndUpdate) для разрешения перед продолжением следующих итераций.

Поскольку вы, очевидно, находитесь в контексте функции async, вы можете использовать для этого await, при условии, что вы замените цикл forEach нацикл for (чтобы вы оставались в том же контексте функции):

async function yourFunction() {
    const instructors = await Instructor.find();
    for (let instructor of instructors) { // Use `for` loop to allow for more `await`
        for (let i=0; i<10; i++) { // You want 10 times, right?
            const course = new Course({
                title: faker.lorem.sentence(),
                description: faker.lorem.paragraph(),
                author: instructor._id,
                prise: Math.floor(Math.random()*11),
                isPublished: 'true',
                tags: ["java", "Nodejs", "javascript"]
            });
            const result = await course.save();
            console.log(result._id);
            instructor = await Instructor.findByIdAndUpdate(instructor._id, { $push: { courses: course._id } });
            console.log(`Instructor Id : ${instructor._id} add Course : ${i}`);
        }
    }
}

Теперь все операции save сериализуются: следующая начинается только после завершения предыдущего.

Обратите внимание, что я не включил обработку ошибок, которую вы имели: лучше всего это сделать с помощью вызова catch, связанного с вызовом этой функции async.

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