Edited at

TCCパターンとSagaパターンでマイクロサービスのトランザクションをまとめてみた


はじめに

UL Systems Advent Calendar 2018の10日目です。

マイクロサービスアーキテクチャでシステムを構築した際、更新対象が複数のサービスをまたがる場合は、トランザクションの扱いが途端に難しくなります。なかでも、障害発生時に各サービス間の処理をロールバックするためには補償(補正)トランザクションが必要になり、複雑なトランザクション制御が求められます。補償トランザクションとは、処理の途中で失敗した場合に、それを取り消すことで実行結果を打ち消す処理のことです。補償トランザクションの実装は、打ち消す処理を提供するサービスと、それを呼び出すサービスの双方に負担があり、設計や実装が複雑になりがちです。

トランザクションには、1つのトランザクション内で1つのリソース(DBなど)処理のみ行うローカルトランザクションと、1つのトランザクション内で複数のリソース処理を行うグローバルトランザクションがあります。グローバルトランザクションは分散トランザクションとも呼ばれ、その実装としてはX/Open XAやWS-Transactionなどが知られています。

マイクロサービスアーキテクチャは、ローカルトランザクションを勧める一方で、グローバルトランザクション(分散トランザクション)は推奨しません。分散トランザクションは、複雑で不具合の原因になる可能性が高いためです。マイクロサービスアーキテクチャで推奨しているのは、分散トランザクションではなく、複数リソースの結果整合性を利用することです。複数リソースの結果整合性を利用する方法として、TCC(Try-Confirm/Cancel)パターンSagaパターンと呼ばれるアーキテクチャがあります。

今回は、TCCパターンとSagaパターンが、マイクロサービスのトランザクション特性に合わせてどう使われるかをまとめてみたいと思います。


マイクロサービスをまたがるトランザクションのタイプ

マイクロサービスをまたがるトランザクションは、サービス連携方法とサービス間の関連付けで分類できます。サービス連携方法には、リクエスト・リプライ方式イベントドリブン方式があります。サービス間の関連付けには、オーケストレーション(中央集権型)コレオグラフィ(分散型)があります。それぞれどのような特徴があるのでしょうか。


サービス連携方法


リクエスト・リプライ方式


  • REST通信による連携方式です。

  • 自身の処理の一部を、他のサービスに依頼します。


イベントドリブン方式


  • イベントの発生によって特定のサービス処理が駆動される連携方式です。

  • 既に発生した事柄を他のサービスに通知することで、発生した事象に応じた処理を実行させます。


サービス間の関連付け


オーケストレーション(中央集権型)


  • 中央の指揮者がサービスの呼び出しをコントロールします。

  • 各サービスは中央からのリクエストによってロジックを実行し、実行結果をレスポンスとして中央に返す、リクエストリプライ方式になります。

  • 同期通信に適しています。


コレオグラフィ(分散型)


  • 個々のサービスが自律的に動き、あらかじめ定義された他のサービスに働きかけます。

  • 個々のサービスは、定められた条件に従って自律して稼動し、条件に合致した他のサービスにデータを送ります。

  • 非同期通信に適しています。

サービス連携方法を縦軸、サービス間の関連付けを横軸としてトランザクションのタイプを分類し、TCCパターンとSagaパターンを当てはめると下図ようになります。

4types_and_pattern.png

それでは、TCCパターンとSagaパターンとはどのようなものでしょうか。


TCCパターン

TCCパターンはTry operations, Confirmation, and Cancellationの略で、コーディネーターとサービス群で構成するアーキテクチャです。TCCパターンは、アメリカで発表された論文をもとにしたアーキテクチャで、一部のECサイトやクラウドベンダでは実装されているようです。論文「Life beyond Distributed Transactions」では、Tentative(暫定)-Confirm/Cancelとなっていますが、今ではTentativeはTryと表現することが多いようです。


TCCパターンによるオーケストレーション

