PayPay100億円キャンペーンを支えたシステムの裏側

Shilei Long氏:こんにちは。私はShileiと申します。少し緊張してはいますが、ここにいられることを光栄に、うれしく思っています。

啓介さんが話してくれたとおり、最初と二番目のキャンペーンをデリバリーするまでに長い道のりがありましたが、そこをどのように通ってきたかお話しします。険しくはありましたが、やりがいのあるものでした。

さてここでお話ししたいのは、もう少し技術的なことについてです。こういったシステムをゼロから、早く、スケーラブルに構築しようとしている方に参考になると思います。わたしたちの経験は秘伝のタレではありませんが、わたしたちの教訓を知っていただければ同じようなミスを防ぐことができるかもしれません。

自己紹介です。

中国で生まれ育ち、数年前にカナダのトロントに引っ越しました。バスケットボールがお好きな方やNBAファンの方がいらっしゃったら数分前の出来事をご存じかと思いますが、私も非常にわくわくしています。

以前私はアマゾンに2年ほど在籍していて、それからPaytmに行って、現在は出向でPayPayのエンジニアたちと大志をともにしています。

こちらがアジェンダです。

まず私たちのマイクロサービスとストレージについて、決済システムのデザインについて、Kafkaをどう使っているのか、あとはざっと私たちがAWSについてどう考えているのか、ということをお話ししたいと思っています。

PayPayのマイクロサービス

これが私たちのマイクロサービスの構造です。

重要なポイントは、私たちのシステムはだいぶ複雑だ、ということです。啓介さんが既に言及してくださってますが、60以上のマイクロサービスによって構成されています。

私たちがどのようにマイクロサービスを活用しているのかについて、いくつかお話しします。

たとえ話として1つ、あなたには本当に時間がなくて、完璧なマイクロサービスのアーキテクチャを作る時間なんてないとしましょう。そんな時はたった1つだけやってください。タイムアウトをセットすることです。マイクロサービスはお互いに通信をします。その時、マイクロサービスの一つひとつの通信にタイムアウトをセットしましょう。

1つお約束できるのは「サービスは失敗する」ということです。これは保証します。今日か10日後か1年後かもしれませんが、失敗します。しかし、チームのために最大限努力してほしいことは、カスケードの障害(failure)を予防することです。というのは、システムやサービスが1つダメになると、全体に影響するからですね。それが1つ予防してほしいことです。

一番シンプルにまずできることがタイムアウトをセットすることなんです。どのサービスでもリソースは限られています。スレッド数や、オペレーティングシステムが許容しているTCPコネクションなど、他にもありますがこういったリソースですね。タイムアウトがなければ下流のサービスは遅くなって、壊れてしまうのは目に見えています。

キャンペーンの初日、何が起きたかはすでにご存じかもしれません。私たちの重要なサービスとしてユーザーモジュールサービスがあります。認証やユーザー情報、アセット管理、そういったものが集まっているものなのですが、下流のサービスが1つ遅くなってクラッシュしてしまったんですね。なぜかというと、200ものスレッドが全てのリソースを使い切ってしまった結果、みんなが決済のメソッドを使おうとしたため、認証リクエストが無視されることになりました。全てのシステムは認証に頼っていますからクラッシュしてしまいました。

時間があるときにできることは「要素ごとに分割すること」です。つまり、大きなサービスをいくつかの小さなサービスへ分割することですね。例えばユーザーモジュールサービスなんかは1つにまとめず3つにしています。みなさんのアセットや決済メソッドの確認機能は他に比べて重要ではないわけです。というのは支払いができなかったとしても、支払い履歴を見たりといった他のことはできるからです。ですから、(サービスごとに)分割することが重要です。どれかがダメになっても大事なものはおそらく生き残れますし、全体のサービスはうまく機能するでしょう。

さらにもうすこし時間がある場合はサーキットブレーカーを導入しましょう。以上がみなさんと共有したかった教訓です。

パイロットテストを行う

2つ目としては、「知らないことは知れない」ということですね。みなさん開発を行う際は、おそらくシステムがきちんと動くようにあらゆる手を尽くしていると思います。タイムアウトの設定やデータベースのオプティマイズ、DBMのサイズはOKかなど、全部チェックしたと思います。それでもいくつか見落としてるところはあって、それが何なのかは分かりません。

