7月 302016
 

キャパシティプランニングをする際に頭がいたいものの一つに通常ではないアクセスがあります。
ぱっと思いつくので

  • 閲覧数がページに表示されているのでF5押しっぱなし
  • スクリプトでスクレイピングしようとしているのか暴走している
  • 足跡をつけるために尋常じゃない速度で訪問しまくる
  • ログイン試行
  • 画像をひたすらダウンロード

などなどいろいろあります。
これらに共通なのが、通常ではないリクエストで大量のリソースを消費することです。(もちろん他の問題(セキュリティ)があるものもあります)
もしキャッシュしていたとしても、アウトバウンド帯域を過剰に利用しますし、キャッシュが出来なければwsやdbなどでの負荷になります。
キャパシティプランニングをする際には様々な条件を考えて構築していきます。
単純にユーザーが増えて負荷が増えていくのは望ましく、喜んでインスタンスを増やしたり負荷対策をしますが
そうでない場合は、通常のユーザが巻き添えを食らわないようにキャップしたいものです。
今回は、Varnishでスロットルを行ってみようという記事です。

Varnishでスロットルをかけるのはすごい簡単で、vmod_vsthrottleというVMODを使用します。(ドキュメント)
これはvarnish-modulesに含まれています。
varnish-modulesはVarnish Softwareが作ったVMODで特に他のライブラリに依存しないものを集めたものです。(なのでdigestが入っていない)
ちなみに以下のVMODが含まれています。

  • cookie
  • header
  • saintmode
  • softpurge
  • tcp
  • var
  • vsthrottle
  • xkey

機会があれば、ほかのもそのうち紹介します。

まずはインストールです。
READMEにも書いてあるとおりにインストールします。


xcir@gw01:~$ sudo apt-get install libvarnishapi-dev
xcir@gw01:~$ git clone https://github.com/varnish/varnish-modules.git
xcir@gw01:~$ cd varnish-modules/
xcir@gw01:~/varnish-modules$ ./bootstrap
xcir@gw01:~/varnish-modules$ ./configure
xcir@gw01:~/varnish-modules$ make
xcir@gw01:~/varnish-modules$ sudo make install

./bootstrapでaclocalとかlibtoolizeで引っかかったらautomakeとかlibtoolを入れましょう
インストールはこれだけです。

次に使い方です。


BOOL is_denied(STRING key, INT limit, DURATION period)

keyはクライアントの識別子(IPなど)をいれます。
limitとperiodは組み合わせてつかいます。
period時間中にlimit回数を超えたらアウトみたいな感じです。
要はトークンバケットアルゴリズムです。
なので時間経過で使用した分は回復していきます。(呼び出し時に経過時間を調べて回復させます)
また、limitとperiodの組み合わせで平均RPSとバースト時のRPSを表現できます。
limitがバースト時RPSです。
limit / periodで平均のRPSになります。
なので例えば
limit=6 period=6sであれば平均は1RPSでバースト時は6RPS
limit=6 period=2sであれば平均は3RPSでバースト時は6RPS
みたいな感じになります。
うまく組み合わせて使いたいですね。

また、keyとありますが実際のkey生成にはkey + limit + periodのsha256をとっていますので
同じkeyを指定しても、limitやperiodが変われば別物として扱われます。

とりあえずテストでは/img/以下の画像(jpg|gif|png)を10秒の間に5回リクエストしたら制限をかけて429を返すようにします。
keyはclient.ipです。
image:というprefixをつけているのは、ルールを複数作ることも考えて意図せず被らないようにするためです。


vcl 4.0;
import vsthrottle;

sub vcl_recv {
  if(req.url ~ "^/img/.*\.(jpg|gif|png)" && vsthrottle.is_denied("image:" + client.ip, 5, 10s)) {
    return (synth(429, "Too Many Requests"));
  }
}

早速リロードしてみましょう


