こんにちは。メルペイでバックエンドソフトウェアエンジニアをしている id:koemu です。
今回は、前回の記事でお話したことを踏まえ、私が開発を担当して実際に動いているバッチプログラム「お急ぎ振込 締め処理バッチ(以下、締め処理バッチ)」について、述べていきます。
本記事では、「締め処理バッチについて」と「前回の記事に照らし合わせてどう設計されているか」の2点に分けて説明します。内容は、特に記載のない限り、本記事執筆時点のものとなります。
締め処理バッチについて
事例の話に移る前に、これから説明する締め処理バッチの処理がどのようなサービスを提供するのか、ご案内します。
お急ぎ振込とは
お急ぎ振込とは、メルカリの中で売り上げたお金を皆様の銀行口座に出金できる機能の1つです。当初から存在している振込申請機能より早く送金することができ、最短1営業日で出金することが可能です。2017年の夏の終わり頃から一部のお客様にサービスの提供を開始し、段階的にすべてのお客様に提供を開始しました。
締め処理バッチ 概要
締め処理バッチは、銀行の営業日限定*1の日次で以下の処理を行っています。
- プロセッシング事業者からの送金結果の受領
- 残高を管理するmicroserviceへ振込結果を通知
- データベースへ振込結果を保存
- お客様への各種通知(Push通知, 個別メッセージ, あなたへのお知らせ, メール)
- クリーンナップ処理
当社では、プロセッシング事業者を経由して、皆様の銀行口座に売り上げたお金を送金しています。
処理フロー
締め処理バッチは大きく分けて2つあり、「締め処理(主処理)」と「クリーンナップ処理」があり、ほぼ同時に実行されます。もともと「クリーンナップ処理」は「締め処理」の中にありましたが、なぜ別れたかについては後述します。
まず、「締め処理(主処理)」の処理フローは、下記の図のとおりです。
続いて、「クリーンナップ処理」の処理フローは、下記の図のとおりです。
前回の記事に照らし合わせてどう設計されているか
処理件数
具体的には申し上げられませんが、現在では日に万単位の件数を処理しています。
サービスインの際に当初はすべてのお客様には提供せず、段階的に提供していました。こうすることで、以下の2点のメリットがありました。
- 万一、バグやトラブルが発生したときでも影響範囲が限定できる。
- 1件あたりどの程度の時間で処理が可能かを計測し、全員に提供した際に想定した処理時間内に処理可能かを確認できる。
その際、段階的に提供していた際に技術的な問題は発生しなかったため、順調に全員のお客様への提供に至ることができました。
処理時間
こちらも大体の数字となりますが、現在は、締め処理が開始できる時間から数十分〜1時間程度で処理が完了します。当初はすべてのお客様に対して、設計通りの18:30〜22:00の範囲内で処理が完了していました。しかし、公開から1年以上を経て、処理時間が徐々に設計予定時間を超える状況が徐々に明るみになってきました。
処理時間が律速していた主な原因は2つあります。
- クリーンナップ処理をプロセッシング事業者との間で行うが、1件あたり数百ミリ秒かかるようになった。
- お客様への各種通知を逐次的に処理していたため、すべての処理が終わる時間が長くなってしまった。
当初、締め処理バッチは「締め処理バッチ 概要」に記載した処理を1件ずつ逐次的に処理していたため、1件が遅いとその分、すべての処理が完了する時間もどんどん遅くなります。そのため、次の対策を施すことになりました。
- クリーンナップ処理を別バッチに分割する。
- クリーンナップ処理に関わっているプロセッシング事業者側のチューニングを依頼する。
- お客様への各種通知をワーカープロセスを使い並列に処理する。
1は、締め処理と同時に行う必要はないことと、バッチ処理に占める時間が半分以上を占めていたため、プログラム自体を独立させる判断をおこないました。これで、クリーンナップ処理以外の締め処理が予定通りの時間に終わるようになりました。
2は、プロセッシング事業者様と協力して、クリーンナップ処理で使っているAPIのパフォーマンス・チューニングを施していただきました。これで、クリーンナップ処理自体も高速化することができました。
3は、通知処理は当社にはQ4MをPHP用に移植した基盤が存在し、こちらを利用して非同期かつ並列に通知を送れるように改修しました。これには php-Parallel-Prefork を用いています*2。詳細は以下の記事を参考にしてみてください。
PHP版 Parallel::Prefork で奥一穂さんと親に感謝しよう – unoh.github.com(ウノウラボ ラボブログのアーカイブ)
上記のチューニングを施した結果、処理時間が4分の1以下まで短縮させることができました。
回復可能か
締め処理は、当初からバッチ処理が途中で中断しても回復可能になるように設計していました。そもそも、締め処理バッチで処理がバグ以外で途中で中断してしまうポイントはどこにあるか、簡単に挙げてみます。
- メルカリの口座残高を管理するmicroserviceとの通信・処理でトラブルが発生する
- プロセッシング事業者との通信・処理でトラブルが発生する
- データベースへの接続・トランザクションが障害で止まる
- バッチサーバそのものが異常停止する (他のバッチ処理がリソースを使い果たすなど)
特に、通信関連は実際に何度も起こっています。そこで、締め処理バッチでは次の対策を施しています。
- プロセッシング事業者から届けられた送金結果をバルク処理する際、処理結果が冪等になるようにした。
- クリーンナップ処理については、「ここまで処理した」という記録をつけ、途中から再開できるようにした。
1は、バッチ起動時、プロセッシング事業者が持っている処理結果がバルクで届けられます。それを使って締め処理を行う際、口座残高を管理するmicroserviceは処理が冪等になるようにそもそも設計がなされていること、そして締め処理バッチ内で扱っている送金ステータスも既に送金済みであることを確認しているため、二重に処理されないようになっています。当社におけるmicroserviceの冪等性についての議論は、こちらのスライドをご覧ください。
2は、処理が終わった状態が都度記録されており、再起動した際に終わっていないところから始まるようになっています。
以上をもって、バッチ処理が途中から回復可能になっています。
多重起動可能か
回復可能か節で説明していますが、万一多重起動したとしても、処理が冪等に行われるか、または途中から処理が行われるようになっているため、トラブルになることはありません。
バッチの突き抜け対策
現時点では、明確な突き抜け対策は行っておりません。「処理時間」の節で述べている処理時間を多少超えた場合でも、次回の実行まで十分に余裕があるためです。また、多重起動可能の節でも説明していますが、万一多重起動した場合でも影響が出ないようになっています。
とはいえ、今後も安定して運用を継続できるよう、設計した時間を超える実行時間を観測した場合は通知を出し、改善を促す仕組みを導入する予定です。
まとめ
ここまで、「お急ぎ振込 締め処理バッチについて」と「前回の記事に照らし合わせてどう設計されているか」について述べてきました。
ある日次バッチ処理の一例として、処理件数や処理内容に応じた対策をとってきたことをお話しました。
当社のような既に一定のお客様がいるサービスの場合は、当初からすべてのお客様に提供できるよう設計していたとはいえ、実際の負荷に耐えられるか心配なこともあるかと思いますし、実際私も心配でした。そのような場合は、段階リリースという方法を用いて徐々に状況を把握することが一つの手段であることを示しました。
また、お客様の利用数が増えたり、システムの負荷が上がってきたりすると、律速するポイントが出てきます。それらを定量的に計測しつつ、チューニングやアーキテクチャの変更を通じて対策していく必要性をお伝えしました。
今後、もし時間があれば、「テストケースの作成」「運用・監視」などについても触れてみたいと思います。
それでは皆様、ごきげんよう。