PayPayの6年の歴史の中で、私たちは急速な成長を遂げてきました。現在、6700万人以上のユーザーにサービスを提供し、日々数百万件の決済を処理しています。しかし、これを成功させるためには、トラフィックと信頼性の要求を満たすための技術的な挑戦が伴います。

PayPayは設立当初からJavaとNodeJSを採用しており、これらの技術は非常にうまく機能してきました。しかし、成長に伴いサービスをスケールアップする必要が生じ、これによりKubernetesクラスターでのCPUとメモリの使用量が増加しました。これにはサーバーコストの増加が伴います。

2023年末、私たちはコアサービスでのトラフィックをより効率的に処理する方法を模索し始め、GraalVMやGo、Rustなどさまざまな技術を調査しました。Rustは、その優れたパフォーマンスとメモリの安全性が保証されていることから、PayPayの多くのユースケースにとって有力な選択肢として際立っていました。PayPayでRustを導入するまでの私たちの道のりを説明します。

動機

新しい言語を導入する前に、その導入が有益である理由を考える必要があります。PayPayでは、Rustを導入する主な動機が3つありました。

  • パフォーマンス
  • JVM管理の複雑さ
  • メモリと型の安全性

PayPayで必要とされる大規模な運用では、JavaやNodeJSではいくつかの制約が見えてきました。PayPayでは、高いパフォーマンス要件を満たすために、数百のKubernetesポッドにスケールする必要があるサービスが多々あります。Rustは、ガベージコレクションを伴う言語よりもこれらの要件を効率的に満たすことができます。

JVMを正しくかつ効率的に動作させるためのチューニングは、優れたエンジニアを抱えているにもかかわらず、PayPayにとっては課題でした。Rustを使用することで、JVMのチューニングやそれに付随する運用上の負担を管理する必要がなくなります。また、コンテナ対ヒープのメモリ使用量のモニタリングが正しく行われていないことが多く、問題発生時にはエンジニアが混乱する原因となっていました。Rustでは、JVMの深い知識がなくても、メモリ使用量のモニタリングがかなり楽になります。

また、Rustの強力な型システムにも着目しました。フィンテックにおいては、正確性が非常に重要であり、Rustの型システムがエラーハンドリングを改善し、データの破損といったリスクを減少させると考えています。

これらがRustを導入する主な理由ですが、Rustの優れた開発ツールや大規模なオープンソースエコシステムなど、他にも多くの理由があります。

PoC(概念実証)ステージ

多くのサービスをRustに移行する前に、Rustが私たちの抱える問題を解決できるかどうか確かめるために、小規模なPoCプロジェクトを計画しました。これにより、Rustによる開発がどのように進むかを体感し、本番環境でRustを運用する経験を積むことになります。このPoCの結果をもとに、組織全体で広範に使用する前に問題を特定し、方向性を検討することができます。

PoCを成功させるためには、以下を証明したいと考えました:

  1. Rustがリソース使用量に有意義な改善をもたらす。
  2. Rustが自社インフラにデプロイ可能で、大きな変更なしにうまく機能する。
  3. 本番環境のトラフィックでRustマイクロサービスを信頼性を持って運用できる。

PoCでは、いくつかのAPIゲートウェイをRustに置き換えることを決定しました。PayPayでは、APIゲートウェイパターンを使用して、フロントエンドクライアントからマイクロサービスを抽象化して隔離しています。APIゲートウェイは、レート制限、認証、リクエストのルーティング、データの集約など、さまざまなタスクを実行する複数のレイヤーで構成されています。APIゲートウェイは、すべてのフロントエンドリクエストを処理するにあたって大量のトラフィックが発生するため、PayPayの運用規模では多くのリソースを必要とします。多くのゲートウェイは、さまざまなPayPayサービスに共通するロジックを持っています。この共通ロジックを高性能モジュールに統合することは、Rustのユースケースとして適しており、実環境でRustを試すには良い機会と考えました。

以下は、ゲートウェイアーキテクチャの概要です。

PoCプロジェクトの目標

