2024-05-25

【Node.js】大容量ファイルをS3からダウンロード / S3にアップロードする

Node.jsで大容量のファイルを取り扱う場合、メモリ使用量を抑えるためにStream処理を活用することがポイントとなります。

Stream処理を使用することで、ファイル全体を一度にメモリに読み込むことなく部分的にデータを処理することが可能となり、効率的かつ安定した大容量ファイルのアップロードやダウンロードを行うことができます。

Node.jsのStream処理における highWaterMark について

Node.jsのStream処理においては、highWaterMark というパラメータを指定することができます。highWaterMark は、ストリームの内部バッファのサイズを指定するオプションで、バイト単位で設定されます。デフォルトでは16KBに設定されていますが、この値を大きくすることでパフォーマンスを向上させることができます。

highWaterMarkの設定値を大きくすると、読み取りや書き込みのパフォーマンスが向上する一方、メモリ消費が増加するため、システムのリソースを考慮して適切な値を設定する必要があります。この記事では256KB(256 * 1024バイト)を指定しています。

手順

前提
  • Node.jsがインストールされていること
  • AWSアカウントがあり、S3バケットが作成されていること
必要なパッケージのインストール

aws-sdk および @aws-sdk/lib-storageを使用します。

npm install aws-sdk @aws-sdk/lib-storage
ファイルのダウンロード

S3からのダウンロードにReadStreamを利用し、さらにファイルに書き込むためにWriteStreamを組み合わせます。

const AWS = require('aws-sdk');
const fs = require('fs');
const path = require('path');

// S3の設定
AWS.config.update({
    accessKeyId: 'your_access_key_id',
    secretAccessKey: 'your_secret_access_key',
    region: 'your_aws_region'
});

const s3 = new AWS.S3();

const downloadFile = (s3Key, downloadFilePath) => {
    const params = {
        Bucket: 'your_s3_bucket_name',
        Key: s3Key
    };

    const fileStream = fs.createWriteStream(downloadFilePath, { highWaterMark: 256 * 1024 }); // 256KBのチャンクサイズ

    return new Promise((resolve, reject) => {
        s3.getObject(params).createReadStream({ highWaterMark: 256 * 1024 }) // 256KBのチャンクサイズ
            .on('error', (err) => {
                console.error(err);
                reject(err);
            })
            .pipe(fileStream)
            .on('close', () => {
                console.log('ファイルがダウンロードされました: ' + downloadFilePath);
                resolve();
            });
    });
};

downloadFile('file/key/in/s3', 'path/to/downloaded/file');
ファイルのアップロード

アップロード時にはStream処理に加えて、@aws-sdk/lib-storage を利用すると、マルチパートアップロード(ファイルを複数のパートに分割してアップロード)することが可能となります。

マルチパートアップロード時には、パートの分割単位を partSize、パートのアップロードの並列数を queueSize で指定することが可能です。各パートは独立してアップロードされ、全てのパートがアップロードされた後でS3側でファイルが結合されます。

具体的なアップロード手順は次の通りです。

const AWS = require('aws-sdk');
const { S3Client } = require('@aws-sdk/client-s3');
const { Upload } = require('@aws-sdk/lib-storage');
const fs = require('fs');
const path = require('path');

// S3の設定
AWS.config.update({
    accessKeyId: 'your_access_key_id',
    secretAccessKey: 'your_secret_access_key',
    region: 'your_aws_region'
});

const s3Client = new S3Client({ region: 'your_aws_region' });

const uploadFile = (filePath) => {
    const uploadParams = {
        Bucket: 'your_s3_bucket_name',
        Key: path.basename(filePath),
        Body: fs.createReadStream(filePath, { highWaterMark: 256 * 1024 }) // 256KBのチャンクサイズ
    };

    return new Promise((resolve, reject) => {
        const parallelUploads3 = new Upload({
            client: s3Client,
            params: uploadParams,
            leavePartsOnError: false, // パーツエラーが発生した場合に部分的なアップロードを削除
            queueSize: 4, // 同時アップロードの最大パート数
            partSize: 1024 * 1024 * 20 // 各パートのサイズを20MBに設定
        });

        parallelUploads3.on('httpUploadProgress', (progress) => {
            console.log(progress);
        });

        parallelUploads3.done().then(() => {
            console.log('ファイルがアップロードされました: ' + uploadParams.Key);
            resolve();
        }).catch(err => {
            console.error(err);
            reject(err);
        });
    });
};

uploadFile('path/to/large/file');

まとめ

Stream処理やマルチパートアップロードを活用することで効率的に大容量ファイルを取り扱うことが可能となりました。

システムリソースに応じて各種パラメータを調整して、効率的なファイル処理を実現してください。

参考