[英]Processing large CSV uploads in Node.js
根据这里的先前线程:
...我正在寻找有关处理大型数据上传文件的更广泛的建议。
设想:
用户上传了一个非常大的 CSV 文件,其中包含数十万到数百万行。 它使用 multer 流式传输到端点:
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
router.post("/", upload.single("upload"), (req, res) => {
//...
});
每一行都被转换成一个 JSON 对象。 然后将该对象映射到几个较小的对象中,这些对象需要插入到几个不同的表中,分布在各种微服务容器中并由其访问。
async.forEachOfSeries(data, (line, key, callback) => {
let model = splitData(line);
//save model.record1, model.record2, etc. sequentially
});
很明显,使用这种方法我会遇到内存限制。 这样做的最有效方式是什么?
为了避免内存问题,您需要使用流处理文件 - 简单地说,增量。 您不是将整个文件加载到内存中,而是读取每一行,它会得到相应的处理,然后在符合垃圾收集条件后立即进行处理。
在 Node 中,您可以结合使用CSV 流解析器以将二进制内容流式传输为 CSV 行和through2 (一种允许您控制流的流的流实用程序)来完成此操作; 在这种情况下,暂时暂停它以允许将行保存在数据库中。
该过程如下:
cb()
移至下一项。 我不熟悉multer
但这里有一个使用来自文件的流的示例。
const fs = require('fs')
const csv = require('csv-stream')
const through2 = require('through2')
const stream = fs.createReadStream('foo.csv')
.pipe(csv.createStream({
endLine : '\n',
columns : ['Year', 'Make', 'Model'],
escapeChar : '"',
enclosedChar : '"'
}))
.pipe(through2({ objectMode: true }, (row, enc, cb) => {
// - `row` holds the first row of the CSV,
// as: `{ Year: '1997', Make: 'Ford', Model: 'E350' }`
// - The stream won't process the *next* item unless you call the callback
// `cb` on it.
// - This allows us to save the row in our database/microservice and when
// we're done, we call `cb()` to move on to the *next* row.
saveIntoDatabase(row).then(() => {
cb(null, true)
})
.catch(err => {
cb(err, null)
})
}))
.on('data', data => {
console.log('saved a row')
})
.on('end', () => {
console.log('end')
})
.on('error', err => {
console.error(err)
})
// Mock function that emulates saving the row into a database,
// asynchronously in ~500 ms
const saveIntoDatabase = row =>
new Promise((resolve, reject) =>
setTimeout(() => resolve(), 500))
示例foo.csv
CSV 是这样的:
1997,Ford,E350
2000,Mercury,Cougar
1998,Ford,Focus
2005,Jaguar,XKR
1991,Yugo,LLS
2006,Mercedes,SLK
2009,Porsche,Boxter
2001,Dodge,Viper
这种方法避免了必须在内存中加载整个 CSV。 一旦处理了row
,它就会超出范围/变得无法访问,因此它有资格进行垃圾收集。 这就是使这种方法如此有效的内存的原因。 理论上,这允许您处理无限大小的文件。 阅读流手册了解更多关于流的信息。
row
s 推入一个 Array,处理/保存整个 Array(块),然后调用cb
移动到下一个块 - 重复该过程。end
/ error
事件对于响应操作是成功还是失败特别有用。multer
。我使用上面的模型将一个 1.7mm x 200 的 csv 数据矩阵导入到 mongo 中,代码如下。 不可否认,这很慢,我可以在学习如何更好地分块数据以提高效率方面提供一些帮助,即不是在每次读取后插入,而是将行累积到 5,10,25k 行的数组中,然后 insertMany 或更好的熟练使用 through2-map 或 through2-filter 方法。 如果有人愿意分享一个例子,提前致谢。
require('dotenv').config();
const parse = require('csv-parser');
const fs = require("fs");
const through2 = require('through2')
const db = require('../models');
const file = "myFile.csv"
const rows = [];
//========Constructor Function for Mongo Import after each read======//
function Hotspot(variable1, variable2,...) {
this.variable1 = variable1;
this.variable2 = variable2;
...}
//========Counter so I can monitor progress in console============//
let counter = 0;
const rows = [];
//This function is imported & run in server.js from './scripts' after mongoose connection established//
exports.importCsvData = () => {
fs.createReadStream(myFile)
.pipe(parse())
.pipe(through2({ objectMode: true }, (row, enc, cb) => {
let hotspot = new Hotspot(
`${row["ROW_VARIABLE_COLUMN_1"]}`,
`${row["ROW_VARIABLE_COLUMN_2"]}`,...)
db.MongoModel.create(hotspot)
.then(result => console.log('created', counter++))
.then(() => { cb(null, true) })
.catch(err => {
cb(err, null)
})
}))
.on('data', (row) => {
rows.push(row);
})
.on('end', () => {
console.log('read complete')
})
}
作为编写此脚本的基础和参考。 似乎工作“很好”,除了我昨晚晚上 10 点开始的,到今天早上 7 点 45 分还不到一半。 这比我在尝试将所有“热点”对象累积到热点数组中以批量插入 mongoDB 后收到的"event": "Allocation failed - JavaScript heap out of memory"
不足"event": "Allocation failed - JavaScript heap out of memory"
错误要好。 我对 Node 中的 readStream/through2/csv-parser 和学习相当陌生,但想分享一些有效的东西,并且目前正在工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.