Node.js Streamに入門してみた
2022/3/31 17:04 追記: メモリ使用量計測方法を変更しました。
広島の吉川です。
今までNode.jsのStream APIについて、「なんか難しそう・・・」と逃げていた感があるのですが、
こちらの記事のような処理を組めるようになりたいと思ったため入門してみます。
Streamのメリットは大きな容量のファイルを読み取り・書き込み・加工する際に「ちょっとずつ処理」することで最大メモリ使用量を抑えることができる点と認識しているため、「大容量CSVを生成してファイルに書き出す」という処理をサンプルにやってみたいと思います。
環境
- macOS Catalina
- node 16.14.0
- typescript 4.6.3
- csv 6.0.5
- esbuild 0.14.29
- esbuild-register 3.3.2
最大使用メモリ量の計測
まず、最大使用メモリ量を測定する仕組みを用意します。
process.memoryUsage()+process.nextTick()
javascript - Monitor maximum memory consumption in Node.js process - Stack Overflow
メモリ使用量を監視し、逐次最大量を更新していき最後に出力するような仕組みを作ることで計測できるようです。
let maxMemory = 0 process.nextTick(() => { let memUsage = process.memoryUsage() if (memUsage.rss > maxMemory) { maxMemory = memUsage.rss } }) process.on('exit', () => { console.log(`Max memory: ${maxMemory / 1024 / 1024}MB`) })
@airbnb/node-memwatch
メモリ使用量を監視できるパッケージです。他にはnode-memwatchも有力そうでした。
import memwatch from '@airbnb/node-memwatch' memwatch.on('stats', (stats) => console.log(`${stats.used_heap_size / 1024 / 1024}MB`) )
/usr/bin/timeコマンド (macOSの場合)
command - How to get the memory usage of a OS X/macOS process - Stack Overflow
node実行コマンドの直前に /usr/bin/time -l
を付けることで実行後に使用メモリを出力することができるようです。
/usr/bin/time -l node -r esbuild-register index.ts
今回はこの方法を採用します。
Streamを使わずに大容量CSVを生成して書き込む
では、CSVデータの生成と書き込みをしてみます。csvパッケージを使って500万行のCSVを生成し、ファイルに書き出してみます。
import { generate } from 'csv/sync' // Sync APIを使う import fs from 'fs' fs.writeFileSync('example.csv', generate({ length: 5000000 }))
約1227.7MBを使用する結果となりました。
Streamを使って大容量CSVを生成して書き込む
続いてStreamを使って上と同じことをしてみます。
import { generate } from 'csv' import fs from 'fs' generate({ length: 5000000 }) .pipe(fs.createWriteStream('example.csv')) .on('finish', () => console.log('finish!'))
約55.7MBの最大量使用に留まりました。
まとめ
ケース | メモリ使用量 |
---|---|
Streamを使わず大容量CSVデータの生成と書き込み | 約1227.7MB |
Streamを使って大容量CSVデータの生成と書き込み | 約55.7MB |
狙い通り、Streamを使うことでメモリ使用量を下げることができました。
今後、AWSリソースを絡めるなどもう少し複雑なケースのサンプルも検証して出せればと思います。