PayPay 100億円キャンペーンの知られざる舞台裏

山本啓介氏(以下、山本):よろしくお願いいたします。本日は「PayPay 100億円キャンペーンのシステム構築」という題材でお話をさせていただきます。

本日は2パートに分けてお話しさせていただきます。

まず私からは、100億円キャンペーンを2度やりましたが、それまでの事績をお話できればと思います。後半パートは弊社のShileiから、PayPayのシステムのより細かいところや問題、今後に関してお話しできればと思っております。

山本と申します。会社の中ではKSKというニックネームで呼ばれております。この後登壇するShileiと共にテクノロジー全般を見ています。2007年にYahoo! JAPANに入社しまして、そこから半分以上の時間は決済関連のプロダクトに携わっています。PayPayについては、立ち上がる3ヶ月前ごろからこのプロジェクトに携わってきました。

私のセッションのアジェンダです。簡単にPayPayとはどういったものなのか、主に3つのミッションに分けてお話しします。最初はローンチ、2つ目は「100億円キャンペーン」、3つ目は「第2弾100億円キャンペーン」について説明をさせていただきます。

PayPayとは?

まず「PayPayとは」。みなさんご存知の通り、PayPayとはQRコード決済サービスになります。

店舗のオーナーさんは初期費用0、決済手数料0、当然入金手数料も0、本当に無料で導入できます。

オーナーさん向けに「PayPay for Business」というダッシュボードも用意しています。

会社の沿革なんですが、2018年6月15日に会社が設立されました。

その約3ヶ月後、10月5日にサービスをローンチし、話題になった「100億円キャンペーン」は12月4日から12月13日まで開催されました。第2弾のキャンペーンが、直近2月12日から5月の中頃まで開かれてます。現在は「ワクワクペイペイ」ということで、6月に関してはドラッグストアでお得なキャンペーンを行っています。

ローンチから7ヶ月間で登録者数が700万人、加盟店数は60万店舗を超えております。

職場環境についてはこの後細かくお話ししますが、われわれプロダクト本部は日本人もさる事ながら、かなりの外国のメンバーもおります。職場はかなりフラットで、スーパーフレックス制を導入しています。当然、開発環境もまだ立ち上がったばかりということもあり、ほぼすべて最新のものを使っております。

Hiringもします。「Be a Rockstar」ということで、ご興味がある方は見ていただければと思います。

連絡先は、こちらのメールアドレスですね。

PayPayのシステム構成

では、本題に入ります。2018年7月頃から今年の6月まで、3段に分けて説明をしたいと思います。

まずはローンチです。

3ヶ月でQRコードの決済のサービスをローンチしなければいけないという、本当にこんなタイミングでスクラッチで作り始めました。

開発期間が短いこともあり、開発のサイクルを上げるためにマイクロサービスアーキテクチャを採用しています。また、多数のサービスを効率的に管理するためにKubernetesを、インフラの構築を最短にするためにAWSを使いました。PayPayはPaytmからのヘルプも得て、カナダやインドのメンバーの協力も得ながら3ヶ月で開発を行いました。

PayPayの中には、だいたい60以上のマイクロサービスがあります。7チームあり、1つのチームは複数サービスを見ていて、各サービスは独立したリソースで動いています。中身はほぼ非同期処理になってまして、各サービス間のやりとりはKafkaにイベントを流して、それをコンシュームするかたちで成り立っています。

少しわかりづらいですが、このようにサービスがいろいろと並んでいます。AWSの中にPayPayのサービスがあるんですが、クレジットカード決済をはじめとした決済情報はすべてYahoo! JAPANのシステムに保存されています。なので弊社の中には、クレジットカードカード番号などのセンシティブな情報は保存していません。

KubernetesはEC2上に独自にKubernetesのクラスタを構築しております。

だいたい40ノード、Pods数は1,000オーバー、サービスが60以上です。環境としては、開発環境、ステージングの環境、それからパフォーマンス環境、負荷試験環境ですね。こちらはほぼ本番と同等の環境になります。それとプロダクションの環境があります。

なぜEKSではなくEC2なのかというと、サービスのローンチ当初は東京リージョンでEKSが まだ利用できなかったので、EC2上でやっています。

他にはRDSですね。ローンチ当初はMySQLでやっていましたが、現在はほぼAuroraに切り替わっています。RedisやSESなどのAWSサービスを使っています。

リソースは基本的にTerraformで管理していて、管理コストの削減につなげています。

その他の技術スタックです。

メインのアプリケーションはJavaで作られていて、一部Scalaを採用しています。フレームワークはSpring Bootを使っていて、CI/CDにJenkins。あとはGitHubだったり、監視の部分はDataDogやPagerDutyという海外のサービスを主に使っています。他には、先ほど言ったようにKafkaを有効活用しています。

先ほどお話ししたように、私はYahoo! JAPANからの出向というかたちでPayPayに来ています。今までYahoo! JAPANのインフラで構築をした経験があったんですが、多くのメンバーがAWSは初めてだったにも関わらず、3ヶ月ほどでローンチできたのはすごくよかったと思っております。無事に約束の日にリリースができました。

100億円キャンペーンで起こったこと

2つ目のミッションです。

100億円キャンペーンが12月4日から始まりました。安定稼働させるためにいろいろとやってきましたが、結果的にはいろいろと障害を起こしてしまい、お客様や加盟店様には多大なご迷惑をおかけしてしまったと思っています。

