PayPayの100億円キャンペーンを支える技術

正木一平氏(以下、正木):PayPay株式会社の正木一平と申します。今日は弊社から2人登壇させていただきます。まずは自分からPayPay全体の簡単な紹介と、自分が担当しているキャンペーン・キャッシュバックエンジンの裏側についてお話しさせていただければと思います。

次のセッションでは、弊社のHarshから決済サービスとしてコアなプラットフォームとなります、ペイメントまわりの裏側のお話をさせていただければと思います。よろしくお願いします。

自己紹介をさせてください。

僕は2016年にYahoo! JAPANに入社し、もともとは加盟店の管理システムや決済のハブシステムの開発や運用に携わっていました。PayPayに異動後は、最初はユーザ情報や認証認可のシステムの開発をしていました。現在はCustomer Lifecycle Managementチームでキャンペーンやキャッシュバックエンジンを担当しております。プライベートでは2児の父として子育てにも奮闘中です。

今日は、まずはPayPayの概要や開発スタイル・環境についてお話をして、その後キャンペーン・キャッシュバックエンジンの課題や実際にどんな技術を採用しているか、運用上の利点や「こんな課題を抱えています」といった話ができればと考えています。

さっそくですが、みなさんPayPayを利用したことはありますか? PayPayを利用したことがある方は手を上げてください。

(会場挙手)

ありがとうございます! PayPayはQR・バーコード決済サービスです。お店でバーコードを読み取ってもらったり、お店のQRコードを自分で読み取ったりすることで、簡単に決済ができるサービスになります。

今なら定常特典として最大3パーセントのPayPayボーナスが付与され、キャンペーンの対象になるとさらに戻ってきます。おかげ様で多くの加盟店様に導入いただいており、最新の情報ではユーザ登録者数が900万、加盟店数が70万を超えました。アプリのマップを開いていただくと、PayPayを使える加盟店がご覧いただけます。

PayPayの概要と開発環境

PayPay株式会社は、昨年の6月15日に会社を設立いたしました。

昨年の10月5日にサービスを開始して、12月4日から12月13日まで「100億円あげちゃうキャンペーン」というキャンペーンを開催させていただきました。今年に入って2月12日から5月13日まで第2弾として、また100億円キャンペーンを実施し、5月からは「ワクワクペイペイ」というキャンペーンを展開しています。

今日はBonfire Backendということで、開発スタイルについてもお話ししたいと思います。

PayPayではアジャイル型の開発スタイルを採用しており、非常にスピードを重視しています。週1のスプリントで開発を進めていまして、毎週社内で新機能のデモをやっています。サービスを開始してからもうすぐで10ヶ月が経過しますが、10ヶ月でアプリを50回以上リリースしております。

開発環境はとてもグローバルです。

外国籍のエンジニアが40パーセント以上なので、半分弱が外国籍のエンジニアとなっております。ベンチャーマインドのあるフラットな組織で、スーパーフレックスなど、自由な働き方ができる環境です。できたばかりのサービスなので、開発環境についても最新のものを使っています。こちらはこのあともう少し詳しくお話させていただきます。

PayPayのマイクロサービス

今日はけっこうこの話が出てきているので「また?」という感じだと思いますが、PayPayでもマイクロサービスアーキテクチャを採用しています。

PayPayではだいたい60以上のサービスがあり、チームは7チーム以上で開発をしております。サービスごとに独立したリソースを持っていて、サービス間はAPIやKafkaを使った非同期のメッセージでやり取りをしています。

小さい図になってしまいますが、具体的にはこのようなシステムになっています。

詳細はお話できないのですが、1番上にあるのがユーザのアプリと加盟店のPOSの端末など、そしてマーチャント様が利用されるツールです。例えばユーザがアプリからクレジットカードを登録した場合、非常にセンシティブな情報なので、そのデータはPayPayには一切保存をしていません。すべてYahoo! JAPANのセキュアな環境に保存されています。

今日は図の真ん中にあるCLMについて僕からお話をさせていただき、ペイメントのコアのシステムについてはHarshから説明させていただきます。

PayPayの技術スタック

技術スタックです。