コーディネーターに該当するサービスは、サービス群を呼び出しながら一連の処理を行います。サブルーチンを呼び出しながら処理するプログラムのイメージに似ています。

tcc_overview.png

TCCパターンは、TryフェーズConfirm/Cancelフェーズの2フェーズによってトランザクションを制御します。


  1. Tryフェーズ


    • コーディネータが各サービスに要求し、仮の状態を登録させます。



  2. Confirm/Cancelフェーズ


    • サービスの仮登録が全て完了した場合、サービスに正式登録を要求し、一連の処理が成功した旨を呼び出し元に返します。

    • Tryフェーズでいずれかのサービスがタイムアウトしたり登録できなかった場合、コーディネーターはキャンセル要求を送信します。

    • いずれかのサービスがタイムアウトしたり確定できなかった場合、コーディネーターは、成功するまで確認を再試行するか、一定回数再試行した後でバッチ処理、または、手動により修正します。



tcc_sequence.png


TCCパターンによるコレオグラフィ

TCCパターンによるコレオグラフィは、コーディネータから呼び出されたサービスが、さらに別のサービスを呼び出す(ネストしたサービスの呼び出し)ケースが該当します。ネストして呼び出されたサービスのトランザクション制御は、オーケストレーションと同様です。

tcc_choreography.png


TCCパターンを実装する際の留意点

TCCパターンでは、一連の処理を制御するコーディネーター(オーケストレーター)を用意し、複数リソース(サービス)の呼び出し制御を実装します。呼び出されるサービスは、Try/Confirm/Cancelの3つのAPIを実装する必要があります。

TCCパターンは、処理完了に2倍かかる場合があるので注意が必要です。これは、TCCパターンでは各サービス間で2回通信し、すべてのサービスからの試行要求の応答を受信したときにのみ確認フェーズを開始するためです。

TCCパターンは、サービスを最終状態ではなく仮(保留)状態を経由するという特徴があり、キャンセル処理が簡単です。例えば、電子メールサービスの送信要求では、電子メールを送信準備としてマークし、確認要求で電子メールが送信されます。それに対応するキャンセル要求はマークするだけになります。一方、後述するSagaパターンは、トランザクションによって電子メールが送信され、対応する補償トランザクションで取消を説明する別の電子メールが送信されることになります。


Sagaパターン

Sagaパターンはイベント駆動のアーキテクチャです。Sagaパターンはマイクロサービス特有のものではなく、SOAでも採用されていたアーキテクチャです。また、Sagaパターンの元になる考え方は古く、1987年の論文が元になっています。なお、「Saga」が何かの略なのかは確認できていません。

Sagaパターンは、ロールバックを行うことができない代わりに、補償という考え方を提供しています。ロールバックを行うことができないので、代わりにインタラクションの操作を逆向きにした取り消し操作により、擬似的なロールバックを行います。

Sagaパターンは以下の流れで処理します。


  • サービスは、処理を行い、次のサービスに新しいイベントを送信します(下図の1,2)。

  • イベントを受信したサービスは、処理を行い、次のサービスに新しいイベントを送信します(下図の3,4)。

  • 分散トランザクションのロールバックは自動ではありません。別の操作/イベントを実装する必要があります(下図のa,b,c,d)。

saga_overview.png

【出典】https://thinkit.co.jp/article/14639?page=0%2C1(2018-11-13)


Sagaパターンによるオーケストレーション

オーケストレーションのアプローチでは、コーディネーターサービスが、Sagaの意思決定とビジネスロジックの順序付けを集中化します。各サービスに何をすべきか、いつ行うのかをコントロールするコーディネーター(サガオーケストレーター)を定義します。サガオーケストレーターは、どのサービスを実行すべきかをMQのイベント/戻りイベントで各サービスと通信します。

コーディネーター(サガオーケストレーター)の各サービスへの要求にはRESTの場合もあります。処理結果は、MQの戻りイベントで待ち受けます。

saga_orchestration.png

