[Varnish]gracefulにキャッシュしたい

varnish-logo

次の様なケースの Varnish の振る舞いを調べてみた

  • キャッシュされていないコンテンツに同時にアクセスされた時(thundering herd problem)
  • TTL 設定したキャッシュコンテンツが expire した時(grace mode)
  • オリジンサーバが HTTP ステータス 500番台を返してきた時(saint mode)

構成

client – Varnish/3.0.4 – nginx/1.1.19 & php-fpm

通常のキャッシュの振る舞い

Varnish を使うとコンテンツがキャッシュされることを確認。

確認方法

現在時刻を返すだけのページ sleep.php を用意。
意図的に処理時間がかかるよう sleep を挟む

<?php
sleep(5);
echo date('Y/m/d H:i:s') . "\n";
?>

未キャッシュ状態でアクセスすると現在時刻を返す。
1回目のレスポンスには5秒かかる。
2回目以降のアクセスでは PHP は実行されずキャッシュされた時刻を即座に返す。

$ curl  -D - http://localhost/sleep.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:13:12 GMT
X-Varnish: 762501511
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:13:12
$ curl  -D - http://localhost/sleep.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:13:24 GMT
X-Varnish: 762501512 762501511
Age: 11
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:13:12

キャッシュ経過時間を表す Age ヘッダーを確認すると、2回目のアクセス時はキャッシュ作成後 11 秒経過している。
しかし Varnish が返すコンテンツは1回目と同じ。

nginx log

127.0.0.1 - - [21/Oct/2013:19:13:12 +0000]  "GET /sleep.php HTTP/1.1" 200 267 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "0.50"

アクセスは1回目のみしか無い。

キャッシュを ban コマンドでパージして再度アクセスすると、コンテンツが更新される。

$ sudo varnishadm ban 'req.url ~ "sleep.php"'
$ curl  -D - http://localhost/sleep.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:18:15 GMT
X-Varnish: 762501518
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:18:15

varnish_basic

Thundering Herd 対応

キャッシュがされていないコンテンツに同時にアクセスされた時に、各リクエストがオリジンサーバにコンテンツをとりに行くと(thundering herd)、オリジンサーバの負荷が高まる上、同じコンテンツを重複してリクエストしてもキャッシュする上では無駄でしかない。
Varnish はオリジンサーバへは1リクエストになるようにロックで制御し、キャッシュ後にまとめてレスポンスを返す。

確認方法

さっきと同じく処理に時間のかかるページ sleep.php を利用

<?php
sleep(5);
echo date('Y/m/d H:i:s') . "\n";
?>

2クライアントから同時にリクエストし

  • オリジンサーバには1リクエストしかいかないこと
  • クライアントへのレスポンスは同時なこと
  • 同じコンテンツであること

を確認

$ curl  -D - http://localhost/sleep.php &
[1] 4901
$ curl  -D - http://localhost/sleep.php &
[2] 4902

HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:31:39 GMT
X-Varnish: 762501531
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:31:39
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:31:39 GMT
X-Varnish: 762501532 762501531
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:31:39

[1]-  Done                    curl -D - http://localhost/sleep.php
[2]+  Done                    curl -D - http://localhost/sleep.php

nginx log

127.0.0.1 - - [21/Oct/2013:19:31:39 +0000]  "GET /sleep.php HTTP/1.1" 200 267 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "0.50"

オリジンサーバへのアクセスは1回しか無い。

 

varnish_thundering_herd

TTL付きキャッシュ

キャッシュに 30 秒の time-to-live(TTL) を設定

  • TTL が expire するまでは同じキャッシュを返すこと
  • TTL が expire するとオリジンサーバにコンテンツを要求すること

を確認

キャッシュに TTL を設定する VCL

vcl_fetch で set beresp.ttl = duration を設定する。

sub vcl_fetch {
    ....
     if (req.url ~ "ttl") {
         set beresp.ttl = 30s;
     }
     return (deliver);
}

未キャッシュ状態からアクセス(Age = 0)

$ curl  -D - http://localhost/ttl.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:34:43 GMT
X-Varnish: 762501533
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:34:43

$ curl  -D - http://localhost/ttl.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:35:00 GMT
X-Varnish: 762501534 762501533
Age: 16
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:34:43

2 回目のアクセスでも Age が TTL 未満なので、キャッシュ済みコンテンツを即座に返す

