継続的Webサービス改善ガイド

第3章パフォーマンスの改善~現状を可視化し、トップダウン/ボトムアップでアプローチする

パフォーマンス面での変化

Webサービスが成長するにつれ、パフォーマンス改善を行わなければならない局面が必ず立ち現れてきます。より複雑な要求に基づく機能の追加、アクセス数の増加、データ量の増大などによるものです。本章では、筆者の勤務先であるpaperboy&co.(以下、ペパボ)での取り組みを紹介します。

まずはじめにパフォーマンス改善とは何か、どのように改善するのかという基本的な考え方について説明し、次にその考え方に基づいて取り組んだ実践の詳細について述べます。

ブクログのパブー

ブクログのパブー ⁠以下、パブー)は、ペパボの子会社であるブクログが運営する電子書籍作成・販売プラットフォームです図1⁠。このサイト1つで電子書籍の作成から販売、さらには外部の電子書籍ストアへの出版すらも可能な、まさに「プラットフォーム」と言うべきサービスを提供しています。

図1 ブクログのパブー
図1 ブクログのパブー

電子書籍N年

「電子書籍元年」という言葉が巷間(こうかん)とりざたされるようになってから、何年経ったでしょうか。ようやく日本でもAmazonのKindleが販売開始されたことで、電子書籍の普及も本格化し始めてきたようです。そのような状況下、おかげさまでパブーも、アクセス数の増加とともに販売数も順調に伸びてきました。

サービスが重い

そんな中、お客様から「ページの表示が重い」という声がちらほら聞こえ始めました。Twitterでも「パブーを開こうとするとしばらく待たされる」という声が上がり始めました。実際に自分でアクセスしてみても、レスポンスが遅いのは明白ですし、数値を計測してみても状況が悪いことがわかりました。

パフォーマンス改善の必要性

ECサイトのパフォーマンス

いちユーザとしての個人的な体験に照らし合わせてみてもわかるとおり、重いサービスのレスポンスをただ待ち続けるのは苦痛ですし、時にはサイトの表示を待たずにブラウザのタブを閉じてしまうこともあるでしょう。また、広い意味ではパブーも含まれるECサイトにとって、レスポンス速度と売り上げの相関はよく知られた事実です。

お客様へのより高い価値を提供するためにも、売り上げを落とさないためにも、なんとしてもパフォーマンスを改善しなければなりません。

継続的な改善

今回は、お客様の声に主に駆動される形で改善に取り組み始めたわけですが、本来なら、お客様に不便を感じさせてしまう前に、あらかじめサイトの変調を予期して改善にあたるべきでした。

そのためには、現状を改善するとともに、レスポンスの可視化・モニタリングを通して、継続的に改善できるしくみを整える必要があります。

パフォーマンス改善の費用対効果

どんなサイトにもいつかは必要になるパフォーマンス改善も、エンジニアのリソースを使って行うからには、費用対効果を考え、できるだけリターンの大きい方法を採らなければなりません。とはいえ、何がパフォーマンス改善にとっての「リターン」なのかは、一概にこれと決められるものではないのが難しいところです。

誰にとっての改善?

パブーには、ユーザが自著を作成するCGMConsumer Generated Mediaとしての側面もあります。CGM系のサービスの場合、サイトを利用するユーザは大きく2つに分かれます。

  • コンテンツを発信する側
  • コンテンツを閲覧する側

どちらの側に対しても改善できればそれに越したことはないのですが、両方を同時に満足させる施策を実施することはなかなか困難です。

コンテンツ発信側

コンテンツ発信側に対して主に影響するパフォーマンス劣化は、それらのユーザにとって便利な機能をどんどん追加していった結果、処理が非効率になったということが多いでしょう。たとえば、管理画面の機能強化や、サイト内アクティビティ通知機能の追加などがその例です。

CGM系サービスの性質上、この側面におけるアクセス数はサイト全体のアクセス数から比べると少なくなりますが、顧客満足度や定着率などのKPIKeyPerformance Indicator重要業績評価指標)に関わってくるため、早急な改善を要します。

コンテンツ閲覧側

CGM系サービスの場合、検索サービスからの訪問者など、サイトの直接のユーザではない閲覧者に対するパフォーマンス改善も重要になってきます。レスポンス速度とサイトへの流入・回遊率には一般に相関が見られ、サイトへの流入・回遊が多ければ多いほど、コンテンツ発信者のモチベーションも上がりますし、メディアとしての広告価値も高まるからです。

また、こちらの側面のほうがアクセス数の割合としては多くを占めるのが普通でしょう。こちらはこちらで、コンテンツ発信側への改善と同様に、力を入れた取り組みを要します。

