長時間実行オペレーションによる作業

API が、完了までにかなりの時間がかかるメソッドを公開することがあります。タスクの実行中にブロックするのではなく、Promise を返して、ユーザーがステータスを確認できるようにします。

Rust 用の Google Cloud クライアント ライブラリは、これらの長時間実行オペレーション(LRO)を操作するためのヘルパーを提供します。このガイドでは、LRO を開始して完了を待つ方法について説明します。

前提条件

このガイドでは、コード スニペットを具体的にするために Cloud Storage サービスを使用します。これらのコンセプトは、LRO を使用する他のサービスにも適用されます。

このガイドに進む前に、次のことを確認してください。

Rust ライブラリの完全な設定手順については、開発環境の設定をご覧ください。

依存関係

Cargo.toml ファイルで Google Cloud 依存関係を宣言します。

cargo add google-cloud-storage google-cloud-lro google-cloud-longrunning

また、tokio のいくつかの機能も必要です。

cargo add tokio --features full,macros

長時間実行オペレーションを開始する

この例では、フォルダの名前を変更を使用します。このオペレーションは、大きなフォルダでは時間がかかることがありますが、小さなフォルダでは比較的短時間で完了します。

長時間実行オペレーションを開始するには、クライアントを初期化して RPC を行う必要があります。

まず、長いパッケージ名を避けるために use 宣言を追加します。

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

次に、クライアントを作成します。

let client = StorageControl::builder().build().await?;

Rust クライアント ライブラリでは、各リクエストはリクエスト ビルダーを返すメソッドで表されます。クライアントでメソッドを呼び出して、リクエスト ビルダーを作成します。

let operation = client
    .rename_folder()
    .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
    .set_destination_folder_id(dest)

サンプル関数は、バケット名とフォルダ名を引数として受け取ります。

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {

リクエストを行い、オペレーションが返されるのを待ちます。この Operation は、長時間実行されるリクエストの結果の Promise として機能します。

    let operation =
        // ...
        .send()
        .await?;

このリクエストにより、オペレーションがバックグラウンドで開始されます。オペレーションが完了するまで待って、成功したかどうかを確認します。

長時間実行オペレーションを自動的にポーリングする

自動ポーリングを構成するには、次のように .send().wait ではなく Poller を使用して長時間実行オペレーションを開始します。

.poller()
.until_done()
.await?;

まず、use 宣言を使用して、スコープ内の Poller トレイトを導入します。

use google_cloud_lro::Poller;

次に、クライアントを初期化し、リクエストを以前と同様に準備します。

let response = client
    .rename_folder()
    .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
    .set_destination_folder_id(dest)

オペレーションが完了するまでポーリングし、結果を出力します。

    .poller()
    .until_done()
    .await?;

println!("LRO completed, response={response:?}");

中間結果を含む長時間実行オペレーションをポーリングする

.until_done() メソッドは便利ですが、長時間実行オペレーションの部分的な進行状況レポートは省略されます。アプリケーションでこの情報が必要な場合は、ポーリングを直接使用します。

    let mut poller = client
        .rename_folder()
        /* more stuff */
        .poller();

次に、ループでポーラーを使用します。

while let Some(p) = poller.poll().await {
    match p {
        PollingResult::Completed(r) => {
            println!("LRO completed, response={r:?}");
        }
        PollingResult::InProgress(m) => {
            println!("LRO in progress, metadata={m:?}");
        }
        PollingResult::PollingError(e) => {
            println!("Transient error polling the LRO: {e}");
        }
    }
    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}

このループは、明示的に待機してから再度ポーリングします。ポーリング期間は、特定のオペレーションとそのペイロードによって異なります。適切な値を判断するには、サービス ドキュメントを参照するか、データをテストします。

長時間実行オペレーションを手動でポーリングする

自動ポーリング アプローチをおすすめしますが、長時間実行オペレーションを手動でポーリングすることもできます。詳細については、オペレーション メッセージのリファレンス ドキュメントをご覧ください。

クライアントを使用して長時間実行オペレーションを開始します。

    let mut operation = client
        .rename_folder()
        /* more stuff */
        .send()
        .await?;

ポーリング ループを開始し、done フィールドを使用してオペレーションが完了したかどうかを確認します。

let response: anyhow::Result<Folder> = loop {
    if operation.done {

オペレーションが完了すると、通常は結果が含まれます。サービスは結果なしで done を true として返す可能性があるため、結果フィールドは省略可能です。たとえば、削除オペレーションが成功した場合、戻り値はありません。この例では、Cloud Storage サービスは常に値を返します。

match &operation.result {
    None => {
        break Err(anyhow!("missing result for finished operation"));
    }

開始されたオペレーションが正常に完了しないことがあります。結果はエラーまたは有効なレスポンスになります。まずエラーを確認します。

Some(r) => {
    break match r {
        longrunning::model::operation::Result::Error(s) => {
            Err(anyhow!("operation completed with error {s:?}"))
        }

エラータイプは Status メッセージ タイプです。標準の Error トレイトは実装しません。Error::service を使用して、有効なエラーに手動で変換します。

結果が成功した場合は、レスポンス タイプを抽出します。このタイプは、LRO メソッドのドキュメントまたはサービス API のドキュメントで確認できます。

longrunning::model::operation::Result::Response(any) => {
    let response = any.to_msg::<Folder>()?;
    Ok(response)
}

型がサービスから送信されたものと一致しない場合、値の抽出が失敗する可能性があります。

Google Cloud 型は、今後フィールドとブランチを追加する可能性があります。Rust 用の Google Cloudクライアント ライブラリは、すべての構造体と列挙型を #[non_exhaustive] としてマークします。このケースに対応します。

_ => Err(anyhow!("unexpected result branch {r:?}")),

オペレーションが完了していない場合は、メタデータが含まれている可能性があります。一部のサービスにはリクエストに関する初期情報が含まれ、一部のサービスには部分的な進行状況レポートが含まれます。次のメタデータを抽出してレポートできます。

if let Some(any) = &operation.metadata {
    let metadata = any.to_msg::<RenameFolderMetadata>()?;
    println!("LRO in progress, metadata={metadata:?}");
}

ポーリングを再試行する前に待機します。切り捨て型指数バックオフを使用してポーリング期間を調整することを検討してください。この例では、500 ミリ秒ごとにポーリングします。

tokio::time::sleep(std::time::Duration::from_millis(500)).await;

オペレーションのステータスをクエリします。

if let Ok(attempt) = client
    .get_operation()
    .set_name(&operation.name)
    .send()
    .await
{
    operation = attempt;
}

この例では、わかりやすくするためにすべてのエラーを無視しています。アプリケーションでは、エラーのサブセットを回復不能として扱い、ポーリング試行回数を制限できます。

次のステップ

  • 例のソースコードは GitHub で確認できます。