マイクロサービスのトランザクション管理をデザインする（事前知識編）

## はじめに

今回は、2年程システムのマイクロサービス化を進めてきて順調にサービスの数も増えて複数マイクロサービス間のトランザクションに対して本腰を入れて考えなければいけなくなってきたので複数回にわたって投稿していきたいと思い投稿した初回になります。

構想としては以下のように全3部以上の構成で考えています

1. 事前知識編
2. 実践編（要件・基本設計）
3. 実践編（実装・運用経過）
4. 実践編（3で実装したこと以外のパターンを採用した場合は都度）

## トランザクションの種類
トランザクションの種類としては以下の2つがあります。

- ローカルトランザクション
単一トランザクションで単一リソースに対しての処理のみを行う

- グローバルトランザクション
単一トランザクションで複数リソースに対して処理を行う
  - トランザクションをネストさせる → DBがクラッシュしたらデータの更新状態は不明となる
  - [XAトランザクション](https://www.ibm.com/docs/ja/iis/9.1?topic=overview-distributed-transaction-processing-model) → Prepare状態ならDBがクラッシュしても更新状態は残っている。
  ※ XAトランザクションのデメリットとしては2フェーズコミットによるロック時間・トランザクションマネージャーが少ない（goとNode.jsではいまのところ出会っていない）

## マイクロサービスでは
ローカルトランザクションをすすめています。

グローバルトランザクションは以下のデメリットがあると言われています

- ブロッキングプロセスであるための関連DBが増えた際の高遅延と低スループット
- トランザクション間のロックの管理の複雑さ
- トランザクション管理の複雑さ（複数Nodeの管理）

そこで出てくる考え方が**BASE**です。

### BASE

- Basically Available（可用性）
- Soft-State（一時的な不整合は許容する）
- Eventual Consistency（結果整合性）

また複数サービスにまたがるという事はネットワークを経由すると言うことなのでCAP定理もふまえて設計をしないといけません。

### CAP定理

分散システムはこの以下3つのうち、同時に2を満たすことはできるが、同時に全てを満たすことはできない.

- Consistency (一貫性)
- Availability (可用性）
- Tolerance to network Partitions (ネットワーク分断への耐性)

## どのように整合性を保つ為の複雑さに立ち向かうのか？

マイクロサービスのトランザクションでは**結果整合性**がポイントになります。
いままで、なにかしらの登録/更新/削除処理をリクエストし、その結果が返ってきたら「データベースへの処理は全て完了している」という思考(ACID)からの脱却が必要になってきます。

それでは結果整合性を達成する為の方法としては何があるでしょうか。

#### TCC(Try-Confirm/Cancel)パターン

2phase commitに類似したパターンです。
Try/Confirm/Cancelの3つのAPIを用意して、登録状態を変更していく方式です。

##### メリット
- 初めの状態は本登録ではなく仮登録なので後述で説明する**補償トランザクション**でのロールバックでは都合が悪い場合に有効

##### デメリット
- 全ての状態において*tryとconform/cancel*のステップをおこなわなければならず処理時間がSagaと比較すると2倍。
- 全ての参加Nodeの状態を確認してから*conform/cancel*のステップに進むので、Node数が多くなるとSagaと比べると処理時間が比例して増加する。

![qiita-20220306-tcc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96623/e873d395-b8c5-2b7d-065c-051ae173e9fb.png)


#### Sagaパターン

イベント駆動式のパターンです。
TCCパターン違い、Tryという仮登録状態を持ちません。
全てを正式登録していき、ロールバックが必要な事態になった場合は**補償トランザクション**という処理の流れを逆向きの操作し、取り消し操作を行っていきます。
取り消し操作はレコードの論理削除かもしれませんし、物理削除かもしれません。ここは状況によりきりなのでProject毎に違うと思います。

##### メリット
- 補償トランザクションにrollbackが発生しないかぎりは1ステップで各Nodeの処理が完結するので処理時間が短くなる
- 登録状態管理がシンプルになる。

##### デメリット
- 補償トランザクション管理の複雑さ。
- 取り消しがきかない処理の場合は不向き。

![qiita-20220306-saga-saga.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96623/4484a263-5a7d-92a7-e472-9ad6e787f932.png)


しかし、上記２つのパターンは結果整合性の手段でありネットワーク分断の可能性とシステム不具合を解決できるわけではありません。
そこで、MQを使ったリトライ処理やバッチや手動による補填処理などがプラスで必要になります。
※ リトライ処理には[冪等性](https://ja.wikipedia.org/wiki/%E5%86%AA%E7%AD%89#:~:text=%E6%95%B0%E5%AD%A6%E3%81%AB%E3%81%8A%E3%81%84%E3%81%A6%E3%80%81%E5%86%AA%E7%AD%89%E6%80%A7,%E5%86%AA%EF%BC%88%E3%81%A8%E3%81%86%E3%81%B9%E3%81%8D%EF%BC%89%E3%81%A8%E3%82%82%E3%80%82)を考慮した仕組み作りも必要です。
こちらの[メルペイでの事例では](https://engineering.mercari.com/blog/entry/2019-06-07-155849/#PaymentService%E3%81%A7%E5%AE%9F%E8%B7%B5%E3%81%97%E3%81%A6%E3%81%8D%E3%81%9F%E6%89%8B%E6%B3%95)、リコンサル処理でデータの整合性信頼度を上げていたりもします。

結果整合性といっても、整合性を担保する事には違いないので２重３重の仕組みが必要ということが伝わってきます。

## 今回のまとめ

今までモノリスなシステムの設計になれてしまった私たちには、マイクロサービスは巨大な壁です。
今マイクロサービスという巨大な壁に立ち向かっている人には「いままでのトランザクション管理でも頭を悩めていたのに、それからさらに複雑なことをしないといけないのか...」と思う人も多いと思います。
おそらく小規模でモノリスなシステムで問題ない要件なら、無理にマイクロサービスに取り組むことはしない方が良いでしょう。
複雑さに立ち向かうほどに得られるメリットがないなら、立ち向かうにはあまりにもコストが大きいことは確かです。

しかし、DX化が求められる昨今の流れを考えると社内システムなら外部Saasとの連携。決済を必要とするサービスなら[stripe](https://stripe.com/en-jp)などのSaasとの連携は必要となるはずです。
自社のシステムをマイクロサービスにしなくても、自然とシステムは分散されているかもしれません。

次回は、現在開発中のトランザクション処理がリリースされた段階でな「ぜその設計にしたか?」の部分を投稿できればと思います。