xcir@gw01:~/varnish-modules$ sudo varnishncsa -q "requrl ~ '/img/'"
***** - - [30/Jul/2016:01:40:14 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:14 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:14 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:14 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:15 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:15 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429★ 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:15 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:15 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:32★ +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200★ 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
6

6件目から429が帰ってるのがわかります。
また制限が10秒のうちに5回なので、しばらくまってリクエストして制限が解除されているのもわかります。
割と簡単だと思います。
ちなみに、1keyあたり100byteのメモリを使うのと、mutexを使用しているのでそこは頭の片隅に入れておくと良いかと思います。
といってもmutexは16分割してるので、通常の利用であればそこまで気にする程ではないと思います。


※ここから先は特にそのサイトのワークロードによってかなり考え方が変わります。
 なのでそのまま適用するのではなく、それぞれのサイトで考えてもらえればよいかと思います。
 また、異常行動に対してのキャップなので、継続的な改善サイクルを回す必要があります。
 そういうのは最初に想定仕切るの難しかったり、ページによってコストも違うからです。

とりあえずこれでスロットル早速やってみよう!という人がいましたらちょっと注意が必要です。
スロットルをかけるということは、ユーザの体験を制限してしまう可能性があります。
そのため安易に設定し、誤爆して普通の使い方をしているユーザを制限してしまわないように細心の注意を払う必要があります。
とはいっても、それでゆるゆるな制限をかけても全く有効ではありません。
個人的にスロットルをする上では2つのことが重要だと考えています。

  • 誤爆しない
  • 理不尽でない

あまりにも厳しく、普通に使っているユーザが誤爆されるような低い閾値であればイライラして離れていってしまうでしょう。
また、仮に引っかかっても、まぁしょうがないなとある程度納得ができるのも重要です。
例えば連打が許容されるボタンがあったとして、それを10秒で100回も連打して引っかかったら、まぁしょうがないなと思えるんじゃないかなと思います。
この辺りは、サイトの特性によって違いますので各々で考える必要があります。

誤爆しないための工夫
閾値の設定はすごく難しいです。
サイトの保護を優先したければ閾値は低くしたいですし
誤爆を防ぐには閾値は高くしたいです。

しかし、低くすれば誤爆が増え、高くすれば意味がなくなります。
ではどうすればよいかというと、トークンバケットへの理解とユーザの行動を考えてみることです。

最初にトークンバケットですが、これを何かに例えるとするとSuicaのチャージです。
飲み物や昼食などのお小遣いとして毎朝1000円チャージするとします。
チャージされてる分は自由に使って良くて、例えば一気に5000円使っても問題はありません(残額があれば)
また、Suicaのチャージ上限は2万円なので、そこに達した時点でそれ以上チャージができなくなります。
さらに開始時点から上限の2万円がチャージされているとします。
これをvsthrottle.is_deniedで表現すると


vsthrottle.is_denied(client.ip, 20000, 20d);

といった感じです。
平均すると1日辺り1000円使えますが、もし毎日2000円使うとなると20日経過した時点で初期にチャージされてた2万を使いきって一日待たないと2000円は使えなくなります。
1000円を超えてる部分はburstといった感じになります。

次に、ユーザの行動をYahooのトップ/ニュースページで考えてみましょう。(記事ページを守る)
ニュースの欄には複数のリンクがあって複数気になる記事があります。
その場合、行動は2パタンに別れると思います。

  1. 開いて読んで、読み終わったら戻って次の記事を読む
  2. 気になる記事を一気に開いて順次読んでいく

1は特に気にする必要はありません。記事を1秒で読んで素早く戻って次の記事を見ることができる人類はまずいないでしょう。
もし、1だけを考えるのであれば1秒に1回で制限をかけてしまえば良いと思います。
しかし、2の場合は一気にリンクを開くため、1秒で2~3は開くことは人類でもできるので先ほどのの制限では誤爆してしまいます。(本筋ではないですがChromeがbackspaceで戻れなくなったのでこういう人増えそうですね)
では、次に制限したいものを考えてみましょう。
今回はすべての記事を舐めてくようなクローラーで想定してみます。
このクローラーは3RPSで舐めていきます。
とりあえず登場するものが揃ったので条件を整理します。

1. 1ページ開いて5秒程度で読んで戻ってまた開くユーザの場合

  • avg=0.2RPS
  • burst=1RPS

2. リンクを最大6リンク程度開いて(秒間3程度)各ページを5秒程度で読むユーザの場合

  • avg=0.1875RPS
  • burst=3RPS

3. クローラー

  • avg=3RPS
  • burst=3RPS

条件が揃いました。
許容したいburstは2の3RPSです。
しかしavgでは0.2もあれば十分です。
これを単純に落としこむと
limit=3 period=15s (avg=0.2 burst=3)
となります。
しかし制限したいクローラーに対してはまだ余裕があるので自分ならですが
limit=4 period=10s (avg=0.4 burst=4)
ぐらいに設定すると思います。

とりあえずVCLを書いてみます。


vcl 4.0;
import vsthrottle;
import std;

sub vcl_recv {
  if(req.url ~ "^/pickup/" && vsthrottle.is_denied("pickup:" + client.ip, 4, 10s)) {
    std.log("THROTTLE:pickup:");
    //return (synth(429, "Too Many Requests"));
  }
}

ここでは一旦429は出さずにlogに出力しています。
これは実際に投入した後に、どういうクライアントが引っかかるかを試すためです。(テストのため引っ掛けるルールをちょろっと変えてます)


xcir@gw01:~$ sudo varnishncsa -q "vcl_log ~ 'THROTTLE:pickup:'"
***** - - [30/Jul/2016:19:48:38 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "http://blog.xcir.net/?p=2283" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:19:48:38 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "http://blog.xcir.net/?p=2283" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"

vslqueryを使用して引っかかるものだけを確認して、問題がなければreturnのコメントを外して有効にしましょう。
個人的には、std.logはそのまま残しておいたほうがqueryで絞込ができるので便利だと思います。

IPは本当に個人を識別するものか
先ほどの例では、クライアントを識別するのにclient.ipを利用しました。
しかし、これは本当に個人を識別できているでしょうか?
例えば、アクセスログを見てるとHost名がgoogle-proxy-*.google.comというホストでアクセスしてくるクライアントがいます。
これはChromeのデータセーバーで一旦Googleを経由しています。(参考1, 2
つまり、同時に同一proxyから異なるユーザがリクエストしてくる可能性があります。
その場合はスロットルが意図せず掛かる可能性があります。
もちろん、Chromeのデータセーバー以外にもこのようなproxyがあります。
サイトの規模にもよりますが、大規模な場合はこの辺りも意識する必要があります。(リクエストが多ければ多いほどかぶる可能性が上がるので)
じゃぁキーをどうすればよいのかというといろいろあります。
ログイン後のページで制限をかけたいのであれば、クッキーからキーを抜き出しても良いと思います。
非ログインの場合は、ブラウザフィンガープリントを調べると良いかなと思います。(参考1, 2
他にもいろいろありますが、制限をしたい箇所によって最適な方法を模索していくと良いかなと思います。

最後に
スロットルはサイトでユーザがどう行動するかや、何が異常でそうでないかの見極めをきちんとしないとユーザ・運営共に酷いことになってしまいます。
しかしうまく使うことができれば、サイトの安定性を向上させることが出来ますので一度検討してみてはいかがでしょうか?


7月 132016
 

Varnish4.1.3がリリースされました。
バグフィックスが主でアップデートを強く進めますが、他にも結構使える新機能が増えています。

ダウンロードはこちら
changes

新機能・変更

varnishncsaにバックエンドに問い合わせにいったものだけを表示するオプションが追加されました(-b)
今までは幾つか絞込の指定をしないと取れなかったのですが-bだけで済むので便利です
また同時にクライアントで絞り込んで表示するオプション(-c)も追加されています。(デフォルト動作)
ちなみに-b -cを同時に指定することも出来ます。

VSMを開放する際にどれだけ待機するかのパラメータが追加されました(vsm_free_cooldown)
今までは60秒でハードコードされていました。デフォルト値も60秒です。
通常の使い方ではいじることはないと思いますが、かなり激しいログの書き込みをするとかの環境だといじる機会はあるかもしれないです。

varnishlogの出力でバックエンドのトランザクションが開始する際にBackendStartが入るようになりました
クライアントのReqStartと対になるものです。

varnishncsaの-Fで指定できるものが増えました
3つ増えました。
 Varnish:side
 先ほど紹介した-b -cオプションに伴って増えたものです。
 バックエンドで絞込をしている場合はbを、クライアントからの表示の場合はcを出力します。
 -b -cを両方指定している場合に使うとどちらのログなのかがわかって良いと思います。
 Varnish:vxid
 vxidを出力します
 VSL:tag VSL:tag[field]
 指定したタグ(TimeStampなど)を出力します。
 基本vsl-queryと同じ指定ですがヘッダ指定は出来ません(VSL:ReqHeader:User-Agentみたいなのは出来ない)
 これできるといいなーと思うのでp-r書こうかな・・
 ちなみに複数引っかかる場合は最初のを出力します。
 例えばVSL:TimeStampを指定した場合は毎回Startのタイムスタンプが表示されます

TCP Fast Openをサポートしました
デフォルトはoffです(tcp_fastopen)

varnishtestに新しい同期用の命令を追加しました(barriers)
semaより使いやすいです

varnishstatで12桁以上の数値を出力する場合は丸めるようにしました#1855
CURRENTの表示でK/M/G/Tのように単位がつけられないカウンタは” %12ju”だったのですが12桁を超える場合は1000で割って” %9ju…”という表示になります。
コードを見た限りではxml/jsonで出力するときには影響しないのでそこまで気にすることはないかなと思います。

バグ修正

今回はかなりバグ修正が多いので幾つかピックアップして紹介します。
特にESI周りの修正が多いのでESIを使っている場合はアップデートすると良いと思います。

varnishncsaで-Lオプションが受け付けられないようになってたのを修正しました#1994

たまにAgeとAccept-Rangesヘッダが複数レスポンスされることがあるのを修正しました#1955

同名のVCLでvarnishをstop->startを繰り返すとその後segfaultを繰り返す事があったのを修正しました。#1933
dlopenのリファレンスカウンタが信用ならなかったみたいです。
vcl名は変わらないのですが、コンパイルしたvgc.soのパスに現在時刻(ナノ秒)が付与されるようになりました。
バッドアイデアとメッセージにあるので割と苦悩した感じがあります。

vcl_init/finiでstd.log/std.syslogを利用するとクラッシュするのを修正しました#1924

VSMのサイズが小さくてオーバーラン検出に問題が合ったのを修正しました#1873
varnishncsa等でオーバーランした際にもクラッシュしなくなりますが
オーバーランしたらvsmサイズを増やしたほうが良いでしょう。
それでも困るようであれば今回追加されたcooldownの調整も考えると良いかも

-Cオプション利用時にテンポラリで使用したディレクトリを消していなかったのを消すようにしました#1869

POSTリクエストをpipeで繋いだ場合に1分待たされることがあるのを修正しました#1806
4.1からはPOSTリクエストのデフォルトの動作がpassになっているので通常の場合は問題ないのですが
以前からの設定を使いまわしてる人などは割りとハマるかもしれないです。

バックエンドの接続数を示すカウンタを復活させました#1725
バックエンド周りのコード修正した際に意図せず消えたカウンタを復活させました。
確かに消えてました・・気づかなかった・・

次は5.0.0かなー
楽しみですね


3月 072016
 

Varnish4.1.2がリリースされました。
バグフィックスが主ですが、一部新機能があります。

ダウンロードはこちら
changes

また2016-03-09 12:00 CET(日本時間 同日20時)からリポジトリの場所が変更になります。
これは去年アナウンスされていたVarnish Cache project autumn cleaningの一環になります。
また、まだドキュメント側は更新されていませんがバグ報告についてもgithubに移行するはずです。

機能追加/改善

REAL型同士で算術演算子を使えるようになりました
4.1.1まではREAL型同士は+以外は使えなかったのですが新たに-*/がサポートされるようになりました。(1.1 – 0.9とかが出来るように)

absoluteURIでhttpsからはじまる場合でもHostをパース出来るようになりました
言葉にするより表のほうがわかりやすいと思いましたので動作パタンを表にしました。

  http://example.com/foo https://example.com/foo
Version req.http.host req.url req.http.host req.url
~4.1.1 example.com /foo https://example.com/foo
4.1.2~(with feature +https_scheme) example.com /foo example.com /foo

PROXYプロトコルに4.1で対応したしといった感じかと思います。
なお、標準ではoffになっているのでfeature +https_schemeを指定する必要があります。

VMODでACLが扱えるようになりました
vmodtool.pyを改善して4.0/4.1でソースツリーを共有出来るようにしました
おもにVMOD作成者向け

vmodtool.pyを改善して1つのディレクトリから複数のvmodを作成コンパイル出来るようにしました
varnish-modulesというのが少し前に公開された(Varnish謹製のVMOD集)のですが、これのための対応といった感じです

バグ修正(一部抜粋)

Content-LengthがないHTTP/1.0かつPOST/PUTした場合すぐ400を返すように変更(#1843)
今まではchunkedとして扱ってしまって結果としてtimeoutを待ってましたが400をすぐ返すように変更しました。

フェッチ時(VFP_Push)にworkspaceがオーバーフローした場合panicしていたのをFetchErrorに変更しました(#1739)
panicするほどのエラーではないということで変更です。

hit-for-pass利用時にbodyが空っぽになるケースがあったのを修正(#1858)
本来不要なIMSの評価を行っていたため結果としてbodyが空っぽになるケースがあったのでhit-for-pass時のIMSの評価を辞めました。

ESI利用時にメモリリークするケースがあったのを修正(#1860)
これはAddressSanitizer(ASAN)で検出したのです。(他にも#1852を検出しています)
ASANについては以下を見てみると面白いと思います
CAN WE RUN C CODE AND BE SAFE?

ban-listを回収(ban-lurker)してる際にpanicするケースがあるのを修正しました(#1863/#1864)


2月 032016
 

Varnish4.1.1がリリースされました。
ドキュメントの修正や、バグフィックスが主ですが幾つかの仕様変更があります。
4.1.0を利用している場合はアップデートをおすすめします。

ダウンロードはこちら
changes

機能追加

varnishncsaに-fオプションが追加されました
これは単純に-Fで指定するフォーマットをファイルから読み込みができるというだけです。
エスケープ等が面倒な時はよいかもしれないです。

仕様変更

varnishncsaはデーモンで動作する際に-wオプションが必須になりました
-wはファイルに出力するオプションで、通常デーモンで動作させる場合は指定しているものなので特に影響はないかと思います。

If-None-Match(INM)とIf-Modified-Since(IMS)リクエストが同時に来た場合IMSを無視します
この動作自体はRFC7232の3.3に記述されている通りです
(A recipient MUST ignore If-Modified-Since if the request contains an If-None-Match header field)

ステータスコードが1xx, 204, 304の場合はContent-Lengthヘッダをレスポンスしません
これらのステータスコードはbodyを含みませんので不要です。

VCLをネストしてincludeする際のカレントディレクトリをそれぞれのVCLのディレクトリに変更しました
いまいち一文で書くとわかりづらいのですが用はこういうことです。


/etc/varnish/default.vcl
/etc/varnish/common/base.vcl
/etc/varnish/common/acl/internal.vcl
/etc/varnish/common/acl/admin.vcl

例えばVCLをこのように配置していて相対パスでincludeする場合


~4.1.0
■/etc/varnish/default.vcl
vcl 4.0;
include "./common/base.vcl";

■/etc/varnish/common/base.vcl
vcl 4.0;
include "./common/acl/internal.vcl";
include "./common/acl/admin.vcl";

4.1.0までは基点となる/etc/varnish/を基準とした相対パスで書く必要がありましたが、
4.1.1からは


4.1.1~
■/etc/varnish/default.vcl
vcl 4.0;
include "./common/base.vcl";

■/etc/varnish/common/base.vcl
vcl 4.0;
include "./acl/internal.vcl";
include "./acl/admin.vcl";

このようにincludeが記述されたVCLのディレクトリを基準として読み込みます。
割と便利だと思います

hit-for-passもgrace動作するようになりました(#1818)

vcl_dir, vmod_dirにコロン区切りで複数のディレクトリを指定できます

varnishreplayが削除されました
少し前からコンパイルされないようになっていたのですがコード自体も削除されました。

バグ修正(一部抜粋)

大きなファイルを扱う際に遅くなっていたのを修正(#1798)

IPv6アドレスのパースに失敗するのを修正(#1801)

PROXYプロトコルを利用した際にvarnishlogが正しく表示されないケースがあったのを修正しました(#1804)
Proxyプロトコルのvxidが0になっていてその影響で-g request, sessionがうまく動きませんでした。

INMリクエストでETagを比較する際に弱い比較関数を使用するようにしました(#1816)
RFC7232に準拠した形です。

vsl-queryでフィールドをfloatsで評価しようとした場合に評価できないのを修正(#1845)
4.1.0からのデグレですが、具体的には”timestamp:resp[2] > 1.”のような絞り込みができなくなってました。


12月 052015
 

この記事はVarnish Cache Advent Calendar 2015の4日目の記事になります。

VCLには様々な変数の型がありまして、それぞれの型の間では暗黙的に変換されるものと、vmod_stdを使用して明示的に変換するものがあります。
型が結構多いのでいまいち何を使うか忘れることが多いので図にしてみました。
types
なお、ここでは特に変換にかかわらない型については取り上げていません(BLOB,ENUM,HTTP,PRIV_CALL,PRIV_VCL,PRIV_TASK,PRIV_TOP,PROBE,VOID)

ちなみに各型から暗黙的にSTRINGへ変換すると以下のようになります。

変換例
BYTES 2.000
DURATION 2.000
REAL 2.000
BOOL false
BACKEND default
INT 1
IP 192.168.1.1
TIME Fri, 04 Dec 2015 18:31:12 GMT
HEADER example.net

また、STRING_LISTですが基本的にはSTRINGと特に違いはないのですが、
これを引数として受け付ける関数の場合は型変換を意識しないとハマります。
例えばstd.logというログを出力する関数があるのですが、これは引数がSTRING_LISTです。
それを意識して以下のようなVCLを書くとエラーになります。


std.log(std.duration("10w",0s));


Command failed with error code 106
Message from VCC-compiler:
Wrong argument type.  Expected STRING_LIST.  Got DURATION.
('input' Line 31 Pos 31)
std.log(std.duration("10w",0s));
------------------------------#-
('input' Line 31 Pos 1) -- ('input' Line 31 Pos 30)
std.log(std.duration("10w",0s));
##############################--
Running VCC-compiler failed, exited with 2

これはdurationから一気にSTRING_LISTに暗黙的に変換できないからです。
そこでどうやるかというと、一度STRINGを経由させてあげればよいので


std.log("" + std.duration("10w",0s));

このように空文字を結合すると良いです。

まとめ
変数がどのように型変換が出きるかを知っておくと、その型では出来ない演算を変換した先でやって戻すみたいなことが出来ます。
覚えておくと結構便利なので、頭の片隅に置いておくと良いかなと思います。


12月 042015
 

この記事はVarnish Cache Advent Calendar 2015の3日目の記事になります。

Varnishは4.0まではfile-storageを使用する場合は特にpre-allocateをしないため、フラグメントによるパフォーマンス低下を防ぐためにdd(1)を使って先に確保してしまうのを推奨していました。
4.1でも事前確保するのを推奨しているのですが、実はbetaまではfallocateを利用してpre-allocateするようになっていました。
正式版ではこの機能はext4限定になってしまい他のファイルシステムではdisableになりました。
何故かxfsだと既に領域を確保しているにもかかわらずfallocateを使おうとするとENOSPCがでるケースがあったためです。
このバグの報告をしたのは自分なのですが、その際にfallocateについて調べてたら割と興味深い動きだったので今回書きます。

fallocateとは
すごく簡単にいうとめっちゃ高速にディスク領域の割当ができます。
どれぐらい速いかというと


# time fallocate -l 10GB varnish_storage.bin

real    0m0.001s
user    0m0.000s
sys     0m0.001s


と気にならないレベルです。
割と便利だと思います。

fallocateの不思議な動き
xfsとext4でfallocateの動きを確認した際に以下のような動きをしました。

xfs-fallocate


root@varnish-trunk:/mnt/xfs# df -T .
Filesystem     Type 1K-blocks  Used Available Use% Mounted on
/dev/xvdh      xfs   15718400 32928  15685472   1% /mnt/xfs
root@varnish-trunk:/mnt/xfs# fallocate -l 10GB varnish_storage.bin
root@varnish-trunk:/mnt/xfs# df -T .
Filesystem     Type 1K-blocks    Used Available Use% Mounted on
/dev/xvdh      xfs   15718400 9798556   5919844  63% /mnt/xfs
root@varnish-trunk:/mnt/xfs# fallocate -l 10GB varnish_storage.bin
fallocate: varnish_storage.bin: fallocate が失敗: デバイスに空き領域がありません
root@varnish-trunk:/mnt/xfs# ls -ltah varnish_storage.bin
-rw-r--r-- 1 root root 9.4G 12月  3 23:55 varnish_storage.bin

ext4-fallocate


root@varnish-trunk:/mnt/ext4# df -T .
Filesystem     Type 1K-blocks  Used Available Use% Mounted on
/dev/xvdi      ext4  15350768 38384  14509568   1% /mnt/ext4
root@varnish-trunk:/mnt/ext4# fallocate -l 10GB varnish_storage.bin
root@varnish-trunk:/mnt/ext4# df -T .
Filesystem     Type 1K-blocks    Used Available Use% Mounted on
/dev/xvdi      ext4  15350768 9804016   4743936  68% /mnt/ext4
root@varnish-trunk:/mnt/ext4# fallocate -l 10GB varnish_storage.bin
root@varnish-trunk:/mnt/ext4# ls -ltah varnish_storage.bin
-rw-r--r-- 1 root root 9.4G 12月  3 23:56 varnish_storage.bin

なぜかxfsは同じ容量を確保しようとしてるのに失敗する。

xfs-stat


root@varnish-trunk:/mnt/xfs# fallocate -l 1GB varnish_storage.bin
root@varnish-trunk:/mnt/xfs# stat varnish_storage.bin
  File: `varnish_storage.bin'
  Size: 1000000000      Blocks: 1953128    IO Block: 4096   通常ファイル
Device: ca70h/51824d    Inode: 131         Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2015-12-03 23:58:42.851567000 +0900
Modify: 2015-12-03 23:58:47.475567000 +0900
Change: 2015-12-03 23:58:47.475567000 +0900
 Birth: -
root@varnish-trunk:/mnt/xfs# fallocate -l 1GB varnish_storage.bin
root@varnish-trunk:/mnt/xfs# stat varnish_storage.bin
  File: `varnish_storage.bin'
  Size: 1000000000      Blocks: 1953128    IO Block: 4096   通常ファイル
Device: ca70h/51824d    Inode: 131         Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2015-12-03 23:58:42.851567000 +0900
Modify: 2015-12-03 23:59:15.123567000 +0900★変わった
Change: 2015-12-03 23:59:15.123567000 +0900★変わった
 Birth: -

ext4-stat


root@varnish-trunk:/mnt/ext4# fallocate -l 1GB varnish_storage.bin
root@varnish-trunk:/mnt/ext4# stat varnish_storage.bin
  File: `varnish_storage.bin'
  Size: 1000000000      Blocks: 1953136    IO Block: 4096   通常ファイル
Device: ca80h/51840d    Inode: 12          Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2015-12-03 23:59:35.871567000 +0900
Modify: 2015-12-03 23:59:35.871567000 +0900
Change: 2015-12-03 23:59:35.871567000 +0900
 Birth: -
root@varnish-trunk:/mnt/ext4# fallocate -l 1GB varnish_storage.bin
root@varnish-trunk:/mnt/ext4# stat varnish_storage.bin
  File: `varnish_storage.bin'
  Size: 1000000000      Blocks: 1953136    IO Block: 4096   通常ファイル
Device: ca80h/51840d    Inode: 12          Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2015-12-03 23:59:35.871567000 +0900
Modify: 2015-12-03 23:59:35.871567000 +0900★変わってない
Change: 2015-12-03 23:59:35.871567000 +0900
 Birth: -

xfsは同じサイズでfallocateを叩くとタイムスタンプが変わる。

xfs-filefrag


root@varnish-trunk:/mnt/xfs# fallocate -l 1GB varnish_storage.bin
root@varnish-trunk:/mnt/xfs# filefrag -v varnish_storage.bin |md5sum
8a5e67e82aa00af5af01c6d825bd8142  -
root@varnish-trunk:/mnt/xfs# fallocate -l 1GB varnish_storage.bin
root@varnish-trunk:/mnt/xfs# filefrag -v varnish_storage.bin |md5sum
8a5e67e82aa00af5af01c6d825bd8142  -

ext4-filefrag


root@varnish-trunk:/mnt/ext4# filefrag -v varnish_storage.bin |md5sum
9785097e3ce0e6eb3dd703fbd54e5d69  -
root@varnish-trunk:/mnt/ext4# fallocate -l 1GB varnish_storage.bin
root@varnish-trunk:/mnt/ext4# filefrag -v varnish_storage.bin |md5sum
9785097e3ce0e6eb3dd703fbd54e5d69  -

両方共、physical_offsetに変化なし
ちなみにext4の場合は割とフラグメントしますがxfsの場合はあまりしませんでした。
(10GB確保時にext4=12~50extents xfs=3~5extents)

これらを見てみると恐らくxfsは毎回空き容量と確保したい容量を付きあわせていて(既にallocateされてる容量は無視?)
ext4は最初にコマンド実施が必要かどうかをチェックしてるように見えます。
なんでこうなるのかxfsのコードを少し追ったのですが流石にキツかったので途中で諦めました。

まとめとか
fallocateは速くて便利なんでdd使ってpre-allocateしてる人はfallocate使うと幸せになれると思います(とは言えext3とかはサポートしてませんが)
そしてvarnish4.1をext4で動かす場合は内部でfallocateするので、今まで手動でやっていたpre-allocateは不要です。
もし、自分でfallocateを使ったコードを書く場合は実行前にfstat等で実際に使用している容量を調べるなどして使うと良いと思います。


 Posted by at 12:43 AM
12月 022015
 

この記事はセカイエ Advent Calendar 2015Varnish Cache Advent Calendar 2015の2日目の記事になります。
かなり前の話(8月)になるのですがリノコという定額リフォームサービスのサイトがTV東京のワールドビジネスサテライト(以下WBS)に取り上げられました
ありがたいことにたっぷり取り上げていただきました。その際に少しだけVarnishを使って負荷対策をお手伝いしたのでその時のことを書きます。
(なおここでとった対策は緊急ということで外しています。)

TV放映される上で負荷対策として検討したこと

  1. スケールアップ・アウト
  2. トップページ及びそこから回遊されるページの静的化
  3. Varnish導入

動き的には1を検討してみてその結果たりなさそうという判断を行い2,3を並列で行った感じです。

スケールアウト
リノコではクラウドを利用しているわけで当然スケールアウトを考えるわけですが
残念がらコード側があまりスケールするような仕組みになっておらず、例えば10倍サーバを投入してもスケールアップ・アウト出来ない箇所がボトルネックになって無駄になってしまう状態でした。
そのため最低限のスケールアップ・アウトを行いました 。

トップページ及びそこから回遊されるページの静的化
現地メンバーにお願いしましてコードを修正することでページの静的化することで、負荷に耐えるように修正を加えようとしました。
このアプローチ自体は間違っているとは考えていませんが残念がら切り戻しています。
単純にバグってしまったということです。敗因は明らかで単純に時間がなかったということにつきます。
今回のTV放送を知ったのが放映前日で、この作業をはじめたのが放映6時間ぐらい前だったからです。(そして切り戻し判断は放映2時間ぐらい前です)

Varnish導入
リノコのサイトでは幸いな事にキャッシュを行うことができる・出来ないの判断が比較的分かりやすかったため、試しに設定を書いてみて投入してみました。
この試みは割とうまく行ったためそのままTV放送を迎えました。

結果について
まずはどの程度のリクエストが来たかですが
image2015-11-29 16-43-13
割とビビるリクエストが着ているのがわかります。
でサイトが落ちたかどうかということなのですが半分落ちました。
この半分というのはVarnishでキャッシュが可能でキャッシュが出来たページについては特に問題なくレスポンスが出来て
キャッシュが不可能なページやキャッシュは可能だが深い階層で未キャッシュのページは落ちた感じです。
負荷対策を行う上で重要なのは、かけることが出きるコストを正確に把握し、何処の優先度を上げて何処を下げるかだと思っています。
特に今回のケースでは対策に費やされる時間がほぼなかったため、最小の対策で最大の成果を得る必要がありました。
今回はTV放映時にサイトを全部落とさない、特にLPとそこから回遊しそうなページについて落とさないを目標としました。
そういう意味では今回の対策はうまく行ったと考えています。

そもそもなぜVarnishを使ったか
どうせお前がVarnish好きだからじゃないかという指摘もあるかもしれませんが
今回のケースではVarnishを使う以外の選択肢はありませんでした。
もう少し時間があれば他の手段も取れましたがその場合でも良い選択肢だと確信しています。
『幸いな事にキャッシュを行うことができる・出来ないの判断が比較的分かりやすかった』
と書きましたがこのルールは以下でした

  • 特定のパス以下のURLはキャッシュ不可
  • クッキー中に特定キーが入っていた場合はキャッシュ不可
  • 同一URLでUAによってコンテンツの出し分けを行っている(PC/スマホ)
  • etc

例えば1URLで最大どの程度のパタンがあるかというと

  • キャッシュ可能+PC
  • キャッシュ可能+SP
  • キャッシュ不可+PC
  • キャッシュ不可+SP

と4パタンがありました。
まさにVarnishの設定言語であるVCLの得意とすることで、設定は47行でした。(実質コードですが)
しかもこの対策を行う上でサイト側のコードは修正しておりません。
このような場合において最低限の対策で効果を得るにはVarnishは最適だと考えています。

まとめ(負荷対策の重要性)
メディアに取り上げられるというのはいわばボーナスステージだと僕は考えています。
しかし、見に来ていただいた方が実際に興味を覚えてもらうためにはまずサイトを見れる状態を作らなければなりません。
普通に考えて初めてサイトを見に行って重くて見れなかった、そもそも見れなかったとかであれば、すぐに記憶から薄れ再訪することはないでしょう。
そのためサイトをなるだけ見れる状態にするのを考えるべきでしょう。

また、負荷のかかり方も様々です。

  • 事前にわかるかどうか
    • 事前連絡がある
    • 事前連絡がない(予測不能)
    • イベントなどでそもそもわかる(エイプリルフールやセール等)
  • 負荷の上がり方
    • 一瞬で上がってその後落ち着く(TV)
    • 一気に上がりつつその後もじわじわ上がる(ネットメディアなど)

どんなパタンがあるだろうとさくっと書いてみましたが(サイトの特性によってここは変わると思います)それぞれの組み合わせでどのような対策を行うか考えてみるのも良いと思います。
例えば事前連絡があるのであれば、インスタンスをもりもり多めにあげて(AWSであれば)ELBの暖気申請をして・・・みたいな対策も打てます。
逆に事前連絡がないのであれば、オートスケールするまでの時間までを如何に稼ぐかという話になってくるかもしれません。
更に一瞬で上がっていつ起こるか予測不能であればその場合でもどのコンポーネントを落とさないようにするかなどの事前の設計・対策が重要でしょう。
もちろん、どのパタンにおいてもスケールするようにコード修正するとかクラウドの機能を使う/業者の選定も大事です。
また、どのパタンでもキャッシュ可能なコンテンツをキャッシュすることは有効な手段の一つだと思います(Varnishでもなんでもいいですが)


12月 012015
 

この記事はVarnish Cache Advent Calendar 2015の1日目の記事になります。

何回か散発的に取り上げたことはあるのですがVSL-Queryの使い方についてまとまっては書いていなかったので書いてみます。
varnishlogのフォーマットの説明についてはv3の頃の記事ですがVarnishのログにアクセスしてみよう!を参考にしてください。

Varnishは他のミドルウェアでよくあるようにアクセスログをファイルに直接書き込むということはせずにリングバッファな共有メモリ(VSM)に出力します。
その際に出力されるログ(VSL)は非常に多岐に渡ります

  • ヘッダ(クライアントからのリクエストヘッダやバックエンドからのレスポンスヘッダなど)
  • ヘッダへの操作
  • どのアクション(vcl_recvなど)が呼ばれて何をリターン(hashなど)したか
  • std.logで出力した任意のログ
  • などなど

標準ではoffにされていますがonにすることでVCL中のどこのブロックを通過したかもトレースすることも可能です。
varnishncsaやvarnishlogなどの各種ツールはこのログを読んで出力します。
vsl
もちろんそのまま使うことも多いのですが、例えばエラーがでた・遅いリクエストだけを抽出したいということがあります。
その際に利用するのがVSL-Queryというもので、要はVSLに対して絞り込みを行う式を作成できます。


varnishncsa  -q "respstatus >= 400"

例えばこうすることでステータスコードが400以上のものを絞り込んで出力することができます。
今回はVSL-Query全般についての記事を書こうと思います。

VSL-Query

VSL-Queryは3つの要素からできています。

  • ログのグループ化
  • トランザクションの階層化
  • クエリ評価する
ログのグループ化

グループの種類は4種類あります。

raw
ログ1行を表します(何もグループ化されていない状態)
rawの場合はReqURLなどトランザクションに関わるデータとヘルスチェック等の非トランザクションデータの両方が含まれています。
ログを見た際にvxidが0のものは非トランザクションデータです。

vxid
複数行のログをHTTPトランザクション毎にグループ化します。
例えばクライアントからのトランザクションを処理する際にバックエンドまで問い合わせを行った場合は
クライアント<->VarnishとVarnish<->バックエンドの2トランザクションに分かれます。
なお、このデータにはraw時に含まれていたトランザクションにかかわらないデータ(ヘルスチェックなど)は含まれていません。
ちなみにvxidとはVarnish Transaction IDの略でトランザクションを識別するためのIDです。
X-Varnishヘッダで挿入される数値についてもそれです。

request
複数のHTTPトランザクションをまとめてリクエスト毎にします。
ESIやrestartを利用した場合はrequest内にrequestが含まれる事があります。
基本的にこの粒度で見ていればクライアント・Varnish・バックエンドでの流れがわかるので便利です。

session
複数のリクエストをまとめてセッション毎にします。
複数のリクエストが含まれるケースはkeep-aliveが有効な場合です。

grp
図にするとこんな感じです。

トランザクションの階層化

vsl-queryではログのグループ化を行うと同時に階層構造を作成します。
例えばVarnishからバックエンドへのトランザクションはその前にクライアントからVarnishへのトランザクションがあります。当然ですがそれは親子関係です。
トランザクション間の親子関係を作っているので、当然ながらログ一行ずつをグループとしているrawやトランザクション単体をグループとしているvxidでは階層化は行われません。
requestとsessionにおいて階層化が行われます。
実際に例で考えてみましょう。

グループ分けをrequestでVarnishがクライアントのリクエストを受付して、バックエンドに問い合わせして返却するケースでの階層は以下になります。


[Lv1] +ClientとVarnishのやりとり
[Lv2] | +VarnishとBackendのやり取り

次にグループ分けをsessionにしてそのセッションからのリクエストが2つあった場合は以下になります。


[Lv1] +セッション
[Lv2] | +ClientとVarnishのやりとり
[Lv3] | | +VarnishとBackendのやり取り
[Lv2] | +ClientとVarnishのやりとり
[Lv3] | | +VarnishとBackendのやり取り

実際のログだとどのように出力されるかというと(グループ分け=request)


*   << Request  >> 44645623
-   Begin          req 44645581 rxreq
-   Timestamp      Start: 1448894183.500204 0.000000 0.000000
-   Timestamp      Req: 1448894183.500204 0.000000 0.000000
-   ReqStart       ****** 47457
-   ReqMethod      GET
...
-   VCL_return     fetch
-   Link           bereq 44645624 pass
-   Timestamp      Fetch: 1448894183.708612 0.208408 0.208408
-   RespProtocol   HTTP/1.1
...
-   Timestamp      Resp: 1448894183.710162 0.209958 0.001442
-   ReqAcct        881 0 881 1994 7476 9470
-   End
**  << BeReq    >> 44645624
--  Begin          bereq 44645623 pass
--  Timestamp      Start: 1448894183.500314 0.000000 0.000000
--  BereqMethod    GET
-

こうなります。
なおレベルは1スタートとなります。

クエリ評価

グループとクエリの関係
何に対してクエリ評価を行うかというと、先ほどグループ化した中でqueryを評価して、一致するものがあればそのグループを出力します。
つまりグループがrawの場合は1行単位で評価するので


# varnishlog -graw -q "respstatus >= 400"
  61146578 RespStatus     c 404
  35190583 RespStatus     c 404
  52614757 RespStatus     c 404
  52656165 RespStatus     c 400

条件にひっかかる行だけが出力されます。

逆にsessionで評価した場合


# varnishlog -gsession -q "respstatus >= 400"
*   << Session  >> 60621569
-   Begin          sess 0 HTTP/1
-   SessOpen       ******* 62077 :80 ******* 80 1448897491.204370 99
-   Link           req 60621570 rxreq
-   Link           req 60621571 rxreq
-   VSL            store overflow
-   End            synth
**  << Request  >> 60621570
--  Begin          req 60621569 rxreq
...
--  RespStatus     200
--  RespReason     OK
...
--  End
**  << Request  >> 60621571
...
--  RespStatus     404
--  RespReason     Not Found
...

同一セッション内で404を返しているリクエストがあるためステータス200で返してるリクエストも引っかかってきています。
たとえば400以上のステータスコードを吐いてるリクエストのみを取得するのであればグループはrequest若しくはvxidが適切でしょう。
このようにグループ分けは狙ったログを出力する際には非常に重要です。

クエリの文法
クエリは次のように書きます。
[レコード] [演算子] [被演算子]
例: respstatus >= 400

レコードは次のように書きます。
{階層レベル}タグリスト:レコードのPrefix[フィールド]
これは全て指定する必要はなく、最低限タグだけを指定すればOKです。
フルで指定するとこのような指定になります
{2+}Timestamp:Resp[2]
1つずつ説明します。

階層レベル ( {2+}Timestamp:Resp[2] )
先ほど説明したトランザクションの階層化におけるレベルになります。
レベルは次のような指定の仕方があります。

指定方法/サンプル 説明
{2} Lv == 2
{2+} Lv >= 2
{2-} Lv <= 2

ちなみに{0+}も指定できます。この場合は全てのレベルと一致します

タグリスト ( {2+}Timestamp:Resp[2] )
タグの一覧はこちらになります
また、複数のタグを以下のように指定できます

指定方法 サンプル 説明
[tag],[tag] respheader,reqheader カンマ区切りでタグをを完全一致で指定します。
*[tag名の一部] *header 後方一致するタグを指定します。
[tag名の一部]* resp* 前方一致するタグを指定します。

[,]と[*]は同時に指定可能です。
また、[*]を途中で挿入することもできません。


#OKパタン
req*,resp*
#NGパタン
*eq*

レコードのPrefix ( {2+}Timestamp:Resp[2] )
データが「:」で区切られている場合はそれをキーとして使えます。
例えばTimestampでのイベントラベルやReqHeaderなどのヘッダ名に対して使用できます。
例:ReqHeader:User-Agent #リクエストヘッダのUser-Agentを指定

フィールド ( {2+}Timestamp:Resp[2] )
データがスペースで区切られている場合はそれを区切り文字として1スタートで指定できます。
例えばレスポンスが完了するまでにかかった時間を指定したい場合は以下のようにします。
例:Timestamp:Resp[2] #Timestamp:Respの第2フィールドを指定

演算子
演算子は以下のものが使用できます

タイプ 演算子
数値(int,float) == != < <= > >=
文字列 eq ne
正規表現 ~ !~

被演算子
以下が使用できます

タイプ サンプル
int 100
float 0. 0.8
string hoge
regex (iPad|iPhone)

なおregexはpcre(3)を利用しているので、例えば大小文字を無視する場合は以下のようにします


(?i)wget

文字列や正規表現は基本的に[‘]か[“]でquoteするのが良いかと思いますが、以下の文字のみで構成する場合はquoteは不要です


a-z A-Z 0-9 + - _ . *

論理演算子
先ほどの[レコード] [演算子] [被演算子]を一つの式として、論理演算子と組み合わせることで複数の条件を指定できます。
使用可能なのは以下です。

指定方法 説明
not [expr] exprが一致しない場合に真
[expr1] and [expr2] expr1と2が両方とも真の場合に真
[expr1] or [expr2] expr1か2のどちらかが真の場合に真

もちろん[expr1] and not [expr2]や[expr1] or not [expr2]の指定も可能です。

その他
他には以下の様な指定が可能です。

指定方法/サンプル 説明
respstatus タグ名だけを指定するとそのタグが存在する場合に真となる
(fetcherror ~’no backend’ and respstatus == 503) or respstatus == 400 ()を式中に含めることが可能です

実際のサンプル

自分がよく使うqueryの一部を紹介します

vcl_log:[key] ~ 任意の文字列
std.log(“key:文字列”)でデバッグ用の文字列を仕込んで、それでマッチさせます。
場合によっては数値を利用して比較演算子と組み合わせるのも便利です。

reqheader:Host ~ [ホスト名]
ホスト名を絞って表示したい場合

respstatus >=400
エラーを起こしたもの

最後に

これ書いてる時にバグっぽいのを見つけたので調べてバグならチケット切ってきます(その部分の記述は削りましたw)


11月 162015
 

だいぶプライベートがゴタゴタしていて(主に引越で)今更の話で申し訳ありませんが次世代 Web カンファレンス(以下nextwebconf)のserver_perfで登壇する機会を頂いたのでその事を書いておこうと思います。

まず最初に、登壇者を見た時凄いメンツ過ぎてビビったというのと、それを実現するJxckさんのすごいなと思いました。
スライドなしでトークのみで議論するというイベントなのでぶっつけ本番かというとそうではなく、当然ながら僕にとってserver_perfってなんだろうというのをイベント当日、そして終った後も考え続けました。そういう意味では割と準備にエネルギーがかかったものだと思います。
おかげであやふやだった自分の中でのイメージが、一定のまとまりを得たというのも非常に成果でした。
このエントリはその際に作ったメモの一部を綺麗にしたものです(全部がパフォーマンス関連というわけではないです)
結論というわけではなく、あくまで僕の考えということをご留意ください。

そうえばサーバサイドパフォーマンスってなんだっけ

サーバサイドはともかくとしてパフォーマンスってすごく広い言葉だと思います。
単純にレスポンスが高速になるというのもパフォーマンスでしょうし
唐突の高負荷にも、サービスを落とさずオートスケールしていくのもパフォーマンスだと思いますし、いろいろあります。
この記事では主にレスポンスについて、そしてネイティブというよりブラウザよりです。

そもそもクライアントとサーバの境界線は何処だろう

いきなり昔話ですがWindows95~98がでたあたりのWebは非常に簡単でした。
ブラウザで動くコードは、せいぜい動いてマウスを追跡するスクリプトぐらいで、単純にDC等においてあるマシンから静的なファイルや、はたまたCGIで掲示板などを動作させてtableタグで段組が終わったHTMLを生成してレスポンスしていたと思います。
地図サイトも、今のようにシームレスに動くようなものではなく、クリッカブルマップで座標を変えていくものでした。
当時のブラウザとDCにあるマシンは、クライアントとサーバの関係でした。
しかしJavascriptがどんどん強力になり、少し前にAjax、そして最近ではHTML5/ServiceWorkerが出てきて、今までクライアントであったブラウザの中にサーバのようなものが出てきてからは、スパっとクライアントとサーバを分けることが難しくなってきています。
昔であれば、サーバサイドでかかった時間やファイルサイズ等のサーバサイドだけを見ていればそこまで大きなズレはありませんでしたが
最近ではサーバサイドをいくら速くしても、ブラウザで動くスクリプトがアレであればレンダリングが秒単位で遅れることなんてのも珍しくありません。
パフォーマンスを考える上で相対的にサーバサイドの重要性が下がり、クライアント側の重要度が上がってきているといえるでしょう(もちろんサーバサイドを御座なりにしていいというわけではないです)
言うならがスパっと境界線が引ける状態ではなくserver/clientのどっちに軸足を起きつつ両方見れるかというのが重要になってきているといえます。
また、誰がコントロールするかというのも意識する必要があると考えています。
サーバであればコントロールの主体は僕らなのでスペックやデータのコントロールもしやすいのですが、クライアントのコントロールの主体は必ずしも僕らではなくスペックもバラバラでデータのコントロールもしづらいです。

個人的に考えている次世代のサーバサイドパフォーマンス

クライアントの処理性能はどんどん向上し、PC・モバイルともにNW環境もよくなり、以前に比べて非常に環境がよくなりました。
しかしWebは高速になったでしょうか?
僕は必ずしもそうではないと考えています。
コンテンツがどんどんリッチにヘビーになっているということ、良くなったNW環境をまだ効率的に使えていないと考えています(HTTP/2が普及するといいなぁ)
そのような中で如何にパフォーマンスを考えていくかというと一つのヒントが昨年のAkamai Edge14カンファレンスのビデオ(Meeting the Grand Challenges of the Internet)の中にあると思います。
この中でVOD関連のキーワードとしてMOVING INTO HOMESというのが出てきています。
これは主にVODにおいて爆発するトラフィックについての話なのですが、僕はそれ以外でも言える話だと考えています。
僕にとっての次世代のサーバサイドパフォーマンスを考える上で

  • データは何処にあるのか
  • データはその場所にいつからあるのか
  • そのデータは個別のクライアントにとって最適であるか

が重要だと考えています。
すごく噛み砕くと

  • 如何にクライアントの近い場所にデータを置くか
  • プリフェッチ・プッシュ・キャッシュ
  • クライアントに応じた最適化

です。
如何にネットの環境が良くなろうとも、物理的に近いほうが良いのに決まっています(そして効率のよいプロトコル)
最近はありがたいことにクラウドがあるので、物理的にクライアントの近い場所にインスタンスを置き、そこからレスポンスをするということが大規模サービス事業者でなくても可能になりました。(選択もRoute53で楽になりました)
ただ、全世界で一つのデータソースを元に何かするということはいろいろ考えることがあり難しいです。
そこで重要なのが、クライアントに応じた最適化そしてキャッシュだと考えています。

まずクライアントに応じた最適化ですが、いわゆるFEOをはじめとするManagedな最適化サービスです。(AkamaiのIonやinstartlogicなど)
クライアント側のコードの最適化は複雑化してクライアントの種類(特に端末)も増えてもはや人力で全てのページで100%を目指すというのは辛くなっていると考えています。
そのためFEOのような透過的に最適化を行うロジックが今後も発展すると考えています。
なによりこれが素晴らしいのが最悪キャッシュが出来ない場合でもパフォーマンスの向上を期待できることです。
もちろん大抵の場合はキャッシュと併用できるはずですが・・・

次キャッシュの場合です。
キャッシュで設定するTTLですが、すごく乱暴な話をしてしまえばその期間は物理的に何処にデータを置いてもいいのです。(もちろんクライアントの近くに)
では単純に、CDNやクラウドのいろんなリージョンにキャッシュサーバを立ててしまえば解決でしょうか?
僕はそうは考えていません。
ここでもう一つ重要なこととしてロジックが動くことです。
何故ロジックが動くことが重要かというと、キャッシュを行うというのは本来危険なことで、静的コンテンツならともかく動的コンテンツに対してキャッシュを行う場合は高度な制御(=ロジックが書ける)が必要です。
もちろんキャッシュ用途だけではなく高度なfailoverなどにも使えます。
そういう点では、VarnishのVCL、まだ途上ではありますがNginxのnginScriptはそれに使えると考えています。またCDNでいえばFastlyにも注目しています。
またこのロジックから最適化の仕方をより自由に選べるようになると思っています。

以上を纏めると

  • データはどんどんクライアントの近くに置かれるようになる
  • 中間経路(CDN等)はよりインテリジェンスになりロジックが動くようになる。
  • 経路で実行可能なロジックはキャッシュを効率的に行うためのもの(よりサーバサイド)と透過的にクライアント向けのコードの最適化を行う(よりクライアント向け)二種類がありそう。
  • 2つは併用することでより効果を発揮する

と考えています。

新しいミドルウェアや方法論は誰のための銀の弾丸か

確かトーク中に銀の弾丸というキーワードを出したと思うのですが、誤解を招きやすいキーワードなのでここで説明したいと思います。
進歩の激しいこの業界ですから新しいミドルウェア・フレームワークが出てきては消えていっています。
出たてのモノはすごくピカピカでまるで銀の弾丸のようにみえます。
ある意味これは正しくて、もともと銀の弾丸とは狼男とかを倒せる的な意味で、熊や竜も倒せるかというとそうではなく、何にでも効くというわけではないのです。
じゃぁ狼男とはなにかというと開発した会社で抱えている問題で、必ずしもあなたの問題ではないということです。
一言で言うならばワークロードが違うわけです。
僕がここで言いたいのは新しいものを見つけた際にいきなり飛びつくのではなく、何を解決したくてそれが生まれたのか、そのワークロードは?というのを考えるべきではないかということです。

よりManaged・複合化へ

例えばHadoopを使いたい・・・けど自社では運用管理出来ないというので様々なManagedなサービスがありますが、今後も様々なミドルウェアでその動きが出て来ると思います。
そしてそれら単機能のサービスを組み合わせた複合サービスというのがでてくると考えています(AWSのMobilehubもそのようなものと考えています)
それはまるで次世代機能のサービスパックのような感じになるかなと思います。
言い方はいやらしいとは思うのですが、イノベーター理論の各グループそれぞれで次世代は違い、ラガードに行くに従ってどんどんManaged/複合化されていく(より小規模でも使える)と考えています。

個人的なパフォーマンスについての思い

僕は毎年エイプリルフールにすごく憤っています。(Twのログみてみたんですがやっぱ憤ってました)
理由は負荷が来るのがわかっていても何故対策をしないのかということです。
お金の問題というのもあると思いますが、恐らくここになんらかのソリューションが出てくると考えています。
また、パフォーマンス関連で新しいモノが出てきた際にそれだけをやれば良いということではなく基本的なことも忘れないといいなと思います。
以前記事で書いたように割と基本的なことができていないことが多かったりします。
インターネットは歴史の積み重ねでできています。その中でパフォーマンスについてもそうといえます。
もちろん陳腐化して使わないようなもの(HTTP/2であればCSSスプライトなどのように)もありますが、対策の6割以上は過去のナレッジが使えるんじゃないかなと思っています。
ぜひ次世代だけでなく積み重ねについても考えてみるといいんじゃないかなーとか思ったりしてます

最後に

運営の方々登壇者の方々参加者の方々、server_perfセッションのmirakuiさん・cubicdaiyaさん、そしてJxckさん、皆様本当にお疲れ様でした。
他のセッションやセッション後、打ち上げでの話など非常に楽しかったです!

#や っ と ぼ く の n e x t w e b c o n f が お わ っ た ぞ !


 Posted by at 2:12 AM
10月 122015
 

Varnish4.1.0がリリースされました。
多くの新機能・改善・バグフィックスとほんの少しのVCLの変更があります。
特にバグフィックスを求めるユーザにとっては朗報です。
今までチケット上は修正されたけど各ブランチにマージされていなかったものが全て今回4.1ブランチにマージされたのでそれを求めるユーザにとっても更新するのも良いと思います。
割と大規模な変更があるので安定性について気になっている方もいると思いますが、4.1.0-beta1を2週間強ほど知り合いのサイト(ESI含む)に入れて運用しましたが
踏んだバグは一つ(#1792)で修正済みで安定して動いています。
また、CPUの使用率も下がっているようです。
ダウンロードはこちら

VCLの変更について
本家のアップデートガイドでも書いてある通り4.0.xのVCLをそのまま使用することが出来ます。
但しdeprecated的なのが増えているのとvmod_stdで引数の追加があるので注意が必要です。

HTTP/0.9のサポートを終了しました
むしろしていたのかという気持ちに・・

PROXY Protocol(v1/v2)のサポート
PROXY Protocol(v1/v2)をサポートしています。
使用する場合は起動オプションで以下のように指定します。(,PROXYを追加)


-a [IP Address]:[Port],PROXY

これに伴いremote.ip/local.ipが追加されました(後述)
接続してくる上位PROXYサーバのIPを制限したい時(ACL)に便利です。
またVarnishではhitchというPROXY Protocolを喋るTLS/SSLProxyを作っており(この前バージョンが1.0.0になった)
これと組み合わせるのを想定していると考えています。

VCLの自動cold/warm機能
Varnishはvclをreloadをした場合でも古いvclを保持しておいて後で使用することが出来ました。
この状態ではオリジンにヘルスチェックが飛んでしまい、それを防ぐには古いvclを削除するには明示的にdiscradをする必要がありました。
しかし4.1では使用されなくなったVCLは一定時間経過後(vcl_cooldown)にcold状態になりヘルスチェックが止まります。

セキュリティの強化(jails)
4.0.3


root     30262  0.0  4.2 124540 84320 ?        SLs  Oct05   0:43 /usr/sbin/varnishd ...
nobody   30264  0.1  4.2 582932 85724 ?        Sl   Oct05  15:19  \_ /usr/sbin/varnishd ...

4.1.0


varnish   1278  0.2  0.2 124768  5376 ?        Ss   16:32   0:00 /usr/sbin/varnishd ...
varnish   1280  1.7  5.8 1639192 117276 ?      Sl   16:32   0:00  \_ /usr/sbin/varnishd ...

今まではマスタープロセスはroot権限で動いていたのですが4.1からはセキュリティの強化のためになるだけ別ユーザで動くようにしました。
デフォルトではvarnishを使います。
もし任意のユーザ名を指定したい場合は起動オプションで-jオプションで指定可能です。

VMODでアクセスできる範囲の大幅な強化
これはどちらかと言うとVMODを作る人向けなのですが
以下のことが可能になっています

  • レスポンスボディに対するフィルタ機能(VFP/VDP)
  • バックエンドの動的生成
  • カスタムバックエンドの作成

まだすべて試しきれていないのですがどれも非常に強力です。
これは機会があれば別記事で取り上げようと思います。

動作変更

stale-while-revalidate(RFC5861)に対応しました
graceの初期値に使う感じです。

VCL変更/アクションのリターン値

vcl_hit
return(fetch)はreturn(miss)に変更する必要があります。
ただし4.1.0時点では変更を促すログを出しますがfetchはmissと同じように動作します
恐らく次の大型バージョンアップ時で消されるのではないかと思います。

vcl_backend_error
return(abandon)が追加されました。

vcl_init
return(fail)が追加されました。
文字通りfailを返すとvclの初期化に失敗します。

VCL変更/変数

remote.ip / local.ip (IP READ)
先程も述べたとおりPROXY Protocolに対応したため、それに伴いclient.ip/server.ipにPROXYサーバから渡された値が入ってくるようになりました。
(※ここで言うPROXYサーバはhaxproxyだったりAWSのELB等のPROXY Protocolを喋る上位サーバです。)
そのためclient.ipがVarnishに接続してきたIPアドレスを示すのではなくPROXYサーバがに接続してきたIPアドレスが入ってくる可能性が出てきました。
言葉で説明するよりマトリックスで示したほうが分かりやすいので以下に示します。


Direct
[client(192.168.1.200)]
   ↓
[Varnish(192.168.1.100:6081)]

Proxy Protocol
[client(192.168.1.200)]
   ↓
[HAProxy(192.168.1.10:8080)]
   ↓
[Varnish(192.168.1.100:6086)]

Description Direct PROXY Protcol
remote.ip ローカル(Varnish)に接続してきたIPアドレス 192.168.1.200(Client) 192.168.1.10(HAProxy)
client.ip クライアントのIPアドレス 192.168.1.200(Client) 192.168.1.200(Client)
local.ip ローカル(Varnish)のIPアドレス 192.168.1.100:6081(Varnish) 192.168.1.100:6086(Varnish)
server.ip クライアントのコネクションを受けたIPアドレス 192.168.1.100:6081(Varnish) 192.168.1.10:8080(HAProxy)

req_top.* (READ)
トップリクエストのreq.*を参照します、書き込みは出来ません。
何に使えるかというとESIの時に親(top)のreq.urlを知りたいといった時です。
取得できるのはあくまでもリクエストに関わるものなので例えばreq_top.backend_hintみたいなものはありません。
使用できるのは以下です。
req_top.method
req_top.url
req_top.http.*
req_top.proto

beresp.was_304 (BOOL READ)
文字通りbackendから304(Not Modified)が返却され、既に取得しているオブジェクトのリフレッシュに成功した場合にtrueになります。

beresp.age / obj.age (DURATION READ)
オブジェクトが生成されてからの経過時間(Age)を返却します。

beresp.backend (BACKEND READ)
fetchに利用したバックエンドを返却します。
例えばバックエンド名を取得したい場合はberesp.backend.nameとします。

resp.is_streaming (BOOL READ)
ストリーミングされている場合にtrueになります。

パラメータの変更

変更されたもの
workspace_session
4.0.3: 384byte
4.1.0: 512byte

vsl_mask
4.0.3: -VCL_trace,-WorkThread,-Hash
4.1.0: -VCL_trace,-WorkThread,-Hash,-VfpAcct
※VfpAcctは4.1.0で追加されたログ項目

削除されたもの
group
group_cc
listen_address
user
pool_vbc
timeout_req
timeout_idleと統一

追加されたもの
vcl_cooldown
VCLがcold状態に遷移するまでの時間

vmod_std

real2integerが追加されました。
型変換関数でfallbackがなかったものにfallbackが追加されました(time2integer / time2real / real2time)

varnishstat

-wがなくなりました

varnishhist / log / ncsa / top

-tオプションが追加されました(VSMを開く際のタイムアウト設定)

その他

vmodを追加しました
追加したといってもデフォルトで入っているわけではなく別reposです
libvmod_saintmode
Varnish3.0系であったsaintmodeです

libvmod_xkey
セカンダリハッシュを作成します。主にパージに利用し高速に動作します。
恐らくですがVarnishPlusで提供されていたhash-ninjaと同等のものと考えています。

libvmod-rtstatus
varnishstatと同等のデータを出力します

リンク

https://www.varnish-cache.org/docs/trunk/whats-new/changes.html
https://github.com/varnish/Varnish-Cache/blob/4.1/doc/changes.rst