varnish4でrestAPIのキャッシュをやってみる

nginxで動くrestAPI用のリバースプロキシとしてvarnishを試したときのメモです。
「とりあえず動けば、、、」という感じなので各設定の解説とかはないです。
（いろんな詳しい記事を参考にさせて頂いたのですが、元ネタのURLをメモっておらず。。）

## 環境

* nginx, varnishの相乗り
* varnishは4.0.3を使用

## コマンド等メモ

### 起動・停止・再起動

```
$ service varnish start|stop|restart
```

### ログ
* デフォルトではファイルに出力されないので、コマンドで確認
* 運用時はファイルに出すこと

```
$ varnishlog
```

* コマンドのオプションでフィルタリングできるので色々調べると便利

### 管理コンソール

ほとんど使わなかった。。。

```
$ varnishadm
```

### 設定ファイルの構文チェック

```
$ varnishd -C -f /etc/varnish/default.vcl
```

### キャッシュ

* デフォルトでキャッシュするステータスコード

```
200: OK
203: Non-Authoritative Information
300: Multiple Choices
301: Moved Permanently
302: Moved Temporarily
307: Temporary Redirect
410: Gone
404: Not Found
```

---

# プロキシ設定
varnishとnginxの相乗り想定なので、ポートを変える程度。
varnishは80ポートでリクエストを受けて、nginxは8080でリクエストを受ける。

### varnish設定変更

* varnishのLISTEN PORT変更

```
$ sudo vi /etc/sysconfig/varnish

VARNISH_LISTEN_PORT=80
```

* バックエンド設定変更

```
$ sudo vim /etc/varnish/default.vcl

backend default {
  .host = "127.0.0.1";
  .port = "8080";
}
```

### nginx設定変更

```
$ sudo vi /etc/nginx/conf.d/hoge.conf

upstream backend {
  ip_hash;
  server 127.0.0.1:8080;
}

…

server {
  …

  listen      8080;
```

---

# Varnish4.0.3

### インストール

* 基本的には下記の手順でOK

```
$ wget http://dl.fedoraproject.org/pub/epel/6/x86_64/jemalloc-3.6.0-1.el6.x86_64.rpm
$ rpm -ivh jemalloc-3.6.0-1.el6.x86_64.rpm

$ rpm --nosignature -i https://repo.varnish-cache.org/redhat/varnish-4.0.el6.rpm
$ yum install -y varnish
```

EC2では、デフォルトのリポジトリをdisableする必要があった。
（そのままでもいけるという説もあり、別作業の変更がダメだったのかもしれません）

```
$ yum --enablerepo=varnish-4.0 --disablerepo=amzn-main,amzn-updates install varnish
```

### VCL設定

varnishの挙動を設定するファイル。
C言語でいろいろ書ける。

デフォルトの`/etc/varnish/default.vcl`を編集して、主に下記２点の対応を行った。

* URLのクエリパラメータ正規化
  * `http://api/books?a=hoge&b=moge`と`http://api/books?b=moge&a=hoge`は同じ内容キャッシュしないとダメだが、そのままでは別のキャッシュになってしまう
  * 4.X系では`boltsort`がstd(標準モジュール)に組み込まれた
* キャッシュの削除については、URLの正規表現でまとめて削除できる`smart ban`を使用
  * BANメソッドでリクエストが来た時に、そのURL（クエリパラメータを除く）をキーとするキャッシュを削除する使い方を想定

#### 設定サンプル

※元々のメモから削除した箇所があるので、そのままでは動かない可能性があります。。

```
vcl 4.0;
import std;

# This is a basic VCL configuration file for varnish.  See the vcl(7)
# man page for details on VCL syntax and semantics.
#
# Default backend definition.  Set this to point to your content
# server.
#
backend default {
  .host = "127.0.0.1";
  .port = "8080";
}

sub vcl_recv {
    # キャッシュ削除
    if (req.method == "BAN") {
        ban("obj.http.X-URL ~ ^" + req.url + "(\?.*)?$");
        return (synth(200, "Banned."));
    }

    # 問答無用でクッキー削除(クッキーがセットされていてもキャッシュされてるっぽいので不要？)
    #if (req.http.Cookie) {
    #    unset req.http.Cookie;
    #}

    # GETリクエストならキャッシュ
    if (req.method == "GET") {
        return(hash);
    } else {
        return(pass);
    }
}

sub vcl_hash {
    # urlをハッシュキーにする前に正規化する
    set req.http.X-Cache-Key = req.url;

    # boolパラメータを0, 1に統一する
    set req.http.X-Cache-Key = regsuball(req.http.X-Cache-Key, "(?i)(\?|&)([^=]+)=true", "\1\2=1");
    set req.http.X-Cache-Key = regsuball(req.http.X-Cache-Key, "(?i)(\?|&)([^=]+)=false", "\1\2=0");

    # URIの末尾に"/"がついていた場合は除外する
    set req.http.X-Cache-Key = regsuball(req.http.X-Cache-Key, "\/\?", "?");
    set req.http.X-Cache-Key = regsuball(req.http.X-Cache-Key, "\/$", "");

    # クエリパラメータのソート
    set req.http.X-Cache-Key = std.querysort(req.http.X-Cache-Key);

    hash_data(req.http.X-Cache-Key);
    return (lookup);
}

sub vcl_backend_response {
    # キャッシュ対象外の処理
    if (
        bereq.url ~ "^\/$" ||
        bereq.url ~ "^\/index\.(html|php)" ||
        bereq.url ~ "^\/phpinfo.php" ||
        bereq.url ~ "^\/ocp.php"
    ) {
        set beresp.uncacheable = true;
        return (deliver);
    }

    # バックエンドからのレスポンスが200系でなければTTLを短くする
    if (beresp.status > 300 && beresp.status != 304) {
        set beresp.ttl = 10s;
    }

    # 圧縮されてない場合圧縮する
    set beresp.do_gzip = true;

    # デバッグ用にキャッシュキーをレスポンスヘッダにセットして返却する
    set beresp.http.X-Cache-Key = bereq.http.X-Cache-Key;
}

sub vcl_deliver {
    ## Varnishのキャッシュ情報をレスポンスヘッダーに付与
    if(!resp.http.X-Served-By) {
        set resp.http.X-Served-By = server.identity;
        if (obj.hits > 0) {
            set resp.http.X-Cache = "HIT";
        }
        else {
            set resp.http.X-Cache = "MISS";
        }
　　　　　// キャッシュのヒット回数
        set resp.http.X-Cache-Hits = obj.hits;
    }
    else {
        # append current data
        set resp.http.X-Served-By = regsub(resp.http.X-Served-By, "$", ", ");
        set resp.http.X-Served-By = regsub(resp.http.X-Served-By, "$", server.identity);
        if (obj.hits > 0) {
            set resp.http.X-Cache = regsub(resp.http.X-Cache, "$", ", HIT");
        }
        else {
            set resp.http.X-Cache = regsub(resp.http.X-Cache, "$" , ", MISS");
        }
        set resp.http.X-Cache-Hits = regsub(resp.http.X-Cache-Hits, "$", ", ");
        set resp.http.X-Cache-Hits = regsub(resp.http.X-Cache-Hits, "$", obj.hits);
    }

    ## remove Varnish header
    unset resp.http.Via;
    unset resp.http.X-Varnish;
    unset resp.http.X-Served-by;
    unset resp.http.X-URL;
}
```