オールアバウトTech Blog

株式会社オールアバウトのエンジニアブログです。

サイトを37倍に高速化した7つの手法

今回はオールアバウトのnnmrが弊社サイトAll About Japanの速度を高速化した経緯についてまとめます。

All About Japanとは

そもそもAll About Japan(以下AAJ)とは何かといいますと、弊社が提供している訪日外国人向けの日本紹介サイトです。
外国人向けサイトで、英語、中国語(繁体字)、中国語(簡体字)、タイ語、韓国語の5か国語に対応しております。
「Anime」「Izakaya」「Ninja」といったような特集や、実際に観光する人向けのモデルルート記事が特色です。

■ 特集
特集記事一覧 (url : http://allabout-japan.com/en/tag/sushi/ )

■ モデルルート記事
モデルルート記事 (url : http://allabout-japan.com/en/article/222/ )

技術的な紹介

LAMP環境です。
(サーバー構成は後に記述します)
また、PHPフレームワークにLaravelを使用しております。

高速化したきっかけ

ある日のこと、

「最近、All About Japanが遅くない?」

という話が上がり、実際に調査を行ったところ、このような結果が出てきました。

重たかった頃のAll About Japanのグラフ

このグラフはNew Relic(後に説明します)によって出力したもので、フロント側のキャッシュを介さずにサーバーへ直接のアクセスがあった際の負荷を表しております。
つまり、キャッシュが切れたタイミングでだいたい4.5sかかっているということです。

もちろん、この数値が低いに越したことはありません。
ちなみに、社内の他webサービスがやれ200ms台で「ちょっと遅いかも」なんて言われているくらいでしたので、4.5sというのはあまりにおぞましい数字です。

このようなことから、平均速度100ms台を目指す長い旅が始まりました。

実践1 : SQL見直し

5か国語対応ということで薄々お気づきかもしれませんが、翻訳が必要な分だけ1つの記事を表示させるためのクエリが以下のように複雑になりがちです。

AAJの記事ページ

  • その記事のIDから翻訳データを取得
  • カテゴリを取得
  • カテゴリから関連記事を取得
  • 関連記事のID毎に翻訳データを取得
  • 関連記事に紐づくタグを関連記事の個数分取得
  • タグ毎に翻訳データを取得

実際にslow queryで確認したところ、10秒以上かかっているクエリを発見しました。
slow queryとは、処理に時間がかかっているクエリをログとして残してくれる機能で、MySQLを使用している場合に設定が可能です。

クエリが重たくなる理由といえば

  1. select * している
  2. indexを貼ってない
  3. そもそもの無駄なクエリの数が多い

等々、いろいろありますが、ここはindexを貼ってないことを疑ってみました。

indexが上手く動いているか否かはexplainで確認できます。
explainを出す方法は割と簡単で、調べたいクエリの頭に"explain"を加えて実行するだけです。
するとこのように結果が出力されます。

explain結果

explainの項目は大体このように確認しております。

  • type → ALLもしくはindexと表記されている場合、レコード数が膨大である場合は見なおした方が良いです。
  • possible_keys → indexで使えるキーが無いとNULLになります。
  • key,key_len,ref → いずれもindexで使えるキーが無いとNULLになります。
  • rows → 少ないほうが好ましいです。大きいほど負荷がかかります。
  • Extra → Using Temporary, Using Filesortと出ている場合はindex追加で見なおした方が良いです。

select_typeに"DEPENDENT SUBQUERY"と書かれていることが多いのはLaravelのアクティブレコード(Eloquent)を使用しているためです。
サブクエリはなるべく避けた方が速度改善に繋がりますが、今回の場合、Eloquentで記述した方がプログラムの見た目は非常に簡潔であるため、
Eloquentの実装を残しつつ、そこで発生したサブクエリはindexで対応するという方針で進めました。

結果

index追加結果

ご覧の通り、4.5s→1.0sまで落ち着きました。

〜ここで学んだこと〜

  • 記事のデータがある程度溜まったらslow queryのログを見よう。
  • slow queryの結果からexplainを使ってindexを追加できる箇所を検討してみよう。

実践2 : SQL見直し(その二)

実践1と同じく、SQLを見直しましたが、実践1と比較して異なることといえば、New Relicを活用したことが挙げられます。
初めの方からちょくちょく登場しているこのNew Relicですが、これは、サーバサイドアプリケーションのパフォーマンス測定のツールです。 一方で、クライアントサイドの速度の計測といえば、GTmetrixが代表的です。https://gtmetrix.com/

New Relic overview

New Relicの基本画面といえばお馴染みのこちら「OverView」というものがあります。
PHP」「MySQL」「Redis」という項目からどの負荷の比率が高いかを確認することができます。

New Relic transactions

負荷の比率をphpのclass、method毎に確認したい場合、「Transactions」が活躍します。
左側の「Transactions」を選択するとこの画面に遷移します。

New Relic transactions

今回の場合、緑色と黄色の項目が時間によって急に負荷が上がっていることが確認できます。

New Relic transactions

更に、左のcontrollerの項目を選択すると、method毎の負荷の内訳が確認できるため
controllerに負荷があるのか、model側に負荷があるのかが確認できます。

今回もmodel側に問題がありそうだったので、実践1と同様に該当するクエリにexplainをかけ、indexが追加出来る場所に追加して対応しました。

結果

index追加結果

ご覧の通り、MySQLのドカドカしている負荷がかなりなだらかになっていることが分かります。
ここで1000ms → 600msまで落ち着きました。

〜ここで学んだこと〜

サーバーサイドの負荷の確認にはNew Relicを活用しよう。

実践3 : サーバー構成見直し

SQLのアプローチによる速度改善のネタが出尽くしつつあるところ、サーバー構成そのものの見直しに踏み込んでみました。

サーバー構成の変遷

AAJはサーバー構成に変遷がありました。
最終的に一番右側の構成となっております。

All About Japanサーバー構成

メディアローンチ時、AAJを構成する全サーバーはオンプレミスでした。 この後、中国からの閲覧に対応するためWebサーバーとキャッシュサーバーのみクラウドへ移行しました。 一部がクラウド、一部がオンプレミスという構成にしていることの問題としては、ネットワークの状況によってサーバー間の通信ができなくなり、サイトが表示されなくなることが挙げられます。
このようなリスクを考慮し、オンプレミスならオンプレミスに、クラウドならクラウドに寄せたほうが良いということになり、全てのサーバーをクラウドに設置しました。

結果

移行前

All About Japanサーバー構成 変更前の負荷

移行後

All About Japanサーバー構成 変更後の負荷

上記のNew Relicの結果から、一番速度が改善されているのがRedis(KVS)であることが分かるかと思います。
旧サーバー構成ではwebサーバー(クラウド)からKVSサーバー(オンプレミス)へのアクセス時に通信コストが発生していました。
これを全てのサーバーをクラウド上に置くことで通信時間が短縮し、速度改善に繋がったと考えられます。

〜ここで学んだこと〜

サーバそのものの構成も速度に影響することを心得るべし。

実践4 : 使用しているフレームワークでできる速度改善

前途の通り、AAJにはフレームワークとしてLaravelを使用しておりますので、Laravelでできる速度改善を調べたところ、
config:cacheとroute:cacheというものがありました。

config:cache

laravelのconfig/app.phpにある内容をキャッシュしてくれるものです。
サーバー上の該当アプリ以下でコマンドを打つか、composer.jsonに記述してcomposer updateを行うことで適用できます。

php artisan config:cache

詳細はこちら : https://laravel.com/docs/5.0/configuration#configuration-caching

route:cache

laravelのapp/routes.phpに記述する、routingに関する設定をキャッシュしてくれるものです。
こちらもconfig:cacheと同様、サーバー上の該当アプリ以下でコマンドを打つか、composer.jsonに記述してcomposer updateを行うことで適用できます。

php artisan route:cache

詳細はこちら : https://laravel.com/docs/5.0/controllers#route-caching

検証は怠らないこと

今回、どれが効果的であったかを明確にするため、(1)config:cacheのみ、(2)route:cacheのみ、(3)config:cache,route:cache両方という組み合わせで検証を行いました。
なんとなく、キャッシュしてくれるならどちらもやっておいた方が良いだろうと考えがちですが、検証の結果では(1)のconfig:cacheのみが一番効果がありました。

結果

config:cache導入前

config:cache導入前

config:cache導入後

config:cache導入後

ページ速度(平均)がおよそ30ms改善しました。

〜ここで学んだこと〜

  • フレームワークや言語等の特有の速度改善方法は調べて実践してみよう。
  • 本番サーバーに導入する前の検証は細かく行うべし。

実践5 : PHPのバージョンアップ

この時点で210ms台に突入し、最初が4.5sだったと考えれば十分に上出来だと思ってしまいがちですが、目標は100ms台です。
SQLの見直しも概ねでき、Laravelのキャッシュも入れたことだし、サーバー構成も見直したとなると、そろそろネタが尽きるところですが、ここでPHPのバージョンアップを検討しました。
元々AAJのPHPのバージョンは5.5.Xでした。※ オールアバウトの標準環境自体はPHP 5.5.Xです。
PHP 7が他のPHPのバージョンより高速であることや、使用しているフレームワークのLaravelがPHP 7に対応していることから、PHP 7へのバージョンアップを試みました。

結果

PHP 7導入前

PHP 7導入前

PHP 7導入後

PHP 7導入後

開発環境での検証と同様、50msの速度改善を図ることができ、 想定していた160ms台に突入しました。

〜ここで学んだこと〜

  • PHPのバージョンアップも高速化に繋がる

実践6 : KVSキャッシュ時間見直し

この、速度改善をあれこれ試みている過程で 「ひょっとしたらKVSキャッシュが利いていないのでは?」 という指摘があり、改めてKVSキャッシュ時間を調べてみました。

AAJのキャッシュ構成

KVSキャッシュの説明の前に、AAJのキャッシュは以下の構成となっております。

All About Japanキャッシュ構成

  • ブラウザキャッシュ
    ※特に設定せずにブラウザ側が生成するキャッシュ
  • varnishキャッシュ
    → webサーバーのアクセスの手前でページデータのキャッシュを返す
  • KVSキャッシュ ページを表示するために必要なデータをキャッシュ
    → DBサーバーのアクセスの手前でDBデータのキャッシュを返す

つまり、ブラウザキャッシュが無かったらvarnishキャッシュ、varnishキャッシュが無かったらwebサーバーにアクセス、webサーバーからKVSキャッシュを参照して、無かったらDBサーバーにアクセスをするようになっております。

varnishの仕組み

varnishのキャッシュ時間は60分に設定していますが、60分でキャッシュが切れる前にアクセスがあると、そこから60分のキャッシュが有効になります。

varnish,KVSキャッシュの仕組み

一方、KVSのキャッシュ時間は10分に設定されておりました。 仮にvarnishのキャッシュが60分で切れたとしても、それまでにKVSのキャッシュが切れてしまっているため、varnishのキャッシュが切れる度に毎回KVSのキャッシュが切れていました。

KVS時間調整

そのため、このようにKVSキャッシュの時間をvarnishキャッシュ時間より長くすることによって、varnishが切れた際にKVSキャッシュが有効になるように設定し直しました。

結果

KVS時間調整前

10:30に本番に適用しましたが、10:30を境にグラフが右下に下がっているのが分かるかと思います。

KVS時間調整後

最終的に120ms台まで落ち着きました。

〜ここで学んだこと〜

キャッシュを組み合わせている場合はキャッシュ時間の設定に気をつけよう

実践7 : New Relicのチェック

この度のAAJでは、サイトをローンチしてから、気がついたら表示速度が4.5秒になっていて慌ててあの手この手で速度改善を試みましたが、 今、思えば日頃から速度をチェックしているという習慣があったら良かったというように思います。

実践7は具体的に何かの施策をしている訳ではありませんが、New Relicをチェックする習慣についてまとめました。

New Relicを確認するポイント

Apdex

Apdexとは、Application Performance Indexの略で、ページ速度に対するユーザーの満足度を数値化したものです。 0から1の範囲で、1に近いほどページを閲覧するユーザーの満足度が高いと言えます。

詳しくはこちら

T値(閾値)の設定方法

Apdexの値は設定されたT値を基準に算出されます。

New Relic Apdex設定

画面左の"SETTINGS" → "Application"から"Apdex T"のフォームに然るべき値を入れて"Save application settings"を押せば設定は完了します。

T値については、あまり高い値を入れてしまうと常に1.0を保ち続けるだけなので、意味がありません。
常にApdex Scoreが0.8台くらいにしたい場合は現在の平均速度の1.15倍くらいに設定しておけばよいかと思います。
同様に、常にApdex Scoreが0.9台が良い場合は平均速度の1.7倍くらいが目安です。

noteの活用

New RelicはLITE PLANだと24時間分のグラフしか保持されません。 過去のグラフを保存する方法について、1つはスクリーンショットで地道に記録するというものがあるかと思いますが、noteという機能を活用するという方法もあります。

使い方についてはこちらに詳しく記載しました。

注意点

New Relic note グラフ

noteの場合、平均速度等、細かい数字は記録されません。

alertの設定

New Relic alert一覧

日頃からチェックとはいったものの、一日中New Relicに張り付いている訳にもいきません。 New Relicにはalert機能があって、例えば先ほどのApdex scoreが一定の値を下回った時にalertをSlackやメールで通知する設定があります。

詳しい設定方法はこちらをご確認ください。

まとめ

これまでの高速化の内容を以下にまとめました。

SQL見直し

  • index追加
  • クエリ見直し

活用したもの

  • slowquery
  • New Relic

サーバー構成見直し

AAJを動かしているサーバーを全てクラウド環境にまとめることで各サーバー間の通信コストを抑える

Laravelによる速度改善

config:cache追加

PHPのバージョンアップ

PHP 5.5.XからPHP 7へバージョンアップ

KVSキャッシュ時間を見直し

varnishのキャッシュ時間が60分に対してKVSキャッシュの時間を80分に修正

さいごに

このようにして、All About Japanは一番重たかった頃の4.5秒から120ミリ秒まで速さが改善しました。数字だけで見ると1/37まで改善したということになります。

All About Japanはサイトローンチして間もなく、データが少ない頃からは想定していなかった負荷が発生したことがきっかけで高速化のあれこれを試みました。 もし、「最近、サイトが重たいな…」と思うことがあって、これが参考になれば幸いです。