TTL の expire 後に同時に2リクエストする。

$ curl  -D - http://localhost/ttl.php &
[1] 4926
$ curl  -D - http://localhost/ttl.php &
[2] 4927
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:35:35 GMT
X-Varnish: 762501536 762501535
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:35:35
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:35:35 GMT
X-Varnish: 762501535
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:35:35

キャッシュがないのでオリジンサーバにコンテンツを要求。
オリジンサーバへは1アクセスのみ。
キャッシュの有効期限が切れて未キャッシュ状態になったということを除くと、さっきの Thundering Herd と同じアクセスパターン。

nginx log

127.0.0.1 - - [21/Oct/2013:19:34:43 +0000]  "GET /ttl.php HTTP/1.1" 200 267 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "0.50"
127.0.0.1 - - [21/Oct/2013:19:35:35 +0000]  "GET /ttl.php HTTP/1.1" 200 265 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "0.53"

graceful mode

TTL を設定したキャッシュが expire した途端にオリジンサーバにリクエストし、処理が終わるまで待たされるのはあまり好ましくない。

Varnish の grace モードでは、expire 後に複数リクエストがあった時

  • 1リクエストだけはキャッシュを更新するためにオリジンサーバにコンテンツをリクエストし
  • 残りのリクエストは grace 期間内であれば expire した(=stale)キャッシュコンテンツをクライアントに即座に返す。

確認方法

キャッシュに TTL を設定し expire 後に同時に2アクセス

  • 片方はオリジンサーバから最新コンテンツを取得すること
  • もう片方は stale cache を即座にクライアントに返すこと

を確認

grace mode を設定する VCL

vcl_recv と vcl_fetch で stale cache の保持期間を set beresp.grace = duration で設定する。

sub vcl_recv {
     set req.backend = default;
     set req.grace = 30s;
}
sub vcl_fetch {
     ...
     if (req.url ~ "ttl") {
         set beresp.ttl = 30s;
     }elseif (req.url ~ "grace") {
         set beresp.ttl = 30s;
         set beresp.grace = 30s;
     }
     return (deliver);
}

未キャッシュ状態からアクセス(Age = 0)
Age が TTL 内であれば、キャッシュ済みコンテンツを返す

$ curl  -D - http://localhost/grace.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:39:12 GMT
X-Varnish: 762501537
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:39:12
$ curl  -D - http://localhost/grace.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:39:39 GMT
X-Varnish: 762501538 762501537
Age: 27
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:39:12

TTL の expire 後に同時に2リクエストする。

片方は expire した stale cache が即座に返ってくる。(レスポンスヘッダーの Age が 35 と TTL より大きいことに注意)
もう片方はオリジンサーバから最新コンテンツを取得

$ curl  -D - http://localhost/grace.php &
[1] 4964
$ curl  -D - http://localhost/grace.php &
[2] 4965
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:39:47 GMT
X-Varnish: 762501540 762501537
Age: 35
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:39:12

[2]+  Done                    curl -D - http://localhost/grace.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:39:51 GMT
X-Varnish: 762501539
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:39:51

[1]+  Done                    curl -D - http://localhost/grace.php

キャッシュ更新後は grace 期間内であっても、新しいキャッシュが返される

$ curl  -D - http://localhost/grace.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:39:56 GMT
X-Varnish: 762501541 762501539
Age: 4
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:39:51

nginx ログ

127.0.0.1 - - [21/Oct/2013:19:39:12 +0000]  "GET /grace.php HTTP/1.1" 200 267 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "0.50"
127.0.0.1 - - [21/Oct/2013:19:39:51 +0000]  "GET /grace.php HTTP/1.1" 200 267 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "0.50"

varnish_grace_mode

saint mode

アプリの不具合、デプロイミスなどでアプリケーション・サーバーがエラーを返すことがある。

Varnish の saint モードでは

  • saint 期間内は問題のオリジンサーバのエラーが起きた URL にリクエストせず(=black list 化)
  • 他のオリジンサーバがあれば、そのサーバーにリクエストし
  • 他のオリジンサーバがなくても grace 期間内の stale cache があれば、クライアントに返す

確認方法

HTTPステータス 500 を返すページ(500.php)を用意

<?php
header('HTTP/1.1 500 Internal Error', true, 500);
?>

