Cloud Storage で大きなオブジェクトを単一の接続で順番にダウンロードすると、使用可能なネットワーク帯域幅が未使用のままになることがあります。オブジェクトをストライプと呼ばれるバイト範囲セグメントに分割し、それらのセグメントを同時に読み取ることで、Rust 用 Cloud クライアント ライブラリを使用して、大きなオブジェクトのダウンロード スループットを向上させることができます。
このドキュメントでは、オブジェクトの作成を使用して大規模なテスト オブジェクトをシードし、ReadRange::segment と同時にバイト範囲をダウンロードして、固定オフセットでローカル ファイルにストライプを書き込む方法について説明します。クライアント ライブラリを初めて使用する場合は、まず Rust で Cloud Storage を使用するをご覧ください。
始める前に
このドキュメントのコードを実行する前に、次の前提条件を満たしてください。
- 課金が有効になっている Google Cloud プロジェクトを作成または選択します。
- プロジェクトに専用のテスト用 Cloud Storage バケットを作成します。
- 使用する環境のアプリケーションのデフォルト認証情報を構成します。
- バケット内のオブジェクトの読み取り、書き込み、作成、削除を行う権限(ストレージ管理者(
roles/storage.admin)など)を持つ Identity and Access Management(IAM)ロールをプリンシパルに付与します。
依存関係
Cloud Storage クライアント ライブラリとサポート クレートを Cargo.toml ファイルに追加します。
cargo add google-cloud-storage google-cloud-gax anyhow bytes futures
cargo add tokio --features macros,rt-multi-thread
大きなテスト オブジェクトを作成する
ネットワーク経由でギガバイト単位のデータをアップロードせずにストライプ ダウンロードをテストするには、小さなシード オブジェクトをアップロードし、Cloud Storage のcompose オペレーションを使用してサーバーサイドでコピーを連結します。client、control、bucket_name を projects/_/buckets/BUCKET_ID 形式で受け入れる非同期 seed 関数内で、次の手順を完了します。
作成リクエストで既存のオブジェクトを参照するには、
SourceObjectをインポートします。1MiB.txtという名前の 1 MiB のシード オブジェクトをアップロードします。32MiB.txtという名前の 32 MiB のオブジェクトを作成するには、サーバー上で1MiB.txtの 32 個のコピーを連結します。1GiB.txtという名前の 1 GiB オブジェクトを作成するには、32MiB.txtの 32 個のコピーを連結します。1GiB.txt(2GiB.txt、4GiB.txt、8GiB.txt、16GiB.txt、32GiB.txt)から 2 のべき乗のテスト オブジェクトを作成するには、作成オペレーションを繰り返します。
同時ストライプ ダウンロードを調整する
オブジェクトを並列でダウンロードするには、サイズと世代をクエリし、ローカルの宛先ファイルを事前に割り当てて、ストライプごとに 1 つの future を生成します。client、control、bucket_name、object_name、stripe_size、destination を受け入れる download 関数内で、次の手順を完了します。
StorageControl::get_objectを使用してオブジェクト メタデータを取得し、オブジェクトの合計サイズを検査して、後続の読み取りを単一の世代にロックできるようにします。宛先ファイルを作成し、ワーカータスクが独立したバイト オフセットで書き込めるように、正確なオブジェクト サイズに事前割り当てします。
stripe_sizeをlimitとして使用して各ストライプ オフセットを計算し、セグメントごとにwrite_stripeフューチャーを作成します。オブジェクト サイズがlimitで割り切れない場合は、最後の部分ストライプを追加します。futures::future::join_allを使用してすべてのストライプ フューチャーを同時に実行し、エラーを伝播します。
個々のストライプをダウンロードして書き込む
各 write_stripe タスクは、Cloud Storage から 1 つのバイト範囲を読み取り、受信したチャンクを共有ローカル ファイル操作の対応するオフセットに書き込みます。write_stripe を実装する手順は次のとおりです。
ReadRangeをインポートし、write_stripe関数を定義します。Storage::read_objectを使用してReadObjectリクエスト ビルダーを作成し、次の手順で生成オプションと読み取り範囲オプションを構成してからリクエストを送信します。すべてのストライプを同じオブジェクト世代に固定し、別のプロセスがオブジェクトを上書きした場合に不整合な読み取りを防ぐには、
sendを呼び出す前にリクエスト ビルダーでset_generationを呼び出します。読み取りをストライプのバイト範囲に制限するには、リクエスト ビルダーで
ReadRange::segment(offset, limit)を指定してset_read_rangeを呼び出します。リーダーからチャンクをストリーミングし、各チャンクを
tokio::task::spawn_blocking内のcurrent_posにある宛先ファイルに書き込みます。Rust の標準ライブラリにはクロスプラットフォームの位置指定書き込みメソッドがないため、この例では Unix でFileExt::write_all_at、Windows でFileExt::seek_writeを使用しています。
パフォーマンスに関する注意事項
ストライプ ダウンロードの有効なスループットは、次のいくつかの要因によって決まります。
- ローカル ディスクの速度。低速なローカル ストレージにチャンクを書き込むと、ボトルネックになる可能性があります。高スループットのローカル SSD ストレージを使用すると、ディスク I/O をネットワーク読み取りよりも優先できます。
- VM CPU 容量。Cloud Storage は転送中のオブジェクト データを暗号化するため、クライアントは受信したすべてのチャンクを復号します。VM に、ストライプ間で同時 TLS 復号を処理するのに十分な vCPU があることを確認します。
- バケットと VM のロケーション。リージョン間でダウンロードすると、広域ネットワークのレイテンシが発生し、スループットが制限されます。可能であれば、バケットと同じリージョンに VM を配置します。
stripe_size のチューニングを試して、非常に小さな残りのストライプが残ったり、小さな範囲リクエストが多数作成されたりしないようにすることもできます。
コードを補完する
次の例では、seed、download、write_stripe の各関数を組み合わせて完全なモジュールにしています。
1 MiB の stripe_size を使用して 32 MiB のオブジェクトで download を実行すると、出力は次のようになります。ダウンロードされたサイズ、経過時間、ストライプ数、有効帯域幅が報告されます。
Completed 32.00 MiB download in 412.5ms, using 32 stripes, effective bandwidth = 77.58 MiB/s
クリーンアップ
seed によって作成されたテスト オブジェクトに対して Google Cloud アカウントに課金されないようにするには、テストが完了したらテスト オブジェクトを削除し、テストバケットを削除します。ローカルの宛先ファイルも削除します。