Papa Parse를 사용해 대용량의 CSV 파일을 열어서 작업을 수행을 하는데 파일이 너무 커서 파일을 읽다가 JavaScript heap out of memory
에러로 죽어버리는 문제가 생겼습니다.
그래서 다음과 같이 stream
을 전달하고 async
하게 시도해 보았지만, 그래도 JavaScript heap out of memory
를 동일한 오류가 발생했습니다.
const stream = fs.createReadStream(path.join(__dirname, './data.csv'));
let count = 0;
process.stdout.write('start\n');
Papa.parse(stream, {
header: true,
delimiter: ',',
skipEmptyLines: true,
worker: true,
step: async (record) => {
process.stdout.write(`${count++} ${Object.values(record).join(',')}\n`);
// Fake asynchronous operation
await new Promise((resolve) => setTimeout(resolve, 100));
}
complete: function(results) {
process.stdout.write('...done\n');
}
});
그래서 Papa Parse의 API 문서에서 이 문제를 해결할 마땅한 방법이 보이지 않아 다른 CSV 라이브러리를 찾아보았습니다. csv-parse를 보다 보니 대용량 CSV 파일을 Generator로 읽어오면 해당 이슈를 해결할 수 있을 것 같다는 생각이 들었습니다.
csv-parse API 문서에 다음과 같은 예제가 있어서 csv-generate에 File Stream을 전달해서 사용할 것이라 생각이 들었지만, 해당 라이브러리는 임의의 값을 생성해 주는 유틸이어서 용도가 맞지 않았습니다.
const assert = require('assert');
const generate = require('csv-generate');
const parse = require('..');
(async () => {
// Initialise the parser by generating random records
const parser = generate({
high_water_mark: 64 * 64,
length: 1000
}).pipe(
parse()
)
// Intialise count
let count = 0;
// Report start
process.stdout.write('start\n')
// Iterate through each records
for await (const record of parser) {
// Report current line
process.stdout.write(`${count++} ${record.join(',')}\n`)
// Fake asynchronous operation
await new Promise((resolve) => setTimeout(resolve, 100))
}
// Report end
process.stdout.write('...done\n')
// Validation
assert.strictEqual(count, 10000)
})()
API 문서를 여기저기 뒤져봤지만, 마땅한 예제가 없어서 직접 시도해본 방법을 글로 공유하면 같은 고민을 하는 분들께 도움이 될 것 같아 포스트를 작성하였고 해당 코드는 아래와 같습니다.
const fs = require('fs');
const path = require('path');
const parse = require('csv-parse');
(async () => {
const stream = fs.createReadStream(path.join(__dirname, './data.csv'));
const parser = stream
.pipe(
parse({
columns: true,
delimiter: ',',
trim: true,
skip_empty_lines: true,
})
);
let count = 0;
process.stdout.write('start\n');
for await (const record of parser) {
process.stdout.write(`${count++} ${Object.values(record).join(',')}\n`);
// Fake asynchronous operation
await new Promise((resolve) => setTimeout(resolve, 100));
}
process.stdout.write('...done\n');
})();