graceful なTTL 付きのキャッシュを用意し、そのコンテンツを 500.php に置き換える

  • キャッシュの expire 後にアクセスしすると、 stale cache が返ってくること
  • saint 期間内であれば、grace 期間が過ぎたあともオリジンサーバにはアクセスしないこと

を確認

saint mode を設定する VCL

vcl_fetch で saint mode 期間を set beresp.saintmode = duration で設定する。
次の設定ではオリジンサーバのレスポンスステータスが 500番台の時に、 saint mode 突入。

sub vcl_recv {
     set req.backend = default;
     set req.grace = 30s;
}
sub vcl_fetch {
     if (beresp.status >= 500 && beresp.status < 600) {
         set beresp.saintmode = 90s;
         return (restart);
     }
     if (req.url ~ "ttl") {
         set beresp.ttl = 30s;
     }elseif (req.url ~ "grace") {
         set beresp.ttl = 30s;
         set beresp.grace = 30s;
     }
     return (deliver);
}

未キャッシュ状態からアクセス(Age = 0)
Age が TTL 内であれば、キャッシュ済みコンテンツを返す

$ curl  -D - http://localhost/grace.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:52:22 GMT
X-Varnish: 1265179024
Age: 0
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:52:22

grace.php を 500.php に変更

$ curl  -D - http://localhost/grace.php
HTTP/1.1 200 OK
Server: nginx/1.1.19
Content-Type: text/html
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Transfer-Encoding: chunked
Date: Mon, 21 Oct 2013 19:52:56 GMT
X-Varnish: 1265179025 1265179024
Age: 34
Via: 1.1 varnish
Connection: keep-alive

2013/10/21 19:52:22

saint mode の条件にマッチするため stale cache を返す(Age: 34 と TTL を超えている)

さらに grace 期間を過ぎると stale cache もなくなるため、素のコンテンツが返される

$ curl  -D - http://localhost/grace.php
HTTP/1.1 503 Service Unavailable
Server: Varnish
Content-Type: text/html; charset=utf-8
Retry-After: 5
Content-Length: 419
Accept-Ranges: bytes
Date: Mon, 21 Oct 2013 19:53:39 GMT
X-Varnish: 1265179026
Age: 0
Via: 1.1 varnish
Connection: close

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title>503 Service Unavailable</title>
  </head>
  <body>
    <h1>Error 503 Service Unavailable</h1>
    <p>Service Unavailable</p>
    <h3>Guru Meditation:</h3>
    <p>XID: 1265179026</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>

saint mode 期間をすぎると、オリジンサーバにリクエストする。

$ curl  -D - http://localhost/grace.php
HTTP/1.1 503 Service Unavailable
Server: Varnish
Content-Type: text/html; charset=utf-8
Retry-After: 5
Content-Length: 419
Accept-Ranges: bytes
Date: Mon, 21 Oct 2013 19:54:39 GMT
X-Varnish: 1265179028
Age: 0
Via: 1.1 varnish
Connection: close

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title>503 Service Unavailable</title>
  </head>
  <body>
    <h1>Error 503 Service Unavailable</h1>
    <p>Service Unavailable</p>
    <h3>Guru Meditation:</h3>
    <p>XID: 1265179028</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>

nginx のログ

2行目のステータスコード 500 が saint mode 突入のきっかけ。
saint mode 期間が過ぎるまではオリジンサーバにはリクエストは行かない。

127.0.0.1 - - [21/Oct/2013:19:52:22 +0000]  "GET /grace.php HTTP/1.1" 200 267 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "0.50"
127.0.0.1 - - [21/Oct/2013:19:52:56 +0000]  "GET /grace.php HTTP/1.1" 500 209 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "-"
127.0.0.1 - - [21/Oct/2013:19:54:39 +0000]  "GET /grace.php HTTP/1.1" 500 209 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3" "-"

varnish_saint_mode

検証用 vcl

/etc/varnish/default.vcl を次のように変更

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

sub vcl_recv {
     set req.backend = default;
     set req.grace = 30s;
}
sub vcl_fetch {
     if (beresp.status >= 500 && beresp.status < 600) {
         set beresp.saintmode = 90s;
         return (restart);
     }
     if (req.url ~ "ttl") {
         set beresp.ttl = 30s;
     }elseif (req.url ~ "grace") {
         set beresp.ttl = 30s;
         set beresp.grace = 30s;
     }
     return (deliver);
}

sub vcl_deliver {
    return (deliver);
}

References

Leave a comment