イベント間の整合性をどう担保するか
話題は、2つ目の「イベント間の整合性を保つ」へと移った。初手のモデリング設計でイベントを正しく保存した上で、次に問われるのは「それらのイベントがどう連携し、破綻なく流れるか」というわけだ。
そもそも非同期メッセージングの主な目的は、パフォーマンスの向上と可用性の確保である。とくに可用性の確保については、「非同期処理を採用することで、障害発生時にもリクエストを受け付けられるようになる」とその利点を説明する。
そのためにスマートバンクでは、ユーザーのリクエストを受け付けるサーバーと、カード管理システムを分離した構成を採用している。同期通信であれば、すべての処理が完了するまでユーザーは待たされるうえ、途中のシステムで障害が起これば全体が停止するリスクがある。非同期通信なら、リクエストを保存したうえで即時に応答を返し、バックグラウンドで処理を進めることが可能だ。
ただし、非同期処理には「整合性をいかに担保するか」という根本的な課題がある。その解決の鍵を握るのが、データベースのトランザクションだ。木田氏は、トランザクションのACID特性を次のように整理する。
原子性(Atomicity)
トランザクションはすべて成功するか、すべて失敗するかのどちらかであり、中途半端な状態は残らない
一貫性(Consistency)
外部キー制約やユニークキー制約、NOT NULL制約などのルールが常に守られる
独立性(Isolation)
各トランザクションは他の影響を受けず、直列に実行した場合と同じ結果になる
耐久性(Durability)
一度コミットされたデータは、バックアップやログによって永続的に保証される
こうしたトランザクションの限界を踏まえ、木田氏は非同期処理における設計アプローチとして、2つの制御パターンを紹介した。
コレオグラフィ(Choreography)
複数のサービスがメッセージキューを介して処理を連携していくスタイルで、各サービスが「自律的に踊る」ように動作する。次に何が起こるかをサービス同士が知らずとも成り立つため、単純なフローに向いている。一方、処理が連鎖すると把握が難しくなり、トラブルシューティングも煩雑になりがちだ。
オーケストレーション(Orchestration)
中央のオーケストレーターが全体の処理順を制御する方式で、複雑な業務フローの管理に適している。一方で、ロジックが中央に集中するリスクや、実装コストの高さが懸念される。ただし、最近ではAWS Step FunctionsやGCP Workflows、Azure Logic Appsといったマネージドなワークフローサービスにより、実装の負担は大きく下がってきている。Rubyであれば、gushというGemを使って、RedisやActive Jobと連携したワークフローの構築も可能だ。
たとえばAWS Step Functionsを用いてカード発行フローを実装すると、ステップごとに処理を視覚的に管理できるようになる。各ステップの状態はコンソール上で色分け表示され、リトライやエラーハンドリングも柔軟に設定可能だ。
「非同期処理の恩恵を最大化するには、その裏側にある整合性の維持設計をいかに丁寧に行うかがカギ」と木田氏は語る。高可用性と柔軟な拡張性を両立させるには、こうしたアーキテクチャ的思考が不可欠なのだ。
