コハム

Webクリエイターのコハムが提供する、Web制作に役立つ情報をまとめたブログです。

動画や実行ファイルもこれでOK!Amazon S3に大容量ファイルを分割してアップロードする方法

​​How to Upload Large Files Efficiently with AWS S3 Multipart Upload

記事は上記記事を意訳したものです。

※当ブログでの翻訳記事は元サイト様に許可を得て掲載しています。


ユーザーが大容量の高画質動画をアップロードするメディアストリーミングプラットフォームを運営していると想像してください。そのような大容量ファイルのアップロードは遅く、ネットワークが不安定な場合は失敗する可能性があります。

大容量ファイルの従来の単一パートアップロードは面倒で非効率的であり、タイムアウトエラーが発生したり、一部が失敗した場合にアップロードプロセス全体を再開する必要が生じたりすることがよくあります。ここで、Amazon S3のマルチパートアップロード機能が活躍し、これらの課題に対する堅牢なソリューションを提供します。

この記事では、Amazon S3マルチパートアップロードを使用して大容量ファイルを効率的に処理する方法を探ります。この機能の利点について議論し、ファイルをパートごとにアップロードするプロセスを詳しく説明し、フルスタックNodeとReactプロジェクトにAWS SDKを使用したコード例を提供します。

この記事を読み終えると、Amazon S3マルチパートアップロードを活用してアプリケーションのファイルアップロードを最適化する方法について、十分な理解が得られるはずです。

前提条件

始める前に、以下のものが揃っていることを確認してください:

  • IAMユーザー認証情報を持つAWSアカウント
  • 開発マシンにインストールされたNode.js
  • JavaScript、React、Node.jsの基本的な知識

仕組み

大容量ファイルのアップロードは、小さなパート/チャンクに分割され、各パートは独立してAmazon S3にアップロードされます。すべてのパートがアップロードされると、それらが結合されて最終的なオブジェクトが作成されます。

例:100MBのファイルを5MBのパートでアップロードすると、20のパートがS3にアップロードされることになります。各パートは一意の識別子でアップロードされ、順序が維持されてファイルが正しく再構築できるようになっています。

失敗したパートを自動的に再試行するように設定でき、アップロードはいつでも一時停止や再開が可能です。これにより、特に大容量ファイルの場合、プロセスがより堅牢で耐障害性の高いものになります。

multipart AWS s3 uploads

詳細については、Amazon S3マルチパートアップロードのドキュメントを参照してください。

それでは始めましょう!

ステップ1:AWS S3のセットアップ方法

S3バケットの作成方法

  1. まず、AWSマネジメントコンソールにログインします。
  2. S3サービスに移動します。
  3. 新しいバケットを作成し、バケット名をメモしておきます。
  4. 簡単にするために、パブリックアクセス設定のチェックを外します。バケットの作成後にIAMポリシーを使用してバケットアクセスを設定します。
  5. その他の設定はデフォルトのままにしてバケットを作成します。

S3バケットポリシーの設定方法

バケットを作成したら、ユーザーがオブジェクト(ファイル/動画)のURLを読み取れるようにポリシーを設定しましょう。

  1. バケット名をクリックし、「アクセス許可」タブに移動します。
  2. 「バケットポリシー」セクションに移動し、「編集」をクリックします。
  3. 以下のポリシーを入力し、your-bucket-nameを実際のバケット名に置き換えます:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}
  • Version:バケットポリシー言語のAmazon S3オブジェクトバージョン番号。
  • Statement:ポリシーを定義する1つ以上の個別のステートメントの配列。
  • Effect:ステートメントがアクセスを許可するか拒否するかを決定します。
  • Principal:ポリシーが適用されるエンティティ。この場合、すべてのプリンシパルを許可しています。本番環境では、アクセスが必要なIAMユーザーまたはロールを指定する必要があります。
  • Action:ポリシーが許可または拒否するアクション。この場合、s3:GetObjectアクションを許可しており、ユーザーがバケットからオブジェクトを取得できるようになります。
  • Resource:ポリシーが適用されるバケットとオブジェクトのAmazon Resource Name(ARN)。この場合、バケット内のすべてのオブジェクトへのアクセスを許可しています。

  • 「変更の保存」をクリックしてポリシーを適用します。

ステップ2:Node.jsでAWS S3バックエンドをセットアップする方法

次に、AWS SDKを使用してファイルアップロードプロセスを処理するバックエンドサーバーをセットアップしましょう。

Node.jsプロジェクトの初期化方法

プロジェクトの新しいディレクトリを作成し、新しいNode.jsプロジェクトを初期化します:

mkdir s3-multipart-upload
cd s3-multipart-upload
npm init -y

