こんにちは、ショッピング統括本部の川口です。
本記事では、2022年2月から3月にかけて開催された超PayPay祭の事例をもとに、クラウドネイティブ環境におけるJavaチューニングの進め方について解説します。
本記事の内容は2022年6月19日に開催された、JJUG 2022 Springで発表した内容をベースにしています。
Yahoo!ショッピングにおける超PayPay祭は高負荷
超PayPay祭は全国のPayPay加盟店とオンラインショップでお得に買い物をお楽しみいただける大規模キャンペーンです。Yahoo!ショッピングにおいても、PayPayポイントがもらえるキャンペーンなどさまざまなお得な施策が実施されます。超PayPay祭では普段よりもとてもお得にお買い物ができるので、非常に多くのユーザーがYahoo!ショッピングに訪れます。
こちらは超PayPay祭期間中にYahoo!ショッピング内に掲載している混雑予想時間です。特に開始直後・終了間際の0時付近に負荷が集中していることがわかります。過去にはこの大きな負荷にシステムが耐えられず、長時間にわたり注文できない障害を起こしてしまったこともありました。
Yahoo!ショッピングの負荷対策
二度とこのような事故を起こさないため、Yahoo!ショッピングでは「全体負荷試験」「常時負荷試験」の2種類の負荷試験を実施しています。
全体負荷試験は超PayPay祭などの大型キャンペーン実施前に行う試験で、想定される高負荷に対して問題なく機能が提供し続けられることを確認しています。常時負荷試験は全体負荷試験の7割程度の負荷を毎週かけることで新たに発生した性能問題の早期発見を行います。これらのテストはいずれも深夜帯に本番環境で実施しています。
今回はクラウドネイティブ環境における性能検証の実例として、2022年2月から3月にかけて開催された超PayPay祭の事例を紹介します。
クラウドネイティブ環境における性能検証の進め方
クラウド環境上で複数のマイクロサービスを連携してシステムを構成する、いわゆるクラウドネイティブといわれる考え方が近年大きく流行しています。その一方で、クラウドネイティブ環境はモノリスなシステムに比べ関連するコンポーネントが多く複雑なことから、問題が発生したときにどこに原因があるかわからない状態に陥ることがよくあります。
そこで、クラウドネイティブ環境においては、「メトリクス」「トレース」「ログ」 の3つの情報を活用して問題の検証を進めていく必要があります。
チューニング対象のシステム紹介
本記事で取り上げるチューニング対象のシステムはポイント計算APIというものです。
Yahoo!ショッピングで開催されるさまざまなキャンペーンでもらえるPayPayポイントを計算して返却するAPIです。「5のつく日」や「Yahoo!プレミアム会員特典+2%」などのキャンペーンが代表的なものですね。APIから返却したPayPayポイントは商品詳細画面、検索画面、カート一覧画面などさまざまな場所に表示しています。
ポイント計算APIはKubernetes上に構築しています。
Yahoo!ショッピング等のサイトからリクエストを受ける際に認証用のSidecarを経由する構成としています。
認証用Sidecarはリクエスト元が適切なシステムかどうか判定して、問題なければポイント計算API本体までリクエストを流します。このSidecarは全社標準のものを使用しており、ヤフー内のさまざまなサービスで同じものが使用されています。
負荷試験で生じた問題とその対応
2022年2月から3月にかけて開催された超PayPay祭では、予想される高負荷に備えて3回の全体負荷試験を実施しました。それぞれの負荷試験の結果と生じた問題への対応を紹介します。全体負荷試験でのポイント計算APIの担保すべき負荷量は20,000rpsでした。
1回目の全体負荷試験
1回目の全体負荷試験では目標20,000rpsに対し5,000rpsほどで大きく性能が劣化する結果となってしまいました。Dynatraceで原因を分析したところ、メモリのチューニング不足でFullGCが頻発していることが判明しました。
そこで、FullGCを抑制しつつ性能を向上させるため次の2つの対応を実施しました。
- GCアルゴリズムをG1GCからCMS GCに変更
- MaxTenuringThreshold値のチューニング
以下、それぞれの対応について解説します。
GCアルゴリズムをG1GCからCMS GCに変更
G1GCはJava9以降のデフォルトGCアルゴリズムですが、高いパフォーマンスを発揮するためにはヒープを潤沢に必要とします。目安となるメモリ量は文献によって異なりますが、Oracleの公式ドキュメントには6GB以上の記載がありました。
当時、ポイント計算APIで使用していたヒープサイズは1.7GB程度であり、6GBまで拡張できるリソースがクラスタ内で確保できませんでした。そこで、少ないヒープサイズでも比較的性能の出やすいCMS GCを採用することにしました。(CMS GCはJava9で非推奨となり、Java14で削除されています)
MaxTenuringThreshold値のチューニング
続いて直接の性能劣化の原因であるFullGC削減に向けた検討を行いました。
FullGCはヒープ領域のうちOld領域が枯渇することによって発生するため、Old領域ではなく、なるべくNew領域でGCを完結させることを考えました。まず、New領域とOld領域の割合を調整し、New領域の割合を相対的に増やしました。
続いて、MaxTenuringThreshold値を調整しました。MaxTenuringThresholdは、この値で指定する回数のマイナーGCを超えて生き残ったオブジェクトがOld領域に移動するという数値です。ポイント計算APIはステートレスなAPIであり、長期間GCを生き残るオブジェクトは少ないと考え、この値をデフォルトの6から最大値の15に増やしました。
その結果、単体の性能試験ではFullGCが発生しなくなったことが確認できました。
左がチューニング前、右がチューニング後の画像です。一番下のグラフがOld領域のGC状況となっており、棒グラフがGCの実行時間を表しています。チューニング後ではGCの実行時間がほとんどなくなっているのが確認できます。
2回目の全体負荷試験
これらのチューニングを行い2回目の全体負荷試験を迎えました。1回目の負荷試験よりも負荷に耐えられたものの、約12,000rpsで性能劣化してしまいました。前回と同様にFullGCが起きていたことを疑って確認したものの、今回はFullGCが起きていなかったことが確認できました。そこで、GC以外のポイント計算APIのメトリクスの確認を続けましたが原因がつかめず、調査が難航しました。
多くの方々にご協力いただき時間をかけて調査をしたところ、思わぬところに原因があることがわかりました。なんとポイント計算API本体ではなく、その手前にある認証用Sidecarがメモリ不足によってダウンしていたのです。
原因箇所がわかったので、対策の検討を進めました。今回は使えるリソースが限られていることもあり、メモリ割り当ての引き上げではなく、Sidecarの代わりに同等の機能を持つクライアントライブラリを適用することにしました。
3回目の全体負荷試験と超PayPay祭本番
これらのチューニングにより、3回目の全体負荷試験は目標の20,000rpsをクリアできました。また、超PayPay祭本番も問題なく終えられました。
まとめ
今回は複数の性能問題が発生しましたが、あらかじめ「メトリクス」「ログ」「トレース」の情報をしっかり取得していたことで、原因の特定から対策を期限内に終えられました。一方、2回目の全体負荷試験で発生したSidecarの割り当てメモリ不足については、ポイント計算API本体に問題があるはずという先入観から、原因の特定に時間を要してしまいました。
クラウドネイティブ環境においては先入観にとらわれず、全ての構成要素のメトリクスを確認することが重要だと改めて感じました。
いかがだったでしょうか? JJUG 2022 Springの登壇アーカイブ動画もありますので、よろしければご覧ください。
Kubernetes Logoは、Creative Commons Attribution 4.0 Internationalライセンス条件の下で、Linux Foundationにより、ライセンスされています。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました
- 川口 貴之
- バックエンドエンジニア
- Yahoo!ショッピングで開催されるさまざまなお得なキャンペーンのPayPayポイントの計算・付与を行うシステムの開発を行っています。