今日はバックエンドのことしかお話ししませんが、バックエンドアプリケーションとしてはJavaやKotlinとSpring Bootがほとんどです。一部ScalaとAkka Streams、Node.jsでも実装をしています。PythonやGoで実装しているツールもあり、アプリケーションはすべてDockernizeされています。

CI/CDやDevOpsについては、JenkinsとKubernetesでオーケストレーションしています。AWSの構成管理はTerraformで行っています。ストレージはAmazon RDSとAurora、それから最近はTiDBの導入も進めています。

キャッシングにはRedisを使っていて、Cassandraも使っています。マイクロサービスの文脈ですと、コミュニケーションにApache Kafkaを採用し、Rate limitにOpenResty、サーキットブレーカーにResilience4j、トレーシングにZipkin、ロギングにFluentd、ElasticSearch、Kibanaを使っています。モニタリングにはDatadog・PagerDuty・NewRelic・VividCortexですね。そういったものを使っております。

キャンペーン・キャッシュバックエンジンにおける課題

それでは、ここから「100億を支える技術」と題しまして、僕の担当しているキャンペーン・キャッシュバックエンジンの裏側についてお話させていただきます。

ここにあるものは一部で、実はもっとたくさんのキャンペーンを実施しています。今は常に10個以上のキャンペーンが動いていて、それぞれのキャンペーンには条件ごとに種類もいくつかあるので、それも考慮すると40から50程度のキャンペーンが常に動いている状態です。

PayPayのキャンペーンについて、右側の図が決済完了画面です。

赤い枠の部分に付与額と付与予定日を表示しております。決済完了時には、どのキャンペーンを適用していくら付与するか、自動で決める必要があります。これを実現するためには課題がいくつかあります。

まず、各決済完了後数秒以内に処理を完了させる必要があります。PayPayではスキャン画面でQRコードを読み取って金額を入力して支払いボタンを押すと、先ほどの決済完了画面に遷移します。この間にキャンペーンの適用とキャッシュバック金額の計算を行わなければいけないので、バッチ処理では対応が難しいです。また、障害が起きたときに復旧してまたすぐに表示できるようにするといった対応も必要です。

また、先ほどお話した通りキャンペーンが非常に多く、それぞれのキャンペーンにもいろいろな条件があります。例えば時間や金額、ユーザ情報、加盟店の情報・支払い方法・上限金額といった条件があります。こういったものを判断するロジックによって、計算量が膨大になります。

また、ユーザのアクションに影響を与えてはいけません。仮に、万が一キャッシュバックエンジンが障害を起こしてしまったとしても、ユーザが決済できないのはまずいので、ユーザの決済やその他のアクションに依存関係を持たせてしまってはいけません。

PayPayのキャッシュバックエンジン

そこでPayPayのキャッシュバックエンジンは、イベント駆動な非同期のストリームシステムとして実装しております。

スライドの左側にAcquining・Payment・UserModuleと書いていますが、こういった他のサービスからのインターフェースはKafkaのみを利用しております。キャッシュバックエンジンでの処理結果のイベントも、KafkaにPublishしています。

では、Kafkaについて簡単にご説明します。

Kafkaはオープンソースの分散ストリーミングプラットフォームで、大規模なデータを分散環境で高速に処理することができます。また、システム間のデータの受け渡しを仲介するメッセージングシステムとして機能します。

パーティションを使うことで、順序付けも保証しながら負荷分散をすることができます。クラスタにマシンを追加すれば、スケールアウトをすることもできます。具体的には下の図のようにProducersとConsumersでストリーミングもできますし、データベースにつなぐこともできます。

もう1回PayPayのキャッシュバックエンジンに戻ります。

他のサービスはKafkaにイベントをPublishすることだけを意識すればよいので、そのあとのキャッシュバックの操作については一切関心を持ちません。逆にキャッシュバックエンジンはKafkaからSubscribeしてきたイベントだけを意識するので、Kafkaから流れてくるユーザアクションを、イベント駆動なストリームシステムとして実装することが可能です。

そのようなシステムを実装するにあたって、PayPayのキャッシュバックエンジンではScalaとAkka Streamsを採用しております。

Akkaの特徴

では、Akkaについてご説明します。