必要なパッケージのインストール

npmを使用して以下のパッケージをインストールします:

npm install express dotenv multer aws-sdk

サーバーファイルの作成

app.jsという名前の新しいファイルを作成し(簡単にするため、このファイルのみを使用してすべてのアップロードロジックを扱います)、以下のコードを追加します:

インポートと設定

const cors = require("cors");
const express = require("express");
const AWS = require("aws-sdk");
const dotenv = require("dotenv");
const multer = require("multer");

const multerUpload = multer();
dotenv.config();

const app = express();
const port = 3001;

インポート: - cors:クロスオリジンリソース共有(CORS)を有効にするミドルウェア。これは、異なるドメインまたはポートでホストされているフロントエンドアプリケーションがバックエンドと対話できるようにするために必要です。 - express:最小限で柔軟なNode.jsウェブアプリケーションフレームワーク。 - AWS:AWSサービスと対話できるJavaScript用のAWS SDK。 - dotenv:.envファイルから環境変数をprocess.envにロードするモジュール。 - multer:主にファイルアップロードに使用される、multipart/form-dataを処理するミドルウェア。

設定: - multerUpload:ファイルアップロードを処理するmulterを初期化します。 - dotenv.config():.envファイルから環境変数をロードします。 - app:Expressアプリケーションを初期化します。 - port:Expressアプリケーションが実行されるポートを設定します。

ミドルウェアとAWS設定

次に、ミドルウェアとAWS SDKを設定するために以下のコードを追加します:

app.use(cors());

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY,
  secretAccessKey: process.env.AWS_SECRET_KEY,
  region: process.env.AWS_REGION,
});

const s3 = new AWS.S3();
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb", extended: true }));
  • app.use(cors()):すべてのルートでCORSを有効にし、クロスオリジンリクエストに関連する問題なくフロントエンドがバックエンドと通信できるようにします。
  • AWS.config.update({ ... }):環境変数からアクセスキー、シークレットキー、リージョンでAWS SDKを設定します。
  • const s3 = new AWS.S3():S3サービスのインスタンスを作成します。
  • app.use(express.json({ limit: '50mb' })):50MBのサイズ制限でJSONボディを解析するようExpressを設定します。
  • app.use(express.urlencoded({ limit: '50mb', extended: true })):50MBのサイズ制限でURL-encodedボディを解析するようExpressを設定します。

ルート

マルチパートアップロードプロセスに必要なルートを作成しましょう。必要なルートは以下の通りです:

  1. アップロードプロセスの初期化
  2. ファイルのパートのアップロード
  3. アップロードプロセスの完了
アップロード開始/初期化エンドポイント

このルートはアップロードプロセスを開始します。マルチパートアップロードプロセスを初期化するエンドポイントを作成するために以下のコードを追加してください:

app.post("/start-upload", async (req, res) => {
  const { fileName, fileType } = req.body;

  const params = {
    Bucket: process.env.S3_BUCKET,
    Key: fileName,
    ContentType: fileType,
  };

  try {
    const upload = await s3.createMultipartUpload(params).promise();
    res.send({ uploadId: upload.UploadId });
  } catch (error) {
    res.send(error);
  }
});

上記の関数は、fileNameとfileTypeプロパティを持つJSONボディを期待する/start-uploadのPOSTエンドポイントを作成します。その後、S3サービスのcreateMultipartUploadメソッドを使用してマルチパートアップロードプロセスを初期化します。成功した場合、ファイルのパートをアップロードするために使用されるuploadIdをユーザーに返します。

パートアップロードエンドポイント

これは、大容量ファイルアップロードの異なる小さなパートが受信されタグ付けされるルートです。ファイルのパートをアップロードするエンドポイントを作成するために以下のコードを追加してください:

app.post("/upload-part", multerUpload.single("fileChunk"), async (req, res) => {
  const { fileName, partNumber, uploadId, fileChunk } = req.body;

  const params = {
    Bucket: process.env.S3_BUCKET,
    Key: fileName,
    PartNumber: partNumber,
    UploadId: uploadId,
    Body: Buffer.from(fileChunk, "base64"),
  };

  try {
    const uploadParts = await s3.uploadPart(params).promise();
    console.log({ uploadParts });
    res.send({ ETag: uploadParts.ETag });
  } catch (error) {
    res.send(error);
  }
});

上記の関数は、uploadId、partNumber、fileNameプロパティを持つform-dataボディを期待する/upload-partのPOSTエンドポイントを作成します。S3サービスのuploadPartメソッドを使用してファイルのパートをアップロードします。成功した場合、アップロードされたパートのETagをクライアントに返します。

ETagは、マルチパートアップロードを完了するために使用される、アップロードされたパートの一意の識別子です。

