首页 > 解决方案 > JavaScript堆内存不足-插入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;

标签: javascriptnode.jsmongodbexpressmongodb-query

解决方案


对于大量数据,您必须使用 cursors

想法尽快处理文档,因为您从 db获得一个。

就像您要求 db 给讲师,而db以小批量回,您使用该批次进行操作并处理它们,直到所有批次结束。

否则 await Instructor.find()会将所有数据加载到内存并使用您不需要的 mongoose 方法填充实例。

甚至await Instructor.find().lean()不会给内存带来好处。

当您在收集时,光标是mongodb 的功能。find

使用猫鼬它可以使用: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来避免猫对模型实例上的猫鼬方法和字段使用额外的堆

奖金:

即使使用游标解决方案您代码将再次出现内存不足问题,请像本示例中那样运行您的代码(根据服务器的 ram 定义大小(以兆字节为单位)):

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

推荐阅读