この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
広島の吉川です。
Node.js Streamに入門してみた | DevelopersIO
さっき書いたこちらの記事の発展として、S3を使ったStream処理をやってみたいと思います。
ちょっとこちらの真似をして、「S3から別のS3にCSVをコピーする」というスクリプトを書いてみたいと思います。大きく違うのはAWS SDKのバージョンで、参考元記事はv2で、本記事はv3でやっていきます。
環境
- node 16.14.0
- typescript 4.6.3
- @aws-sdk/{client-s3,lib-storage} 3.58.0
- csv 6.0.5
S3のWriteStream
まずS3にCSVをアップロードする処理を行っていきます。
v2だとS3クライアントから .upload()
というメソッドが生えていたのですが、v3には同じものがなさそうでした。
node.js - How to upload a stream to S3 with AWS SDK v3 - Stack Overflow
こちらのStackOverflowによると、 @aws-sdk/client-s3
の他に @aws-sdk/lib-storage
パッケージを組み合わせることで同等処理の実現ができるようです。そのように書いていきます。
import { S3Client } from '@aws-sdk/client-s3'
import { Upload } from '@aws-sdk/lib-storage'
import { generate } from 'csv'
const region = 'ap-northeast-1'
const s3Client = new S3Client({
region,
})
const bucketName = 'YOUR_BUCKET_NAME'
const keyName = 'example.csv'
const main = async () => {
const csvStream = generate({ length: 5000000 })
const upload = new Upload({
client: s3Client,
params: {
Bucket: bucketName,
Key: keyName,
Body: csvStream,
},
})
upload.on('httpUploadProgress', (progress) => {
console.log(progress)
})
await upload.done()
}
main()
Body
引数に stream.Readable
を渡せるようになっているので、そのまま使わせてもらう形です。
S3のReadStream
AWS SDK v3ではなんと最初から GetObjectOutput.Body
が stream.Readable
になっています。なので、普通に扱えばReadStreamでの読み取りになります。
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import fs from 'fs'
import { Readable } from 'stream'
const region = 'ap-northeast-1'
const s3Client = new S3Client({
region,
})
const bucketName = 'YOUR_BUCKET_NAME'
const keyName = 'example.csv'
const main = async () => {
const output = await s3Client.send(
new GetObjectCommand({
Bucket: bucketName,
Key: keyName,
})
)
;(output.Body as Readable).pipe(fs.createWriteStream('example.csv'))
}
main()
ただ、 Body
を stream.Readable
で返す仕様が逆に利用者を混乱させている面もあるようで、下記のようなIssueが建っていました。
S3.GetObject no longer returns the result as a string · Issue #1877 · aws/aws-sdk-js-v3
たしかにそれほど大きくないテキストファイルなら最初から string
型などで取得できた方が楽な場合も多そうです。上のIssueによると string
に変換したい場合はget-streamというライブラリを使うのが簡単そうです。
Streamを使ってS3から別のS3へCSVをコピーする
では、あるS3バケットのCSVファイルを別のS3バケットにコピーするという処理を書いていきます。
今回は362MBのCSVファイルをS3バケット1に入れておきました。
これをS3バケット2にコピーするというシナリオでコードを書きます。
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { Upload } from '@aws-sdk/lib-storage'
const region = 'ap-northeast-1'
const s3Client = new S3Client({
region,
})
const bucketName1 = 'YOUR_BUCKET_NAME_1'
const keyName1 = 'example.csv'
const bucketName2 = 'YOUR_BUCKET_NAME_2'
const keyName2 = 'copied-example.csv'
const main = async () => {
const getObjectOutput = await s3Client.send(
new GetObjectCommand({
Bucket: bucketName1,
Key: keyName1,
})
)
const upload = new Upload({
client: s3Client,
params: {
Bucket: bucketName2,
Key: keyName2,
Body: getObjectOutput.Body,
},
})
upload.on('httpUploadProgress', (progress) => {
console.log(progress)
})
await upload.done()
}
main()
前回と同じ方法で計測したところメモリ使用量は約244.7MBでした。
Streamを使わずS3から別のS3へCSVをコピーする
比較のためにあえてStreamでデータを流すことを避けたコードも書きました。それが下記です。
import {
GetObjectCommand,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import getStream from 'get-stream'
import { Readable } from 'stream'
const region = 'ap-northeast-1'
const s3Client = new S3Client({
region,
})
const bucketName1 = 'YOUR_BUCKET_NAME_1'
const keyName1 = 'example.csv'
const bucketName2 = 'YOUR_BUCKET_NAME_2'
const keyName2 = 'copied-example.csv'
const main = async () => {
const getObjectOutput = await s3Client.send(
new GetObjectCommand({
Bucket: bucketName1,
Key: keyName1,
})
)
const csvString = await getStream(getObjectOutput.Body as Readable)
await s3Client.send(
new PutObjectCommand({
Bucket: bucketName2,
Key: keyName2,
Body: csvString,
})
)
}
main()
さきほど紹介したget-streamを使って stream.Readable
を string
に変換した後、アップロードするようにしてみました。
こちらも計測したところメモリ使用量は約1568.5MBでした。
まとめ
ケース | メモリ使用量 |
---|---|
Streamを使ってS3から別のS3へCSVをコピーする | 244.7MB |
Streamを使わずS3から別のS3へCSVをコピーする | 1568.5MB |
こちらの例でもStreamを使うことでメモリ使用量を抑えることができました。
参考
- 【S3からS3へ】Node.js の Streaming API を使って Lambda Function のみで CSVファイルを JSON Lines ファイルへ変換する | DevelopersIO
- node.js - How to upload a stream to S3 with AWS SDK v3 - Stack Overflow
- amazon web services - AWS s3 V3 Javascript SDK stream file from bucket (GetObjectCommand) - Stack Overflow
- S3.GetObject no longer returns the result as a string · Issue #1877 · aws/aws-sdk-js-v3
- Node.jsの使用メモリを観測する方法 - Qiita
- airbnb/node-memwatch: A NodeJS library to keep an eye on your memory usage, and discover and isolate leaks.
- fujiwara/lambroll: lambroll is a minimal deployment tool for AWS Lambda.
- esbuild - An extremely fast JavaScript bundler
- command - How to get the memory usage of a OS X/macOS process - Stack Overflow