ゴールデンウィーク累計2,300万PVのアクセスを捌いた負荷対策 ~ アソビュー!が行った3つの手法 ~

こんにちは! アソビュー開発チームの山内です!

先日、自転車でしまなみ海道を渡ってきました。 途中、ゲリラ豪雨に遭いながらもなんとか75km完走しました。ゴール後の温泉は最高ですね。 次は、晴れた日に渡って温泉に入ろうと思います。

はじめに

アソビュー!では、2022年のゴールデンウィークの4/29 ~ 5/8の間に約2,300万のPVがありましたが、システムダウンすることなく、多くのゲストにワクワクを届けることができました。 今回は、私達がゴールデンウィークに向けて準備した背景と、アプリケーションでの負荷対策を紹介します。

背景

アソビュー!は、全国の遊びやレジャーチケットを予約・購入して体験できるサービスになります。 そのため、サービスの特性上、行楽シーズンの大型連休にアクセスが集中しやすくなっています。

また、今回は2022年4月15日〜2022年5月5日までの期間中に関東・東海・関西・九州の地域でTVCMを放映しました。 更には、2年ぶりの制約のないゴールデンウィークということもあり、今まで体験したことのない多くのアクセスがあることが予想されました。

日本国内で待ちに待った大型連休、アソビューのシステムダウンにより、ゲスト・パートナーに迷惑をかけることは絶対に起こしてはなりません。

インフラ側での負荷対策はもちろん、アプリケーション側でも負荷対策を行いました。

負荷対策について

アプリケーション側で行ったシステム負荷対策は主に以下の3つです。

  • OLTPのパフォーマンス改善

  • 各ページのCDN配信

  • APIタイムアウト設定・サーキットブレーカー設置

それぞれを紹介していきます。

OLTPのパフォーマンス改善

概要

データ構造の更新や、追加によりOLTP(オンライントランザクション処理)性能は劣化することがあります。 性能の劣化により、SQLのレイテンシが大きくなることで、負荷を増大させシステムダウンの原因となり得ます。

主要となる導線の各ページの全てのSQLの性能を確認し、改善の余地が無いかを検討しました。

どうやって行ったのか

レイテンシが大きい各SQLを確認し、以下のようなパターンでSQLを改善していきました。

1 indexが貼られているかどうか

参照しているテーブルの適切なカラムにindexが無いと、処理速度が遅くなることがあります。 indexを確認し、適切に設定されていない場合はindexを追加していきました。

2 不要な相関サブクエリがないか

相関サブクエリは、結果行に対して全てチェックしていくため、パフォーマンスが悪いと言われています。 また、EXSITS句も相関サブクエリを利用している場合においては、パフォーマンスが悪いケースがあります。 これらは内部結合に書き換え、不要な相関サブクエリの排除を行い、再度実行計画を確認して処理の改善をしていきました。

3 ユースケースや処理の見直し

改修が重なり、仕様が変更されるにつれ、そもそも不要なデータを取得している場合や、処理自体が不要になっている場合もあります。 SQLのみに着目するのではなく、そもそもの処理の見直しをすることで改善につながったケースもありました。

結果

パフォーマンス改善の一例を紹介します。 上記の改善を行うことで、多くのSQLの処理を改善することができました。 以下の例では、レイテンシが100ms ほどでしたが、改善後は 2msほどになっています。

各ページのCDN配信

概要

以前のアーキテクチャでは、Javaアプリケーションが直接リクエストを受けて、コンテンツを返却していました。 そのため、アプリケーション及びDBに大きな負荷がかかっている状態でした。

そこで、主要なページをURLパスごとにキャッシュし、CDN配信することで負荷を軽減させました。

どうやって行ったのか

アソビューはSSRでコンテンツを表示しております。 そのため、そのままページをキャッシュしてしまうと、以下のような問題が発生します。

  • 会員情報がそのままキャッシュされてしまう
  • Cookieで判定や処理していたものが動かなくなる
  • SpringMobileでデバイス判定していたので、初回にキャッシュされたデバイス表示でコンテンツが表示されてしまう
  • ページ内容を変更した際に、瞬時に反映されない

各問題に対して、以下のように解決しました。

  • 会員情報や、Cookieの問題

