コンテンツに移動
デベロッパー

Workflows で Saga パターンを実装する

2022年2月18日
Google Cloud Japan Team

※この投稿は米国時間 2022 年 2 月 5 日に、Google Cloud blog に投稿されたものの抄訳です。

マイクロサービスベースのアーキテクチャでは、サービスごとに個別のデータベースを使用することが一般的です。そうすることで、個別に設計してデプロイしたマイクロサービスをデータレイヤでも個別に扱うことができます。しかしながら、そこには別の問題が生じます。それぞれに独自のローカル データベースが設定されている複数のマイクロサービスにまたがるトランザクション(個々の作業単位、通常は複数のオペレーションで構成される)の場合、どのように実装すればよいのかという問題です。

従来のモノリス アーキテクチャでは、単一のデータベースに基づく ACID トランザクション(アトミック性、整合性、独立性、耐久性)に依存できます。マイクロサービス アーキテクチャでは、サービス固有の複数のデータベースにわたってデータの整合性を確保することが難しくなります。単に、ローカル トランザクションだけに依存することはできません。サービス間トランザクション戦略が必要です。そこで、Saga パターンの出番となります。

Saga パターンとは

Saga は、ローカル トランザクションのシーケンスです。各ローカル トランザクションがデータベースを更新して、次のローカル トランザクションをトリガーします。1 つのローカル トランザクションが失敗すると、それより前のローカル トランザクションが行った変更を元に戻す一連の代替トランザクションが Saga によって実行されます。Chris Richardsonブログ シリーズでは、コレオグラフィーとオーケストレーションの両方のシナリオにおける Saga パターンが詳細に説明されていますので、ぜひご覧ください。

ここでは、Chris の例をとって、Workflows のコンテキストに Saga パターンを適用する方法を見てみましょう。現在、e コマース アプリケーションを構築しているとします。注文を受けた後、その顧客が十分なクレジットを持っているかどうか確認したうえで、注文を処理するか拒否する必要があります。

ネイティブ実装

ネイティブ実装では、注文を受ける OrderService と顧客のクレジットを管理する CustomerService の 2 つのサービスを使用することが考えられます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/workflows_retry_saga.max-900x900.max-900x900.png

この場合、次のようにこれらのオペレーションをワークフローで簡単に連結できます(ordering-v1.yaml)。

読み込んでいます...

これは、すべてのサービスが失敗しない場合には機能しますが、常にそうとは限りません。ご覧のように、どのステップにもエラー処理や再試行が指定されていません。いずれかのステップでエラーが生じた場合、ワークフロー全体が失敗することになり、理想とはほど遠くなります。

再試行を適用する

まれに CustomerService が(HTTP 503 などにより)利用不可になった場合、簡単な解決策としては、CustomerService の呼び出しを 1 回以上再試行する方法があります。

https://storage.googleapis.com/gweb-cloudblog-publish/images/Implementation_with_a_retry.max-900x900.max-900x900.png

この再試行ロジックを各サービスに実装することもできますが、Workflows では、try/retry プリミティブが提供されるため、サービスを変更する必要なく、一貫した再試行ポリシーを簡単に適用できます。

reserve_credit ステップで、HTTP 呼び出しを再試行ポリシーでラップできます(ordering-v2.yaml)。

読み込んでいます...

デフォルトの Workflows HTTP 再試行ポリシー では、特定の HTTP エラーが生じた場合に最大 5 回再試行が行われます。ほとんどの一時的障害からの回復にはこれで十分ですが、再試行ポリシーを詳細に構成することもできます。

このように、再試行ポリシーの適用は、一時的な障害にはうまく機能しますが、顧客が実際にはクレジットを持っていない場合やサービスが恒久的に利用不可になった場合など、回復不能なエラーによる障害の場合はどうすればよいでしょうか。

Saga パターンを適用する

CustomerService で生じた障害が回復不能な場合、ロールバック ステップを適用して、注文を拒否する必要があります。

https://storage.googleapis.com/gweb-cloudblog-publish/images/Implementation_with_a_saga.max-1400x1400.max-1000x1000.png

これが Saga パターンです。連結の下方向の呼び出しが失敗すると、上方向の代替呼び出しがトリガーされます。

reserve_credit ステップで、回復不能エラーを確認して、reject_pending_order ステップにそれらのエラーをルーティングします(ordering-v3.yaml)。

読み込んでいます...

reject_pending_order ステップで、OrderService への代替呼び出しが行われます。

読み込んでいます...

これらの変更によって、一時的な障害は再試行ポリシーで処理され、回復不能エラーは代替ステップで処理されるようになります。ワークフローの復元性が大きく向上しました。

代替ステップを連結する

ここまで、2 つのサービスが含まれるシンプルなワークフローで再試行と Saga パターンを適用する方法を見てきました。マルチサービス ワークフローやマルチステップ ワークフローはもっと複雑です。失敗したステップの後に複数の代替ステップを適用することが必要な場合があります。そのような場合は、サブワークフローにロールバック ステップを定義することをおすすめします。そうすることで、必要に応じて except 句でそれらのロールバック ステップを再利用できます。

たとえば、ステップ 4 が失敗した場合、サブワークフローを呼び出すことで、ステップ 1、2、3 をロールバックできます。

読み込んでいます...

マイクロサービスを連結する場合、障害に備えて設計しておくことが重要です。この点において、Workflows ではプリミティブが提供されるので、柔軟な再試行ポリシーや、Saga パターンのようなさらに高度なエラー処理を実現できます。詳細については、GitHub にある、Workflows での再試行と Saga パターンに関するチュートリアルをご覧ください。ご不明な点やフィードバックがございましたら、Twitter で @meteatamel までお気軽にお問い合わせください。


- デベロッパー アドボケイト Mete Atamel
投稿先