a-blog cms + Varnish 触ってみる

これは、 a-blog cms Advent Calendar 2015 の25日目の記事です。
2013年の今日と同じ25日にAdvent Calendar で a-blog cmsをリバースプロキシ(nginx)で高速化 という記事を書いていました。
そこで今回は、Nginx と同じ リバースプロキシ機能をもっている 「Varnish」について調べてみたいと思います。
目標
- ESIできるようにする
- TTL を テンプレートから指定できるようにする
- ログインコンテンツも部分キャッシュで利用できるようにする(途中で時間切れ...途中まで書きます)
環境
User Request --> Nginx:80 --> Varnish:6081 --> Nginx:8080 --> php-fpm:9000
最初は上の構成で動かしたかったのですが、うまく動かず時間もなかったので、以下のような環境で動かしています。フロントにNginxをおかず、Varnishを直で置いています。テストなのでよしとします。
User Request --> Varnish:80 --> Nginx:8080 --> php-fpm:9000
Varnishでキャッシュをし、キャッシュがなかった場合は、バックエンドのNginxにプロキシします。 Nginxは php-fpm と 連動して a-blog cms を post:8080 で動かします。OS は CentOS。
ESI とは
今回の目的の一つである ESI(Edge Side Includes)はインクルードの一種なのですが、面白いとろころとして、そのインクルード毎にキャッシュを保持できるようになっているというところです。
ページを構成する要素を見てみると、タイトルヘッダーや、フッターなど更新頻度の少ないパーツや、ログイン情報を出力するパーツ、記事を更新するまで静的なパーツなど様々です。
特にログインするような動的サイトだとキャッシュが難しいです。もちろんページごとキャッシュはできません。 そこで、このESIを使って、ログインしていても部分的にキャッシュをすることにより、パフォーマンス向上や負荷軽減につながります。
Varnishを動かしてみる。
とりあえずVarnishを動かしてみたいと思います。(環境はCentOS)
Post 80 で動かす
$ sudo vim /etc/sysconfig/varnish
DAEMON_OPTS="-a :80 \
-T localhost:6082 \
-f /etc/varnish/default.vcl \
-S /etc/varnish/secret \
-s malloc,256m"
起動
$ sudo service varnish start
設定ファイル
Varnishの設定を /etc/varnish/default.vcl にしていきます。まずは全体のコードを貼ります。
# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;
import std;
# Default backend definition. Set this to point to your content server.
backend default {
.host = "127.0.0.1";
.port = "8080";
}
sub vcl_recv {
# Happens before we check if we have this in cache already.
#
# Typically you clean up the request here, removing cookies you don't need,
# rewriting the request, etc.
set req.http.Host = req.http.host;
set req.http.X-Real-IP = client.ip;
set req.http.X-Forwarded-Host = req.http.host;
set req.http.X-Forwarded-Server = req.http.host;
set req.http.X-Forwarded-For = client.ip;
# GET以外はキャッシュしない
if ( req.method != "GET" ) {
return (pass);
}
# ttl を抜き出し
if ( req.url ~ "(\?|&)ttl=" ) {
set req.http.x-varnish-ttl = regsub(req.url, "^(.*)ttl=([^&]+)(.*)?$", "\2");
}
# ログイン中でESIなら cookieを削除してキャッシュ処理
if ( req.http.Cookie && req.http.Cookie ~ "sid=" && req.http.x-varnish-ttl ) {
set req.http.x-varnish-hash = regsub(req.http.cookie, "^(.* )?sid=([^;]+)(;.*)?$", "\2");
set req.http.x-varnish-suid = regsub(req.http.cookie, "^(.* )?suid=([^;]+)(;.*)?$", "\2");
set req.http.x-varnish-tmpcache = req.http.Cookie; # キャッシュを利用するためcookieを一時退避
unset req.http.Cookie; # cookie を削除
return (hash);
}
# 通常ログインのアクセスならキャッシュしない
if ( req.http.Cookie && req.http.Cookie ~ "sid=" ) {
return (pass);
}
unset req.http.cookie; # cookieを削除
return (hash);
}
sub vcl_miss {
# cookieを再セット
if ( req.http.x-varnish-tmpcache ) {
set req.http.Set-Cookie = req.http.x-varnish-tmpcache;
}
return (fetch);
}
sub vcl_backend_response {
# Happens after we have read the response headers from the backend.
#
# Here you clean the response headers, removing silly Set-Cookie headers
# and other mistakes your backend does.
# ESI を有効化
set beresp.do_esi = true;
# TTL を設定
if ( bereq.http.x-varnish-ttl ) {
unset beresp.http.set-cookie;
set beresp.ttl = std.duration(bereq.http.x-varnish-ttl, 1m);
}
# image
if ( bereq.url ~ "\.(png|gif|jpg)$" ) {
unset beresp.http.set-cookie;
set beresp.ttl = 1d;
}
}
sub vcl_hash {
# ログインユーザ毎にキャッシュを振り分け
if ( req.http.x-varnish-suid ) {
hash_data(req.http.x-varnish-suid);
}
}
TTLをテンプレートから指定
TTL(time to live)とは、キャッシュの有効時間になります。TTLが短いと短時間でキャッシュがクリアされ、長いと長い間キャッシュを保持します。
このTTLをテンプレートから指定できると便利なので、その設定をしています。 まず sub vcl_recv で 正規表現でインクルードパスに指定されている ttlを抜き出し、後で利用できるように変数にセットします。
sub vcl_recv は リクエストが発生した時に最初に呼ばれるメインのサブルーチンになります。
if ( req.url ~ "(\?|&)ttl=" ) {
set req.http.x-varnish-ttl = regsub(req.url, "^(.*)ttl=([^&]+)(.*)?$", "\2"); # 正規表現でttlを抜き出しreq.http.x-varnish-ttlにセット
}
次に vcl_backend_response で TTLをレスポンスヘッダにセットしています。vcl_backend_response はバックエンド(a-blog cms)へアクセスして、そのレスポンスが帰ってきたときに呼ばれます。
if ( bereq.http.x-varnish-ttl ) {
unset beresp.http.set-cookie;
set beresp.ttl = std.duration(bereq.http.x-varnish-ttl, 1m);
}
この設定により、以下のように インクルードを書くテンプレートからttlを指定できるようになり、開発が楽になります。
<body>
<!-- ヘッダーは一週間キャッシュ -->
<esi:include src="/include/header.html?ttl=1w">
<div class="row">
<div class="large-9 columns">
<!-- Main Blog Content -->
<!--#include file="/include/mainTop.html" -->
</div>
<div class="large-3 columns">
<!-- サイドナビは1時間キャッシュ -->
<esi:include src="/include/sidebar.html?ttl=1h">
</div>
</div>
...
ログインユーザ毎にキャッシュ
ここは、時間がたりずまだできてません。。すいません。。
やろうとしている事は、ユーザ毎に別々のキャッシュを作らないといけないので、a-blog cmsのuidをハッシュキーにしてキャッシュをしようとしています。
ログイン中のユーザIDを Cookie にセットをして Varnish側で呼びだしてハッシュキーに利用したいのですが、a-blog cmsではログイン中のユーザIDはCookieにセットされないので、Hookを使ってユーザIDをセット出来るようにします。
public function afterBuild(&$res)
{
if ( SUID ) {
setcookie('suid', SUID, REQUEST_TIME + 604800, '/', COOKIE_HOST, COOKIE_USESECURE);
} else {
setcookie('suid', null, REQUEST_TIME - 1, '/', COOKIE_HOST, COOKIE_USESECURE);
}
}
次に、Varnish の sub vcl_recv で Cookie にセットされている suid を抜き出し変数にとっておきます。
# ログイン中でESIなら cookieを削除してキャッシュ処理
if ( req.http.Cookie && req.http.Cookie ~ "sid=" && req.http.x-varnish-ttl ) {
set req.http.x-varnish-suid = regsub(req.http.cookie, "^(.* )?suid=([^;]+)(;.*)?$", "\2"); # suidを取得
set req.http.x-varnish-tmpcache = req.http.Cookie; # キャッシュを利用するためcookieを一時退避
unset req.http.Cookie; # cookie を削除
return (hash);
}
ここで、Cookieを削除しないとうまくキャッシュ処理が動かないので、Cookieを一時的に削除しているのですが、再セットがうまくいかず、キャッシュはできるのですが、セッションが切れてしまいます。まだうまく動いていないので今後調べてブログに書きます。。
次に現状だとユーザ毎にキャッシュが作られない(インクルードするパスが同じなら全て同じ内容)になってしまうので、ユーザ毎にキャッシュをつくる設定をします。
sub vcl_hash {
# ログインユーザ毎にキャッシュを振り分け
if ( req.http.x-varnish-suid ) {
hash_data(req.http.x-varnish-suid);
}
}
先ほどセットした x-varnish-suid を使いキャッシュを振り分けています。 これでユーザ毎にキャッシュが作られるはずです!
というわけで...
中途半端で申し訳ないですが、ここまでで Advent Calendar の投稿とさせてください。。 まだまだ、うまく動いていない部分や、キャッシュのクリア部分など、やる事はいっぱいあるので、少しづつ情報をだせたらと思います。
Varnishを始めて触った感触としては、いろいろ設定を行えば、大きな負荷軽減につながりそうです。ただうまくやらないと逆に重くなったりするので、これからいろいろ触ってみたいと思います。
a-blog cmsとの相性
a-blog cms単体ではページ単位のキャッシュで、クリアの仕方もブログ単位でごそっと消す方式なので、効率はあまり良くありません。
Varnishと組み合わせてる事で、うまいキャッシュ運用が出来るようになるはずですので、みなさんもいかがでしょうか?
以上になります! Merry Christmas!!