[英]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.find
和collection.updateOne
來提高性能,以避免 mongoose 對模型實例上的 mongoose方法和字段使用額外的堆 。
獎金:
即使 與此光標解您的代碼就會出現內存不足的問題再次 ,在這個例子中運行 代碼一樣(定義根據服務器的RAM以兆字節大小):
nodemon --expose-gc --max_old_space_size=10240 app.js
原因是您沒有等待save
返回的promise,並立即繼續執行for
和forEach
循環的下一次迭代。 這意味着您正在啟動大量(待定) 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.