C++ クライアント ライブラリの再試行ポリシー

このページでは、C++ クライアント ライブラリで使用される再試行モデルについて説明します。

クライアント ライブラリは、RPC(リモート プロシージャ コール)を代理で発行します。これらの RPC は、一時的なエラーによって失敗する可能性があります。サーバーの再起動、ロードバランサの過負荷状態またはアイドル状態の接続の終了、レート制限を有効にできますが、これらは一時的な障害の一部の例にすぎません。

ライブラリはこれらのエラーをアプリケーションに返すことができます。ただし、これらのエラーの多くはライブラリで簡単に処理できるため、アプリケーション コードが簡略化されます。

再試行可能なエラーと再試行可能なオペレーション

一時的なエラーのみ再試行できます。たとえば、kUnavailable は、リクエストの進行中にクライアントが接続できなかったか、サービスへの接続を失ったことを示します。これはほとんどの場合は一時的な状態ですが、復元に長い時間がかかる場合があります。これらのエラーは常に再試行可能です(オペレーション自体が安全に再試行できることを前提としています)。契約で kPermissionDenied エラーを解決するには、追加の介入(通常は人間による)が必要です。このようなエラーは「一時的」とはみなされず、少なくともクライアント ライブラリの再試行ループによって考慮されるタイムスケールでは一時的とはみなされません。

同様に、エラーの性質にかかわらず、一部のオペレーションは安全に再試行できません。これには、増分変更を行うオペレーションが含まれます。たとえば、「X」というリソースの複数のバージョンが存在する場合のある「X の最新バージョン」を削除するオペレーションを再試行することは安全ではありません。これは、呼び出し元はおそらく単一のバージョンを削除することを意図していたためであり、このようなリクエストを再試行するとすべてのバージョンが削除される可能性があります。

再試行ループを構成する

クライアント ライブラリは、再試行ループを制御する 3 つの異なる構成パラメータを受け入れます。

  • *IdempotencyPolicy は、特定のリクエストがべき等かどうかを判断します。このようなリクエストのみが再試行されます。
  • *RetryPolicy は、(a)エラーを一時的な障害とみなすかどうか、(b)クライアント ライブラリがリクエストを再試行する時間(回数)を決定します。
  • *BackoffPolicy は、クライアント ライブラリがリクエストを再発行するまで待機する時間を決定します。

デフォルトのべき等性ポリシー

一般に、複数回の関数の呼び出しが成功して、システムを 1 回の関数の呼び出しが成功したのと同じ状態にすると、オペレーションはべき等になります。べき等オペレーションのみ安全に再試行できます。べき等オペレーションの例には、すべての読み取り専用オペレーションと、1 回だけ成功できるオペレーションが含まれますが、これらに限定されません。

デフォルトでは、クライアント ライブラリは、GET 動詞または PUT 動詞を介して実装される RPC のみをべき等として扱います。これは、一部のサービスでは、一部の POST リクエストがべき等であっても、保守的すぎるかもしれません。デフォルトのべき等性ポリシーは、必要に応じていつでもオーバーライドできます。

一部のオペレーションは、前提条件を含む場合にのみべき等です。たとえば、「最新バージョンが Y の場合は最新バージョンを削除」は、1 回しか成功できないため、べき等です。

随時、クライアント ライブラリでは、より多くのオペレーションがべき等として扱われるように改善が行われています。Google ではこれらの改善によるバグ修正を考慮しているため、もしクライアント ライブラリの動作を変更しても、停止しません。

オペレーションを再試行しても安全ですが、オペレーションが 2 回目の試行と 1 回目の成功した試行で同じ結果になるわけではないことに留意してください。たとえば、一意に識別されたリソースを作成すると、2 回目の連続の試行が失敗してシステムが同じ状態のままになるため、再試行しても安全になります。ただし、クライアントは再試行時に「すでに存在する」エラーを受け取る場合があります。

デフォルトの再試行ポリシー

aip/194 で概説されているガイドラインに従い、ほとんどの C++ クライアント ライブラリは UNAVAILABLE gRPC エラーのみを再試行します。これらは StatusCode::kUnavailable にマッピングされます。デフォルトのポリシーでは、リクエストを 30 分間再試行します。

kUnavailable エラーは、サーバーがリクエストを受信できなかったことを示していないことに留意してください。このエラーコードは、リクエストを送信できない場合に使用されますが、リクエストが正常に送信され、サービスによって受信され、レスポンスをクライアントが受信する前に接続が失われる場合にも使用されます。さらに、リクエストが正常に受信されたかどうかを判断できる場合は、よく知られている分散システムでのありえない結果である二人の将軍問題を解決できます。

そのため、kUnavailable で失敗するすべてのオペレーションを再試行することは安全ではありません。オペレーションのべき等性も重要です。

デフォルトのバックオフ ポリシー

デフォルトでは、ほとんどのライブラリでは、ジッター付きで切り捨て型指数バックオフ戦略が使用されています。初期バックオフは 1 秒、最大バックオフは 5 分、再試行のたびにバックオフは 2 倍になります。

デフォルトの再試行ポリシーとバックオフ ポリシーを変更する

各ライブラリでは、これらのポリシーを構成する *Option 構造体を定義します。これらのオプションは、*Client クラスの作成時に、または各リクエストで指定できます。

たとえば、Cloud Pub/Sub クライアントの再試行ポリシーとバックオフ ポリシーを変更する方法を次に示します。

namespace pubsub = ::google::cloud::pubsub;
using ::google::cloud::future;
using ::google::cloud::Options;
using ::google::cloud::StatusOr;
[](std::string project_id, std::string topic_id) {
  auto topic = pubsub::Topic(std::move(project_id), std::move(topic_id));
  // By default a publisher will retry for 60 seconds, with an initial backoff
  // of 100ms, a maximum backoff of 60 seconds, and the backoff will grow by
  // 30% after each attempt. This changes those defaults.
  auto publisher = pubsub::Publisher(pubsub::MakePublisherConnection(
      std::move(topic),
      Options{}
          .set<pubsub::RetryPolicyOption>(
              pubsub::LimitedTimeRetryPolicy(
                  /*maximum_duration=*/std::chrono::minutes(10))
                  .clone())
          .set<pubsub::BackoffPolicyOption>(
              pubsub::ExponentialBackoffPolicy(
                  /*initial_delay=*/std::chrono::milliseconds(200),
                  /*maximum_delay=*/std::chrono::seconds(45),
                  /*scaling=*/2.0)
                  .clone())));

  std::vector<future<bool>> done;
  for (char const* data : {"1", "2", "3", "go!"}) {
    done.push_back(
        publisher.Publish(pubsub::MessageBuilder().SetData(data).Build())
            .then([](future<StatusOr<std::string>> f) {
              return f.get().ok();
            }));
  }
  publisher.Flush();
  int count = 0;
  for (auto& f : done) {
    if (f.get()) ++count;
  }
  std::cout << count << " messages sent successfully\n";
}

そのライブラリの具体的な名前と例を知るには、各ライブラリのドキュメントをご覧ください。

次のステップ