次の様なケースの 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
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回しか無い。
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"
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" "-"
検証用 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); }