Akkaはアクターモデルを取り入れた分散・並行処理のツールキットで、低レベルなマルチスレッドプログラミングを行うことなく、分散・並行処理を実装できるものです。

ビジネス的にリアクティブなシステムを必要とする環境に適しています。リアクティブなシステムについては「リアクティブ宣言」というものがありまして、リアクティブ宣言にはこの4つの特性が挙げられています。1つ目が即応性です。システムは可能な限り速やかに応答する。なので、ユーザが何かアクションとかをした場合にすぐに応答できる必要があります。

2つ目は耐障害性。システムは障害に直面しても即応性を保ち続ける。仮に何か障害が起きてしまったときにシステムを全断してしまうと即応性を保つことができなくなるので、こういった耐障害性を備える必要があります。

3つ目は弾力性。システムはワークロードが変動しても即応性を保ち続ける。なので、例えば決済やユーザアクションが急に増えてシステムの負荷が非常に上がった場合に、処理を遅延させてしまったり、全断させてしまったりすると即応性がまた保てなくなるので、弾力性も重要になります。

最後にメッセージ駆動。リアクティブシステムは非同期なメッセージパッシングによってコンポーネント間の境界を確立する。こういった即応性・耐障害性・弾力性というものを実現するための手段として、メッセージ駆動が挙げられます。

先ほど僕から紹介させていただいたキャンペーンシステムの課題は、まさにこういったところが求められています。決済完了後数秒以内に結果を表示しなければいけない即応性が求められていますし、計算量が非常に膨大になってしまっても弾力性を保持しなければいけないですし、他にユーザのアクションに影響を与えてはいけないので、耐障害性も保つ必要があります。

Akka StreamsはAkkaライブラリの1つです。ストリーム処理を定式化するための直感的で安全な方法を提供しています。再利用可能なパーツを下の図のように柔軟なパイプラインでまとめて、ストリーム処理を簡単に書くことができます。イベントストリームを限られたリソース内で効率的に処理することができて、外部の非同期サービスとも柔軟に相互接続ができる仕組みになっています。

このAkka StreamsはReactive Streamsの仕様に準拠しています。Reactive Streamsというのは、ノンブロッキングなback pressureが可能な非同期システム処理の標準的な仕様のことで、back pressureというのはイベントをConsumeする側が自身の処理能力をPublisher側に伝えることで、無駄なくSubscriberが処理できる仕組みです。つまり、処理ができない量を受け取らないようにするものです。

現行システムの利点と課題

最後に現行のシステムの運用上の利点について紹介します。

Kafkaをインターフェースとしているので、例えばアプリケーションを新しい環境へ移行したい、本番環境で移行したいという要件があった場合に、新しい環境で同じKafkaのConsumer Groupを使うことで一部のイベントだけを新しい環境に流すことが可能になります。

そうすると新しい環境で動作の確認ができるので、そこで問題がなければ古い環境のConsumerを止めるだけで新しい環境にすべてのリクエストが流れます。なので、まったく遅延させずにアプリケーションの環境を移行することが可能になります。

また、他のサービスに依存していないのでメンテナンス無しでDBのマイグレーションもできます。例えば、最近Amazon RDSからAmazon Auroraへのマイグレーションを計画したのですが、KafkaのConsumerを一旦止めてしまえばDBへの書き込みはすべて止まります。

なので、その状態で新しいDNSへアプリケーションからの向き先を変えてConsumerを再開すれば新しいDBを使ってサービスが再開できます。どうしても作業中にKafka内で遅延が発生してしまいますが、Consumerの再開後にはすぐに反映されるので、とくにメンテナンスは不要になります。

現行システムの課題としては、パフォーマンスの改善についてです。Akka Streamsは非常に便利なので、それほど意識しなくてもそれなりにパフォーマンスが出てしまいます。ただ、現在ユーザ数も決済数も非常に増えてきているので、こういったチューニングが必要であると考えています。

Akkaには内部のパフォーマンスを可視化する仕組みがあるので、まずは可視化をしてボトルネックを探したいと思っています。あとはAkkaの強みの1つとして分散システムを簡単に構築できるので、そういったところも検討しています。

以上になります。ご清聴ありがとうございました。

(会場拍手)