何が起きたのかを分析をしてみると、やはりアクセスの急増です。アクセスが急増して、システムが不安定になってしまいました。そのため、何回も緊急メンテナンスを実施しています。ユーザが増加した時のアクセス数も予測していたのですが、結果的に私たちの読みよりもはるかに短い時間でキャンペーンが終了しました。

こちらは実際のアクセスの状況です。左が決済トランザクション、右がユーザのサインアップ数です。ほぼ毎日決算トランザクションが伸びている状況でしたので、ずっとサーバの追加をやっていました。ユーザのサインアップは凹凸がありますが、テレビの影響でかなりスパイクしたこともあり、増えたり減ったりを繰り返しましたが、基本的には右肩上がりでした。

どんな問題が発生したか?

では、どんな問題が発生したかをお話しします。

まず、外部の連携サービスでトランザクションが滞留しました。そして、加盟店向けツールから投げられる負荷の高いクエリが想定以上にコールされてしまい、不安定になってしまいました。あとはRedisのBigKeysですね。他には、SESのリミットに達してメール送信が止まってしまったり、サインアップの際のボーナスとして500円を付与しているんですが、サインアップが多すぎてボーナスの付与が1時間以上遅延するといったことが起きていました。

クレジットカード決済に関してはYahoo! JAPAN側にリクエストを飛ばしているんですが、そもそもその出口のところのPods数が少なかったです。そこが詰まることによってどんどん内部も詰まってしまい、サービスが停滞してしまいました。

当然ノードやPods、RDSもスケールアップをしていきましたが、なかなか追いつかない状況でした。

あとは、負荷の高いクエリの呼び出しですね。加盟店向けツールから、決済履歴などを参照しにいきます。その参照が重くなることによって、ペイメントサービスにも影響がでてしまいました。

問題を解決するためにやったこと

これらの問題に対応するためにどんなことをやったかというと、何回かメンテナンスを入れさせていただきました。

夜中に緊急メンテナンスもいれたりしたのですが、まずは加盟店の情報をRedisのキャッシュに格納するように急遽変更しました。DBの読み込みに関しても、リードだけの読み込みに関しては、極力Slaveに振り向けるように対応しました。

機能も削りました。加盟店ツールのダッシュボードではオートリロードをしていたんですが、このオートリロードを停止しました。他には、決済のトランザクションを加盟店さん側でダウンロードできるんですが、大量に決済があるところでは一部制限をさせていただきました。

あとはアプリのホーム画面のデザインですね。サービスをローンチした当初は、支払い手段がホーム画面に並んでいました。それによって支払い手段、とくにクレジットカードの情報をYahoo! JAPAN側から引っ張ってくるんですが、そこでYahoo! JAPAN側にアクセスしないように、PayPayのシステムの中で完結するようなかたちにしました。

あとはRedisのBigKeysですね。認証はRedisの中に情報を格納していたんですが、Redisの負荷が上がってしまいました。とくに1つバグがあって、使っていないkeyに対して要素が375万件ぐらい入ってしまっていて、負荷があがってしまいました。アクセスがある度にどんどん要素が追加されていくので、一旦これはShardの再配置で分散をして逃げ切りました。その後使っていなかったkeyだったので削除しました。

次の問題です。内部はすべて非同期処理だったんですが、サインアップが多いことで500円の付与を行うキャンペーンシステムが追い付かなくなってしまい、非同期でやっているにもかかわらず1時間以上の遅延を起こしてしまいました。

こちらに関しては捌かれるのを待っていました。

あとは、SMSの送信遅延も起きました。

サインアップの際にSMSでワンタイムパスワードを送信して認証を行っていたのですが、サインアップが急激に増えたことで遅延が発生し、有効期限が切れてしまうという状況が発生しました。どんな手を打ったかというと、サインアップ用のSMS送信とその他のSMS送信を分割しました。

第2弾のキャンペーンに向けてやったこと

第1弾キャンペーンはいろいろあったので、第2弾のキャンペーンに向けてどんなことをしたのかをお話しします。

1番の課題は、一部の機能停止で重要な機能が停止してしまう点でした。負荷試験は事前に行っていましたが、パターンが足りていませんでした。また、チューニングが甘いといった課題があったので、1弾目のキャンペーンが終了したあと、すぐに対応しました。

まずGraceful Handlingとデータの可視化を行いました。

Rate Limitなんですが、突然大量アクセスが来たとしても、システムの表側でブロックというか、流入量の制限を行いました。なので突然大量のアクセスが来て一定のリミット値を超えたユーザに対しては、「ただいま込み合っています」という画面が出るようにしました。

そして、一定時間が経つと次のユーザがアクセスできるようになる、そういったRate Limitを入れています。詳細は次の章で説明します。

Circuit Breakerです。

外からの流入だけでなく、中のどこかしらのコンポーネントが応答できなくなったときに、そのコールをスキップする処理を入れました。

あとは遅い処理の可視化です。

アプリケーションパフォーマンスモニタリングとして、NewRelicを導入しました。これによってどこがシステムのボトルネックになるのか、簡単なもので言うとインデックスが貼られてないものを可視化して、すぐに修正するようにしました。

最後に、3D Secureの導入も行いました。

これらの作業を行ったことで、2弾目のキャンペーンでは大きな障害も起こすことなく無事に終えることができました。

では、後半はShileiから説明させていただきます。