マイクロサービスにおけるリコンサイルの話
この記事はMerpay Advent Calendar 2021 の23日目の記事です。メルペイPayment Platformチームの @foghost がお送りします。
本記事は決済システム開発の観点でリコンサイルを通して防ぎたいリスクについて説明した後、メルペイでリコンサイルについての取り組みについてご紹介します。
リコンサイルとは
リコンサイルで検索するとwikipediaではリスク管理の手法の1つと書かれています。人間の様々の活動においてミスが必ず発生するので、リコンサイルはそれら活動の結果が期待通りになっているかどうかを再度確認することで、誤差を検知して様々なリスクを回避することができます。
定義だけ説明してもピンと来ない方が多いかと思いますが、実は普段の生活でも広い意味でのリコンサイルが必要となる場面が多いです。例えば以下の売店で買い物するときの流れを考えてみましょう。
- お客さまが1000円のコップ2個を選んで会計する
- 店員が金額を精算して、「2000円になります」と言ってお金を請求する
- お客さまは2000円を払う
- 店員はコップ2個とレシートを渡してくれる
こんな簡単のシーンでもリコンサイルが何度も発生することがあります。
- 店員はお客さまにお金を請求する際に、お客さまはその金額が自分が認識している金額に合っているかどうかのリコンサイルができる
- お客さまは2000円を払ったときも、店員はその金額が請求金額と一致しているかどうかのリコンサイルもできる
- お客さまは最後にコップが自分が選んだものと同じか、数が合っているかどうかのリコンサイルもできる
- 返品の可能性も考えると、お客さまはレシートに記載されている商品及び金額が一致しているかどうかのリコンサイルもできる
そして、もしこの場合リコンサイルを一切やらなかった場合どうなるかを想像してみましょう。
- 店員が金額の精算を間違って、違う金額をお客さまに請求するリスクがある
- お客さまが1000円しか払わなかったが、店員が2000円として間違った売上を計上してしまうリスクがある
- 店員が間違った商品をお客さまに渡してしまうリスクがある
- 店員がレシートの記載が間違って1000円となってしまったので、後日返品時にトラブル発生するリスクがある
このように店員にとってもお客さまにとっても金銭面の損や、トラブル発生のリスクがあります。なぜこういったことが起き得るのでしょうか。その理由は人間は必ず間違えるからです。様々な活動の中で人間のミスによるリスクを防ぐにはもちろん機械やシステムによる自動化も有効ですが、一方で機械やシステムも人間が作ったものなので間違うことがあります。そのため、人間のミスによるリスクを注意深く回避するには最終結果が期待通りになっているかどうか、リコンサイルを行うのはとても有効な手段です。
決済システムにおけるリコンサイル
決済システムが冒頭で紹介した例に比べると、ほとんどの処理がシステム化されているので、システムの品質さえ担保できれば、リコンサイルがほとんど不要なのではないかと思われるかもしれません。しかし実は逆で決済システムこそリスクに対して非常に敏感なので、リコンサイルはそれらのリスクを回避するための最後のガードとも言える重要な役割を果たしています。
決済システムにおけるリスク及び回避するためのリコンサイル
すべてを網羅しているわけではありませんが、決済システムにおいても以下のようなリスクがが考えられます。
- システムリスク
- 実装上の不備や予想せぬシステム障害の影響で不正確な処理結果が確定されてしまうリスク
- 信頼リスク
- お客さまや加盟店さまに不正確な精算を行ってしまうと会社の信頼性に大きな影響を受けるリスク
- 財務リスク
- 財務諸表上認識している結果がシステム側として確定した結果とズレが発生する場合、間違った財務判断にもつながりかねないので、会社の経営にも大きく影響するリスク
- 法的リスク
- 決済領域でもさまざまな業法によって定められた法的要件があるため、例えば資金移動業者としては、顧客から受け入れた資金(未達債務の管理)の100%以上の額を履行保証金として保全しなければない規定があるため、未達債務の管理に必要な帳簿がずれると法律違反するリスク
これらのリスクを防ぐため、メルペイではシステム開発の品質担保の面でもさまざまな工夫をしています。それだけではなくより厳密にリスクを管理するためにさまざまなところで最終結果のリコンサイル処理も行っています。
そして、決済システムにおけるリコンサイルは対象で大きく分けると主に2種類があるかと思います。
- 内部帳簿と実際の資金フローのリコンサイル
- システム内部で管理される残高・会計・法定帳簿上認識している金額が銀行など経由して行った実際の資金フローの変動と一致しているかの確認
- システム処理フローと内部帳簿のリコンサイル (外部処理を含め)
- システム側の一つ一つの決済処理が正しく行われたかまたはその処理結果が各種内部帳簿に正しく反映されたかのリコンサイル
内部帳簿と実際の資金フローのリコンサイルについては、実際の資金フローまで把握する必要があるので、コーポレート部門のファイナンス、リーガル、会計などのチームが各種内部帳簿データをリコンサイルした上で、実際の銀行側の残高変動とのリコンサイルのプロセスを構築して日々運用しています。
システム処理フローと内部帳簿のリコンサイルについては、システム開発側で担保する必要があるので、内部帳簿に関連するシステム処理については依存しているサービスすべてにリコンサイル処理を求めています。本記事は開発観点から主にこちらのリコンサイルについて後で紹介していきたいと思います。
またこれらのリコンサイルが適切に行われているかどうかを確認するために、内部統制報告制度の一環として、内部監査チームによるシステム処理のログ、帳簿データ、資金フローについての定期的確認もあって、システム処理及び各種リコンサイルが正しく機能しているかのモニタリングを行ってくれています。
システム処理フローと内部帳簿のリコンサイルのやり方
メルペイではマイクロサービスアーキテクチャを採用していて、システム側の一つ一つの決済処理を正確に各種帳簿に正しく記帳していることをリコンサイルさせるには主に2種類のリコンサイル処理を行っています。
- 1つの処理に参加するすべてのサービス間で、処理フローベースで結果のリコンサイルが求められている
- ダブルチェックの目的で、各種内部帳簿の間でも最後にリコンサイルしている。例えば、会計帳簿上認識している預り金と残高帳簿上の金額が一致しているかどうかの確認
そして、処理フローベースのリコンサイルでは
- 各マイクロサービスが自分の依存先及び期待する結果(金額、処理ステータス)を把握しているため、リコンサイルも各マイクロサービスでバッチもしくは結果の通知イベントベースで1件1件の決済処理についてローカルの結果と依存先の結果の最終確認をしている
- 依存先が外部サービスの場合は、最終的に確定された売上精算の明細データを入手できることが多いので、明細データと内部の処理データとのリコンサイルで最終確認するのもできる
リコンサイルで誤差が検知された場合の対応方法
リコンサイルなので、万が一誤差が検知された場合の対応方法についても考えないといけません。依存先サービスとのリコンサイルの誤差の発生はまとめると大体3つのパターンとして整理できます。
- ローカルに成功した処理データがあるが、依存先に処理データがない
- バグによるものであれば、ローカルの処理の取消、もしくは依存先への処理を再実行することでリカバリができる
- また、バグによるものではないが、日跨ぎの場合処理時刻の境でずれることもあり得る。この問題を解消するには、依存先の処理時刻に合わせてリコンサイルするか、ローカルの時刻で翌日再リコンサイルする方法がある
- ローカルに成功した処理データがないが、依存先に成功した処理データがある
- タイムアウトなどのネットワークの異常ケースの考慮が漏れて、依存先の処理の取消を忘れたときに発生する、この場合は依存先の処理を取消すれば対応できる
- また、こちらも同じく日跨ぎ問題によってずれることがありえる
- ローカルにも、依存先にも成功した処理データがあるが、金額などの内容が一致しない
- この場合はケース・バイ・ケースの対応になるのが多いが、お客さまとしてローカルの結果を正にすべきであれば、依存先への処理を取り消しして再度正しい処理内容で依頼することでリカバリができる
マイクロサービスにおけるリコンサイルの課題及び改善
最後に、今までマイクロサービスでリコンサイルを取り組んできて感じた課題及び改善に向けての取り組みを少し紹介します。
決済処理の全体の整合性の証明問題
モノリシックなアーキテクチャを採用しているシステムなら、処理フロー及び帳簿データは同じサービス中で一貫した管理ができるので外部システムとのリコンサイルができれば処理の全体の整合性が証明できます。しかし、マイクロサービスアーキテクチャを採用したシステムだと1つの処理に参加するマイクロサービスが複数でありデータの管理も分散されている状態になるので、各自リコンサイルに取り組んでくれても全体の整合性を証明するには以下の課題を抱えています。
- 強制的な統一されたリコンサイルの仕組みがないので、各サービスが自分の都合でリコンサイル処理をやっていると、上記のようにどれかのリコンサイルが漏れると1つの処理を全体でみたときに、決済処理の全体整合性の観点では確認が不完全になってしまう。
- 仮にすべてのサービスがリコンサイルしたとしても、1つの決済処理の結果が複数のマイクロサービスを跨いでも正しく処理されていることを証明できる「Single Trustable Source」が存在しないため、全体の処理結果の整合性を確認するためには、各マイクロサービスのAPIやDBをすべて参照して突合する必要がある。
改善方法
マイクロサービスが持つ独立性を維持しながら、上記の課題を解決できないかと色々考えた結果、Payment Platformでは以下のような新しい仕組みを検討しています。詳細についてはまた今後別記事で詳しく書きますが、本記事でまず軽くコンセプトについて紹介したいと思います。
分散リクエストトレーシングをイメージしてもらえるとわかりやすいですが、1つのリクエストが複数のマイクロサービスを跨いで処理されるときに、各サービスのビジネスロジックがわからなくても、必要なトレーシング情報を報告して貰えば、リクエストの全体処理像を描けるようになります。
似たような考え方で、複数のマイクロサービスを跨ぐ処理の全体をProcessingとして抽象化して、各サービスからリコンサイルの結果(リコンサイルのロジックはドメイン知識が必要なので、既存と同じく各サービス側に任せる)をトレーシング情報のように報告してもらえば、上記の図のように、1つの決済処理にどのサービスが参加していて、全体の整合性の確認はどうなったのかを証明できるデータソース「True of Processing」が作れます。
コンセプト
- Processing
- 複数のマイクロサービスを跨ぐ整合性確認が必要となる処理をProcessingと呼ぶ
- 1つのProcessingを特定するにはProcessingIDが必要
- Participant Service
- 1つのProcessingに参加されるサービスのことです。参加していると報告されたら、整合性確認の結果の報告が必須になるので、Participant Serviceとして記録される
- Consistency Report
- 1つのProcessingに参加されるサービスから報告してもらう整合性確認の結果レポート
簡単な処理フロー
- 決済処理の起点となる上位サービスがProcessingIDを発行して、結果のリコンサイルが必要となる依存先のサービスにも処理時に伝播していく。
- ProcessingIDの発行機能も作れるが、可用性及び性能観点で依存コンポネントを減らしたい場合、起点サービスでのローカル採番も可能
- 起点サービスがProcessingID、ServiceIDを含めたProcessing情報を報告する
- この時点ではParticipant Serviceは起点サービスのみになる
- Processing情報が登録された後、非同期で一定の期間で整合性確認が済んでいない処理を検知するコマンドを動かして、そのProcessingに登録済みのParticipant Service向けに非同期の通知イベントを送信する
- Participant Serviceとなるサービスはイベント通知を受け取るワーカーを起動して、イベントをハンドリングする。イベントで送信されるProcessingIDからローカルで紐付いて処理レコードを見つけて、その処理依存しているサービス側の処理結果とリコンサイルを行う。リコンサイルが終わった後にリコンサイルの結果及び依存先を含めてConsistency Reportを作って報告する
- 新たに報告された依存先をParticipant Serviceとして登録しておく
- 3と4を繰り返して、すべてのParticipant Serviceから報告完了したら、Processing全体の整合性が確認できる。もし未報告のサービス見つけたら、アラートとして検知して復旧対応が進められるので、上記の課題の中のリコンサイル漏れが防げる
まとめ
以上でメルペイで実践してきたリコンサイルの話でした。決済領域じゃなくて似たような悩みを持たれている方も多いかと思いますが、少しでもご参考になれば幸いです。
最後まで読んでいただきありがとうございました。
メルペイのPayment Platform Teamは今回ご紹介した仕組み以外、Chaos Engineering, 分散トランザクション管理SDK、マイクロサービスのリアーキテクチャなどの新しい取り組みもチャレンジしています。ご興味がある方はこちらのTech Talkの記事もご覧ください。
明日の Merpay Advent Calendar 2021 は、QAチームのmiisan さんが担当です。引き続きお楽しみください。