Node.js 大文件上传到 MongoDB 阻塞事件循环和工作池
Node.js Large File Uploads to MongoDB blocking the Event Loop and Worker Pool
所以我想 使用 Node.js 服务器 使用 Express、Mongoose 和 Multer 的 GridFS 存储引擎将大型 CSV 文件上传到 mongoDB 云数据库,但是 当文件上传开始时,我的数据库变得无法处理任何其他 API 请求。例如,如果在上传文件时不同的客户端请求从数据库中获取用户,服务器将收到请求并尝试从 MongoDB 云端获取用户,但是请求会卡住 因为大文件上传吃光了所有的计算资源。因此,客户端执行的获取请求不会return用户直到正在进行的文件上传完成。
我理解如果一个线程需要很长时间来执行回调(事件循环)或任务(Worker),那么它被认为是“阻塞的”并且 Node.js 运行 JavaScript 事件循环中的代码,同时它提供了一个工作池来处理像文件 I/O 这样的昂贵任务。我在 this blog post by NodeJs.org 上读到,为了让您的 Node.js 服务器保持快速,在任何给定时间与每个客户端相关的工作都必须“小”,我的目标应该是 尽量减少任务时间的变化。这样做的原因是,如果 Worker 的当前任务比其他任务昂贵得多,它将无法处理其他待处理的任务,从而将 Worker Pool 的大小减一,直到任务完成。
换句话说,执行大文件上传的客户端正在执行一个昂贵的任务,降低了工作池的吞吐量,进而降低了服务器的吞吐量。根据上述博客post,当每个子任务完成时,它应该提交下一个子任务,当最后一个子任务完成时,它应该通知提交者。 这样,在长任务的每个子任务之间(大文件上传),Worker 可以处理较短任务的子任务, 从而解决阻塞问题
但是,我不知道如何在实际代码中实现这个解决方案。是否有任何特定的分区功能可以解决此问题?我是否必须使用特定的上传架构或节点包而不是 multer-gridfs-storage 来上传我的文件?请帮助
这是我当前使用 Multer 的 GridFS 存储引擎实现的文件上传:
// Adjust how files get stored.
const storage = new GridFsStorage({
// The DB connection
db: globalConnection,
// The file's storage configurations.
file: (req, file) => {
...
// Return the file's data to the file property.
return fileData;
}
});
// Configure a strategy for uploading files.
const datasetUpload = multer({
// Set the storage strategy.
storage: storage,
// Set the size limits for uploading a file to 300MB.
limits: { fileSize: 1024 * 1024 * 300 },
// Set the file filter.
fileFilter: fileFilter,
});
// Upload a dataset file.
router.post('/add/dataset', async (req, res)=>{
// Begin the file upload.
datasetUpload.single('file')(req, res, function (err) {
// Get the parsed file from multer.
const file = req.file;
// Upload Success.
return res.status(200).send(file);
});
});
你能做到architecture/infrastructure吗?如果是这样,这个挑战最好通过不同的方法来解决。这实际上是无服务器解决方案的完美候选者,即 Lambda。
Lambda 不会 运行 在一台机器上并行执行任何请求。 Lambda 将一个请求分配给一台机器,在请求完成之前,这台机器将不会接收任何其他流量。因此,您永远不会达到现在遇到的极限。
我认为这个问题源于buffer
。因为缓冲区 必须接收所有块,然后整个缓冲区被发送
消费者,所以缓冲 需要很长时间 。 Streams 可以解决这个问题,因此 streams 允许我们在数据从源到达时立即处理数据,并做一些不可能通过缓冲数据和一次性处理所有数据。我在 multer GitHub 页面上找到了 storage.fromStream()
方法,并通过上传 122 MB 文件 对其进行了测试,它对我有用,感谢 Node.js 流, 每一个
数据块一收到就被消耗并保存到云数据库中。上传总时间不到 1 分钟,并且服务器可以轻松响应 上传期间的其他请求。
const {GridFsStorage} = require('multer-gridfs-storage');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const express = require('express');
const fs = require('fs');
const connectDb = require('./connect');
const app = express();
const storage = new GridFsStorage({db:connectDb()});
app.post('/profile', upload.single('file'), function (req, res, next) {
const {file} = req;
const stream = fs.createReadStream(file.path); //creates stream
storage.fromStream(stream, req, file)
.then(() => res.send('File uploaded')) //saves data as binary to cloud db
.catch(() => res.status(500).send('error'));
});
app.get('/profile',(req,res)=>{
res.send("hello");
})
app.listen(5000);
所以经过几天的研究,我发现问题的根源不是 Node.JS 或者我的文件上传实现。 问题是 MongoDB Atlas 无法在处理其他操作(例如从我的数据库中获取用户)的同时处理文件上传工作负载。正如我在问题 post 中所述,Node.js 收到了来自其他客户的 API 呼叫,但他们没有返回任何结果。我现在意识到那是因为 他们卡在了数据库级别 。一旦我切换到 MongoDB 的本地部署,问题就解决了。
根据 this blog post about MongoDB Best Practices,相对于 CPU 数量的活动线程总数(即并发操作)会影响性能,因此会影响 Node.js 服务器的吞吐量。但是,我已经尝试使用最多 8 个 vCPU 的专用 MongoDB 集群(M50 集群包)并且 MongoDB Atlas 在处理其他客户端请求时仍然无法上传文件.
如果有人让它与 云解决方案一起工作,我想了解更多。谢谢。
所以我想 使用 Node.js 服务器 使用 Express、Mongoose 和 Multer 的 GridFS 存储引擎将大型 CSV 文件上传到 mongoDB 云数据库,但是 当文件上传开始时,我的数据库变得无法处理任何其他 API 请求。例如,如果在上传文件时不同的客户端请求从数据库中获取用户,服务器将收到请求并尝试从 MongoDB 云端获取用户,但是请求会卡住 因为大文件上传吃光了所有的计算资源。因此,客户端执行的获取请求不会return用户直到正在进行的文件上传完成。
我理解如果一个线程需要很长时间来执行回调(事件循环)或任务(Worker),那么它被认为是“阻塞的”并且 Node.js 运行 JavaScript 事件循环中的代码,同时它提供了一个工作池来处理像文件 I/O 这样的昂贵任务。我在 this blog post by NodeJs.org 上读到,为了让您的 Node.js 服务器保持快速,在任何给定时间与每个客户端相关的工作都必须“小”,我的目标应该是 尽量减少任务时间的变化。这样做的原因是,如果 Worker 的当前任务比其他任务昂贵得多,它将无法处理其他待处理的任务,从而将 Worker Pool 的大小减一,直到任务完成。
换句话说,执行大文件上传的客户端正在执行一个昂贵的任务,降低了工作池的吞吐量,进而降低了服务器的吞吐量。根据上述博客post,当每个子任务完成时,它应该提交下一个子任务,当最后一个子任务完成时,它应该通知提交者。 这样,在长任务的每个子任务之间(大文件上传),Worker 可以处理较短任务的子任务, 从而解决阻塞问题
但是,我不知道如何在实际代码中实现这个解决方案。是否有任何特定的分区功能可以解决此问题?我是否必须使用特定的上传架构或节点包而不是 multer-gridfs-storage 来上传我的文件?请帮助
这是我当前使用 Multer 的 GridFS 存储引擎实现的文件上传:
// Adjust how files get stored.
const storage = new GridFsStorage({
// The DB connection
db: globalConnection,
// The file's storage configurations.
file: (req, file) => {
...
// Return the file's data to the file property.
return fileData;
}
});
// Configure a strategy for uploading files.
const datasetUpload = multer({
// Set the storage strategy.
storage: storage,
// Set the size limits for uploading a file to 300MB.
limits: { fileSize: 1024 * 1024 * 300 },
// Set the file filter.
fileFilter: fileFilter,
});
// Upload a dataset file.
router.post('/add/dataset', async (req, res)=>{
// Begin the file upload.
datasetUpload.single('file')(req, res, function (err) {
// Get the parsed file from multer.
const file = req.file;
// Upload Success.
return res.status(200).send(file);
});
});
你能做到architecture/infrastructure吗?如果是这样,这个挑战最好通过不同的方法来解决。这实际上是无服务器解决方案的完美候选者,即 Lambda。
Lambda 不会 运行 在一台机器上并行执行任何请求。 Lambda 将一个请求分配给一台机器,在请求完成之前,这台机器将不会接收任何其他流量。因此,您永远不会达到现在遇到的极限。
我认为这个问题源于buffer
。因为缓冲区 必须接收所有块,然后整个缓冲区被发送
消费者,所以缓冲 需要很长时间 。 Streams 可以解决这个问题,因此 streams 允许我们在数据从源到达时立即处理数据,并做一些不可能通过缓冲数据和一次性处理所有数据。我在 multer GitHub 页面上找到了 storage.fromStream()
方法,并通过上传 122 MB 文件 对其进行了测试,它对我有用,感谢 Node.js 流, 每一个
数据块一收到就被消耗并保存到云数据库中。上传总时间不到 1 分钟,并且服务器可以轻松响应 上传期间的其他请求。
const {GridFsStorage} = require('multer-gridfs-storage');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const express = require('express');
const fs = require('fs');
const connectDb = require('./connect');
const app = express();
const storage = new GridFsStorage({db:connectDb()});
app.post('/profile', upload.single('file'), function (req, res, next) {
const {file} = req;
const stream = fs.createReadStream(file.path); //creates stream
storage.fromStream(stream, req, file)
.then(() => res.send('File uploaded')) //saves data as binary to cloud db
.catch(() => res.status(500).send('error'));
});
app.get('/profile',(req,res)=>{
res.send("hello");
})
app.listen(5000);
所以经过几天的研究,我发现问题的根源不是 Node.JS 或者我的文件上传实现。 问题是 MongoDB Atlas 无法在处理其他操作(例如从我的数据库中获取用户)的同时处理文件上传工作负载。正如我在问题 post 中所述,Node.js 收到了来自其他客户的 API 呼叫,但他们没有返回任何结果。我现在意识到那是因为 他们卡在了数据库级别 。一旦我切换到 MongoDB 的本地部署,问题就解决了。
根据 this blog post about MongoDB Best Practices,相对于 CPU 数量的活动线程总数(即并发操作)会影响性能,因此会影响 Node.js 服务器的吞吐量。但是,我已经尝试使用最多 8 个 vCPU 的专用 MongoDB 集群(M50 集群包)并且 MongoDB Atlas 在处理其他客户端请求时仍然无法上传文件.
如果有人让它与 云解决方案一起工作,我想了解更多。谢谢。