クラウドにおける再試行

クリエーションラインの佐伯です。本記事は[クリエーションライン Advent Calendar 2023](https://qiita.com/advent-calendar/2023/creationline)の 12/19 担当分です。
今年もクラウドのあれこれに巻き込まれていたのでその話でも。

## 初めに - クラウドサービスを利用するということ

少し前はデータセンターのマシンに自前のソフトウェアやサードパーティソフトウェアを搭載し構成したシステムが多かったと思いますが、最近は、システムの全体、あるいは一部にクラウドサービスを採用する構成が増えてきました。
クラウドサービスは運用責任がクラウドベンダーにあるため、利用する側の運用負荷が減り、ありがたい面もありますが、反面、自分で作っていた頃に見えていた部分・制御できていた部分がブラックボックスとなり、クラウドサービスの仕様だとして振り回される場面も多いと思います。

特に、アプリケーションとクラウドサービスの間の対話が成立しなかった状況となると大きな問題となります。アプリケーションとクラウドサービスの間の対話は、アプリケーションからのリクエストとクラウドサービスからのレスポンスのネットワーク越しのやり取りで構成されますが、対話が成立しないパターンとしては、

1. アプリケーションからのリクエストがクラウドサービスに到達しなかった
2. リクエストを受け取ったクラウドサービスが正常に動作しなかった
3. アプリケーションがクラウドサービスからのレスポンスを受け取れなかった

の 3 パターンが少なくとも考えられます。

1\. と 3\. の場合、アプリケーションの問題か、あるいは調査が難しい状況として、アプリケーションとクラウドサービスの間のネットワークの問題が考えられます。アプリケーションが自社ネットワーク内にあったとしても、インターネットを経由している場合がほとんどかと思いますので、ネットワークの調査観点は自社ネットワーク、インターネット、クラウドベンダー所有ネットワーク、となります。いずれのネットワークも責任者が異なり、現在も続いている事象であればまだしも、過去の事象であれば調査はほぼ不可能ではないでしょうか。
2\. の場合、クラウドサービスの障害やメンテナンス等が考えられます。クラウドサービスによっては計画メンテナンスというものがあり事前に通知を受け取れる場合もあり事前に対策が打てる状況もありますが、非計画メンテナンスというのもあるようです。こうなってしまうと、いくらクラウドサービスにリクエストを送ってものれんに腕押しです。

## クラウドとうまく付き合うために

こういった問題に対してクラウドサービスは、アプリケーションがリクエストを「再試行」することを強く推奨しています。上記のいずれのパターンの場合でも、クラウドサービス側は、アプリケーションがリクエストを出したか・レスポンスを受け取ってもらえたかの全体像を正確に判断できないため、利用する側が制御可能なアプリケーションの責任として、失敗を定義・検知し、対策の動作として「再試行」をすることで期待する結果となる確率を高める、という考え方となります。

* https://aws.amazon.com/jp/builders-library/timeouts-retries-and-backoff-with-jitter/
* https://learn.microsoft.com/ja-jp/azure/architecture/best-practices/transient-faults
* https://learn.microsoft.com/ja-jp/azure/architecture/patterns/retry
* https://cloud.google.com/storage/docs/retry-strategy?hl=ja
* https://firebase.google.com/docs/functions/retries?hl=ja

GCP はサービス毎の資料となっていますが、AWS と Azure はクラウドを採用したシステム構成の指針としての資料がありました。
いずれも同様のことが書かれており、観点としては以下 3 つとなりそうです。

### タイムアウトしよう

アプリケーションがクラウドサービスへリクエストを送りレスポンスを受け取るまで、待つことが出来る期限を設定しておけば、問題の状況に早く気づくことが出来ますし、アプリケーションが無限に待つより対策にするべき次の対策へ処理を移せるため、効率的になります。
ネットワーク関連では多くの場合接続タイムアウトの設定がありますし、例えばデータベース関連ではデータ更新・取得タイムアウトの設定があると思います。
タイムアウトの時間はいくらでも短く設定できるでしょうが、現実的にこの程度は待っても良い、という時間を決めて設定するのが望ましいと思います。あらかじめ検証用の環境で、実環境で起こりうる複数パターン複数回動作させ、最大かかった時間を設定するのも良いと思います。

### 再試行しよう

タイムアウトした場合、何らかの問題が発生していた状況が想定できるため、もう一度アプリケーションがやろうとしていた処理を試す、あるいは問題が解決するまで繰り返し試す、となります。
処理を再試行したい場合、アプリケーションはタイムアウトした処理の内容を覚えておかなければなりません。もう一度実行する処理が膨大だと無駄が大きくなるので、再試行するべき処理範囲を小さくしておく工夫も必要です。また、上記の問題パターンの 3\. の様に、クラウドサービスはアプリケーションからのリクエストを受け処理は完了したがアプリケーションへレスポンスが返らなかった、といった状況でアプリケーションが再試行すると、クラウドサービスは同じリクエストを 2 回受け取り処理することになるので、冪等性が求められます。そのためには、クラウドサービス上の状態（結果データの状態）に制約を持たせる、処理 1 つずつにユニークな ID を割り当て処理・リクエスト・レスポンスを紐付けられるようにする（再試行した場合 ID は再利用し同じ）、といった仕組みをあらかじめ導入しておく必要があります。

### 適度に待とう

タイムアウトのタイミングが適当でも、無闇矢鱈に再試行してしまっては、クラウドサービスがダウンしていたりネットワークがダウンしていたりしている状況では意味がありません。クラウドサービスはオートヒーリングであったり、中の人が頑張って直してくれたりしますが、時間は必要です。
そのため、アプリケーションが処理を再試行する際も時間を置いて実施するべきです。しかし待つ時間が長いと、クラウドサービスにとってはありがたいでしょうが、アプリケーション側は何もできず処理の停止影響が大きくなるでしょう。
クラウドサービスでは、再試行を実施する際の待ち時間として、バックオフ、特にエクスポーネンシャルバックオフを推奨しています。例えば、1 回目の再試行までは 2 秒、それに失敗した場合 2 回目の再試行までは 4 秒、3 回目の再試行までは 8 秒、といった具合に、再試行が失敗する度に待ち時間を増やします。一時的な問題であれば短時間の待ちでアプリケーションの処理は再開できます。そうでない場合はクラウドサービスの問題復旧に時間がかかっていると想定されるため、アプリケーションも無駄な再試行を減らし、復旧を待つ動作となります。
また、アプリケーションが一斉に複数処理の再試行をしないよう、ジッターと呼ばれるランダム待ち時間を入れ再試行タイミングをばらすことで、クラウドサービスが復旧した際の急な負荷を避けられます。

## まとめ

クラウドサービスの採用は今後も増えていくでしょう。今年大いに流行した OpenAI の ChatGPT や Azure AI のようなサービスは、頑張れば出来るのかもしれませんが、自分で作り維持する労力を想像しただけで震えます。
クラウドサービスは大抵の場合、100% 未満の SLA 稼働率が定義されており、ダウンタイムがあることを利用者と同意の上として提供されています。クラウドサービスがダウンした時、慌てたり文句を吐いたりすることがないよう、起こるべきことが起こったと構えられるように、クラウドサービスとうまく付き合うための準備、アプリケーションのお作法として、「再試行」を取り上げました。
これを読んだ皆さんが、クラウドサービスを使いこなし、新たな価値を生み出すシステムを作ることを期待しています。