現状を把握する

前述のどちら側への対応を行うにせよ、パフォーマンス改善をするからには、まずは現状を正しく把握する必要があります。本節では状況の可視化の取り組みについて、アプリケーションでログを吐くところから、Fluentdによるログの収集・集約、そしてGrowthForecastによるグラフ化までを説明します。

理解のために対象を分類する

「状況を可視化する」とひと口に言っても、単にすべてが見えるようになっていればよいというわけではありません。構造化されない五月雨的な可視化は、単なる混沌と変わりません。まずは、ひとめで状況を把握できる程度に対象を分類する必要があります。

URLのグルーピング

ここでは、サイトの意味的なまとまりをURLごとにグルーピングして、サイトの全体性を容易に理解可能な単位に分割します。

パブーの場合、次のように分類しました。

  • 読むread⁠:電子書籍を読むためのページ群
  • 買うbuy⁠:買うためのページ群
  • 探すfind⁠:探すためのページ群
  • 書く(write⁠⁠:書くためのページ群
  • 交流communication⁠⁠:作者、読者の交流のためのページ群
  • 書籍bookinfo⁠⁠:書籍情報ページ
  • トップtop⁠⁠:トップページ
  • その他others⁠⁠:上記のいずれにも当てはまらないページ群

レスポンスタイムをログに吐く

さて、上記のようにURLのグルーピングを行ったら、今度はそのグループごとにレスポンスタイムを記録します。パブーでは、リバースプロキシの裏側に配置された複数台のApache+mod_phpによってアプリケーションを実行しています。今回はそのApacheのレスポンスタイムを、ログに記録して集約しました。

Apacheのレスポンスタイムを記録する

Apacheのレスポンスタイムをログに記録するには、特定のログフォーマットの設定をApacheの設定ファイルに施す必要があります。レスポンスタイムのフォーマットには次の2つがあります。

  • %D:リクエストを処理するのにかかった時間を、マイクロ秒単位で記録する
  • %T:上記と同様の数値を、秒単位で記録する

ここでは前者の%Dを用いることにしました。

どのURLグループへのアクセスなのかを記録する

また、上記のレスポンスタイムがどのURLグループへのアクセスなのかを記録する必要があります。PHPにはapache_note関数という、Apacheへのリクエスト中のデータを、モジュール間で受け渡すために使われるnote領域に記録するための関数が用意されているので、これを使うのが簡単です。

// $group に、上記のURLグループのうちのどれかが格納されているapache_note("group", $group);

アクセスされたURLに基づいて、それがどのグループに属するかを判定し、上記のようにnote領域する具体的なコードについては、利用しているフレームワークによって事情が異なるでしょうから、ここでは詳述しません。上記のようなコードを、リクエストの最後に必ず実行されるような個所に追加しておくとよいでしょう。

Apacheのログフォーマット

URLグループとレスポンスタイムをログに記録するための、Apacheのログフォーマット設定例は次のとおりです。

LogFormat "time:%t\thost:%h\tmethod:%m\tpath:%U%q\tversion:%H\tstatus:%>s\tsize:%b\treferer:%{Referer}i\tua:%{User-Agent}i\trestime:%D\tgroup:%{group}n" ltsv

ここでは、後述するログ収集時に扱いやすいよう、LTSVLabeled Tab-Separated Valuesフォーマットでログを書き出すように設定しています。LTSVフォーマットについては、WEB+DB PRESS Vol.74「LTSVでログ活用 ─⁠─ 拡張性の高いフォーマットで柔軟解析」を参照してください。

レスポンスタイムの%Dについては先述のとおりです。URLグループは、groupというキーにひもづくnoteの内容を取り出すための設定%{group}nを用いて、ログに記録しています。

Fluentdでログを収集・集約する

Fluentdは、Treasure Dataが中心となって開発しているオープンソースのログ収集・集約用ツールです。

上述の設定でそれぞれのアプリケーションサーバで記録したログを、それらのサーバに同居するログ収集用Fluentd で拾いあげ、ログ集約用の中間Fluentdに送信します図2⁠。

図2  状況可視化システムの構成図

図2 状況可視化システムの構成図

ログ収集用の設定

ログ収集用のFluentdの設定は非常に単純です。

  • in_tailプラグインでApacheのログをパースする
  • out_forwardプラグインで集約用Fluentdにログを送信する

具体的にはリスト1のようになります。LTSVに従ったログフォーマットで記録しておけば、このように簡単な設定でログを収集できます。

リスト1 ログ収集用のFluentdの設定
<source>
  type     tail
  path     /var/log/httpd/access_log
  pos_file /var/log/td-agent/httpd.access_log.pos
  tag      forward.app.httpd.access
  format   ltsv
</source>

<match forward.**>
  type forward

  <server>
    host log.example.com
    port 24224
  </server>

  buffer_type file
  buffer_path /var/log/td-agent/buffer/forward
</match>

ログ集約用の設定

ログ集約Fluentdのほうは、収集用Fluentdよりはいくらか設定が複雑です。次のことを行う設定を施しています。

以上を実行するログ集約Fluentdの設定は、リスト2のとおりです。

リスト2 ログ集約用のFluentdの設定
<source>
  type forward
  port 24224
</source>

<match forward.app.httpd.access>
  type rewrite

  remove_prefix forward.app.httpd.access
  add_prefix filtered

  # (1)可視化する必要のないログを無視する
  <rule>
    key path
    pattern ^\/(?:image|css|js|favicon)
    ignore true
  </rule>

  <rule>
    key response_time
    pattern ^$
    ignore true
  </rule>

  <rule>
    key status
    pattern ^(?!200)\\d+$
    ignore true
  </rule>

  <rule>
    key method
    pattern ^(?!GET).+$
    ignore true
  </rule>

  # (2)ログを、URL グループごとに分けて処理する
  <rule>
    key uriGroup
    pattern ^(.+)$
    append_to_tag true
    fallback other
  </rule>
</match>

# (3)マイクロ秒単位の数値を、ミリ秒単位の数値に変換する
<match filtered.**>
  type forest
  subtype amplifier_filter
  remove_prefix flattened

  <template>
    add_prefix amplified
    ratio 1000
    key_names response_time
  </template>
</match>

# (4)URL グループごとに、平均値、最小・最大値、パーセンタイル値を計算
<match amplified.**>
  type forest
  subtype numeric_monitor
  remove_prefix amplified

  <template>
    unit minute
    tag response_time.__TAG__
    aggregate tag
    monitor_key response_time
    percentiles 90,95
  </template>
</match>

# (5)その数値を、URL グループごとにGrowthForecast に送信しグラフ化する
<match response_time.**>
  type forest
  subtype growthforecast

  <template>
    gfapi_url http://graph.example.com:5125/api/
    service puboo
    name_key_pattern .*_(avg|max|min|num|percentile_\d+)
    tag_for section
    remove_prefix response_time
  </template>
</match>

GrowthForecastによる可視化

このようにして収集・集約されたログは、最終的にGrowthForecastによりグラフ化されます図3図4⁠。GrowthForecastは、長野雅広氏によって開発されている、扱いが非常に簡単で、必要十分な機能のそろったオープンソースのグラフツールです。

図3 実際に記録されたグラフ(一覧)
図3 実際に記録されたグラフ(一覧)
図4 実際に記録されたグラフ(詳細)
図4 実際に記録されたグラフ(詳細)

以上のような簡単な実装と設定によって、ログの記録から、収集・集約、可視化まで行うことができました。あとはこのグラフに現れる数値が改善するよう、ひたすらコードやシステム構成に手を加えていけばよいだけです。

さらに定量化を進める

上述した可視化施策により、サイト全体を適切に構造化したうえで、状況を把握できるようになりました。ここではもう一歩、さらに定量化を進めていく方法を紹介します。ここまでやって初めて、効率の良いパフォーマンス改善を行う一歩を踏み出すことができたと言えると思います。

グラフによる可視化の不十分な点

グラフによる可視化には、

  • ぱっと見ただけでビジュアルに状況がわかる
  • 時系列に沿って改善結果を継続的にモニタリングしやすい

というメリットがあります。しかし、サイト全体の状況を俯瞰(ふかん)したうえで、どこから改善に手をつければより効果の高い取り組みを行えるかという意思決定のためには、いま一つ役立ち難いと言えます。

全体の状況をスプレッドシートに落とし込む

そこで、前述のレスポンスタイムとURLグループが記録されたアクセスログをもとに、1日ごとに全体のアクセス状況とレスポンスタイムをGoogleスプレッドシートにまとめるようにしました図5⁠。

図5 全体状況のスプレッドシート
図5 全体状況のスプレッドシート

このシートは次のカラムからなります。

  • ① URLグループ
  • ② URLグループごとのレスポンスタイムの平均値
  • ③ URLグループごとのレスポンスタイムの合計
  • ④ URLグループごとのアクセス数
  • ⑤ サイト全体のレスポンスタイムの総計に占める③の割合

また、毎日の状況の変化を見落とすことがないように、IRCInternet Relay Chat経由で定期的に通知するようにもしています。

GitHubのGistにシートを作成するスクリプトを掲載していますので、参考にしてください。

優先順位を決定する

この手法の良い点は、どのURLグループをどれだけ改善すれば、サイト全体にとって何パーセントの改善になるかを、定量的に主張可能なところです。

今回このようなシートを作成したことで、どこから手をつければより良い改善ができるかについて、チームメンバー全員が、迷いなく意思を共有できました。もしこのシートがなかったとしたら、やみくもに手を動かし、細かいチューニングに時間を割いてみた結果、全体としては影響の小さい範囲の改善にとどまった、ということもあり得たでしょう。

改善戦略への2つのアプローチ

前節までで、Webサービスのパフォーマンスの現状について、可視化・定量化の具体的な方法を紹介しました。本節では、改善戦略のアプローチについてあらためて整理することで、考察の一助を提供します。そのアプローチには、大別すると次の2つがあります。

  • ボトムアップアプローチ
  • トップダウンアプローチ

ボトムアップアプローチ

ボトムアップアプローチとは、冒頭で紹介したユーザからの声などの、実感に基づく問題の発見を起点にパフォーマンス改善をしていこうという戦略です。また、体感的な指標だけでなく、遅いページに関するWebサーバのレスポンスタイムのログや、データベースサーバのスロークエリを指標として改善していくというのも、このアプローチに含まれます。

メリット/デメリット

Webサービスを実際に利用しているユーザが感じている問題を解決することは、その効果の高さはもちろん、ユーザへの価値提供にとって、なくてはならないことです。CGM系サービスにとって、直接の価値の源泉となるユーザのサービスへの満足度を増大させることは、至上命題と言ってよいでしょう。

ただ、表面化した問題ばかりにとらわれていると、⁠木を見て森を見ず」というような局所最適な改善にとどまってしまう危険性もあります。パフォーマンス問題は、えてして見えないところに存在することが往々にしてあるからです。

トップダウンアプローチ

トップダウンアプローチは、問題発見のプロセスにおいてボトムアップアプローチと異なります。このアプローチでは、あらかじめ問題があってそれに対処するのとは違い、サイト全体のパフォーマンスがどうなっているかを統合的に把握し、全体にとってより改善効果が高い問題を発見し、その解決を試みます。

つまり、サイト全体のアクセス傾向とその割合を把握し、定量的な意味で改善効果が高い個所から取り組む、ということです。

メリット/デメリット

このアプローチでは、局所最適に陥ることなく、サイト全体にとって影響の大きい改善が可能であることがメリットです。また、全体的なパフォーマンスの定量化を前提としていますので、改善結果も定量的に評価できます。

とはいえ、全体最適のみにとらわれて個々のユーザへの不便をかえりみないという結果に陥るなら、それはそれで問題でしょう。ボトムアップアプローチと組み合わせて、両面からバランス良く改善していくことが大切です。

PDCAサイクルをまわす

ボトムアップとトップダウンとでアプローチに違いはあるにせよ、パフォーマンス改善においてもPDCA(Plan:計画、Do:実行、Check:評価、Act:改善)サイクルを回すことにより、短いスパンでプロセスそのものの軌道修正を進めながら、少しずつ改善を行っていくことが重要です。

そのPDCAサイクルにおける改善指標として、先述したような可視化・定量化の取り組みが活きてきます。

  • URLグループごとのグラフ
  • 全体状況のスプレッドシート

これらの指標を各々の改善実施前と実施後とで比較することで、改善仮説の検証、施策の評価を行います。また、それぞれの指標は、毎日決まった時間にIRCに通知するなどして、継続してウォッチできるようなしくみを整えておくとよいでしょう。

本章のまとめ

推測するな、計測せよ

サーバ/インフラを支える技術注2に、⁠推測するな、計測せよ」というパフォーマンス改善における金言が紹介されています。計測なしの推測で改善を進めたところで、満足な成果を上げることは期待できません。推測は往々にして誤るものだからです。

本章では、可視化手法について説明するとともに、効率的な改善のための優先順位決定に役立つ手法も紹介しました。

改善を継続する

重要なのは、問題が起こったその場限りの改善ではありません。いったん改善を行ったとしても、Webサービスが成長を続ける限り、同じような問題が再び形を変え、より複雑化して現れてくるでしょう。そのときにまた効率的に改善を行えるよう、日ごろから継続的に指標をモニタリングし、必要とあらばすぐに、少しずつでも改善を続けていかなければなりません。

本章がそのための一助となれば幸いです。

おすすめ記事

記事・ニュース一覧