リファレンスアーキテクチャ
参考例高性能トランザクションプラットフォーム
Java、Spring Boot、Kafka、Redis、リレーショナル台帳の上に構築する、金融トランザクション向けのマイクロサービスプラットフォームです。正確性を第一に、スループットを第二に置いて設計します。
課題
Web、モバイル、パートナーの各チャネルから支払いや送金の指図を受け付け、その一つひとつを正確に一度だけ記録しなければならないプラットフォームを想定します。取引量は一定ではなく、給与支給日、プロモーション、決済の締め時刻の前後に鋭いピークが生じます。下流のネットワークやパートナーシステムは、このプラットフォームに期待される水準よりも遅く、信頼性も劣ります。
難しいのは純粋な速度ではありません。クライアントがリトライし、メッセージが二重に届き、ノードがトランザクションの途中で障害を起こし、外部の相手先が処理したかどうかを告げないままタイムアウトする状況で、正確性を保つことです。負荷試験では良好な性能を示しても、通貨1単位の欠落を説明できない設計は、目的に適いません。
設計が満たすべき要件
- クライアントや内部コンポーネントがリトライした場合でも、トランザクションは顧客から見て正確に一度だけ適用されなければなりません。
- 台帳は常に貸借が一致し、すべての資金移動が不変の複式簿記の仕訳として記録されなければなりません。
- プラットフォームは、再設計を伴わず、残高の整合性を損なうこともなく、水平スケールによってピーク負荷を吸収しなければなりません。
- 単一のサービス、ノード、アベイラビリティゾーンのいずれに障害が発生しても、受け付け済みのトランザクションを失ってはなりません。
- すべての状態変化が監査可能であり、内部の記録は外部の決済ファイルと照合されなければなりません。
- 運用担当者は、レイテンシ、エラー率、キューの深さ、業務上の異常をほぼリアルタイムで把握できなければなりません。
設計
コンポーネント
チャネル
- Web
- モバイル
- パートナーAPI
- バッチファイル
APIゲートウェイ
- 認証
- レート制限
- 冪等性キー
- リクエスト署名
トランザクションサービス
JavaとSpring Boot、ステートレス、水平スケール
- 支払い開始
- オーソリゼーション
- 台帳への記帳
- 限度額とリスク
Kafkaイベント基盤
- トランザクションイベント
- アウトボックスリレー
- パーティション分割トピック
- デッドレタートピック
台帳DB・Redis
正となるのは台帳であり、キャッシュではない
- 複式簿記台帳
- リードレプリカ
- 残高キャッシュ
- 分散ロック
照合(リコンサイル)・監査
- 決済照合
- 不一致の検出
- 不変の監査ログ
- レポート出力
モニタリング
- レイテンシ・エラーのSLO
- コンシューマーラグ
- トレーシング
- ビジネスアラート
APIゲートウェイ
ゲートウェイはTLSを終端し、呼び出し元を認証し、クライアント単位でレート制限を適用し、パートナーからのトラフィックについてはリクエスト署名を検証します。状態を変更するすべてのリクエストに冪等性キーを要求し、それを下流へ引き渡します。ゲートウェイの背後にあるものは、パブリックネットワークからは一切到達できません。
トランザクションサービス
ステートレスなSpring Bootサービスが、開始、オーソリゼーション、限度額、記帳をそれぞれ固有の境界づけられたコンテキストの中で処理します。トランザクションは明示的なステートマシンに沿って進むため、そのステータスは常に、少数の既知の値のいずれかになります。サービスはゲートウェイの背後で水平にスケールし、再起動で失われるような状態を保持しません。
台帳データベース
リレーショナルデータベースが、複式簿記の台帳を追記専用の仕訳として保持し、残高はそこから導出されます。ACIDトランザクションと制約によって、コミット時に借方と貸方が一致することを強制します。明細とレポーティングはリードレプリカが処理するため、分析系のクエリが書き込み経路と競合することはありません。
Kafkaイベント基盤
各サービスは、状態変化とアウトボックスレコードを同一のデータベーストランザクションで書き込み、リレーがアウトボックスの内容をKafkaに発行します。トピックは口座キーまたはトランザクションキーでパーティション分割され、必要な箇所で順序が保たれます。コンシューマーは冪等であり、繰り返し失敗するイベントは調査のためにデッドレタートピックへ送られます。
Redisによるキャッシュと協調制御
Redisは、冪等性レコード、レート制限カウンター、および限度額や参照データといった短命のキャッシュ済み読み取りデータを保持します。あくまで高速化のための手段として扱い、記録システム(システム・オブ・レコード)としては決して扱いません。Redisが利用できなくなるとプラットフォームは遅くなりますが、残高は台帳から読み取るため、正確なまま保たれます。
照合(リコンサイル)と監査
照合サービスは、内部の仕訳を決済ファイルおよびパートナーファイルと突き合わせ、一致する相手がないものや内容が食い違うものを不一致(ブレイク)として起票します。不一致は、完全な履歴を伴う運用キューを通じて処理されます。不変の監査ログが、各トランザクションを誰が、何が、いつ変更したのかを記録します。
クラウドインフラ
サービスは、複数のアベイラビリティゾーンにまたがるKubernetes上でコンテナとして稼働し、データベースはプライマリと同期スタンバイの構成でデプロイします。インフラストラクチャはTerraformで定義し、デプロイは段階的に行うため、不具合のあるリリースはまずトラフィックのごく一部にしか届きません。バックアップは定期的にリストアし、復旧が機能することを実証します。
モニタリングとアラート
メトリクス、ログ、分散トレースは、ゲートウェイから台帳まで、トランザクション識別子によって相互に関連づけられます。サービスレベル目標(SLO)はレイテンシとエラー率を対象とし、Kafkaのコンシューマーラグは問題の早期の兆候として監視します。異常な拒否率や照合バックログの増加といったビジネスアラートも、技術的なアラートと並べて扱います。
トレードオフ
設計判断
| 判断 | 理由 |
|---|---|
| 正本となる台帳にリレーショナルデータベースを採用 | この用途では、結果整合性のストアが持つ書き込みスケーラビリティよりも、強い整合性とトランザクション制約のほうが価値があります。その代償として台帳がスケーリング上の制約になりますが、トランザクションを短く保ち、必要に応じて口座範囲でパーティション分割し、読み取りをレプリカへ移すことで対処します。 |
| 二重書き込みではなくトランザクショナルアウトボックス | データベースとKafkaへ別々に書き込むと、一方が成功してもう一方が失敗する時間的な隙間が残ります。アウトボックスではリレーとわずかな発行遅延が加わりますが、その見返りとして、コミットされた変更は必ずいずれ発行され、コミットされていないものは決して発行されないことが保証されます。 |
| 冪等性はゲートウェイで強制し、台帳でも再度強制 | クライアントもネットワークもリトライし、at-least-onceのメッセージングは重複を配信します。エッジで冪等性キーを確認すれば、よくあるケースを低コストで処理できます。一方、台帳の一意制約は、キャッシュが失われた場合でも有効な最後の防御線です。 |
| オーソリゼーションは同期、それ以外はすべて非同期 | 顧客は、トランザクションが受け付けられたかどうかについて、即座に明確な回答を必要とします。そのため、この経路は同期とし、短く保ちます。通知、レポーティング、リスクスコアの更新、決済はイベントを起点に実行するため、レイテンシバジェットから外れ、それぞれ独立して失敗し、復旧できます。 |
| 技術レイヤーではなく、ビジネス機能ごとにサービスを分割 | マイクロサービスには運用コストが伴います。デプロイが増え、ネットワーク呼び出しが増え、デバッグが難しくなります。分割が正当化されるのは、機能ごとにスケールや変更の速度が異なる場合に限られます。そのため、設計は少数のサービスから始め、具体的な理由がない限りそれ以上の分割は行いません。 |
意図する成果
- 構造上必ず貸借が一致し、すべての資金移動を説明できる台帳
- リトライや重複メッセージが重複トランザクションを生まない仕組み
- 再設計ではなく、インスタンスとパーティションの追加によって拡張できるキャパシティ
- 外部の相手先との差異を、顧客ではなく照合によって検出できる状態
この設計が達成を目指すものです。参考例であり、クライアント案件の実測結果ではありません。
代表的な技術
- Java
- Spring Boot
- Kafka
- Redis
- PostgreSQL
- Kubernetes
- Docker
- Terraform
- AWS
- OpenTelemetry
- Prometheus
- Grafana