ユーザの注文を受注する簡単なシーケンスを以下に示します。


  1. 受注サービスの注文リクエストは、仮受注を保存し、オーダーサガオーケストレーター(OSO)にオーダー作成トランザクションを開始するように依頼します。

  2. OSOは在庫サービスに引当要求イベントを送信し、在庫サービスは引当済みメッセージで応答します。

  3. OSOは顧客サービスに支払い実行要求イベントを送信し、顧客サービスは決済実行メッセージで応答します。

saga_orchestration_seq.png


Sagaパターンによるコレオグラフィ

コレオグラフィでは、各サービスは他のサービスのイベントを生成したり待ち受けたりして、処理するべきかどうかを決定します。コレオグラフィは、各サービスが単一のローカルトランザクションになります。 最初のトランザクションは、システム操作に対応する外部要求によって開始され、その後の各ステップは、前のトランザクションの完了によってトリガーされます。

saga_choreography.png

ユーザの注文を受注する簡単なシーケンスを以下に示します。


  1. 受注サービスは新しいオーダーを保存し、状態を保留として設定し、ORDER_CREATED_EVENTというイベントを発行します。

  2. 在庫サービスはORDER_CREATED_EVENTを待ち受け、在庫を更新し、注文で購入した商品を準備し、RESERVED_EVENTを発行します。

  3. 顧客サービスは、RESERVED_EVENTを待ち受け、クライアントに請求し、イベントBILLED_ORDER_EVENTを公開します。

  4. 最後に、オーダー・サービスはBILLED_ORDER_EVENTを待ち受け、オーダーの状態を終了として設定します。

なお、注文の状態を追跡する必要がある場合、受注サービスはすべてのイベントを監視してその状態を更新する場合があります。

saga_choreography_seq.png


トランザクションのロールバック

例えば、取引中に決済が失敗した場合は、処理を取り消すためのイベントを発行してロールバックを図ります。


  1. 顧客サービスは AUTHORITY_ERROR_EVENTを生成します。

  2. 注文サービスと在庫サービスの両方がメッセージを待ち受けします


    • 在庫サービスは引当済み在庫を取り消します。

    • 受注サービスは、受注状態を失敗として設定します。



すべてのCunsumerが参照するトランザクションをすぐに知ることができるように、投げるイベントに各トランザクション共通の共有IDを定義することが重要です。

saga_rollback.png


Sagaパターンを実装する際の留意点

Sagaパターンによる処理は、イベントをConsumeすることが起点となり、次のサービスへの伝搬は、別のイベントをPublishすることで行われます。一連の処理はこれをチェーンし、最後の処理が一番最初の処理に完了イベントをPublishして終了します。Publishするイベントは、リトライを考慮し、各サービスが管理するデータベースにも保持します。

Richardson-microservices-part5-local-transaction-e1449727484579.png

【出典】Event-Driven Data Management for Microservices

Sagaパターンでは、サービス間は疎結合で柔軟であり、ビジネスロジックへの影響が局所化される点では、TCCパターンよりも優れていると言えます。一方で、補償トランザクションの実現可能性を考慮する必要があり、チェーンが長くなり過ぎないよう注意が必要です。


まとめ

今回は、TCCパターンとSagaパターンという、なかなか聞き慣れないアーキテクチャに触れてみました。いかがだったでしょうか?これらパターンの情報は少なく、まだ普及していないのかもしれません。

昨今はKubernetesによるコンテナオーケストレーションやistioなどによるサービスメッシュが普及してきており、マイクロサービスアーキテクチャの導入が比較的容易になりつつありますが、それら技術はトランザクション制御を容易にするものではありません。マイクロサービスアーキテクチャの実現に向けては、引き続きトランザクションには十分な考慮が必要だと言えます。特に基幹システムのマイクロサービス化に向けては、トランザクション制御が極めて重要になるでしょう。

本記事が、マイクロサービス導入に向けて少しでもお役に立てれば幸いです。