VarnishCacheでキャッシュ付きの地図サーバを立ててみた

この記事は MIERUNE Advent Calendar 2020 の18日目の記事です。
# はじめに

オープンストリートマップのオンラインカンファレンスである[State of the Map Japan 2020](https://stateofthemap.jp/2020/)でGeorepublicの松澤さんの発表を見まして、OpenStreetMapの国内向け地図配信サーバのキャッシュサーバとしてVarnishCacheを使っていることを知り私も触ってみようと思いまして、触ってみました。以下そのまとめです。

# つくりたいもの
地図配信はバックエンドでOpenMapTilesを使い配信します。
VarnishCacheではタイルの画像(raster)データをキャッシュさせたいので、フロントにNginxを配置し、画像(raster)へのリクエストだったらキャッシュサーバへリクエストを飛ばします。vectorへのリクエストだったら、OpenMapTilesへそのままリクエストを送ります。


```
User --- Nginx(80) --- (raster)  varnish(6081) ---- OpenMapTiles(8080)
                    |
                    ---(vector)-------------------- OpenMapTiles(8080)
```

# 環境
```
Platform: GCP Compute Engine
マシンタイプ: n2-standard-4（vCPU x 4、メモリ 16 GB）
イメージ: ubuntu-minimal-1804-bionic-v20200131 (Ubuntu v.18)
ディスクサイズ: 110 GB (HHD)
```
# 手順
## Docker, docker-compose の Install
[こちらの記事](https://qiita.com/youtangai/items/ff67ceff5497a0e0b1af)を参考にしました。

```Bash
curl https://get.docker.com | sh
usermod -aG docker $USER
systemctl start docker
systemctl enable docker

# docker-compose latest: 1.27.4 
curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# confirm docker, docker-compose
docker -v
# result like: Docker version 19.03.8, build afacb8b7f0
docker-compose -v
# result like: docker-compose version 1.27.4, build 8d51620a
```

## OpenMapTiles のインストール & Tile 作成
OpenMapTilesとは地図のタイル生成から配信までできる便利なツールで、今回はこちらを使っていきます。

```Bash
git clone https://github.com/openmaptiles/openmaptiles.git
cd openmaptiles

# ZoomレベルをMin0, Max15にしたかったので.envを編集
vi .env
# modify MIN_ZOOM, MAX_ZOOM as
# MIN_ZOOM=0
# MAX_ZOOM=15

# 以下のコマンドでTileデータが作成されるが、作成に6時間～12時間程度かかるので
# セッションが切れても困らないようにScreen等張っておく
# こちらが終わると、data配下にjapan.mbtilesが作成され、それがタイル本体になる
./quickstart.sh japan

# quickstart.sh が終わり次第、以下コマンドでタイルサーバの起動が可能
make start-tileserver
```
これで http://[id address]:8080/ でTileサーバにアクセスが可能になる
![Tileservergl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112174/6841c94c-f251-d4fc-3892-97782d7b5c9e.png)

## VarnishCacheのインストール & 設定
[公式の手順](https://packagecloud.io/varnishcache/varnish60lts/install#manual-deb)を参考にすすめていく

```Bash
sudo apt install curl gnupg apt-transport-https
curl -sL https://packagecloud.io/varnishcache/varnish60lts/gpgkey | sudo apt-key add -
# aptレポジトリ追加
sudo add-apt-repository 'deb https://packagecloud.io/varnishcache/varnish60lts/ubuntu/ bionic main'

sudo apt update
sudo apt install varnish

# バージョン確認
varnishd -V
# varnishd (varnish-6.0.4 revision 14d4eee6f0afc3020a044341235f54f3bd1449f1)
# Copyright (c) 2006 Verdens Gang AS
# Copyright (c) 2006-2019 Varnish Software AS

```
設定(起動)ファイルは /etc/systemd/system/multi-user.target.wants/varnish.service なので見てみる

```
[Unit]
Description=Varnish Cache, a high-performance HTTP accelerator
After=network-online.target nss-lookup.target

[Service]
Type=forking
KillMode=process

# Maximum number of open files (for ulimit -n)
LimitNOFILE=131072

# Locked shared memory - should suffice to lock the shared memory log
# (varnishd -l argument)
# Default log size is 80MB vsl + 1M vsm + header -> 82MB
# unit is bytes
LimitMEMLOCK=85983232

# Enable this to avoid "fork failed" on reload.
TasksMax=infinity

# Maximum size of the corefile.
LimitCORE=infinity

ExecStart=/usr/sbin/varnishd \
          -a :6081 \
          -T localhost:6082 \
          -p feature=+http2 \
          -f /etc/varnish/default.vcl \
          -s malloc,256m
ExecReload=/usr/sbin/varnishreload

[Install]
WantedBy=multi-user.target
```
ExecStartの引数で、起動設定ができます。
-a でVarnishがListenするポートを設定できるのですが、今回は特にCacheを動かすポートにこだわりはなかったのでデフォルトの6081番のままで進めていきます。

続いて -f で設定されている /etc/varnish/default.vcl を確認してみましょう。

```Bash
vcl 4.0;

# 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.
}

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.
}

sub vcl_deliver {
    # Happens when we have all the pieces we need, and are about to send the
    # response to the client.
    #
    # You can do accounting or modifying the final object here.
}
```
backend defaultでキャッシュのオリジンとなるバックエンドのサーバを指定します。
今回のバックエンドとなるOpenMapTilesのサーバはlocalhost:8080で起動しているので、この設定のままで大丈夫そうです。

では起動してみましょう

```Bash
# サービスファイルを反映するには systemctl daemon-reload　が必要
systemctl daemon-reload
systemctl start varnish
systemctl status varnish

```
systemctl status varnish でvarnishd.serviceで設定した設定で引数が起動されているか念のため確認しましょう

## Nginxのインストール&設定
[こちら](qiita.com/kuribo2110/items/d725c4f866cf6e4e3727)を参考にしてNginxをインストールしました
設定ですが、今回ラスター(画像)のみをキャッシュしたいので、以下のように /etc/nginx/conf.d/default.conf を編集してNginxを再起動しておきましょう。

nginxの設定は以下のサイトを参考にしました。
・[OpenMapTiles/Tileserver-GL/MaputnikによるMapbox Vector TileのOSS実装環境のまとめ](https://smellman.hatenablog.com/entry/2017/12/10/210900)

Nginxは80番ポートでListenしリクエストがpng画像であれば、VarnishCacheが動いている6081番ポートへ、そうでなければ直接OpenMapTilesが動いている8080番ポートへリクエストを送信します。

```
server {
    listen       80;
    server_name  localhost;

  location / {
    proxy_set_header Host $http_host;
    proxy_pass http://localhost:8080;
  }

  location ~ ^/.*\.png {
    proxy_set_header Host $http_host;
    proxy_pass http://localhost:6081;
  }
}
```
Nginxを再起動してhttp://[id address]/でアクセスしてTileServerGLの画面が出てきたらバッチリOKです。

# 負荷テスト
せっかくCacheサーバをたててみたので、特定の画像に対しどれぐらい早くなったのか負荷テストをしてみましょう。
今回は自分の手元にあるPCからクラウド上に構築した上記の環境に対してApache Benchで負荷テストを行ってみました。
(ipアドレスは[ip-address]で置換しています)

## Varnish有
```Bash
# 同時接続数100で1000回アクセス
$ ab -c 100 -n 1000 http://[ip-address]/styles/klokantech-basic/6/55/27.png
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking [ip-address] (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.18.0
Server Hostname:        [ip-address]
Server Port:            80

Document Path:          /styles/klokantech-basic/6/55/27.png
Document Length:        1680 bytes

Concurrency Level:      100
Time taken for tests:   10.359 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      2033545 bytes
HTML transferred:       1680000 bytes
Requests per second:    96.54 [#/sec] (mean)
Time per request:       1035.882 [ms] (mean)
Time per request:       10.359 [ms] (mean, across all concurrent requests)
Transfer rate:          191.71 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       68  266 195.1    215    1243
Processing:    68  358 500.1    202    4735
Waiting:       68  325 444.8    201    4735
Total:        185  624 557.2    428    4979

Percentage of the requests served within a certain time (ms)
  50%    428
  66%    472
  75%    510
  80%    615
  90%   1471
  95%   1817
  98%   2135
  99%   2948
 100%   4979 (longest request)
```
## Varnish無し
```Bash
$ ab -c 100 -n 1000 http://[ip-address]/styles/klokantech-basic/6/55/27.png
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking [ip-address] (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.18.0
Server Hostname:        [ip-address]
Server Port:            80

Document Path:          /styles/klokantech-basic/6/55/27.png
Document Length:        1680 bytes

Concurrency Level:      100
Time taken for tests:   12.373 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      1945000 bytes
HTML transferred:       1680000 bytes
Requests per second:    80.82 [#/sec] (mean)
Time per request:       1237.316 [ms] (mean)
Time per request:       12.373 [ms] (mean, across all concurrent requests)
Transfer rate:          153.51 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       55  263 245.8    194    1311
Processing:    83  748 1214.8    227    7220
Waiting:       83  714 1194.4    225    5502
Total:        180 1011 1243.8    446    7352

Percentage of the requests served within a certain time (ms)
  50%    446
  66%    529
  75%   1213
  80%   1357
  90%   3132
  95%   4073
  98%   4969
  99%   5269
 100%   7352 (longest request)

```
単一の画像のアクセスで、素のアクセスよりおおよそ67%通信時間の時間でアクセスできました:tada:

# まとめ
以上、ほぼ手順書になってしまいましたが、VarnishCacheでキャッシュしつつ地図を配信することができました。バックエンド+nginxでアプリケーションを配信するのも素敵ですが、キャッシュをつけることでなんとなくいい感じになりますね。みなさんも是非お試しください。