アップロード完了エンドポイント

パートがアップロードされたら、最後のステップは全てのパートを結合して最終的なオブジェクトを作成することです。

マルチパートアップロードプロセスを完了するエンドポイントを作成するために以下のコードを追加してください:

app.post("/complete-upload", async (req, res) => {
  const { fileName, uploadId, parts } = req.body;

  const params = {
    Bucket: process.env.S3_BUCKET,
    Key: fileName,
    UploadId: uploadId,
    MultipartUpload: {
      Parts: parts,
    },
  };

  try {
    const complete = await s3.completeMultipartUpload(params).promise();
    console.log({ complete });
    res.send({ fileUrl: complete.Location });
  } catch (error) {
    res.send(error);
  }
});

上記の関数は、uploadId、fileName、partsプロパティを持つJSONボディを期待する/complete-uploadのPOSTエンドポイントを作成します。S3サービスのcompleteMultipartUploadメソッドを使用してアップロードされたパートを結合し、最終的なオブジェクトを作成します。成功した場合、完了したアップロードに関するfileUrlを含むデータオブジェクトを返します。

サーバーの起動

最後に、Expressサーバーを起動するために以下のコードを追加してください:

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

このコードは、ポート3001でExpressサーバーを起動し、サーバーが実行されているときにコンソールにメッセージを記録します。

環境変数

プロジェクトのルートディレクトリに.envという名前の新しいファイルを作成し、以下の環境変数を追加してください:

AWS_ACCESS_KEY=your-access-key
AWS_SECRET_KEY=your-secret-key
AWS_REGION=your-region
S3_BUCKET=your-bucket-name

your-access-key、your-secret-key、your-region、your-bucket-nameを実際のAWS認証情報とバケット名に置き換えてください。

サーバーの実行

サーバーを実行するには、ターミナルで以下のコマンドを実行してください:

node app.js

これによりポート3001でサーバーが起動します。

ステップ3:Reactでフロントエンドをセットアップする方法

バックエンドがセットアップされたので、サーバーと対話してS3にファイルをアップロードするためのReactフロントエンドを作成しましょう。

フロントエンドは、ファイルをパートに分割し、各パートをサーバーにアップロードし、アップロードプロセスを完了する役割を担います。

Reactプロジェクトの初期化方法

Create React Appを使用して新しいReactプロジェクトを作成します:

npx create-react-app s3-multipart-upload-frontend
cd s3-multipart-upload-frontend

必要なパッケージのインストール

npmを使用して以下のパッケージをインストールします:

npm install axios

コンポーネントの作成

src/componentsディレクトリにUpload.jsという名前の新しいファイルを作成し、以下のコードを追加してください:

import React, { useState } from "react";
import axios from "axios";

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB

const FileUpload = () => {
  const [file, setFile] = useState(null);
  const [fileUrl, setFileUrl] = useState("");

  const handleFileChange = (e) => {
    setFile(e.target.files[0]);
  };

  const handleFileUpload = async () => {
    const fileName = file.name;
    const fileType = file.type;
    let uploadId = "";
    let parts = [];

    try {
      // マルチパートアップロードの開始
      const startUploadResponse = await axios.post(
        "http://localhost:3001/start-upload",
        {
          fileName,
          fileType,
        }
      );

      uploadId = startUploadResponse.data.uploadId;

      // ファイルをチャンクに分割し、各パートをアップロード
      const totalParts = Math.ceil(file.size / CHUNK_SIZE);

      console.log(totalParts);

      for (let partNumber = 1; partNumber <= totalParts; partNumber++) {
        const start = (partNumber - 1) * CHUNK_SIZE;
        const end = Math.min(start + CHUNK_SIZE, file.size);
        const fileChunk = file.slice(start, end);

        const reader = new FileReader();
        reader.readAsArrayBuffer(fileChunk);

        const uploadPart = () => {
          return new Promise((resolve, reject) => {
            reader.onload = async () => {
              const fileChunkBase64 = btoa(
                new Uint8Array(reader.result).reduce(
                  (data, byte) => data + String.fromCharCode(byte),
                  ""
                )
              );

              const uploadPartResponse = await axios.post(
                "http://localhost:3001/upload-part",
                {
                  fileName,
                  partNumber,
                  uploadId,
                  fileChunk: fileChunkBase64,
                }
              );

              parts.push({
                ETag: uploadPartResponse.data.ETag,
                PartNumber: partNumber,
              });
              resolve();
            };
            reader.onerror = reject;
          });
        };

        await uploadPart();
      }

      // マルチパートアップロードの完了
      const completeUploadResponse = await axios.post(
        "http://localhost:3001/complete-upload",
        {
          fileName,
          uploadId,
          parts,
        }
      );

      setFileUrl(completeUploadResponse.data.fileUrl);
      alert("ファイルが正常にアップロードされました");
    } catch (error) {
      console.error("ファイルのアップロード中にエラーが発生しました:", error);
    }
  };

  return (
    <div>
      <input type="file" onChange={handleFileChange} />
      <button disabled={!file} onClick={handleFileUpload}>
        アップロード
      </button>
      <hr />
      <br />
      <br />
      {fileUrl && (
        <a href={fileUrl} target="_blank" rel="noopener noreferrer">
          アップロードされたファイルを表示
        </a>
      )}
    </div>
  );
};

