ホームページ > 記事 > ウェブフロントエンド > 大きなファイルのアップロードの最適化: AWS S3 への安全なクライアント側マルチパート アップロード
大きなファイルをクラウドにアップロードするのは困難な場合があります。ネットワークの中断、ブラウザの制限、および巨大なファイル サイズにより、プロセスが簡単に中断される可能性があります。 Amazon S3 (Simple Storage Service) は、データとアプリケーションのオンライン バックアップとアーカイブ用に設計された、スケーラブルで高速な Web ベースのクラウド ストレージ サービスです。ただし、大きなファイルを S3 にアップロードするには、信頼性とパフォーマンスを確保するために慎重な取り扱いが必要です。
AWS S3 のマルチパート アップロードをご利用ください。これは、大きなファイルを小さなチャンクに分割し、各パートを個別に処理し、パートを並行してアップロードすることで、より高速で信頼性の高いアップロードを可能にする強力なソリューションです。この方法は、ファイル サイズの制限 (S3 では 5 GB を超えるファイルのマルチパート アップロードが必要です) を克服するだけでなく、失敗のリスクも最小限に抑えるため、シームレスで堅牢なファイル アップロードを必要とするアプリケーションに最適です。
このガイドでは、S3 へのクライアント側のマルチパート アップロードの詳細を明らかにし、大きなファイルを処理する場合に S3 が賢明な選択である理由、安全に起動して実行する方法、注意すべき課題を示します。のために出かけます。信頼性の高いクライアント側のファイル アップロード ソリューションの実装に役立つ、段階的な手順、コード例、ベスト プラクティスを提供します。
ファイルのアップロード エクスペリエンスをアップグレードする準備はできていますか?飛び込んでみましょう!
ファイル アップロード システムを設計する場合、サーバー経由でファイルをアップロードする (サーバー側) か、クライアントから S3 にファイルを直接アップロードする (クライアント側) という 2 つの主なオプションがあります。それぞれのアプローチには長所と短所があります。
セキュリティの強化: すべてのアップロードはサーバーによって管理され、AWS 認証情報を安全に保ちます。
エラー処理の改善: サーバーは再試行、ログ記録、エラー処理をより堅牢に管理できます。
集中処理: ファイルは S3 に保存する前にサーバー上で検証、処理、または変換できます。
サーバー負荷の増加: 大規模なアップロードはサーバー リソース (CPU、メモリ、帯域幅) を消費し、パフォーマンスに影響を与え、運用コストを増加させる可能性があります。
潜在的なボトルネック: アップロード トラフィックが多い場合、サーバーが単一障害点またはパフォーマンスのボトルネックとなり、アップロードの遅延やダウンタイムにつながる可能性があります。
コストの増加: サーバー側でアップロードを処理するには、ピーク負荷に対処するためにインフラストラクチャの拡張が必要になる場合があり、運用コストが増加します。
サーバー負荷の軽減: ファイルはユーザーのデバイスから S3 に直接送信され、サーバー リソースが解放されます。
速度の向上: アプリケーション サーバーをバイパスするため、ユーザーはより高速なアップロードを体験できます。
コスト効率: 大規模なアップロードを処理するためのサーバー インフラストラクチャの必要性がなくなり、コストが削減される可能性があります。
スケーラビリティ: バックエンド サーバーにストレスを与えずにファイルのアップロードを拡張するのに最適です。
セキュリティリスク: AWS 認証情報と権限を慎重に扱う必要があります。署名付き URL は、不正アクセスを防ぐために安全に生成される必要があります。
制限された制御: アップロードに対するサーバー側の監視が少なくなります。エラー処理と再試行は多くの場合クライアントで管理されます。
ブラウザの制約: ブラウザにはメモリと API の制限があり、非常に大きなファイルの処理が妨げられたり、ローエンド デバイスのパフォーマンスに影響を与えたりする可能性があります。
クライアント側のアップロードを安全に実装するには、フロントエンド アプリケーションと安全なバックエンド サービスの間の調整が必要です。バックエンド サービスの主な役割は、署名付き URL を生成し、クライアントが機密の AWS 認証情報を公開せずにファイルを S3 に直接アップロードできるようにすることです。
クライアント側のアップロードを効果的に実装するには、以下が必要です。
このアーキテクチャにより、フロントエンドがアップロード プロセスを管理しながら、機密性の高い操作がバックエンドで安全に処理されることが保証されます。
署名付き URL を使用すると、クライアントは S3 と直接対話でき、クライアント側で AWS 認証情報を必要とせずにファイルのアップロードなどの操作を実行できます。これらは次の理由で安全です:
サーバー上に以下を担当するサービス クラスを作成します。
a. S3 バケットとリージョンの定義
b. AWS 認証情報を安全に確立します。
c.署名付き URL を生成し、マルチパート アップロードを管理するメソッドを提供します。
// services/S3UploadService.js import { S3Client, CreateMultipartUploadCommand, CompleteMultipartUploadCommand, UploadPartCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; // Import credential providers import { fromIni, fromInstanceMetadata, fromEnv, fromProcess, } from '@aws-sdk/credential-providers'; export class S3UploadService { constructor() { this.s3BucketName = process.env.S3_BUCKET_NAME; this.s3Region = process.env.S3_REGION; this.s3Client = new S3Client({ region: this.s3Region, credentials: this.getS3ClientCredentials(), }); } // Method to generate AWS credentials securely getS3ClientCredentials() { if (process.env.NODE_ENV === 'development') { // In development, use credentials from environment variables return fromEnv(); } else { // In production, use credentials from EC2 instance metadata or another secure method return fromInstanceMetadata(); } } // Generate a presigned URL for single-part upload (PUT), download (GET), or deletion (DELETE) async generatePresignedUrl(key, operation) { let command; switch (operation) { case 'PUT': command = new PutObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'GET': command = new GetObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'DELETE': command = new DeleteObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; default: throw new Error(`Invalid operation "${operation}"`); } // Generate presigned URL return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); // Expires in 1 hour } // Methods for multipart upload async createMultipartUpload(key) { const command = new CreateMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, }); const response = await this.s3Client.send(command); return response.UploadId; } async generateUploadPartUrl(key, uploadId, partNumber) { const command = new UploadPartCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, PartNumber: partNumber, }); return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); } async completeMultipartUpload(key, uploadId, parts) { const command = new CompleteMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts }, }); return await this.s3Client.send(command); } async abortMultipartUpload(key, uploadId) { const command = new AbortMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, }); return await this.s3Client.send(command); } }
注: AWS 認証情報が安全に管理されていることを確認してください。本番環境では、認証情報をハードコーディングしたり環境変数を使用したりするのではなく、EC2 インスタンスまたは ECS タスクにアタッチされた IAM ロールを使用することをお勧めします。
フロントエンドからのリクエストを処理するために、バックエンドに API エンドポイントを作成します。これらのエンドポイントは、S3UploadService を利用してアクションを実行します。
// controllers/S3UploadController.js import { S3UploadService } from '../services/S3UploadService'; const s3UploadService = new S3UploadService(); export const generatePresignedUrl = async (req, res, next) => { try { const { key, operation } = req.body; // key is the S3 object key (file identifier) const url = await s3UploadService.generatePresignedUrl(key, operation); res.status(200).json({ url }); } catch (error) { next(error); } }; export const initializeMultipartUpload = async (req, res, next) => { try { const { key } = req.body; const uploadId = await s3UploadService.createMultipartUpload(key); res.status(200).json({ uploadId }); } catch (error) { next(error); } }; export const generateUploadPartUrls = async (req, res, next) => { try { const { key, uploadId, parts } = req.body; // parts is the number of parts const urls = await Promise.all( [...Array(parts).keys()].map(async (index) => { const partNumber = index + 1; const url = await s3UploadService.generateUploadPartUrl(key, uploadId, partNumber); return { partNumber, url }; }) ); res.status(200).json({ urls }); } catch (error) { next(error); } }; export const completeMultipartUpload = async (req, res, next) => { try { const { key, uploadId, parts } = req.body; // parts is an array of { ETag, PartNumber } const result = await s3UploadService.completeMultipartUpload(key, uploadId, parts); res.status(200).json({ result }); } catch (error) { next(error); } }; export const abortMultipartUpload = async (req, res, next) => { try { const { key, uploadId } = req.body; await s3UploadService.abortMultipartUpload(key, uploadId); res.status(200).json({ message: 'Upload aborted' }); } catch (error) { next(error); } };
Express アプリまたは使用しているフレームワークでこれらのエンドポイントのルートを設定します。
フロントエンドは、ファイルの選択、ファイル サイズに基づいてシングルパート アップロードを実行するかマルチパート アップロードを実行するかを決定し、アップロード プロセスを管理します。
一般に、AWS は「オブジェクトのサイズが 100 MB に達した場合、オブジェクトを 1 回の操作でアップロードするのではなく、マルチパート アップロードの使用を検討する必要があります。」と推奨しています。ソース
// services/S3UploadService.js import { S3Client, CreateMultipartUploadCommand, CompleteMultipartUploadCommand, UploadPartCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; // Import credential providers import { fromIni, fromInstanceMetadata, fromEnv, fromProcess, } from '@aws-sdk/credential-providers'; export class S3UploadService { constructor() { this.s3BucketName = process.env.S3_BUCKET_NAME; this.s3Region = process.env.S3_REGION; this.s3Client = new S3Client({ region: this.s3Region, credentials: this.getS3ClientCredentials(), }); } // Method to generate AWS credentials securely getS3ClientCredentials() { if (process.env.NODE_ENV === 'development') { // In development, use credentials from environment variables return fromEnv(); } else { // In production, use credentials from EC2 instance metadata or another secure method return fromInstanceMetadata(); } } // Generate a presigned URL for single-part upload (PUT), download (GET), or deletion (DELETE) async generatePresignedUrl(key, operation) { let command; switch (operation) { case 'PUT': command = new PutObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'GET': command = new GetObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'DELETE': command = new DeleteObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; default: throw new Error(`Invalid operation "${operation}"`); } // Generate presigned URL return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); // Expires in 1 hour } // Methods for multipart upload async createMultipartUpload(key) { const command = new CreateMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, }); const response = await this.s3Client.send(command); return response.UploadId; } async generateUploadPartUrl(key, uploadId, partNumber) { const command = new UploadPartCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, PartNumber: partNumber, }); return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); } async completeMultipartUpload(key, uploadId, parts) { const command = new CompleteMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts }, }); return await this.s3Client.send(command); } async abortMultipartUpload(key, uploadId) { const command = new AbortMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, }); return await this.s3Client.send(command); } }
// controllers/S3UploadController.js import { S3UploadService } from '../services/S3UploadService'; const s3UploadService = new S3UploadService(); export const generatePresignedUrl = async (req, res, next) => { try { const { key, operation } = req.body; // key is the S3 object key (file identifier) const url = await s3UploadService.generatePresignedUrl(key, operation); res.status(200).json({ url }); } catch (error) { next(error); } }; export const initializeMultipartUpload = async (req, res, next) => { try { const { key } = req.body; const uploadId = await s3UploadService.createMultipartUpload(key); res.status(200).json({ uploadId }); } catch (error) { next(error); } }; export const generateUploadPartUrls = async (req, res, next) => { try { const { key, uploadId, parts } = req.body; // parts is the number of parts const urls = await Promise.all( [...Array(parts).keys()].map(async (index) => { const partNumber = index + 1; const url = await s3UploadService.generateUploadPartUrl(key, uploadId, partNumber); return { partNumber, url }; }) ); res.status(200).json({ urls }); } catch (error) { next(error); } }; export const completeMultipartUpload = async (req, res, next) => { try { const { key, uploadId, parts } = req.body; // parts is an array of { ETag, PartNumber } const result = await s3UploadService.completeMultipartUpload(key, uploadId, parts); res.status(200).json({ result }); } catch (error) { next(error); } }; export const abortMultipartUpload = async (req, res, next) => { try { const { key, uploadId } = req.body; await s3UploadService.abortMultipartUpload(key, uploadId); res.status(200).json({ message: 'Upload aborted' }); } catch (error) { next(error); } };
AWS S3 は最大 5 TiB (テラバイト) のサイズのオブジェクトをサポートしますが、そのような大規模なファイルをブラウザから直接アップロードすることは非現実的であり、ブラウザの制限やクライアント側のリソースの制約により不可能なことがよくあります。非常に大きなファイルを処理するとき、特にファイルをメモリ内で処理する必要がある場合、ブラウザがクラッシュしたり応答しなくなったりすることがあります。
大きなファイルをアップロードすると、アップロード プロセス中にネットワークの中断や障害が発生するリスクが高まります。ユーザー エクスペリエンスを向上させ、アップロードを確実に成功させるには、堅牢な再試行戦略を実装することが重要です。
不完全なマルチパートアップロードは S3 バケットに蓄積され、ストレージスペースを消費し、コストが発生する可能性があります。
ライフサイクル ルールの構成例:
// services/S3UploadService.js import { S3Client, CreateMultipartUploadCommand, CompleteMultipartUploadCommand, UploadPartCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; // Import credential providers import { fromIni, fromInstanceMetadata, fromEnv, fromProcess, } from '@aws-sdk/credential-providers'; export class S3UploadService { constructor() { this.s3BucketName = process.env.S3_BUCKET_NAME; this.s3Region = process.env.S3_REGION; this.s3Client = new S3Client({ region: this.s3Region, credentials: this.getS3ClientCredentials(), }); } // Method to generate AWS credentials securely getS3ClientCredentials() { if (process.env.NODE_ENV === 'development') { // In development, use credentials from environment variables return fromEnv(); } else { // In production, use credentials from EC2 instance metadata or another secure method return fromInstanceMetadata(); } } // Generate a presigned URL for single-part upload (PUT), download (GET), or deletion (DELETE) async generatePresignedUrl(key, operation) { let command; switch (operation) { case 'PUT': command = new PutObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'GET': command = new GetObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'DELETE': command = new DeleteObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; default: throw new Error(`Invalid operation "${operation}"`); } // Generate presigned URL return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); // Expires in 1 hour } // Methods for multipart upload async createMultipartUpload(key) { const command = new CreateMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, }); const response = await this.s3Client.send(command); return response.UploadId; } async generateUploadPartUrl(key, uploadId, partNumber) { const command = new UploadPartCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, PartNumber: partNumber, }); return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); } async completeMultipartUpload(key, uploadId, parts) { const command = new CompleteMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts }, }); return await this.s3Client.send(command); } async abortMultipartUpload(key, uploadId) { const command = new AbortMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, }); return await this.s3Client.send(command); } }
大きなファイルのアップロードはリソースを大量に消費する可能性があり、ブラウザのメイン スレッドが応答しなくなり、ユーザー エクスペリエンスが低下する可能性があります。
クライアント側のマルチパート アップロードを実装する場合、ブラウザの互換性が確かに懸念されます。 *ファイル API、Blob スライス、Web ワーカー、ネットワーク リクエスト処理* など、大きなファイルのアップロードを処理するために必要な API や機能のサポート レベルはブラウザごとに異なります。 。これらの違いをうまく処理することは、サポートされているすべてのブラウザーで一貫性と信頼性の高いユーザー エクスペリエンスを確保するために非常に重要です。
署名付き URL とマルチパート アップロードを使用したクライアント側のアップロードを実装することで、任意のサイズのファイルの S3 への直接アップロードを効率的に処理でき、サーバーの負荷が軽減され、パフォーマンスが向上します。 AWS 認証情報を安全に管理し、署名付き URL の権限と有効期間を制限することで、セキュリティを最前線に保つことを忘れないでください。
このガイドでは、AWS S3、AWS SDK for JavaScript、および署名付き URL を使用して、安全でスケーラブルなファイル アップロード システムをセットアップするための段階的なアプローチを説明しました。提供されているコード例とベスト プラクティスを活用すれば、アプリケーションのファイル アップロード機能を強化することができます。
以上が大きなファイルのアップロードの最適化: AWS S3 への安全なクライアント側マルチパート アップロードの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。