PoCを開始する際に、いくつかの大きなプロジェクト目標を設定したいと考えました。これらの目標は、システム設計における意思決定に役立ちます。

  • 信頼性が最優先事項です。単一障害点を避けたいと考えました。
  • サーバーコストを大幅に削減する。
  • 6ヶ月以内にPoCを完了し、大量のトラフィックを移行する(小規模なチームで行う)。
  • 他のチームによる他の機能開発を妨害しない。

設計

当時、ゲートウェイは2つのレイヤーで構成されていました。

  1. レート制限レイヤー: レート制限を行うNginxデプロイメント
  2. ビジネスロジックレイヤー: ルーティングやリクエストの集約、認証、ヘッダー正規化、メトリックス収集などを行うJavaデプロイメント

各ゲートウェイは、上記の共通ロジックを異なる方法で実装していました。私たちは、高性能でありながら、必要に応じてカスタマイズ可能な標準実装を提供したいと考えました。

プロトタイピングを経て、最初のPoCとして満足する設計に落ち着きました。レート制限のためにオリジナルのNginxデプロイメントを維持し、Java/Nodeレイヤーでそれを行う代わりに、共通のロジックにもNginxを使用することに決めました。リクエスト集約ロジックまで全て移植するには非常に時間がかかるため、それらのロジックはJava/Node JSのビジネスロジックレイヤーに残しました。

この設計を実現するために、既にRustで開発しておいた「Proxy Companion」と呼ばれるサイドカーサービスを用いました。Proxy Companionは、 Actix Webを用いた軽量のHTTPサーバーです。既にNginxでレート制限を行うロジックにLuaを使用していたため、NginxからProxy CompanionへLuaでHTTPリクエストを送ることで、両者を接続することにしました。

単独のKubernetesサービスではなくサイドカーコンテナを使用することにした理由はいくつかあります:

  1. 単一障害点がない、各Nginxポッドには専用のProxy Companionがある
  2. Kubernetesノード間のトラフィックを避ける。ポッドのすべてのコンテナは常に同じノード上にある

設計が確定した後、PoCの開発を開始しました。

PoC開発

Actix Webを使った新しいプロジェクトを作成し、開発を開始しました。ビジネスロジックの実装は比較的簡単で、あまり時間はかかりませんでしたが、いくつかの課題に直面しました。

これがPayPayでの最初のRustサービスだったため、多くのインフラやツールがRustを適切に扱う機能に欠けていました。ビルドとデプロイのCIを設定し、モニタリング機能を整備し、内部ライブラリをRustに移植するのにかなりの時間を費やしました。

開発中の最大の課題の一つは、PayPayのオブザーバビリティ基盤に対応するために様々なRustライブラリを適用することでした。RustはOpen Telemetryに対しての対応がすぐれており、これが非常に役に立ちましたが、比較的新しい標準であるため、すべてを正しくセットアップし、動作させるのに苦労しました。幸い、これは些細な問題に過ぎず、トラブルシューティングとテストを経て、最終的にRustサイドカーにトレーシング、メトリクス、ログを組み込みました。これを機会に、将来のRustサービス開発に向けて、オブザーバビリティ用のRustライブラリを社内向けに開発しました。

本番環境へ導入する前に、新しいゲートウェイのパフォーマンステストを行い、本番のワークロードを処理できることを確認したいと考えました。PayPayでは、定期的にパフォーマンステストを実施しているため、そのプロセスに則って検証しました。いくつかの本番シミュレーションを実行した結果、非常に良好な結果が得られたので、本番へのリリースの準備を開始しました。

最初のロールアウト

数ヶ月にわたる開発とテストの後、実際に使用できるプロトタイプが完成しました。PayPayの中でも比較的トラフィックの少ないサービスに対して新しいゲートウェイを導入し始めました。段階的なロールアウト手法を採用し、新しいゲートウェイを、1ヶ月かけて、いくつかのNginxのルートごとに、注意深くモニターしながら、ゆっくりと展開していきました。