私たちの経験からいうと、テストするしかありません。まずはステージング環境でテストをしましょう。それだけでは不十分な場合は、本番環境でもテストしましょう。

実際のところ、本番環境でテストするのはいつも大変です。本物のデータですし、決済業界なので扱うのは人のお金で資産ですよね。非常に大変ですが、本番環境でテストすことはきわめて価値があります。

本番環境でのテストには2つのパートがあります。本番環境へ向けられるWriteトラフィックとReadトラフィックがあります。後者は簡単で、なんでもいいですがテスト用のクライエントを持ってシミュレートして、どんなときにクラッシュするのかを見ればいいだけです。あるいは、事前に問題点を知ることができますね。でもWriteトラフィックでのテストは本当に難しいんです。

そこで私たちは、ボランティアで内部のユーザーを増やしました。PayPay、Yahoo! JAPAN、ソフトバンクなど、グループの内部のユーザーがいますので、彼らにウォレットへの入金をお願いしました。そして、シミュレートするために1円のトランザクションを使用してみました。リスクは伴いますが、このメソッドによって非常に多くの(悪い)可能性をはらんだ問題点を見つけることができました。それらはお客様であるユーザーに打撃があるであろう問題で、実際に起こりうるものでした。

もちろん、これを行うためには会計的にテスト後に返金処理をしたりなどのケアしなければならないことがあるため、非常に大変です。ですが、チームでテストをすることが、見えない問題を解決するのに有効です。

スケールを容易にするコツ

次は、システムを本当にスケールしたいとき、例えば、私たちのシステムは60~70くらいありますが、他の会社ではもしかしたら100~200くらいあるかもしれません。Amazonなんかは数えきれないほどあると思います。

デベロッパーたちを不安にさせずサービスをスケールさせるためには、標準化することが重要です。私たちはみんなが使うものを標準化しました。Java + Spring Bootなどですね。

例えばロギングに関しては、log4j/fluentdからKafkaにして、Kafka Connectを使い、ESを経てKibanaで視覚化します。それからモニタリングや分散トレーシングも行っています。重要なのはKubernetesです。今言えることは、もし可能ならKubernetesは前向きに考慮しておいたほうがいいということですね。未来につながるといってもいいからです。「マイクロサービスの大きなクラスタをマネジメントするには何が一番のソリューションなのか」についてはいろいろ意見が飛び交っているとは思います。

私の経験に基づいてお話ししますと、もし新しいテクノロジーを使ってスクラッチからサービス開発を考えているようであれば、Kubernetesを使ってみてください。望んでいることをやってくれるでしょう。日々進歩していますし、オープンソースで信頼もできます。

それから、もう1つはSREsやDevOpsに特化したスーパースターたちとチームを組むことです。私たちはDevOpsのエンジニア7人のチームがあって、会社のバックボーンを担っています。彼らはスタンダードなサービスや問題に関するルールなどや、サプライズ的なものが起きないように確実なものとしてくれています。1つミスが起きたら、プラットフォームレベルで修正できます。そのあと60以上もあるサービスを1つ、また1つと直さなくていいようにです。私たちは採用を行っているので、腕のいいSREsエンジニアを募集しています。

このスライドは弊社のSREテックリードがシェアしてくれた「弊社で簡単にサービスを追加する方法」について、です。共通のDocker Base Imageを使用すること、Jenkinsでセットアップするのに数行のコードを書くだけで済むようにすること。KubernetesはKustomizeを使ってテンプレートからのデプロイを行っています。モニタリングは自動化していて、これらをできるだけ簡単にしています。

私はAmazonで2年ほど働いていましたが、ビューシステムというものがあります。PythonのプロジェクトからAmazonのプロジェクトに持っていくのも非常に簡易にできました。その観点から、システムが本番環境で使用可能になるには10分とかかりません。もしご自分のビジネスを改造したいのなら、これを学ぶのがサービスの標準化において最もバリュアブルなのではないかと思います。

流量制限について

それから流量制限についてです。啓介さんがすでに言及していますが、トラフィックのピークに立ち向かうために平準化をしています。