会員情報を取得するAPIを作成し、フロントから取得するように変更いたしました。 また、Cookieに関してもAPIで代替できるものでしたので、APIを作成し適用していきました。

  • SpringMobileでデバイス判定

こちらは、CloudFrontのヘッダーを条件とするPC/SPの表示切り替え対応を行いました。 具体的には、SpringMobileでデバイス判定をした後に、CloudFrontで判定した結果を上書きすることで解決できました。

  • ページ内容を変更した際に、瞬時に反映されない

社内システム等でページ内容を適用すると同時に、CloudFrontのinvalidationが走るようにすることで、瞬時にコンテンツを更新することができました。

結果

主要なページをCDN配信にすることで、オリジンへのアクセスを大幅に減少することができました。 アプリケーション、DBに直接アクセスが行かないことで、負荷を減少することができています。

CDN配信前後のオリジンへのアクセス件数

APIタイムアウト設定・サーキットブレーカー設置

概要

アプリケーション間や外部連携先との通信において、高負荷状態のためにアプリケーションのスレッドが枯渇し、クライアントからアクセスができないケースや、 そのアクセスが更に高負荷状態を促進させてしまう悪循環に陥ることがあります。

この状態を解決すべく、APIタイムアウトとサーキットブレーカーの導入を行いました。

どうやって行ったのか

APIタイムアウト設定

まず、アソビューのフロントとバックエンドから呼んでいるAPIのうち、タイムアウト設定がされていないものに対して設定していきました。

タイムアウトの設定値については、直近のAPMを参考に正常時ではタイムアウトが起きない値をAPIごとに設定しました。

以下、RestTemplateでの実装イメージです。

@Bean
RestTemplate restTemplate() {
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate restTemplate = restTemplateBuilder
            .setConnectTimeout(3000)
            .setReadTimeout(3000)
            .build();
    return restTemplate;
}

サーキットブレーカー

サーキットブレーカーとは、呼び出し先のアプリケーションが高負荷や障害等で一定のエラーやレスポンスがない際に、呼び出し元で判断して一定時間リクエストを遮断する仕組みです。 これは、障害の際に復旧するまでの期間にアクセスをしないことで、呼び出し先に不要な負荷をかけず復旧を早める狙いがあります。

アソビューから呼び出している複数のアプリケーションの呼び出し部分に対して、サーキットブレーカーを設置しました。

サーキットブレーカーは、リクエスト先アプリケーションの状態を以下3つのステータスで管理しています。

サーキットブレーカーの状態遷移

  • CLOSED

    正常な状態。直近のエラー数をカウントし、閾値を超えるとOPENに移行します。

  • OPEN

    エラーによりリクエストを遮断している状態。指定の時間を超えると、復旧の確認であるHALF-OPENに移行します。

  • HALF-OPEN

    システムが障害から復旧したかを確認している状態。少数のリクエストを行い成功すればCLOSEDに移行します。

これらのステータス変化の設定値を直近のAPMから計算して設定していきました。

以下、Resilience4jでの実装イメージです。

public CircuitBreakerConfig circuitBreakerConfig() {
    return CircuitBreakerConfig.custom()
            .failureRateThreshold(100)
            .slowCallRateThreshold(1000)
            .slowCallDurationThreshold(Duration.ofSeconds(10))
            .minimumNumberOfCalls(10)
            .slidingWindowSize(50)
            .waitDurationInOpenState(Duration.ofSeconds(60))
            .permittedNumberOfCallsInHalfOpenState(10)
            .automaticTransitionFromOpenToHalfOpenEnabled(false);
            .build();
}

結果

これらの設定の成果としては表しにくいのですが、 APIタイムアウト設定による高負荷状態の悪循環の防止や、万が一のためにサーキットブレーカー設定をしたことで、 高負荷状態の助長を防ぐことができ、大量のアクセスでもさばききれたのだと思います。

最後に

今回は、アプリケーションで行った負荷対策の一部を紹介しました。 参考になる情報があれば幸いです。

エンジニアチームとして、できることはまだまだあると思っております。 アソビュー!は、遊び、レジャーのインフラとして快適安心なサービスをゲスト・パートナーに届けれるよう、更に改善していきます!

アソビューでは「生きるに、遊びを。」をミッションに、一緒に働くメンバーを大募集しています! カジュアル面談もありますので、少しでも興味があればお気軽にご応募いただければと思います!

www.asoview.com