簡體   English   中英

JavaScript堆內存不足 - 插入mongodb時出錯

[英]JavaScript heap out of memory - error while inserting into mongodb

我想在MongoDB中插入1500000個文檔。 首先,我查詢一個數據庫並從那里獲得一個15000個教師的列表,對於每個教師,我想每個插入100個課程。

我運行兩個循環:首先它循環遍歷所有教師,其次,在每次迭代中,它將為該id插入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));
    }
});

這是我的package.json文件,我在網上找到了一些東西:

{
    "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;

對於 數據量您對使用游標

想法是盡可能處理文檔,因為你從db獲得了一個文檔。

就像你要求db給教師數據庫發送小批量一樣 ,你運行該批處理並處理它們直到所有批次結束

否則 await Instructor.find()會將所有數據 加載 到內存中,並使用您不需要的mongoose方法填充實例

甚至await Instructor.find().lean()也不會給內存帶來好處。

當您在集合上find時,光標是mongodb的功能

使用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.findcollection.updateOne提高性能,避免 mongoose 對模型實例上的 mongoose方法和字段使用額外的堆

獎金:

即使 光標解您代碼就會出現內存不足的問題再次 ,在這個例子中運行 代碼一樣(定義根據服務器的RAM以兆字節大小):

nodemon --expose-gc --max_old_space_size=10240 app.js

原因是您沒有等待save返回的promise,並立即繼續執行forforEach循環的下一次迭代。 這意味着您正在啟動大量(待定) save操作,這確實會增加mongodb庫的內存使用量。

在繼續下一次迭代之前,最好等待save (和鏈接的findByIdAndUpdate )解析。

由於您顯然位於async函數上下文中,因此可以使用await ,前提是使用for循環替換forEach循環(以便保留在相同的函數上下文中):

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操作都是序列化的:下一個只在前一個完成后才開始。

請注意,我沒有包含您的錯誤處理:最好使用鏈接到此async函數調用的catch調用來完成此操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM