LCL Engineers' Blog

夜行バス比較なび・格安移動・高速バス比較を運営する 株式会社LCL開発者のブログ

Varnishを導入して、急激なアクセス増に耐える

弊社で運営している「バスとりっぷ」というメディアに、キャッシュサーバとしてVarnishを導入しました。導入する上でハマった事・得たノウハウなどを書きたいと思います。

www.bushikaku.net

Varnishとは

Varnish は、リバースプロキシとして動作して、HTMLなどのキャッシュができます。 詳しくは、こちらの記事を参考にさせていただきました。

Varnish入門と仕組み - Qiita

導入の背景・目的

「バスとりっぷ」は、Nginx + Unicorn ( Rails ) で構成しており、記事の閲覧中心のサイトなので、普段のサーバ負荷は大したことがありません。 ただし、Yahoo!ニュースからリンクを貼って頂いた時など急激にアクセスがある状況では、Webサーバの負荷が高くなり、レスポンスタイムも大きく落ちてしまっている状態でした。

元々の構成では、記事にアクセスされるたびに必ず Unicorn(Rails)までリクエストが到達していました。Railsのキャッシュを利用してDBアクセス等はキャッシュしているもの、大量アクセス時にはRailsでの処理がボトルネックとなります。

今回、リクエストをRailsに到達させないようにするため、Varnishの導入を選択しました。

導入した構成

Amazon Linux 上に、Varnish 4.1を導入しました。 より速いレスポンスを実現するために、Nginxより前にVarnishを配置しています。

---> ELB --> Varnish --> Nginx --> Unicorn ( Rails )

※ この構成は、Nginxでアクセスログが一元管理できないため、ELBのログをS3に保存するなどの工夫をしたほうが良いです。

インストール

Amazon Linuxの場合は、デフォルトだとvarnish3.xがインストールされてしまったので、repositoryを指定してインストールします。

rpm --nosignature -i https://repo.varnish-cache.org/redhat/varnish-4.1.el6.rpm
yum install –disablerepo=* –enablerepo=varnish-4.1 varnish 

設定ファイルの書き方

Varnishの設定ファイル(default.vcl)は、VCLという独自言語で記述するため癖があります。

設定ファイルを書くためには、各メソッド間の流れを理解する必要がありますが、以下のメソッドの役割押さえておけば、要件は満たせました。

  • vcl_recv
    • 条件に応じたキャッシュの可否、キャッシュ時間などを設定。
  • vcl_hash
    • デバイス種別によって異なるキャッシュをする場合等の設定。
  • vcl_backend_response
    • バックエンドからの結果を受け取って、キャッシュを可否などの設定。
  • vcl_deliver
    • クライアントにレスポンスする前に呼び出される。必要に応じて、レスポンスヘッダを設定。

主要メソッドのみの大まかな処理フロー

f:id:lcl-engineer:20160223021711p:plain

※ 詳細な仕様を確認したわけではなく、実際の動きから想定したフローなので間違ってるかもしれません。

キャッシュの要件に応じて、上記メソッドに適切な設定が必要となります。 各要件に応じて、どのように記載しているかを簡単に記載していきます。

requestの条件に応じてキャッシュ有無の制御する

requestでの判定なので、backendへ送る前のvcl_recvで判定処理を記載します。

sub vcl_recv {

  # GET以外はキャッシュしない
  if (! req.method == "GET") {
      return (pipe);
  }

  # 特定のURL(ディレクトリ)だけキャッシュさせる
  if (req.url ~ "^/xxxx/") {
      return (hash);
  }

} 

ステータスコード応じてキャッシュ有無の制御する

ステータスコードでの判定なので、backendからのレスポンス後のvcl_backend_responseで判定処理を記載します。

sub vcl_backend_response {

  # 404や500等はキャッシュしない
  if (! (beresp.status == 200 || beresp.status == 304)) {
    set beresp.uncacheable = true;
    return (deliver);
  }
}

デバイスごとにキャッシュを分ける

同一URLでデバイス( PC/SP/TAB)によって異なるHTMLを返している場合は、キャッシュも別々に行う必要が有ります。

自前で精度の高いデバイス判定をするのは大変なので、こちらのライブラリを利用しています。

https://github.com/willemk/varnish-mobiletranslate

使い方

  1. default.vclと同ディレクトリに、mobile_detect.vclを配置する。
  2. mobile_detect.vclをincludeして、devicedetectメソッドを呼び出す。
  3. hashキーにhttp.X-UA-Deviceを設定する。
include "mobile_detect.vcl";

sub vcl_recv {
 # HTTP HeaderのX-UA-Deviceに、デバイス種別(pc,sp,tablet)が設定される。
 call devicedetect;
  return (hash);
} 

sub vcl_hash {
  # request urlとデバイス種別をキーにしてキャッシュ生成。
  hash_data(req.url);
  hash_data(req.http.X-UA-Device);
  return (lookup);
} 

Vary User-Agentが付与されている場合の対応

SEO対策として、backend側でレスポンスヘッダに"Vary User-Agent"を設定している場合、VarnishはUser-Agent別のキャッシュを作成してしまいます。 (例えば、同じ PC Chromeでもバージョンが異なると別のキャッシュになってしまいます。)

この状態を避けるために、次の処理をしています。

sub vcl_backend_response {
  # backendから送られたVary Httpe Headerを、別の変数に退避して一度削除
  set beresp.http.X-Vary = beresp.http.Vary;
  unset beresp.http.Vary;
}

sub vcl_deliver {
  # 退避したVaryヘッダを復元
  set resp.http.Vary = resp.http.X-Vary;
  unset resp.http.X-Vary;
}

varnishとbackendのデバイス判定の統一化

varnishとbackend側でデバイス判定が統一できていないと、vanirsh側ではスマホと判定、backend側ではタブレットと判定というような矛盾が発生してしまう可能性があります。 そのため、デバイスの判定は全てvanirsh側で行い、backend側ではvarnishによって設定された「http.X-UA-Device」の値を元にデバイス判定をしています。

キャッシュのクリア

Varnishのキャッシュは、デフォルト120秒でクリアされるようになっています。( /etc/sysconfig/varnishで設定できます)

特定のディレクトリを強制的にクリアしたい場合は、banメソッドを利用します。

# キャッシュクリアを許可するクライアントIP
acl purge {
 "localhost";
}

sub vcl_recv {

  # BANメソッドの場合に、キャッシュをクリア(BAN)する
  if (req.method == "BAN") {
    if (! client.ip ~ purge) {
      return(synth(405,"Not allowed."));
    }
    if (req.url == "/xxxxx/") {
      ban("req.url ~ /xxxxx/.*");
      return(synth(200, "Ban added"));
    }
  }

}

BANメソッドを呼び出すと、キャッシュがクリアされます。

curl -X BAN -v "http://localhost/xxxxx/"

デバッグのための情報を付与する

キャッシュにHITしたかどうかを、vcl_deliverでレスポンスヘッダに情報を付与するとデバッグがしやすいです。

sub vcl_deliver {
  if (obj.hits > 0){
    set resp.http.X-Cache = "HIT " + req.http.X-UA-Device;
  }else{
    set resp.http.X-Cache = "MISS " + req.http.X-UA-Device;
  }
}

Varnish導入の効果

導入してから急激なアクセス増が何度かありましたが、Webサーバの負荷は通常時とほぼ変わらず、パフォーマンスも数倍に向上しました。

実際に、Varnish未導入サーバと導入済みサーバに対して、Apache abで負荷をかけてみたところ、

未導入サーバの場合、160ms/requestかかってますが、

$ ab -n 1000 -c 10 [Varnish未導入サーバURL]
Time per request:   159.423 [ms] (mean, across all concurrent requests)

導入済みサーバの場合、20ms/requestとなり、8倍ほど高速になりました。

$ ab -n 1000 -c 10 [Varnish導入済みサーバURL]
Time per request:   20.319 [ms] (mean, across all concurrent requests)

まとめ

Varnishは、なれるまで設定ファイルの記載が難しいですが、なれると簡単に柔軟なキャッシュの制御ができるようになります。

Varnishを入れておけば、急激なアクセス増にもある程度までは耐えれるようになるので、いざという時のために主要なページに適用しておくと安心だと思います。