信頼できるシステムを構築されているとは思いますが、それでもどんなトラフィックでも大丈夫というわけにはいかないと思います。それが1つ目の理由です。

2つ目の理由としては、みなさんご存じの通り日本は、特に金融産業においては高い水準を求められる社会です。どんなシステムの機能停止も、他の国では企業がチュートリアルを用意するか小さいバナーで注意喚起を出せば問題ないのですが、日本では規制当局に報告されなければならないんですね。例えば金融庁などですね。

興味深いのは機能停止の定義で、これを確かなアドバイスとして話していいのかどうか分かりませんが、個人的に機能停止というのはお客様へ提供できる最低限のサービスも提供できない状態と定義されていると思っています。もしあなたのシステムが数パーセントのカスタマーにサービスを提供できる状態なのであれば、機能停止の報告はしなくてもいいです。

さて、こうした要因が私たちをシステムの改善に駆り立てたので、スライドにあるとおり流量制限レイヤーを加えることにしました。

これがKubernetesの典型的なトラフィックフローです。アクセスがどこかから来てKubernetesが介入するわけですが、ここに1つレイヤーを置きました。OpenRestyです。そうしてこれがサービスに到達するわけです。

興味深いのは、OpenRestyをLua Scriptで使用するとクリエイティブに流量制限ができるということです。私たちが奮闘したタスクというのは、少しのカスタマー、数パーセントのみのカスタマーがサービスを特定の時間に使えるようにすることです。

この特定の時間内のみ、また制限されるまでは継続的にシステムを使用することが可能にする。そのあと、またさらに数パーセントの別のお客様が使えるようにする。

お客様に保証したいのは、2分しか開かないからといって影響はないということです。これは重要で、例えば誰かが支払いのためにアプリを開いて、QRコードを見せたときにアクセス可能じゃないとダメなのですが、支払いは2分でできること、というのが私たちのアイデアです。

コードをスライドに載せていますが、デバイスのIDを使っていてハッシュしてモジュラー関数にいれています。ここに面白くてシンプルなロジックがあります。これが驚いたことにものすごくパワフルでパフォーマンスを出してくれるんですね。覚えていてほしいのは、これはすべてのリクエストがサービスに来るたび行われているということです。全部が3,000~5,000TPSのあいだこのLua Scriptを通るわけです。これと似たようなことをするときには、OpenResty と Lua Scriptを使うことを強くお勧めします。非常に面白い流量制限のオペレーションを見ることができると思います。

このスライドは今お話ししたことを日本語に訳したものですね。

ストレージデザインの変遷

次はストレージのデザインについてお話ししたいと思います。

ストレージは常にシステムのクリティカルな部分であり、スケールの最も難しい部分でもあります。サービス自体は好きなようにできても、ストレージは難しいですよね。

私たちが通ったのは、こういった道のりです。

まずはMySQLから始めました。理由は、(MySQLは)簡単で弊社のほとんどのデベロッパーがMySQLをよく知っていること、支払いシステムなので信頼性を担保できるものがいいということでした。私が考えていたのは、MySQLならインターネット産業におけるユースケースのうち、少なくとも90パーセントはメジャーな障害抜きで満たしてくれるだろう、ということですね。

まずmulti-AZ automatic failoverのある信頼の厚いAWS RDSからスタートして、今もまだMySQLを使用しています。もちろんデータベースの基本的なチューニングはしなくてはなりません。例えばインデックスの適切な設定などいろいろありますが、うまくチューニングできれば問題ないと思います。私たちはVividCortexというツールを使っていて、Performancce Insightsも使用しております。これらは(悪い)可能性をはらんだ問題なんかを認識してくれるものですね。インデックスが無いであるとか、ロックタイムであるとか。ですからとても便利です。以上が私たちの第一歩です。

数カ月後、2回目のキャンペーンを終える頃には非常に多くのお客様がいたわけですが、データベースが1億行以上を超えていました。システムを見ていたらReadとWriteのトラフィックが非常に高くなっていたので、ちょっと怖くなったんですね。そこで次のステップについて考えることになりました。

