10ms以下のレスポンスタイムを支える継続的負荷テスト
この記事は、はてなエンジニアアドベントカレンダー2016の12月18日の記事です。
はてなエンジニアアドベントカレンダー2016を始めます - Hatena Developer Blog
昨日はid:ikesyoさんの「オープンソース活動への取り組み方」でした。
オープンソース活動への取り組み方 - Hatena Developer Blog
こんにちは。はてなでWebオペレーションエンジニアとして働いているid:taketo957です。
2016年の4月に新卒として入社してからは、社内の仮想化基盤のリソース最適化に取り組んでみたり、
社内の広告配信システムの刷新プロジェクトに関わってきました。
本記事では広告配信システムの刷新を行う中で取り組んだ負荷試験環境を構築する際に考えたことと「継続的にパフォーマンス改善を行うためにはどうすれば良いか」という問題意識の元で取り組んでいることを紹介します。
はじめに
はてなにおける広告配信システムとは何かについて簡単に説明します。 はてなブックマークなどのサービスにはPRタグが付与された広告枠が複数存在しています。 これら複数の広告枠のどこに、どの広告を、いつからいつまで掲載するかを管理し、それらに基づいて適切に配信を行うのがはてなにおける広告配信システムです。
広告配信システムに求められるもの
広告配信システムに対する要求には、以下のようなものが存在します。
高可用性…広告配信は何があっても止めてはいけない。はてなブックマークなどサービスのPV数に比例するリクエストを受けても落ちないようにする。
低レイテンシ…ユーザ体験を維持するために広告表示部分で時間をとってはならない。広告要求リクエストに対して、高速でレスポンスを返却する必要がある。
配信結果の保存…ユーザに広告が表示された、ユーザに広告がクリックされたといった情報の根拠になるログは欠損してはならない。
本記事では、上記3つの要求のうち「低レイテンシ」を実現するために行った取り組みと、それを継続的に行うために取り組んでいることについて紹介します。
低レイテンシを実現するために
低レイテンシを実現するためには、広告配信を行うアプリケーションサーバの性能を最大限に引き出す必要があります。
「アプリケーションサーバの性能を最大限に引き出す…」
短絡的ですが、ここから新卒の私は「要するにISUCONできる環境をつくれば良いのか」という結論に至りました。
ISUCONができる環境を言語化すると、以下のものが必要になります。
- 配信システムに負荷をかけられるもの
- 負荷をかけた結果をチームの全員で確認できるもの
これらを実現するために、負荷試験ツールのgatlingを用いました。 「広告を表示してからクリックする」などユーザの行動を柔軟に記述できるものである点、ScalaベースのDSLでシナリオを記述することができ、 固有の設定ファイルの記述方法を覚えなくて良くて開発チームにScalaを書ける人がいたという点、負荷試験の実行結果を詳細なHTMLレポートで出力してくれる点という3点からgatlingを選択しました。
負荷試験環境
負荷試験を行うにあたって、本番環境と同等のstaging環境、gatling用のサーバを準備しました。 また、gatlingは負荷試験を実行した結果をHTMLとして吐き出してくれるので、Nginxを通して結果を閲覧できるようにしました。 以下の図で全体のアーキテクチャと併せて今回の環境を示しています。
負荷試験のシナリオについて
実際の負荷試験において検証したパターンの一部を以下で紹介します。
- ユーザの行動
- 配信OKの広告を表示、広告をクリックしない
- 配信OKの広告を表示、広告クリックする
- 配信NGの広告に対して表示要求
- アクセス数のパターン(上記ユーザの行動に対して定義)
- ピークタイムに耐えられることを確認するために、現状の広告配信システムのピーク値を再現、短い時間実行
- 長期的に動作した際に異常が発生しないことを確認するために、現状の広告配信システムの平均的な値を再現、長時間実行
- 配信サーバ1台あたりの限界値を見極めるために、負荷を徐々に増加させていく
上記の環境とシナリオを用いて、負荷試験を行い、アプリケーションプロファイラを用いつつアプリケーションエンジニアとボトルネック改善を行っていきました。
改善内容の概要については、はてなのサーバ/インフラを支える技術〜2016年新卒編〜 / OSC Tokyo 2016 Fall // Speaker Deckで紹介しています。
印象的だったものの一つに、DBへの都度接続を行っていたのですがシステムコールやアプリケーションプロファイラから、この部分のコストが馬鹿にならないことが判明したために常時接続方式に変更したというものがあります。
DBへの接続方式に関しては、Webシステムにおけるデータベース接続アーキテクチャ概論 - ゆううきブログ が詳しいです。
結果
入念に負荷試験を行った結果、現状本番環境において99%ile 10ms以内での応答を実現しており、非常に安定して稼働しています。
開発チーム内でパフォーマンス改善を意識し続けるために
これまで、リリース前に取り組んだことを紹介してきました。 アプリケーションには継続的に変更が加わり続けるわけですが、そのような状況でもパフォーマンスを維持し続け、 パフォーマンスに影響する変更を本番に投入する前に検知するにはどうすれば良いのでしょうか。 先程も述べたように、広告配信は絶対に止めたくないので、デプロイしてからロールバックという選択肢は基本的にはありません。 一般的に言われていることですが、パフォーマンス改善は計測することから始まります。 この計測のプロセスを継続的に自動で行う仕組みがあれば良いと考え、CIのプロセスに負荷試験を取り込むということに取り組んでいます。
概要を以下の図に示します。
アイデアとしては非常に単純で、Jenkinsでビルドとテストが成功した場合にstaging環境へデプロイし、 その後で負荷試験用のシナリオをgatlingサーバにデプロイして負荷試験を実行し、実行結果画面のURLをSlackを通じて開発メンバーに通知するというものです。
負荷試験をCIに組み込むために
負荷試験をCIプロセスに取り込む際に考えられる障壁には以下のようなものが考えられます。
エンドポイントの一覧が必要になる
今回取り組んだ広告配信システムの特性の一つに、一般的なWebアプリケーションと比較してエンドポイントが少ないという特性がありました。 一般的なWebアプリケーションに取り込むことを考える場合には、エンドポイントの一覧を自動で生成するといった仕組みや、重い処理を行う部分に絞って負荷試験を行うというような回避策が考えられます。 現状取り組んでいる中での工夫点として、アプリケーションの変更に追従してエンドポイントの一覧を更新するという作業をしたくないので、 アプリケーション側で「データ生成とそれに対応するエンドポイントを返却するエンドポイント」を実装してもらい、 gatlingのJSON feeders という機能を使ってこの部分の自動化を試みています。
シナリオ記述のためのユーザの行動パターンの列挙
上記に関連しますが、エンドポイントの数が少ないために予想されるリクエストのパターンが非常に少ないという特性も存在しました。 そのため、予想されるリクエストのパターンを列挙し、シナリオを記述するということが可能になっているのだと思います。
また、負荷試験をCIに組み込むためにはアプリケーションの変化に追従して負荷試験用のシナリオもメンテナンスしていく必要があります。 今回の広告配信システムにおいては、上述の通り「広告を表示してからクリックする」というようなレスポンスに基づいて次のリクエストを組み立てるということを実現したかったのでgatlingを選択しました。 その為シナリオを記述するという作業が発生してしまいますが、単純にエンドポイントを一覧するだけで負荷試験を実施できるvegetaなど他にも様々な負荷試験ツールが存在するので、 Webアプリケーションに合わせて負荷試験ツールを選択することも重要です。
おわりに
はてなの広告配信サーバの性能を維持し続けるために、負荷試験を行いそれを継続的に行うための取り組みについて紹介しました。 負荷試験をリリースサイクルに組み込むことで、最新版のソースコードを本番環境のトラフィックにさらしても大丈夫かというテストが自動的にできることに非常に魅力を感じました。 不安を抱えながら祈るようにデプロイするのではなく、これなら大丈夫と安心してデプロイできるような環境を作っていきたいですね。
今回は仕組みの面にフォーカスして紹介させていただきましたが、またの機会に実際にどのようにしてチューニングをしていったかについても触れていきたいですね!
明日のアドベントカレンダーはid:masayoshiです!