OSS へ大規模ファイルを信頼性高くアップロードするには、フラグメントアップロードをご利用ください。ファイル全体を単一のリクエストで送信する代わりに、ファイルを複数のパート(フラグメント)に分割し、各パートを並列でアップロードした後、それらを結合して 1 つの完全なオブジェクトとして作成します。この手法により、スループットが向上し、ネットワーク切断からの迅速な回復が可能となり、中断されたアップロードを最初からやり直さずに再開できます。
前提条件
開始する前に、以下の条件を満たしていることを確認してください。
オブジェクトを保存するリージョンに OSS バケットが存在すること。詳細については、「リージョンとエンドポイント」をご参照ください。
OSS バケットに対して
oss:PutObject権限が付与されていること。詳細については、「カスタムポリシーを使用した RAM ユーザーへの権限付与」をご参照ください。有効な STS 一時アクセス認証情報(AccessKey ID、AccessKey Secret、およびセキュリティトークン)が取得済みであること。
仕組み
フラグメントアップロードは、以下の 3 つのステップで実行されます。
初期化 —
client.initiateMultipartUploadを呼び出します。OSS は、このアップロードセッションを一意に識別するグローバルにユニークなアップロード ID を返します。パートのアップロード — 各パートをパート番号で識別し、
client.uploadPartを呼び出します。パートは並列でアップロード可能です。完了 —
client.completeMultipartUploadを呼び出します。OSS は、パート番号の順序に従ってパートをアセンブルし、1 つの完全なオブジェクトを作成します。
パート番号の動作:
パート番号は、各パートが最終オブジェクト内で占める位置を識別します。
既存のパート番号で新しいパートをアップロードすると、既存のパートが上書きされます。
OSS は、受信した各パートの MD5 ハッシュ値を
ETagヘッダーで返します。アップロードされたデータの MD5 ハッシュ値が SDK によって計算されたハッシュ値と一致しない場合、OSS は
InvalidDigestエラーを返します。
ファイルをパート単位でアップロード
以下の例では、ローカルファイルを 10 MB 単位のパートに分割し、Promise.all を使用して同時並列でアップロードします。
import Client, { FilePath, RequestError, THarmonyEmptyBodyApiRes } from '@aliyun/oss';
import { fileIo as fs } from '@kit.CoreFileKit';
// OSS クライアントインスタンスを作成します。
const client = new Client({
// STS 一時アクセス認証情報から取得した AccessKey ID に置き換えます。
accessKeyId: 'yourAccessKeyId',
// STS 一時アクセス認証情報から取得した AccessKey Secret に置き換えます。
accessKeySecret: 'yourAccessKeySecret',
// STS 一時アクセス認証情報から取得したセキュリティトークンに置き換えます。
securityToken: 'yourSecurityToken',
// バケットが配置されているリージョンを指定します。たとえば、バケットが
// 中国 (杭州) リージョンにある場合は、region を oss-cn-hangzhou に設定します。
region: 'oss-cn-hangzhou',
});
const bucket = 'yourBucketName'; // 実際のバケット名に置き換えます。
const key = 'yourObjectName'; // 実際のオブジェクト名に置き換えます。
const multipartUpload = async () => {
try {
// ステップ 1:フラグメントアップロードを初期化し、アップロード ID を取得します。
const initRes = await client.initiateMultipartUpload({ bucket, key });
const uploadId = initRes.data.uploadId;
// ステップ 2:ファイルをパートに分割し、並列でアップロードします。
const filePath = new FilePath('yourFilePath'); // 実際のローカルファイルパスに置き換えます。
const fileStat = await fs.stat(filePath.filePath);
const chunkSize = 1024 * 1024 * 10; // 各パートのサイズ:10 MB。
const totalParts = Math.ceil(fileStat.size / chunkSize);
const waitList: Promise<THarmonyEmptyBodyApiRes>[] = [];
for (let partNumber = 1; partNumber <= totalParts; partNumber++) {
const offset = (partNumber - 1) * chunkSize;
const uploadPromise = client.uploadPart({
bucket,
key,
uploadId,
partNumber,
data: filePath,
length: Math.min(chunkSize, fileStat.size - offset), // 現在のパートのサイズ。
offset, // 現在のパートの開始オフセット。
});
waitList.push(uploadPromise);
}
// 全パートのアップロード完了を待機します。
await Promise.all(waitList);
// ステップ 3:フラグメントアップロードを完了します。
const completeRes = await client.completeMultipartUpload({
bucket,
key,
uploadId,
completeAll: true, // アップロード済みのすべてのパートを自動的にアセンブルします。
});
console.log(JSON.stringify(completeRes));
} catch (err) {
if (err instanceof RequestError) {
console.log('code: ', err.code);
console.log('message: ', err.message);
console.log('requestId: ', err.requestId);
console.log('status: ', err.status);
console.log('ec: ', err.ec);
} else {
console.log('unknown error: ', err);
}
}
};
multipartUpload();主なパラメーター:
| パラメーター | 説明 |
|---|---|
uploadId | initiateMultipartUpload から返される一意の識別子です。以降のすべての uploadPart および completeMultipartUpload の呼び出しに渡す必要があります。 |
partNumber | 各パートの連続する番号(1 から開始)。最終オブジェクト内でのパートの位置を決定します。 |
offset | ソースファイル内のこのパートの開始位置(バイト単位のオフセット)。 |
length | このパートのサイズ(バイト単位)。最後のパートは chunkSize より小さくなる場合があります。 |
completeAll | true を指定すると、OSS は明示的なパートリストを必要とせず、アップロード済みのすべてのパートを自動的にアセンブルします。 |
その他の操作
フラグメントアップロードの中止
進行中のアップロードをキャンセルし、アップロード済みのパートが使用していたストレージを解放するには、client.abortMultipartUpload を呼び出します。
import Client, { RequestError } from '@aliyun/oss';
const client = new Client({
accessKeyId: 'yourAccessKeyId',
accessKeySecret: 'yourAccessKeySecret',
securityToken: 'yourSecurityToken',
region: 'oss-cn-hangzhou',
});
const bucket = 'yourBucketName';
const key = 'yourObjectName';
const abortMultipartUpload = async () => {
try {
const res = await client.abortMultipartUpload({
bucket,
key,
// initiateMultipartUpload から返されたアップロード ID、または listMultipartUploads で取得したアップロード ID。
uploadId: 'yourUploadId',
});
console.log(JSON.stringify(res));
} catch (err) {
if (err instanceof RequestError) {
console.log('code: ', err.code);
console.log('message: ', err.message);
console.log('requestId: ', err.requestId);
console.log('status: ', err.status);
console.log('ec: ', err.ec);
} else {
console.log('unknown error: ', err);
}
}
};
abortMultipartUpload();アップロード済みパートの一覧表示
指定されたアップロード ID について、すでにアップロード済みのパートを取得するには、client.listParts を呼び出します。アップロードの進捗状況の確認や、中断されたアップロードの再開にご利用ください。
import Client, { RequestError } from '@aliyun/oss';
const client = new Client({
accessKeyId: 'yourAccessKeyId',
accessKeySecret: 'yourAccessKeySecret',
securityToken: 'yourSecurityToken',
region: 'oss-cn-hangzhou',
});
const bucket = 'yourBucketName';
const key = 'yourObjectName';
const listParts = async () => {
try {
const res = await client.listParts({
bucket,
key,
// initiateMultipartUpload から返されたアップロード ID、または listMultipartUploads で取得したアップロード ID。
uploadId: 'yourUploadId',
});
console.log(JSON.stringify(res));
} catch (err) {
if (err instanceof RequestError) {
console.log('code: ', err.code);
console.log('message: ', err.message);
console.log('requestId: ', err.requestId);
console.log('status: ', err.status);
console.log('ec: ', err.ec);
} else {
console.log('unknown error: ', err);
}
}
};
listParts();進行中のフラグメントアップロードの一覧表示
OSS バケット内で初期化済みだが、まだ完了も中止もされていないアップロードをすべて一覧表示するには、client.listMultipartUploads を呼び出します。未完了のアップロードの監査や、再開・中止対象のアップロード ID の特定に役立ちます。
import Client, { RequestError } from '@aliyun/oss';
const client = new Client({
accessKeyId: 'yourAccessKeyId',
accessKeySecret: 'yourAccessKeySecret',
securityToken: 'yourSecurityToken',
region: 'oss-cn-hangzhou',
});
const bucket = 'yourBucketName';
const listMultipartUploads = async () => {
try {
const res = await client.listMultipartUploads({ bucket });
console.log(JSON.stringify(res));
} catch (err) {
if (err instanceof RequestError) {
console.log('code: ', err.code);
console.log('message: ', err.message);
console.log('requestId: ', err.requestId);
console.log('status: ', err.status);
console.log('ec: ', err.ec);
} else {
console.log('unknown error: ', err);
}
}
};
listMultipartUploads();中断されたアップロードの再開
HarmonyOS 上では、アプリが一時停止されたり、ネットワークが途中で切断されたりする可能性があります。ただし、アップロード ID は初期化時に返され、OSS 内で永続的に保持されるため、完了済みのパートを再アップロードすることなく再開できます。
initiateMultipartUploadから返されたuploadIdを、永続ストレージ(たとえばローカルファイルやデータベースなど)に保存します。アップロードが中断された場合、保存済みの
uploadIdを使用してclient.listPartsを呼び出し、すでにアップロード済みのパートを特定します。client.uploadPartを使用して、不足しているパートのみをアップロードします。すべてのパートが揃った時点で、
client.completeMultipartUploadを呼び出します。
このパターンにより、不要な帯域幅の消費を回避でき、特にモバイルネットワーク環境において重要です。