はじめに
バックアップアップローダーでは、成功したアップロードを完了し、中断後に残った不要なパートを解放する必要があります。この実験では、サイズを制限した合成ファイルを分割してマルチパートセッションを完了します。その後、別の未完了セッションを確認して中止します。完了済みのオブジェクトには影響を与えません。
先に、R2 オブジェクトとスコープ付き認証情報に関する前のレッスンを完了してください。この新しい VM には、Node.js 22.22.0、Wrangler 4.131.1、AWS SDK 3.888.0 が用意されています。新しい非公開の Standard バケットを 1 つ作成し、そのバケット専用の短期間有効な認証情報を作成します。R2 が有効になっている必要があります。マルチパートの制限 と 料金 を確認してください。未完了のパートもストレージ使用量に含まれます。この実験で転送するのは小さな合成ファイルだけで、ドメインは必要ありません。以前のアップロードやバケットは再利用しないでください。
非公開のドキュメントバケットを作成する
このステップでは、この VM を認証し、使い捨てのバケットを 1 つ作成します。デバイス認証によって、学習用アカウントを確認します。R2 バケットの管理には、そのアカウントだけに制限した別の API トークンを使用します。
以下で使用するコマンド構文のために Bash を起動し、準備済みのプロジェクトへ移動してツールを確認します。リソース名の変数を使えるように、同じターミナルを開いたままにしてください。
bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version
表示されたデバイスコードを、自分のブラウザーで認証してください。同意する前に、学習用アカウントと、要求されているアカウントおよびユーザーの読み取りスコープを確認します。
npx wrangler login --device --browser=false --scopes account:read user:read
npx wrangler whoami --json
loggedIn: true になっていることを確認します。アカウントが 1 つだけ表示される場合でも、アカウント名を確認してください。以下の YOUR_ACCOUNT_ID を、そのアカウントの実際の 32 文字の ID に置き換えます。openssl rand -hex 6 は 12 個のランダムな 16 進文字を生成するため、以前の実行結果と名前が衝突しません。ヒアドキュメントによって標準設定ファイルが作成され、シェルが変数をその中に展開します。
ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r06-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON
バケット管理のため、Cloudflare プロファイルの API Tokens ページを開き、この実験の名前を付けたカスタムトークンを作成します。Account → Workers R2 Storage → Edit を許可し、Account Resources を、保存した ID の学習用アカウントだけに制限します。有効期限は短く設定してください。他のアカウントや関係のない権限は含めないでください。この管理トークンは、作成や削除などのバケット管理に使用します。このステップの後半では、S3 SDK でオブジェクトを操作するために、このバケットに権限を限定した別のオブジェクトトークンを作成します。
トークンを 1 回だけ表示されるこの非表示 VM プロンプトにコピーします。umask 077 によりファイルを自分のユーザーだけが読めるようにし、read -s により入力を非表示にします。このファイルでは Wrangler の標準トークン変数を使用し、Git の対象から除外されます。
umask 077
read -r -s -p 'R2 management API token: ' R2_MANAGEMENT_TOKEN; printf '\n'
printf 'CLOUDFLARE_API_TOKEN=%s\n' "$R2_MANAGEMENT_TOKEN" > .env.management
unset R2_MANAGEMENT_TOKEN
R2 の管理コマンドに限り --env-file=.env.management を使用します。通常の whoami は、引き続き VM のデバイス認証を確認します。
--env-file は各 Wrangler コマンドの末尾に置きます。これにより、ファイル引数のリストにコマンド名が取り込まれるのを防げます。各バケットの作成後、Wrangler が設定へのバインディング追加を尋ねたら、n を入力して Enter を押してください。必要なバインディングはすでに設定されています。
npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management
バケット一覧を表示し、生成された正確な名前を探します。他のバケットは別の作業に使われているため、そのままにしてください。
npx wrangler r2 bucket list --env-file=.env.management
Dashboard で Storage & databases → R2 → Overview を開き、この正確な名前のバケットを選択して、オブジェクト一覧が空であることを確認します。設定では、パブリック開発用 URL とカスタムドメインを無効のままにしてください。Dashboard に表示されるバケット名で対象を確認できます。後のダウンロードチェックでは、保存されたバイト列が正しいことを確認します。
S3 互換 API により、標準のストレージ SDK から R2 にアクセスできます。Wrangler のデバイストークンではなく、別のアクセスキーのペアを使用します。R2 の Overview で Account Details → API Tokens → Manage を開き、この実験で生成したリソース名を付けた User API token を作成します。Object Read & Write を選択し、この正確な名前の新しいバケットだけに制限します。フォームに項目がある場合は、短い有効期限を選択してください。すべてのバケットや Admin アクセスは選択しないでください。1 回だけ表示されるシークレットを保存するまで、このトークンのページを開いたままにします。
VM で次の Bash プロンプトを使用します。read -s により入力を非表示にし、umask 077 により認証情報ファイルを自分のユーザーだけが読めるようにします。以下の名前は AWS SDK の標準環境変数です。Access Key ID と Secret Access Key をそれぞれのプロンプトに貼り付け、Enter キーを押してください。一般的な API トークンの値は貼り付けないでください。
umask 077
read -r -s -p 'Access Key ID: ' AWS_ACCESS_KEY_ID; printf '\n'
read -r -s -p 'Secret Access Key: ' AWS_SECRET_ACCESS_KEY; printf '\n'
printf 'AWS_ACCESS_KEY_ID=%s\nAWS_SECRET_ACCESS_KEY=%s\n' "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" > .env.s3
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
再利用できる標準 SDK クライアントを作成します。SDK にはリージョン文字列が必要で、R2 では auto を使用します。既存の設定を読み込むことで、CLI と SDK の操作対象を同じアカウントとバケットにそろえます。
cat > storage.mjs <<'JS'
import { S3Client } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("wrangler.jsonc", "utf8"));
export const Bucket = config.r2_buckets[0].bucket_name;
export const s3 = new S3Client({
region: "auto",
endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
JS
Wrangler を使って、保持する合成ハンドブックをアップロードします。完了操作や中止操作の間も、内容を変更しないでください。
npx wrangler r2 object put "$BUCKET/retained/handbook.txt" --remote --file retained.txt --content-type text/plain --env-file=.env.management
サイズを制限したマルチパートアップロードを開始する
このステップでは、マルチパートアップロードを開始します。これは、番号付きのパートを受け取ってから最終オブジェクトを組み立てる、サーバー側のアップロードセッションです。アップロードしたパートは、まだダウンロード可能なオブジェクトではありません。アップロード ID を保存すると、この正確なセッションを再開または中止できます。
通常の Python を使って、6 MiB の合成バイナリファイルを作成します。最初のパートは 5 MiB、最後のパートは 1 MiB になります。R2 では、対応しているパートサイズを使用する必要があります。最後のパート以外は少なくとも 5 MiB で、同じサイズにしてください。この小さなファイルで、大容量転送を行わずにプロトコルを確認できます。
python3 - <<'DATA'
from pathlib import Path
Path("archive.bin").write_bytes(bytes(range(256)) * (6 * 1024 * 1024 // 256))
DATA
cat > start.mjs <<'JS'
import { CreateMultipartUploadCommand } from "@aws-sdk/client-s3";
import { writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const Key = "exports/archive.bin";
const result = await s3.send(new CreateMultipartUploadCommand({ Bucket, Key, ContentType: "application/octet-stream" }));
writeFileSync("upload.json", JSON.stringify({ Key, UploadId: result.UploadId }));
console.log("Started multipart upload for", Key);
JS
node --env-file=.env.s3 start.mjs
upload.json はこの操作を識別するファイルであり、成功を示すマーカーではありません。必要がない限り start を再実行しないでください。呼び出すたびに、後で削除が必要な別の未完了アップロードが作成されます。
作成時に保存したアップロード ID を引き続き使用します。テストした R2 エンドポイントでは、一覧に異なる不透明な ID 文字列が返されました。正確なオブジェクトキーを照合し、保存した ID で ListParts を呼び出して有効なセッションを確認します。
パートを順番にアップロードしてオブジェクトを完成させる
このステップでは、2 つのパートを送信し、返されたパート識別子のどれを最終オブジェクトに含めるかを R2 に伝えます。パート番号は 1 から始まります。完了リクエストには、各パートのアップロードで返された ETag をそのまま含めます。自分で元ファイル全体をハッシュした値とは異なります。
cat > complete.mjs <<'JS'
import { UploadPartCommand, CompleteMultipartUploadCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync, writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const { Key, UploadId } = JSON.parse(readFileSync("upload.json", "utf8"));
const bytes = readFileSync("archive.bin");
const size = 5 * 1024 * 1024;
const Parts = [];
for (let offset = 0, PartNumber = 1; offset < bytes.length; offset += size, PartNumber++) {
const result = await s3.send(new UploadPartCommand({ Bucket, Key, UploadId, PartNumber, Body: bytes.subarray(offset, offset + size) }));
Parts.push({ PartNumber, ETag: result.ETag });
console.log("Uploaded part", PartNumber);
}
await s3.send(new CompleteMultipartUploadCommand({ Bucket, Key, UploadId, MultipartUpload: { Parts } }));
const object = await s3.send(new GetObjectCommand({ Bucket, Key }));
writeFileSync("completed.bin", await object.Body.transformToByteArray());
console.log("Completed and downloaded", Key);
JS
node --env-file=.env.s3 complete.mjs
2 つのパートのアップロード行に続いて、完了行が表示されることを確認します。ダウンロードしたファイルをバイト単位で比較します。
cmp archive.bin completed.bin && printf "Multipart bytes match\n"
マルチパートオブジェクトの ETag は、完成したファイルの MD5 とは限りません。バイト比較によって、内容が直接保持されていることを確認できます。Dashboard でこの実験のバケットを開き、exports/archive.bin を確認します。保持したハンドブックも残っている必要があります。
View prefixes as folders のチェックを外すと、この例のように両方の完全なオブジェクトキーが表示されます。生成されるバケット名は例と異なります。6.29 MB は 6 MiB(6,291,456 バイト) の十進表示です。上部の Bucket Size: 0 B は更新が遅れる場合があります。オブジェクトの行と API で検証したバイト列で保存内容を確認してください。

未完了のアップロードを確認する
このステップでは、新しいアップロードを意図的に未完了のまま残し、そのセッションとパートを一覧表示します。通常のオブジェクト一覧には完成したファイルしか表示されませんが、未完了のパートもストレージを消費します。そのため、クリーンアップにはオブジェクト一覧だけでなく、アップロード一覧も必要です。
cat > abandon.mjs <<'JS'
import { CreateMultipartUploadCommand, UploadPartCommand, ListMultipartUploadsCommand, ListPartsCommand } from "@aws-sdk/client-s3";
import { readFileSync, writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const Key = "temporary/unfinished.bin";
const result = await s3.send(new CreateMultipartUploadCommand({ Bucket, Key }));
const UploadId = result.UploadId;
writeFileSync("abandoned.json", JSON.stringify({ Key, UploadId }));
await s3.send(new UploadPartCommand({ Bucket, Key, UploadId, PartNumber: 1, Body: readFileSync("archive.bin").subarray(0, 5 * 1024 * 1024) }));
const uploads = await s3.send(new ListMultipartUploadsCommand({ Bucket }));
console.log(uploads.Uploads.map(upload => ({ key: upload.Key, uploadId: upload.UploadId })));
const parts = await s3.send(new ListPartsCommand({ Bucket, Key, UploadId }));
console.log(parts.Parts.map(part => ({ part: part.PartNumber, bytes: part.Size })));
JS
node --env-file=.env.s3 abandon.mjs
マルチパート一覧には temporary/unfinished.bin が含まれます。ListParts リクエストには保存したアップロード ID を使用し、パート 1 と 5,242,880 バイトのサイズが返ることを確認します。一覧の ID 文字列との比較や、再確認のための新規セッション作成は不要です。保存した ID と標準の一覧 API を使用し、このアップロードが存在する間にプラットフォームのチェックを実行します。
放棄したセッションだけを中止する
このステップでは、正確なアップロード ID を指定して中止し、未完了のパートを解放します。中止は、完成済みオブジェクトの削除とは異なります。完成したアーカイブとハンドブックの両方を残す必要があります。
cat > abort.mjs <<'JS'
import { AbortMultipartUploadCommand, ListMultipartUploadsCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const { Key, UploadId } = JSON.parse(readFileSync("abandoned.json", "utf8"));
await s3.send(new AbortMultipartUploadCommand({ Bucket, Key, UploadId }));
const uploads = await s3.send(new ListMultipartUploadsCommand({ Bucket }));
console.log("Incomplete uploads:", uploads.Uploads || []);
JS
node --env-file=.env.s3 abort.mjs
この新しいバケットのマルチパートアップロード一覧が空になっていることを確認します。プラットフォームチェックでは、完成した 2 つのオブジェクトもダウンロードして、変更されていないことを確認します。一覧取得に失敗した場合、それを空の一覧と解釈しないでください。
完成したファイルとバケットをクリーンアップする
このステップでは、中止のチェックが成功した後、完成済みの 2 つのオブジェクトを正確に削除します。明示的なクリーンアップにより、デフォルトの未完了アップロードのライフサイクルルールを待つ必要がなくなります。
npx wrangler r2 object delete "$BUCKET/exports/archive.bin" --remote --env-file=.env.management
npx wrangler r2 object delete "$BUCKET/retained/handbook.txt" --remote --env-file=.env.management
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
npx wrangler r2 bucket list --env-file=.env.management
生成されたこのバケットの名前だけを確認します。成功した一覧取得結果にこのバケットが存在しないことを確認し、認証情報を無効化する前にプラットフォームのクリーンアップチェックを実行してください。
実験用認証情報を無効化してログアウトする
このステップでは、この実習で残ったアクセスを閉じます。R2 API Tokens ページで、この実験用の名前が付いたオブジェクトトークンだけを無効化します。プロファイルの API Tokens ページで、この実験用に作成した別の R2 管理トークンを無効化します。バケットを削除してもトークンは無効化されません。また、Wrangler からログアウトしても S3 認証情報は無効化されません。
無効化した後、ローカルの認証情報ファイルを削除し、この VM からログアウトします。
rm .env.s3 .env.management
npx wrangler logout
構造化された ID 情報を確認します。ログアウト後にステータスが 0 以外になるのは想定どおりです。
npx wrangler whoami --json || true
loggedIn: false になっていることを確認します。通常の Dashboard ログインは維持してください。プラットフォームチェックでは、ローカル認証情報の削除と Wrangler のログアウトを確認します。2 つのトークンの無効化は、このチャレンジでは Dashboard で手動確認する項目です。ファイル削除の結果から自動的に判断されるものではありません。
まとめ
正確なバイト列でマルチパートオブジェクトを完成させ、未完了のパートを確認して中止し、他のオブジェクトを保持したままストレージ認証情報を削除しました。



