Daprの回復性を支える仕組み 現状と展望

Posted on Dec 11, 2021

このエントリは、Dapr Advent Calendar 2021の参加作品(12/11)です。

何の話か

クラウドでは、一過性の障害が起こりがちです。その原因は、以下のドキュメントで具体的に説明されています。

クラウドで一過性の障害が起こる理由 - 一時的な障害の処理

故障や負荷だけでなく、メンテナンスも原因となり得ます。

しかし多くの場合、クラウド内部で復旧や切り替えが行われます。呼び出し元が少し時間をおいてからリトライすれば、高い確率で成功するでしょう。よって、リトライなど回復性向上の仕組みを、アプリケーションに組み込むことが推奨されています

Daprをクラウド上で使いたい、という人は多いでしょう。そしてDaprは、分散アプリケーションを作る開発者の負担を減らすために生まれました。アプリケーション開発者が「回復性に関わるあれこれは、Daprさんのほうで面倒を見てくれないかな」と思うのは自然です。

そこでこのエントリでは、Daprの回復性を高める仕組みについて、現状と展望をまとめます。

なお展望について、Daprコミュニティでの議論を直に見たいのであれば、ぜひ以下のIssueを読んでください。よく整理された提案です。

[Proposal] Resiliency policies across all building blocks

前置きが長くなりました。当エントリの目的は、いま紹介したIssueを読む時間と、 現状調査をする余裕がない人向けのまとめです。また、Daprの回復性について関心を持つきっかけになればいいな、と思いながら書いています。

現状 (Dapr v1.5)

呼び出し元側のDaprランタイム(サイドカー)の実装が、今回整理する対象です。呼び出す先の回復性は、Kubernetesの各種回復機能など基盤を活用して担保するものとします。

呼び出しパターンは、サービス呼び出し(Service-to-service Invocation)と、コンポーネント呼び出しに分けられます。それぞれの現状は以下の通りです。

このように、面倒を見てくれるいっぽう、課題もあります。

課題

  • サービス、コンポーネント間で仕様や実装の一貫性がない
  • 仕様に明記されておらず、実装の確認が必要なものがある
  • リトライ回数や間隔など、パラメータを指定できないものがある
  • リトライの実装が多いが、サーキットブレイカーなど他のパターンも欲しい

ということで、仕切り直しましょう、という状況です。

展望

前述した提案で、課題解決の方向性と進め方が議論されています。

回復性を高める仕組みは利益が大きい反面、提案のCautions/Considerationsで触れられている通り、リスクも含みます。リトライによる重複書き込みが代表的です。なので慎重に、段階的なアプローチが検討されています。

  • フェーズ1: 回復性に関するポリシーを共通化し、各ビルディングブロックやコンポーネントに割り当てられるようにする
    • 回復性ポリシーをKubernetesのカスタムリソースとして定義
    • タイムアウト、リトライ、サーキットブレイカーに関するポリシーから
  • フェーズ2: API固有のポリシーで、上書きできるようにする

おそらくフェーズ1のリリース後に、多くのフィードバックがあるでしょう。それを見越し、フェーズ1では基本的な機能にフォーカスしています。

フェーズ1

フェーズ1では、以下のようなカスタムリソースが提案されています。代表的な部分を抜き出します。コメントは削っていますが参考になるため、ぜひ原文もご覧ください。

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: daprResiliency
scopes:
  - app1
  - app2
...
  policies:
    timeouts:
      general: 5s
      important: 60s
      largeResponse: 10s

    retries:
      general: {}

      pubsubRetry:
        policy: constant
        duration: 5s
        maxRetries: 10

      retryForever:
        policy: exponential
        maxInterval: 15s
        maxRetries: 0
...
    circuitBreakers:
      general: {}

      pubsubCB:
        maxRequests: 1
        interval: 8s
        timeout: 45s
        trip: consecutiveFailures > 8

  buildingBlocks:
    services:
      appB:
        timeout: general
        retry: general
        circuitBreaker: general
...
    components:
      statestore1:
        timeout: general
        retry: general
        circuitBreaker: general

      pubsub1:
        retry: pubsubRetry
        circuitBreaker: pubsubCB
...

タイムアウト、リトライ、サーキットブレイカーのポリシーをそれぞれ定義し、ビルディングブロックを構成するサービスやコンポーネントに割り当てます。タイムアウトを判断するまでの時間や、リトライ間隔を固定にする/指数的に増やすなど、目的に応じたポリシーを作れます。

現時点でフェーズ1は、2022/1に予定されているDapr v1.6でのリリースを目指しています。もちろんこのカスタムリソースを使うように、各コンポーネント向けパッケージでも対応しなければいけませんが、まずは重要な一歩目です。

フェーズ2

フェーズ1のフィードバックによりますが、現時点ではAPI固有のポリシー定義による上書き機能が予定されています。代表例を抜き出します。

...
  apis:
    invoke:
      - match: appId == "appB"
        rules:
          - match:
              request.method == "GET" &&
              request.metadata.count > 1000
            timeout: largeResponse
            retry: largeResponse
          - match:
              request.path == "/someOperation"
            retry: someOperation
...

サービス呼び出しのポリシーを、リクエストのメソッドやパスに合わせて個別のポリシーで上書きする、という使い方は分かりやすい例ですね。

いま意識したいこと

以上、Daprの回復性を支える仕組みについて、現状と展望をざっと整理しました。それを踏まえ、いまDaprを使うなら、以下を意識するといいかな、と思います。

  • 各コンポーネント向けパッケージが、どのような回復性向上機能を持っているか、選定時に確認する
  • 呼び出し元のサービス、アプリケーションで実装すべき機能を検討する
  • 展望として紹介した回復性ポリシーはすぐに使えるわけではないが、状況をウォッチしておく
    • 導入タイミングを見極める

Enjoy Dapr!