すべてのトラフィックが移行された後の結果は良好でした:

Java Rust 差異
CPU 1.5 CPUs 0.15 CPUs 10倍の削減
メモリ 6GB 75MB 80倍の削減

CPUを1/10に削減し、メモリを1/80に削減したことで、新しいゲートウェイを他のサービスに展開することにより期待が持てました。また、ロールアウト中に問題が発生しなかったことで、サービスの信頼性がより明確になりました。

これはRustサービスの有意義な検証となりましたが、最初のサービスはPayPayの中でもトラフィックの少ないものの一つでした。すぐにより大規模なスケールを持つサービスに展開する必要がありました。

より多くのサービスへのロールアウト

最初のデプロイが成功したことで、次のサービスに着手しました。このサービスでは、さらに桁違いに多いトラフィックを処理することになるため、より大きなチャレンジとなります。ここでは、認証のみを処理していた冗長なNode JSサービスを完全に排除することで、アーキテクチャを簡素化することができました。

以前と同じロールアウト手法を採用し、NginxのルートごとにProxy Companionを段階的に有効化していきました。ロールアウト中に、Open Telemetryコレクターへのメトリクス送信において多少の問題が見られたものの、幸いなことにユーザーへの影響はなく、Rustサービスの設定を修正することで迅速に解決できました。

ロールアウトがトラフィックの100%に達した後の結果は非常に良好でした:

Node JS Rust 差異
CPU 40 CPUs 2.4 CPUs 16倍の削減
メモリ 24GB 240MB 100倍の削減

上記に示す通り、CPUとメモリの大幅な削減が見られ、Kubernetesデプロイメントをスケールダウンし、サーバコストを削減することができました。リソース使用量を削減しただけでなく、平均レイテンシーも約30%削減しました(主に高パーセンタイルの改善によるものです)。

トラフィックの40%へのロールアウト

トラフィックの100%へのロールアウト

このレイテンシーの改善は、いくつかの要因によるものです:

  • 不要なサービスを排除することによるネットワークホップの削減
  • Rustが以前のJavascript実装よりも優れていること

PoCの評価

ロールアウトが完了し、安定していることが確認された時点で、RustのPoCを終え、結果を評価するのに適切なタイミングであると判断しました。

まず、これまでに移行したサービスのリソース使用量の改善を評価しました。上記のメトリクスからわかるように、期待を上回る素晴らしい改善が見られました。Javaと比較してRustのメモリ使用量が非常に優れていることが確認できました。

次に、Rustを自社インフラにデプロイする際の難易度を検証しました。この点では、大きな問題は見られませんでした。いくつかの課題はありましたが、それらのほとんどは解決可能であり、既存のツール/インフラに大規模な変更を加える必要はありませんでした。私たちのRustソリューションにはまだ未熟な部分がありますが、それを上回る大きなメリットがあると判断しました。

最後に、これまでのRustサービスの信頼性を評価しました。これまでに大きな問題は発生しておらず、Rustゲートウェイからの5xxエラーはゼロという状況です。まだ改善の余地があると感じていますが、全体としてRustゲートウェイは非常に安定しています。

将来への展望

上記の検証結果を踏まえ、場面に応じてPayPayのサービスにRustを導入できると判断しました。

そこで、社内の他のチームが使用できるようにPoC実装をより汎用化する作業を始めました。さらに多くの社内ライブラリやツールをRustに移植し、アプリケーションチームが必要に応じて利用できるように整備を進めています。

PayPayの他の領域でもRustを使用することへの関心が高まっています。Rustの学習への興味も非常に高まっており、週に一度Rustを学ぶための社内学習グループを設けています。これまでのところ、エンジニアからは週次学習セッションについて非常に良いフィードバックを受けています。

現時点でも、いくつかのチームで新しいプロジェクトにRustを採用しています。また、新しいRustゲートウェイを他のPayPayサービスへ引き続き導入していっています。PayPayのRustエコシステムが構築され、チームによって採用されている今、PayPayは非常にエキサイティングな時期にあります。

Product Blogをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む