export default FileUpload;

上記のFileUploadコンポーネントは、マルチパートアップロード方法を使用してファイルアップロードプロセスを処理します。ファイルをチャンクに分割し、各パートをサーバーにアップロードし、アップロードプロセスを完了します。

コンポーネントは以下の主要な部分で構成されています:

  • CHUNK_SIZE:バイト単位の各パートのサイズ。この場合、5MBのパートを使用しています。
  • handleFileChange:選択されたファイルを状態にセットする関数。
  • handleFileUpload:ファイルをパートごとにサーバーに送信することでマルチパートアップロードプロセスを開始する関数。
    • /start-uploadエンドポイントを呼び出してアップロードプロセスを開始し、uploadIdを取得します。
    • ファイルをチャンクに分割し、/upload-partエンドポイントを使用して各パートをサーバーにアップロードします。
    • uploadIdとparts配列を使用して/complete-uploadエンドポイントを呼び出し、アップロードプロセスを完了します。
  • fileUrl:アップロードされたファイルのURLを保存する状態変数。

コンポーネントは、ファイルを選択するための入力フィールド、ファイルをアップロードするボタン、およびアップロードされたファイルを表示するためのリンクをレンダリングします。

Appコンポーネント

srcディレクトリのApp.jsファイルを以下のコードで更新してください:

import React from "react";

import FileUpload from "./components/FileUpload";

function App() {
  return (
    <div className="App">
      <h1>S3マルチパートアップロードによる大容量ファイルアップロード</h1>
      <FileUpload />
    </div>
  );
}

export default App;

Appコンポーネントは、ファイルアップロードプロセスを処理するFileUploadコンポーネントをレンダリングします。

フロントエンドの起動方法

フロントエンドを実行するには、ターミナルで以下のコマンドを実行してください:

npm start

これによりポート3000でReact開発サーバーが起動し、デフォルトのWebブラウザでアプリケーションが開かれます。

テスト

フロントエンドを使用して大容量ファイルをアップロードすることでアプリケーションをテストしましょう。ネットワークタブを確認すると、ファイルがパートごとにアップロードされ、その後サーバー上で最終的なオブジェクトを作成するために結合されるのが見えるはずです。

パートのアップロード

下の画像では、start-uploadエンドポイントが呼び出されてアップロードプロセスが初期化および開始されています。アップロードされた大容量ファイルはチャンクに分割され、upload-partエンドポイントでアップロードされます。各チャンクの合計ファイルサイズに応じて、10以上のアップロードパートが表示される場合があります。

各アップロードパートには、完全なアップロードに使用される一意の識別子Etagがあります。

パートアップロードの完了

プロセスの最後の最終ステップは、complete-uploadエンドポイントで、アップロードされたパートが結合されてアップロードされたファイルの単一のオブジェクトが形成されます。

「アップロードされたファイルを表示」をクリックして、アップロードされたファイルにアクセスできます。

GitHubの完全なコード

以下のリンクをクリックして、GitHubの完全なコードにアクセスしてください:

ReactとNodeJSを使用したマルチパートファイルアップロード

結論

この記事では、Amazon S3マルチパートアップロードを使用して大容量ファイルを効率的に処理する方法を探りました。この機能の利点について議論し、ファイルをパートごとにアップロードするプロセスを詳しく説明し、Node.jsとReactを使用したコード例を提供しました。

これはマルチパートアップロードプロセスの高レベルな実装であり、進捗状況の追跡、エラー処理、再開可能なアップロードなどの機能を追加してさらに拡張できます。

Amazon S3マルチパートアップロードを活用することで、大容量ファイルを小さなパートに分割し、それらを独立してアップロードし、最終的なオブジェクトを作成するために結合することで、アプリケーションのファイルアップロードを最適化できます。このアプローチは、アップロードのパフォーマンスを向上させるだけでなく、耐障害性を追加し、不安定なネットワーク上で大容量ファイルを処理する際に理想的な、アップロードの一時停止と再開の柔軟性も提供します。

©コハム