そこで、MySQLを使いながらAWS Auroraも使うことにしました。理由はいたってシンプルで、Read-Writeを分割したいと考えていたからです。MySQLとAWS Auroraであれば主従関係間のレプリケーションのラグが低いからですね。AWS Auroraについては面白くてとても良い研究があって、なぜこのアーキテクチャはレイテンシーが低いのか、レプリケーションのラグが低い理由を説明しています。

それからMySQLのコンプライアントについてですが、私たちが行ったテストによるとWriteにおいてAuroraはMySQLよりも2.5倍速いということが分かりました。スライドのとおりレイテンシーはMySQLでは35秒、AuroraとMySQLでは13秒でした。同じトラフィック、同じ環境でです。

1つご提案できるとしたら、サービスを見てストレージを選ぶことがあればAWS Auroraをお勧めします。これでおそらく最高のMySQLを手にできると思います。すばらしいパフォーマンスを提供してくれますし、すべての速度が保証されています。

データベースのこれから

では、将来の話です。

来年あたりのお話ですが、予想ではおそらくデータベースは5億行、トランザクションやリクエストは20,000RPS、2,000TPS程度になると予測しているので、水平方向へのスケールが必要となると思われます。

この(次の)段階ではShardingやNewSQL、Auroraでは今年の末に提供開始となるMulti-Masterなどの選択肢を考えています。その中でも、TiDBは面白くて、こSQLデータベースのメリットを提供してくれますし、MySQLと互換性があります。AWS EKSなんかもデプロイを楽にしてくれますね。以上が候補となります。

これは私たちの決済システムのデザインです。

お見せしたいのは、CQRSを達成しているところですね。(図右下部分以外)このあたりが全部Writeシステムです。データベースをシェアしていて、データベースも複数ありますがWrite DBと呼ばれています。データベース内のインデックスはほぼゼロです。こうしている理由は、Writeトラフィックを最速にしておきたいからですね。(図右下部分)このあたりがReadサービスで、ユーザーが情報を読み込むところです。

WriteシステムがReadシステムとコミュニケーションをとれる唯一の方法は、Kafkaを使うこと、これだけです。APIコールはありませんし、DBに書き込むこともありません。Kafkaだけで非常に信頼できるWriteシステムのパフォーマンスを生み出し、同時にReadシステムをスケーラブルにできました。

WriteシステムとReadシステムの間でイベントがなくなっているのではないか、と思っているかもしれませんがReconciliation Jobがあります。これによってKafkaのイベントの成功を保証することができます。詳細について聞きたい方は、このあとお声がけください。

先ほどもお話ししましたが、最後にKafkaについてです。

超重要です。EC2上に直接デプロイできていいのですが、いくつか問題点もあります。

今やろうとしているのはKubernetes上でKafkaを動かすということで、StatefulSetやPersistenVolumeなんかとStrimziを使おうと思っています。

(Strimziは)ほんの数行の宣言ファイルだけでKubernetesにデプロイできるんです。こんな感じです。

私には魔法みたいにしか見えないですね。

AWSの良いところ、悪いところ

最後にAWSのいいところ、悪いところ。悪いところも言います(笑)。いいところは、はじめるのが簡単で速いところ。これはもうここにいらっしゃるほとんどの方がご存じでしょう。それから、とても信頼できるところ。今までに起きた問題は自分たちのミスに起因することのみで、AWSによるものはありません。そして、パワフルでスケーラビリティーが高いです。

良くなかったところは、大阪に本格的なリージョンがなかったので、近々実現してほしいなと思っています。データベースも信頼はできますが、新しいバージョンへのサポートなどを期待しています。最後はコストですね。大きなサービスやリソースを持つと金額が跳ね上がります。

ですが私たちにとってそれがあまり問題にならないのは、ご覧のとおり強大なプレーヤーたちが後ろ盾となっているからですね。

ソフトバンクグループ、ソフトバンク、Yahoo! JAPAN、あとはPaytmです。Paytmはインド最大の決済関連の企業ですね。おかげさまで費用に関してはあまり問題にはなっていないです。

以上で私のスライドはおしまいで、採用情報です。

私たちはエンジニアの数を倍にしたいのでかなり前向きに来年に向けて採用を行っています。もしご興味ありましたらメールでご連絡いただくか、弊社の採用ページをご覧ください。ありがとうございました。