こんにちは!サーバーサイドエンジニアのkurisuです。
2024年06月に、B/43あとばらいチャージの裏側でサービス提供していただいている事業者の移行を行いました。その際、返済データのリコンサイルバッチの設計と実装を担当しました。
外部サービスとのデータ連携における不整合は、多くのシステムで避けられない問題です。 本記事では、B/43のあとばらいチャージ機能での事例をもとに、リコンサイルの重要性と実装時の検討ポイントをご紹介します。
リコンサイルとは
Wikipediaでは以下のように記載されています。
お互いが持っている取引データを突合(とつごう)し、誤差等の原因究明を行う作業
システムにおけるリコンサイルは、自社システムと外部サービスのデータを比較し、不整合を検出・修正するプロセスを指します。
特に金融や決済システムにおいて、データの正確性はビジネスの信頼性に直結するため、リコンサイルは不可欠な機能です。
外部サービス連携における課題と不整合の要因
外部サービスとの連携では、さまざまな要因でデータの不整合が発生します。主な原因として「1. 自社から外部サービスへの通信エラー」と「2. Webhook通知の遅延と不達」が挙げられます。
1. 自社から外部サービスへの通信エラー
外部サービスへの通信では、ネットワークエラーやサービスの過負荷などにより不整合が生じることがあります。
- 成功パターン
sequenceDiagram participant 自社システム participant 自社DB as 自社DB participant 外部サービス participant 外部DB as 外部DB 自社システム->>自社DB: データ取得 自社システム->>外部サービス: データ送信リクエスト 外部サービス->>外部DB: データ更新 外部サービス-->>自社システム: リクエスト成功のレスポンス 自社システム->>自社DB: 処理結果保存 note over 自社システム,外部サービス: データが正常に同期される
- ネットワーク障害による通信エラー
- 発生状況
- ネットワーク障害により、最初のリクエストが外部サービスからレスポンスがない
- 不整合の原因
- 自社システムはタイムアウトを検知し、リトライを行う。
- 最初のリクエストが外部サービスに届いていた場合、再送リクエストと合わせて二重に処理される。
- その結果、外部サービスのデータベースに重複データが登録され、自社システムとのデータ不整合が生じる。
- 発生状況
sequenceDiagram participant 自社システム participant 自社DB as 自社DB participant 外部サービス participant 外部DB as 外部DB 自社システム->>自社DB: データ取得 自社システム->>外部サービス: データ送信リクエスト note over 自社システム,外部サービス: ネットワーク障害でリクエスト未達 自社システム-->>自社システム: タイムアウト発生 自社システム->>自社システム: リトライ処理 自社システム->>外部サービス: 再リクエスト送信 外部サービス->>外部DB: データ更新 外部サービス-->>自社システム: リクエスト成功のレスポンス note over 外部サービス,外部DB: データが二重に登録される可能性 note over 自社システム,自社DB: 重複データによる不整合が発生
- サービス過負荷による応答遅延
- 発生状況
- 外部サービスが過負荷状態で、応答が遅延する。
- 不整合の原因
- 自社システムはタイムアウトを検知し、リトライを行う。
- 遅延していた最初のリクエストも後から処理されるため、データが二重に更新される可能性がある。
- 外部サービスのデータベースに重複データが発生し、自社システムとのデータ不整合が生じる。
- 発生状況
sequenceDiagram participant 自社システム participant 自社DB as 自社DB participant 外部サービス participant 外部DB as 外部DB 自社システム->>自社DB: データ取得 自社システム->>外部サービス: データ送信リクエスト note over 外部サービス: 過負荷で応答遅延 自社システム-->>自社システム: タイムアウト発生 自社システム->>自社システム: リトライ処理 自社システム->>外部サービス: 再リクエスト送信 外部サービス->>外部DB: データ更新(遅延処理) 外部サービス-->>自社システム: 遅延したレスポンス受信 note over 外部サービス,外部DB: 複数のリクエストが処理され、重複データが発生 note over 自社システム,自社DB: データ整合性の確認が必要
- サービスダウンによるデータ未反映
- 発生状況
- 外部サービスがダウンしており、リクエストが送信できない。
- 不整合の原因
- 自社システムはデータを外部サービスに送信できず、未送信データを一時保存する。
- 外部サービスにデータが反映されないため、自社システムと外部サービス間でデータ不整合が生じる。
- サービス復旧後に再送が行われない場合、データ不整合が恒久的になる。
- 発生状況
sequenceDiagram participant 自社システム participant 自社DB as 自社DB participant 外部サービス participant 外部DB as 外部DB 自社システム->>自社DB: データ取得 自社システム->>外部サービス: データ送信リクエスト note over 外部サービス: サービスダウン中 自社システム-->>自社システム: タイムアウト発生 自社システム->>自社DB: 未送信データを一時保存 note over 自社システム,自社DB: データが外部サービスに反映されず不整合が発生
- 部分的な失敗によるデータ不整合
- 発生状況
- 一つのリクエストで複数のデータ更新を行う際、一部の更新が失敗する。
- 不整合の原因
- 外部サービスがトランザクションをサポートしていない場合、部分的な更新が行われる。
- データAは更新されるが、データBは更新されず、データの整合性が崩れる。
- 自社システムと外部サービス間でデータの状態が異なり、不整合が生じる。
- 発生状況
sequenceDiagram participant 自社システム participant 外部サービス participant 外部DB as 外部DB 自社システム->>外部サービス: 複数データの更新リクエスト alt 一部成功、一部失敗 外部サービス->>外部DB: データA更新成功 外部サービス->>外部DB: データB更新失敗 外部サービス-->>自社システム: 部分的な成功のレスポンス note over 外部サービス,外部DB: データが不完全に更新される note over 自社システム: データの整合性が崩れる end
その他、競合状態や非同期処理の遅延、タイムアウト設定の不一致等でも不整合になる可能性があります。
2. Webhook通知の不達や遅延
Webhook(外部サービスからのリアルタイム通知)を使用する場合、以下のリスクがあります。
- 成功パターン
sequenceDiagram participant 自社システム participant 自社DB as 自社DB participant 外部サービス participant 外部DB as 外部DB 外部サービス->>外部DB: データ更新(イベント発生) 外部サービス->>自社システム: Webhook通知送信 自社システム->>自社DB: データ更新 note over 自社システム,外部サービス: イベントが正常に反映される
- 通知の未達
- 発生状況
- 自社システムのWebhookエンドポイントがダウンしており、通知が受信できない。
- 不整合の原因
- 外部サービスで発生したイベントが自社システムに伝わらず、データが更新されない。
- リトライが行われても、エンドポイントが復旧しない限り通知は届かない。
- 発生状況
sequenceDiagram participant 自社システム participant 自社DB as 自社DB participant 外部サービス participant 外部DB as 外部DB 外部サービス->>外部DB: データ更新(イベント発生) 外部サービス->>自社システム: Webhook通知送信 note over 自社システム: 自社システムがダウンしている 外部サービス-->>外部サービス: 通知失敗を検知 外部サービス->>外部サービス: リトライまたは放棄 note over 自社システム,自社DB: イベントが反映されずデータ不整合が発生
- 通知の遅延
- 発生状況
- ネットワークの遅延や外部サービスの処理遅延により、通知が遅れて届く。
- 不整合の原因
- データ更新が遅れ、リアルタイム性が求められる業務に支障が出る。
- 一時的に自社システムと外部サービスでデータの状態が不一致となる。
- 発生状況
sequenceDiagram participant 自社システム participant 自社DB as 自社DB participant 外部サービス participant 外部DB as 外部DB 外部サービス->>外部DB: データ更新(イベント発生) note over 外部サービス: 通知の遅延発生 外部サービス-->>自社システム: 遅延した通知受信 自社システム->>自社DB: データ更新 note over 自社システム,自社DB: データ反映が遅れている間不整合発生
- 順序の乱れ
- 発生状況
- 複数の通知が送信された際、ネットワークや処理の遅延で順序が入れ替わる。
- 不整合の原因
- イベントの処理順序が逆転し、データの状態が不正確になる。
- 例えば、在庫の増減やステータスの更新で、後の状態が先に適用される。
- データの整合性が崩れ、業務に支障が出る。
- 発生状況
sequenceDiagram participant 自社システム participant 自社DB as 自社DB participant 外部サービス participant 外部DB as 外部DB 外部サービス->>外部DB: イベントA発生 外部サービス->>外部DB: イベントB発生 外部サービス->>自社システム: イベントA通知 外部サービス->>自社システム: イベントB通知 %% 通知が順不同で到着 par 自社システム->>自社DB: イベントB処理 自社システム->>自社DB: イベントA処理 end note over 自社システム,自社DB: 順序逆転によりデータ不整合が発生
その他、ハンドリング不足やペイロード破損によるデータ不整合、Webhookの設定ミスなど実装不備でもデータ不整合は常に起きる可能性があります。
外部サービスとの通信では、実装や設定が正しくても、データ不整合が起きる可能性を0にすることはできません。
外部サービスにおけるリコンサイルができる条件
外部サービスとのリコンサイルを実施するためには、以下の条件が必要です。
- データ取得手段の確保
- 外部サービスからデータの突合が可能な手段が提供されている
- システムへの影響
- 外部サービスへのリクエスト増加が許容範囲内であるかを確認する。負荷が高すぎる場合は別の手段を検討する
リコンサイルバッチの実装時の検討ポイント
具体的にリコンサイルを定期的に実行するバッチについて考えてみます。
今回実際にあとばらいチャージに導入するにあたって、検討したポイントをまとめているので、設計時のチェック項目になれば幸いです。
前提
あとばらい機能の一連の流れは以下のとおりです。
sequenceDiagram participant ユーザー participant B/43 participant 外部サービス participant 銀行・コンビニ等 %% 後払いチャージリクエスト ユーザー ->> B/43: 後払いチャージを依頼 B/43 ->> 外部サービス: 後払いチャージリクエスト 外部サービス -->> B/43: チャージ完了応答 B/43 -->> B/43 :残高を増やす B/43 -->> ユーザー: チャージ完了通知 %% 返済リクエスト ユーザー ->> B/43: 支払い期日までに返済リクエスト(返済方法を選択) B/43 ->> 外部サービス: 返済リクエスト 外部サービス -->> B/43: 返済受付応答 B/43 -->> ユーザー: 返済方法の提示 %% ユーザーが銀行・コンビニで返済 ユーザー ->> 銀行・コンビニ: チャージした金額を支払う 銀行・コンビニ ->> 外部サービス: 返済処理 外部サービス -->> B/43: 返済完了Webhook通知 B/43 -->> ユーザー: 返済完了通知
実例では、今回の返済リクエストデータのリコンサイルバッチをどう設計・実装したかを記述します。
1. 外部サービスとのデータの不整合箇所の洗い出し
上記シーケンス図にあるように、一連の流れにおいて、外部サービスとの通信は全て失敗し不整合が起きる可能性があります。どの通信に対する不整合に対してリコンサイルをするかを整理します。
実例:
今回のケースだと「返済完了Webhook通知」が失敗した時のリカバリ対応として、リコンサイルを設計・実装しました。
sequenceDiagram participant ユーザー participant B/43 participant 外部サービス participant 銀行・コンビニ等 %% リコンサイル処理 B/43 ->> 外部サービス: 未完了の返済データを確認 外部サービス -->> B/43: 返済状況の提供 B/43 ->> B/43: 返済済みに更新(不整合の修正)
2. データ量と処理時間/実行頻度
大量のデータやバッチの実行時間と頻度は、処理時間やシステムへの負荷が増大し、業務に支障をきたす可能性があります。
検討項目:
- データ量
- 全件データを毎回取得すると線形的にデータ量が増えるため他のサービスへの負荷が大きくなります。
- 処理時間
- 頻度に影響しますが、1度の実行時間が長いと次のバッチが起動するまでに処理が終わらない可能性があります。
- 実行頻度
- リカバリまでの時間は短い方がユーザ影響が小さくなります。一方で外部サービスのAPI仕様が耐えうるかや、問題ないデータを定期的にチェックするのが問題なかなどの検討も必要です。
- 日跨ぎ
- 日を跨いだ時に意図しない対象データが含まれていないかの検討が必要です。
実例:
- 仕様上、返済リクエストをしてから返済までの期限が2日なので、実行時から2日前までの未返済の返済データをリコンサイル対象にしました。これにより対象を絞りチェックするデータ量を減らしました。
- 1つの返済データの取得にかかる時間と件数から実行時間を推測し、実行頻度はなるべくユーザ影響が少なくなるように、1回/1h実行するようにしました。
- B/43のバッチは起動時、APIとは別のECSタスクが立ち上がるので、既存サービスには影響なく実行が可能になっています。
3. 不整合検知時の対応
不整合が検出された際の対応方法を事前に定めておかないと、迅速かつ適切な対応ができず、問題が拡大する可能性があります。
検討項目:
- データ修正の戦略
- 自社か外部サービスどちらのデータが正しいかを決める必要があります。
- 通知
- リコンサイルで発覚した不整合はそのままリカバリできるのが良いと思いますが、自動で対応できない場合、チームに即時通知し必要に応じて手動対応できるようにする必要があります。
データの主管理 | 外部サービスにある | 外部サービスにない |
---|---|---|
自社にある | 外部サービスの担当者と相談して、どちらを正にするかを判断する | 外部サービスへ再度リクエストを送る/自社データを取り消しする |
自社にない | 外部サービスから情報を再取得する | - |
実例:
- 返済管理は外部サービスが管理しているため、外部サービスが正です。なので自社システム上は外部サービスが正しいという前提でシステムを組み、不整合があった場合は、外部サービスのデータを自社DBに反映します
- リコンサイルで意図しないエラーなどが起きた場合は、slack通知を行いチームメンバーで必ず原因調査と対応可否を都度行なっています。
4. エラーハンドリングと冪等性の確保
エラー発生時にデータの整合性を保ち、再実行しても問題が起きないようにするためです。
検討項目:
- 冪等性
- 同一の処理を複数回実行しても結果が変わらないようにします。これにより、再試行時に意図しないデータにならないようにします。
- エラー時の再試行
- リコンサイルでの外部通信エラーが発生した際は、次のバッチ起動で拾えるようにしたり、自動で再試行できるようにします
実例:
- バッチ実行時、期間を指定できるようにし、エラーが発生した時でも何回でも再実行が可能にしました。
5. イレギュラーケースへの対応
外部サービスのメンテナンスや予期せぬ障害は避けられないため、これらに対応できる仕組みが必要です。
検討項目:
- 外部サービスのメンテナンス対応
- 外部のAPIが利用できなくなることがあるため、メンテナンス期間中は処理を一時停止し、終了後に再開するロジックを組み込みます。
- 予期せぬ障害
- 予期せぬ障害が発生した時のリカバリ方法をは検討する必要があります。
実例:
- B/43の管理画面からあとばらいのメンテナンス時間を設定できるので、その設定時間の間はバッチが起動しないようにしました。
- 4でも記載していますが、時間を設定してバッチを走らせることができるので、メンテ明けに手動で実行して拾うことも可能にしています。
6. ドキュメンテーション
リコンサイルの仕組みや運用方法をチーム全体で共有し、担当者不在時でも対応できるようにします。
検討項目:
- 設計・実装手順の文書化
- バッチの目的、エラー時の対応、データフローなどを明確に記載します。
実例:
- B/43ではRakeタスクをAirflowのDAGとして呼び出しています。エラー発生時、共通処理の中でSlack通知が実行されます。Notionのドキュメントや自動復旧対象かどうかが記載されており、他の開発メンバーが対応しやすくなるような運用になっています。
- B/43で実行されるバッチは、以下フォーマットで全てドキュメント化されています。
## 概要 返済データが{外部サービス}とB/43側で一致しているかチェックする。 ## 詳細 {外部サービス}側は返済完了したが、B/43側でデータが取り込めなかった時、不整合が発生する。 ### 処理時間 30min ## 実行タイミング 1回/1h ## 失敗時の対応緊急度 低 次回のバッチ起動時にエラーが出ていなければ対応不必要 実行されなくてもユーザーからの問い合わせで手動実行すれば問題ない ### ユーザー影響 低 ## 単純リラン可否 可 ## リランの方法 - AirFlowから`any_command_exec`にて実行 {"COMMAND":"bundle exec rake 'post_pay:xxx_repayment_reconcile[2024-03-01 00:00:00,2024-03-03 00:00:00]'"} ## エラー発生時の調査方法 - repayment.object_uidから返済IDを引っ張ってくる - AirFlow上のログから対象返済情報を確認する 1. 返済データ取得に関するエラーの場合は、次のバッチで再実行時に確認。 2. その他の場合は、アプリケーション側のエラーなので、管理画面で返済状況を確認。
まとめ
外部サービスとのデータ連携におけるリコンサイルは、データの正確性と信頼性を維持するために不可欠です。 決済システム以外にも外部サービスとの連携においてはリコンサイルは有用な手段の1つです。 通信障害やシステムエラーによる不整合は避けられないため、リコンサイルを通じて問題を検出・修正することが重要です。
本記事が、外部サービスとのリコンサイルを実装する際の参考になれば幸いです。 また、このブログをきっかけにB/43の開発に興味を持ってもらえたら嬉しいです。
SmartBankでは、サーバーサイドエンジニアを募集しています! smartbank.co.jp
B/43の開発について少しでも聞いてみたい方は、カジュアル面談もご検討ください! smartbank.co.jp