2月 102017
 

4.1系の最新版の4.1.5がリリースされました。 [changelog] [公式のリリース文]

公式でも触れられてる通りほぼバグフィックス(あとドキュメント周り)なのですが、1点パフォーマンスに影響する修正があります。
個人的に重要かなと思うのは#2106の修正です

バグ修正

workspaceが溢れた場合にpanicしていたのを修正 #1834

Basic認証でAuthorizationのフォーマットがおかしい場合varnishncsaでデコードできない問題の修正 #2148

VCLでバックエンドの比較が動いていなかったのを修正 #2168

VSMの使用状況のカウンタ(vsm_free)がおかしかったのを修正 #2188

ESIのincludeでパスが存在しない場合にpanicしたのを修正 #2197
「http://foo/」の場合は問題なかったのですが「http://foo」の場合は今までpanicしていました
その修正になります。

INT/REAL型において「-」をパースできなかったのを修正 #2167
要は-10とかを扱えるようになりました。

beresp.backend.ipが本来使用できないbackend_errorで呼び出すとpanicしたのを修正 #1865
backend_responseでのみ利用できるように修正されました

CNT_Requestでパニックしていのを修正 #2106

仕様変更

バックエンドへの接続にNagleアルゴリズムを使っていたのをやめました #2134
クライアントとの通信はもともとTCP_NODELAYだったのですがバックエンドとの通信はTCP_NODELAYを指定していませんでした。
キャッシュが出来ないオブジェクト(pass)の場合だと多少効いてくるのではと考えています。

改善

devicedetect.vclをアップデートしました(bot情報)


2月 092017
 

少し前にスロットルでサイトを守る(vmod-vsthrottle)で異常なリクエストに対してスロットルをかける方法を紹介しました。
しかしこれはあくまでリクエストレートのみで、そのリクエストがどれだけ帯域を使うかは制限しません。
例えば特定のリソースについては帯域を制限をかけたい場合や(動画のようにDL中でも再生できるのであれば、必要なビットレート+αでもいいかなとか)
ユーザの属性(課金状況など)や、はたまたリクエストが多いユーザに対しては制限をかけたい場合などいろいろあるかと思います。
今回は帯域制限を行うvmod-tcp(ドキュメント)を紹介します。

環境について

vmod-tcpでの帯域制限はtc-fqのSO_MAX_PACING_RATEを指定することによって実現しています。
これはカーネル3.13からの機能のためUbuntuであればTrusty以降が必要です、CentOSの場合はカーネルを新しくする必要があるかと思います。(7だと3.10なので)

インストール

これはvarnish-modulesの中に入ってる一つなので、インストールについては前回の記事を参照ください。

tc-fqに切り替え

先程も述べたとおりtc-fqの機能を利用しているためこれを使うようにします。
tcについてはこちらが詳しいので参照ください(よくわかるLinux帯域制限)

まず、現在のqdiscを確認します


xcir@gw01:~$ tc qdisc show dev eth0
qdisc pfifo_fast 0: root refcnt 2 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1

eth0のところは環境によって変更してください(制限したいインタフェース)
今はpfifo_fastを利用していることがわかります。

次にfqに変更します


xcir@gw01:~$ sudo tc qdisc add dev eth0 root handle 1: fq
xcir@gw01:~$ tc qdisc show dev eth0
qdisc fq 1: root refcnt 2 limit 10000p flow_limit 100p buckets 1024 quantum 3028 initial_quantum 15140

qdiscがfqになりました。
これでvmod-tcpによる帯域制限が利用できます。

使い方

すごい簡単です。
例えばdatファイルに対して制限をかけたいのであれば


sub vcl_recv {
  if(req.url ~"\.dat(\?.*)?$"){
    // 1000KB/s(8Mbps)
    tcp.set_socket_pace(1000);
  }
}

とするだけです。

では、実際に動作確認をしてみましょう。


xcir@gw01:~$ curl http://*****:6081/10MB.dat  > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10.0M  100 10.0M    0     0   944k      0  0:00:10  0:00:10 --:--:--  943k

xcir@gw01:~$ curl http://*****:6081/10MB.dat2  > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10.0M  100 10.0M    0     0   260M      0 --:--:-- --:--:-- --:--:--  263M

10MBのファイルでやってみましたが、帯域制限に引っかかる方は943KiB/sなので大体8Mbpsでうまく制限されています。(10MB.dat)
対して引っかからない方では263MiB/sと制限されていないことがわかります。

注意事項

割りと簡単に使用できる帯域制限ですが注意する点がいくつかあります。

keep-alive時の制限の持続
この制限はtc-fqを利用してコネクション単位で行われます。
そのためkeep-aliveでコネクションを再利用した場合は当然のことながら制限は持続します。
なので例えば以下のように取得してみると


xcir@gw01:~$ wget http://*****:6081/10MB.dat  http://*****:6081/10MB.dat2
--2017-02-09 20:02:10--  http://*****:6081/10MB.dat
Connecting to *****:6081... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M) [application/x-ns-proxy-autoconfig]
Saving to: '10MB.dat'

100%[============================================================>] 10,485,760   943KB/s   in 11s

2017-02-09 20:02:20 (945 KB/s) - '10MB.dat' saved [10485760/10485760]

--2017-02-09 20:02:20--  http://*****:6081/10MB.dat2
Reusing existing connection to *****:6081.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M)
Saving to: '10MB.dat2'

100%[============================================================>] 10,485,760   942KB/s   in 11s

2017-02-09 20:02:31 (944 KB/s) - '10MB.dat2' saved [10485760/10485760]

FINISHED --2017-02-09 20:02:31--
Total wall clock time: 22s
Downloaded: 2 files, 20M in 22s (945 KB/s)
xcir@gw01:~$

制限がかからないはずの10MB.dat2についても制限がかかっているのがわかります。

なので、例えば先程の場合であればdatファイルをダウンロードした後(8Mbps)にコネクションを再利用して画像などのリソースをダウンロードする場合は8Mbpsとなります。
なにかリセットする方法がないかなと0を指定しても特に解除されなかったため、vcl_recvの先頭で十分な大きさの指定をしておくと良いと思います。


sub vcl_recv {
//8Gbps
tcp.set_socket_pace(1000000);
  if(req.url ~"\.dat(\?.*)?$"){
    // 1000KB/s(8Mbps)
    tcp.set_socket_pace(1000);
  }
}

このようにして試してみると


xcir@gw01:~$ wget http://*****:6081/10MB.dat  http://*****:6081/10MB.dat2
--2017-02-09 20:03:40--  http://*****:6081/10MB.dat
Connecting to *****:6081... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M) [application/x-ns-proxy-autoconfig]
Saving to: '10MB.dat'

100%[============================================================>] 10,485,760   943KB/s   in 11s

2017-02-09 20:03:51 (945 KB/s) - '10MB.dat' saved [10485760/10485760]

--2017-02-09 20:03:51--  http://*****:6081/10MB.dat2
Reusing existing connection to *****:6081.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M)
Saving to: '10MB.dat2'

100%[============================================================>] 10,485,760  47.7MB/s   in 0.2s

2017-02-09 20:03:51 (47.7 MB/s) - '10MB.dat2' saved [10485760/10485760]

FINISHED --2017-02-09 20:03:51--
Total wall clock time: 11s
Downloaded: 2 files, 20M in 11s (1.81 MB/s)

と、意図したどおりに動作します

TLS終端を別MWで行いlocal接続しているケースに効かない
Varnishはhttpsを喋れないのでTLS終端をnginxやhaproxyやhitchなどで行いローカルで接続しているケースもあると思いますが
その場合はloインタフェースもtc-fqを適用する必要があります。

http/2利用時
http/2は複数のストリームが1コネクションに入っています。
そのため特定のリソース(=特定のストリーム)のDL時のみを帯域制限をかけたい場合には使用できません。
この場合全体での制限になります。
もちろん、特定の行動をしたクライアントの帯域を絞りたいという用途には利用できます。


12月 042016
 

VarnishCacheはOSSですがPHKとVarnishSoftwareなどが主となって行っています。
個人や組織は当然ながら資金なしで活動できるわけではなく、VarnishSoftwareの場合はVarnishに多数の機能が追加されているVarnishPlusをサブスクリプション販売したいたりします。
ではPHKはどんな感じで資金を得ているかというとVarnish Moral License(VML)というので資金を得ています。
VMLで資金を得てその範囲でPHKが様々な新機能や改修を行っていたりするのですが、少し前のVarnish5の開発で資金が足りなくなってきました。
そのためVarnishの開発時間を取るためにVML購入を求める記事が公式にあがっていましたが
(Send us (more) money!)
今回、自分が実際にVMLを500EURほど購入してみたので、どんな感じのフローでやったのかなどを紹介します。

そもそもVMLとは

先程も述べたとおりPHKがVarnish関連の活動をするための資金です。
VMLのページに書いてあるとおりなのですが

Software development may be open, and the result shared with an Open Source Software licence, but the actual hours of programming are not gratis.
Just like everybody else, I need money for mortgage, kids and food, money I make by doing things with computers, for people who are willing to pay for that.
One of the things I do for money, is develop Varnish, and the Varnish Moral License is the vehicle I invented for the paperwork.

(意訳)ソフトウェアの開発はオープンで、その成果物はOSSとして共有されますが、その開発に費やされる時間は無償ではありません。皆さんと同じように、私は住宅ローンを、子供を養う、食べていくためにコンピュータを使って稼ぐ必要があります。私が稼ぐ一つの手段がVarnishの開発で、VMLはそのために開発した仕組みです。

ちなみに、現在VMLを継続的に購入しているユーザは

  • Fastly
  • VarnishSoftware
  • UPLEX

の3社です。

ちなにみVMLを購入することで何かしらのサービスやサポートが受けられたりするわけではありません。
(当然、欲しい機能をリクエストして優先されるわけでもありません)
支払った金額の分だけPHKがVarnishに時間を費やすことで開発が前に進む感じです。
VMLのFAQについてはここにあるのでぜひ目を通してみてください。

2019/05/02追記

現在VMLを€240以上購入すると、1年の間にセキュリティインシデントがあった場合は早期に共有されるようです

実際にVMLを購入したときの手順

購入希望のメールを送る
まずはPHK宛にメールを送ります。
メアドはここにあるものです。

含まれる必要がある内容は

  • 購入するラインセンス額(How much do you want to pay for your license ?)
  • それは定期購入(期ごと、月ごと)か1回のみか(Do you want to pay it monthly or quarterly ? (or onetime ?))
  • Webページに記載される名前(What name should appear on the web-page ?)
  • 請求書に記載される名前(What name should appear on the invoice ?)

です。
自分が実際に送ったメールはこんな感じです。
vml1
ついでにこの記事を書きたかったので書いてもいいかを聞いています。

ちなみに今回500EUR(6万円ぐらい)を送金しています。もっと少なくてもいいのでしょうか?
これは海外送金の難しいところで自分はそこまで詳しくないのですが、
海外送金の場合だと受け取るのにもお金が必要で、FAQにもあるのですが100EUR以下の場合は手数料のほうがかかってしまうらしいです。
また、当然ながらPHK側の事務コストが発生しますのでそのあたりを勘案して考えると良いかなと思っています。
(メールで「私が避けたいのは5ドルの請求書を200枚も書くような状態に陥ることだ」と言ってました)
もし、VMLは煩雑なので他の方法がないかといえばWishlistもあります。
実際、自分はVMLの海外送金がめんどくさくて何度かリリースがあったタイミングでWishlistを消化してました。
なぜ今回VMLを購入したかというと公式サイトにVMLについて書いてあったのと、Wishlistに買えるものがなかったからです。

請求書が来る
請求書はPDFで送られてきます。(自分の場合は1週間ぐらいできました)
vml2
マスクしてる箇所は住所とか電話番号が書いてあった感じです(一応公開してOKって確認とりました)

海外送金をする
最初なんでPaypalが使えないんだろうと思ったんですが
以前に資金集めを行った際に使っていろいろあったようです。
ということで銀行で海外送金をする必要があります。
海外送金のめんどくさいところが、500EURを送ろうとしても実際に500EURが相手に渡るかわからないところです。
中継銀行で手数料が引かれたりすることもあります。
海外送金できる銀行はたくさんあるのですが(ゆうちょでもできます)
中継手数料などを先に支払ってしまえる銀行を探したところ楽天や三井住友などがあったので今回は三井住友銀行を使いました。
vml3
これ書いた後に大変だったのが、銀行側で「æ」に対応するキーがなくてなんて入力していいかがわからなかったことですw
aeで書いて着金したのでこれで大丈夫みたいです。

たしか翌週に三井住友から明細書が自宅に送られてきました。
vml4

これで送金は完了です。

で、翌月ぐらいにVMLの明細のところに名前がのります。
vml5

まとめ

Varnish5がリリースされてhttp2に実験的なサポートをするなどしましたが、まだまだバギーで改善が必要です。
今回少額ながらVMLを購入しましたが少しでもその改善に役に立てば嬉しいですし、この記事を公開することでVarnishを使っている方でVMLの購入を検討してくれると嬉しいなと思います。


12月 022016
 

4.1系の最新版の4.1.4がリリースされました。 [changelog] [公式のリリース文]
主にバグ修正とドキュメントの改善ですが
1件すごくタイムリーなバグ修正が存在します。
また、今回のリリースで4.0.xがEOLになりました。

バグ修正

バックエンドの現在の接続数のカウンタが機能していないのを修正 #2011

ラウンドロビンDirector利用時にクラッシュすることがあるのを修正 #2024

フェッチ時にワークスペース・ストレージ確保失敗するとリークする可能性があったのを修正しました

return(pipe)時にstd.cache_req_bodyが機能しないのを修正 #1881

vmod_named利用時にVBE_Deleteでpanicすることがあるのを修正しました #2008
コードの修正を見た感じnamedに限らずに動的にバックエンド定義をUpdateしていくようなDirectorの場合に問題が起きそうです。

エラーなどで起動できなかった場合にvsmファイルが残っていたのを修正しました #2115

keepalive時に複数のrangeリクエストが来た場合に先頭リクエスト以外をしばらくブロックすることがあるのを修正しました #2035

バックエンド選択時に稀にpanicするのを修正しました #2027

時刻が逆行(STEP)した場合に子プロセスが再起動する場合があるのを修正しました #1874
うるう秒もありますのでこのあたり注意しておいたほうが良いと思います。
条件としてはセッションがクローズ(close)されるタイミングの時刻がセッション開始時刻(open)が早くなっていた場合です。
Varnishのこの時間の精度はナノ(clock_gettime/CLOCK_REALTIME)もしくはマイクロ秒(gettimeofday)なので
うるう秒で23:59:58->59->59->00のように59を繰り返す場合(STEP)でリクエスト自体を1秒未満で処理していると引っかかる可能性があります。
そのため、今回のうるう秒でSTEP動作をする可能性がある場合は強くアップデートを勧めます。
なお5.0.0も影響を受けます。今のところ5.0.1がリリースされる気配はないのでSLEWで動かすか独自で現時点のmasterのものをビルドするかをおすすめします。(4.0.xは影響なさそうです)
また、この問題の解決のためにclock_stepといパラメータが追加されています。

機能強化

varnishhistの表示にy軸が追加されました
5.0.0のhistと同じ表示です。

仕様変更・追加

varnishtestで外部のコマンドを実行するfeature cmdが追加されました。
すでにあるshellコマンドとの違いですが、shellの場合はその実行結果でテストが成功・失敗しますが
feature cmdの場合はテスト自体が実行されるかされないかです。
混在環境(OSやCPU)で特定環境のみのテストを行う際に便利に使えると思います。

varnishtestのresp.msgはresp.reasonに変更されました

clock_stepが追加されました
時刻同期で一気に時刻が変更(STEP)された場合の許容秒数です。
デフォルトは1秒です。

thread_pool_reserveが追加されました
重要なタスクのために予約されるスレッド数です。
例えば、クライアントの処理でスレッドを使い尽くした場合でもreserveされているスレッドがあればバックエンドの問い合わせが可能です。
この値は引き上げるとクライアント処理で使われるスレッド数が少なくなるため注意する必要があります。
あくまで一気にリクエストが来てスレッドが足りなくなった場合に発動するものなので、普段から適切なスレッド数を指定していればそこまで問題無いと思います。
デフォルトは0(auto)で、thread_pool_minの5%が自動的にセットされます。

パッケージ構成が変更されました
5.0の構成とおなじになりました。(varnish = varnish + libvarnishapi + varnish-doc)
そのためそのままapt-get installで更新しようとすると失敗します。
一度4.1.3までのバージョンをremoveしてアップデートするのをおすすめします

その他変更

ドキュメントの改善やRaceコンディションで起きる幾つかのバグが修正されています。


10月 032016
 

二年ぶりぐらいにメジャーバージョンが更新されました。[公式リリース] [ダウンロード]
今回の目玉機能としてHTTP/2対応とDirectorのConsistent Hash対応があります。
なにはともあれ、とりあえずHTTP/2を使うセットアップをしてみましょう。

Hitch1.4.0+Varnish5でHTTP/2を使う

Varnishはよく知られてるようにHTTPSに対応していません。
HTTP/2に対応したこのバージョンにおいても、やはり本体では対応をしていません。
そのため通常のブラウザからVarnishに対してHTTP/2にアクセスするにはTLSを解かなくては行けません。
今回はVarnishSoftwareが提供しているHitchを利用します。
なお、HTTP/2を利用するには1.4.0以降のバージョンが必要です。
また、一部ディストリビューションではパッケージが提供されていますが、私が利用しているUbuntu 16.04向けのパッケージはなかったのでソースからbuildしていきます。

Hitchのインストール
公式手順を参考に行います


sudo apt-get install libev-dev libssl-dev automake python-docutils flex bison pkg-config
git clone https://github.com/varnish/hitch.git
cd hitch
./bootstrap
./configure --prefix=/opt/hitch/
make
sudo make install

これだけです。(prefixはお好きなところにでも)
なお、HTTP/2を使うにはNPNもしくはALPNのサポートが必要です。
NPNはOpenSSL1.0.1、ALPNの場合は1.0.2が必要ですが、既にChromeでは今年の6月ごろにNPNでのHTTP/2サポートは打ち切られALPNのみでのサポートとなりました。
そのため実質HTTP/2を利用するにはALPNをサポートしている必要があります。
とりあえず確認する程度であれば、FireFox49はNPNでも接続できるので1.0.1でも問題はないです。

Hitchの設定


HTTPS:443           HTTP:80
   ↓               ↓
Hitch:443--(localhost:81)-->Varnish:80
                 ↓
                Origin

今回はこんな感じで構成します。
まずはHitchの設定です。


#/etc/hitch/hitch.conf
frontend       = "[*]:443"
ciphers        = "ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:EECDH+HIGH:EDH+HIGH:HIGH:+3DES:!RC4:!MD5:!aNULL:!eNULL:!LOW:!EXP:!PSK:!SRP:!DSS:!KRB5:!DH"
backend        = "[127.0.0.1]:81"
write-proxy-v2 = on
workers        = 2
backlog        = 1024
keepalive      = 3600
syslog         = on
user           = "nobody"
daemon         = on
alpn-protos    = "h2,http/1.1"
pem-file       = "/etc/hitch/******.pem"

ciphersとかは割と適当ですがきちんと設定したい人はこちらで考えると良いかと思います。
なお、hitchのpem-fileは
秘密鍵 + 証明書 + 中間CA証明書 + dhparam
です。
Let’s Encryptでやる場合は
privkey.pem + fullchain.pem + dhparam
になります。
また、どうやって複数のvhostsに対応するのかというとpem-fileを列挙します。
ちなみにデフォルトのものは最後に定義されたものになります。
Hitchの起動


/opt/hitch/sbin/hitch --config=/etc/hitch/hitch.conf

次はVarnishの設定です。
Varnishの設定
まずはListenの指定をしましょう。
ポート80で通常のHTTPを受付して81でPROXYプロトコルで受付します。
そのための設定はこのような感じです。


-a :80 \
-a 127.0.0.1:81,PROXY \

また、HTTP/2はデフォルトでは有効にされていません。
そのためfeatureでhttp2を指定する必要があります。
varnishadmから設定する場合は


root@proxy02:~# varnishadm param.set feature +http2

root@proxy02:~# varnishadm param.show feature
feature
        Value is: +esi_disable_xml_check,+http2
        Default is: none

        Enable/Disable various minor features.
           none                       Disable all features.

        Use +/- prefix to enable/disable individual feature:
           short_panic                Short panic message.
           wait_silo                  Wait for persistent silo.
           no_coredump                No coredumps.
           esi_ignore_https           Treat HTTPS as HTTP in
                                      ESI:includes
           esi_disable_xml_check      Don't check of body looks like
                                      XML
           esi_ignore_other_elements  Ignore non-esi XML-elements
           esi_remove_bom             Remove UTF-8 BOM
           https_scheme               Also split https URIs
           http2                      Support HTTP/2 protocol

です。
起動パラメータで指定する場合は


-p feature=+http2 \

を追加すれば良いです。
これで動作します。

なお、後述しますが絶対本番環境では行わないでください。

それでは、HTTP/2での動作のさせかたを紹介したところで、いつも通り新機能などの紹介を行います。

全体的な話

過去のメジャーバージョンアップを振り返ってみると
2.1->3.0

  • VMODのサポートとVCL文法の整理

3.0->4.0

  • リクエスト・フェッチ・レスポンスといった一連の動作をClient/Backendスレッドに分割
  • VCLにバージョン番号付与とスレッド分割に対応するための変更

4.0->4.1(マイナーバージョンアップですが)

  • PROXYプロトコルのサポート
  • 内部処理にフックできるようなしくみの追加

4.1->5.0

  • HTTP/2の実験的サポート
  • shard director(consistent-hash)対応
  • バックエンドフェッチがPROXYプロトコルサポート
  • 別VCLを動的リンクして使うlabel機能のサポート

こうしてみると4.0の頃からHTTP/2の準備を着実にすすめていたのがわかるかと思います。
また、嬉しいことに5.0では3.0->4.0のときのような破壊的なVCLの変更はありません。
もちろん幾つかのVCLの変更はあります。
しかし大抵の場合は変更せずとも動き、もしくは少しのreplaceで済んでしまいます。
また、statsの項目も削られたものはあるものの出力フォーマットは変わっておらず、
LOGについてもHTTP/2関連の追加はあるものの大きな変更はなく、
大抵のVMODもリビルドすれば動き、累積バグは修正されています。
そのため、4.1で動いていた監視や周辺ツールはほぼ変更なしで動き既存の資産を使いやすいと思います。
10/03時点で知り合いのサイトの本番に投入して、20日程度経過していますが大きな問題もなく動作しています。
当然メジャーバージョンアップのため慎重な検証は必要ですが、3.0->4.0のときのような苦しみはなく
HTTP/2を使わないのであれば、どちらかと言うと4.0->4.1のときのような変更と思ってもらえればよいかと思います。

新機能

HTTP/2のサポート(experimental)

先程も紹介しましたがHTTP/2の極めて実験的(Very Experimental)なサポートを行っています。
有効にする場合は起動パラメータ、もしくはvarnishadmでfeatureパラメータを+http2に指定します。
なお、現時点で私が確認しているバグだと回避不能でリクエストで容易にクラッシュを起こすものが1件、回避可能なものが1件、仕様なのか判断できないが実装漏れっぽいのを1件見つけてます。(また今度泣きながら英語書きます)
そのため決して本番に投入することはおすすめしません。
しかし実際に触ってみていろいろバグを見つけて報告するとよいかと思いますので触ってみてほしいところです。

vmod-directorでsharedをサポート

consistent-hashのサポートです。
元々はUPLEXが提供していたvslpを公式に持ってきた形になります。
consistent-hashについてはぐぐるといろいろ出てくるとおもうので詳しくは割愛します(参考
今まではbackend数に変動があった場合には全体で再計算が走るのでキャッシュヒット率が全体で落ちる問題がありましたがこれを使うことで解決することが出来ます。
特に複数台のクラスタで複数段のキャッシュを組んでる環境ではかなり効いてくると思います。

VOID shard.set_rampup(DURATION duration=0)
0秒以上を指定した場合、バックエンドがhealthyになった場合にいきなり100%のトラフィックを流すのではなく、指定秒数(duration)の間に徐々にトラフィックを流していくようになります。(slow start)

BOOL shard.add_backend(BACKEND backend, STRING ident=0, DURATION rampup=973279260)
バックエンドを追加します。
identはノードの識別子で、デフォルトではバックエンドの名前を使うようになっています。
通常は使うことはないと思いますが、weight的な指定をしたいときなどに使うと良いかもしれないです。
rampupはサーバ個別に指定する場合に指定します。(973279260はmagic valueでset_rampupで指定された値を使うようになります)

BOOL shard.remove_backend(BACKEND backend=0, STRING ident=0)
バックエンドを削除します。
backendを指定した場合は一致するすべてが削除されます。

BOOL shard.clear(PRIV_TASK)
全てのバックエンドを削除します。

VOID shard.set_warmup(REAL probability=0.0)
指定の確率で次の代替バックエンドにリクエストがいくようになります。(0.5=50%)

warm

要はそのバックエンドが死んでキャッシュが落ちたとしても、
代替バックエンドがキャッシュをある程度持っている状態なのでダウン時の影響をより減らすことができるようになります
(ただし当然ながらキャッシュの二重持ちが起きるので空間効率は悪くなります)
なお、rampup期間中のバックエンドはwarmupの対象外になります。

BOOL shard.reconfigure(INT replicas=67, ENUM {CRC32,SHA256,RS} alg=”SHA256″)
consistent-hashで使うハッシュリングを作ります。
replicasは1つのバックエンドが仮想的に何個のバックエンドになるかです。(consistent-hashの仮想ノード)
algはどのハッシュ関数を使ってハッシュリングに配置していくかです。
なお、shardディレクターを使う場合は必ず一度は呼び出す必要があります。
イメージ的にはcommitと思ってもらえれば良いかなと思います。

BACKEND shard.backend(ENUM {HASH,URL,KEY,BLOB} by=”HASH”, INT key=0, BLOB key_blob=0, INT alt=0, REAL warmup=-1, BOOL rampup=1, ENUM {CHOSEN,IGNORE,ALL} healthy=”CHOSEN”)
バックエンドを選択します。

by=HASH(デフォルト)
vcl_hashで生成したhash値を使用して振り分けサーバを決めます。

by=URL
req.url/bereq.urlを使用します。

by=KEY
keyで指定したINT数値を使って振り分けします。
例えばcookieをもとに振り分けしたい場合は


shard.backend(by=KEY,key=shard.key(bereq.http.cookie))

といった感じです。

by=BLOB
key_blob指定したblobを使って振り分けします。
通常は使うことはないと思います。
また、blob全体を使うのではなく先頭4バイトを使用します。

key
by=keyを指定した場合に指定する数値です

key_blob
by=blobを指定した場合に使用するblobです

alt
指定した場合はn番目の代替バックエンドを選択します。
どのようなときに使うかというと何かしらのエラーが起きて別のバックエンドを選択したい場合(restart/retries時)に使用します。
alt=req.restart or bereq.retriesと指定しているとリスタート/リトライのたびに別のバックエンドを選択することが出来ます。
なお、rampup/warmupはaltが0のときのみ有効で、1以上のときはaltが優先されます。

warmup
代替サーバが選択される確率を指定します。(-1,0~1)
-1の場合はset_warmupで指定した値を使用します。

rampup
rampup機能を使うかどうかです。

healthy=CHOSEN(デフォルト)
healthyなバックエンドを選択します。
altが0以上の場合はバックエンドのhealthyを無視してn番目の代替バックエンドを選択します。
ドキュメントにはthen return the next healthy backend.とあるのですが、コードと実際の動作を見る限りだとalt指定時の代替バックエンド選択ではhealthyかの評価をしていません。

healthy=IGNORE
helathyを無視します。
altを指定した場合はhealthyを無視した状態で代替バックエンドを選択します。
またrampup/warmupは機能しません。

healthy=ALL
基本的にはCHOSENと同じですが、altが0以上のときに代替バックエンドを選択する際にもヘルスチェックをします。

ちょっとややこしいのでまとめてみました

healthy alt==0 alt>0
CHOSEN ヘルスチェックを行う ヘルスチェックを行わない
IGNORE ヘルスチェックを行わない ヘルスチェックを行わない
ALL ヘルスチェックを行う ヘルスチェックを行う

 

INT shard.key(STRING string, ENUM {CRC32,SHA256,RS} alg=”SHA256″)
指定した文字列を指定したハッシュアルゴリズムを通したときのINT値を得ます。
例えばSHA256は256bitなんですが、32bit分だけ返します。
この関数はshard.backendをby=KEY指定で使う場合に使用します。

で、実際のところどうやって使うかというとこんな感じです。



sub vcl_init {
  new ws_cache = directors.shard();
  ws_cache.set_rampup(10s);
  ws_cache.add_backend(ws01);
  ws_cache.add_backend(ws02);
  ws_cache.add_backend(ws03);
  ws_cache.add_backend(ws04);
  ws_cache.reconfigure();
}

sub vcl_recv {
  //x-varnish-hashにはユーザIDとかそんなのが入ってる
  ...
  set req.backend_hint = ws_cache.backend(by=KEY, key=ws_cache.key(req.url + ":" + req.http.host + ":" + req.http.x-varnish-hash));
}

 

INT,BACKEND型がBOOLで評価できるようになりました

こういうことができるようなります。


sub vcl_recv {
  if(1){
    set resp.http.foo = "1";
  }
  if(req.backend_hint){
    set resp.http.bar = "1";
  }
}

INT型で0はfalseになります。
また、BACKEND型では割り当てられていない場合はfalseになります。

別VCLを動的リンクして使うlabel機能のサポート

いままで異なるVCLを読み込む方法としてはincludeがありました。
これは単純にincludeを記載した場所に指定したVCLを展開するものでした。
そして今回追加されたlabelは別のコンパイルされたVCLを動的リンクします。
とりあえず使い方を紹介します

まずラベルを持つVCLをloadします


varnishadm vcl.load a_example_net /etc/varnish/a_examplet_net.vcl
varnishadm vcl.load b_example_net /etc/varnish/b_examplet_net.vcl
varnishadm vcl.load c_example_net /etc/varnish/c_examplet_net.vcl

varnishadm vcl.label vcl_a_example_net a_example_net
varnishadm vcl.label vcl_b_example_net b_example_net
varnishadm vcl.label vcl_c_example_net c_example_net

default.vcl


vcl 4.0;
//最低限1つはバックエンドがないと動かないのでダミー
backend default { .host = "127.0.0.1"; }

sub vcl_recv {
  if(req.http.host ~ "a\.example\.net$"){
    return(vcl(vcl_a_example_net));
  }
  if(req.http.host ~ "b\.example\.net$"){
    return(vcl(vcl_b_example_net));
  }
  if(req.http.host ~ "c\.example\.net$"){
    return(vcl(vcl_c_example_net));
  }
  return (synth(403));
}

a.example.netの場合はvcl_a_example_netというラベルを持つVCL(/etc/varnish/a_examplet_net.vcl)を使うようになります。
これは何に使えるかというとCDNのMWとしてVarnishを使っていたりして外部の人にVCLの記述を解放したいけど
VCLがそれぞれ干渉しないようにしたい場合に使えます。
vcl_a_example_netの中からb.examplet.netに関する処理を書いてもそもそもそういうリクエストが割り振られないからです。
また、今回はreq.http.hostで分けましたが、もちろん他のキーで振り分けを行うことが出来ます。

なお、設定を更新する場合は再度vcl.loadしてvcl.labelを叩く感じです


varnishadm vcl.load a_example_net_1 /etc/varnish/a_examplet_net.vcl
varnishadm vcl.label vcl_a_example_net a_example_net_1

ちなみにvcl.listを見てみるとこんな感じになります


# varnishadm vcl.list
active      auto/warm          0 boot
available   auto/warm          0 a_example_net
available  label/warm          0 vcl_a_example_net -> a_example_net_1
available   auto/warm          6 a_example_net_1 (1 label)

vcl_a_example_netがa_example_net_1になっているのがわかると思います。
また、reloadを行った場合はラベルの先のVCLについてはreloadされませんので注意が必要です。

バックエンドフェッチがPROXYプロトコルサポート

バックエンドの定義に.proxy_header=[プロトコルバージョン];を指定することでproxyプロトコルが使えるようになりました。


backend ws01 {.host = "192.168.1.1";.proxy_header = 1;}//PROXYプロトコルv1
backend ws02 {.host = "192.168.1.2";.proxy_header = 2;}//PROXYプロトコルv2

varnishtestの強化・変更

HTTP/2のテストやproxyプロトコルのサポートが行われました。
また、resp.msgはresp.reasonへ
semaはbarrierに名前が変更されました。

std.integerがREAL型の数値をパースできるようになりました

1.2といったものはもちろん1E+2みたいな表記もパースできるようになりました。
もちろんINT型になるように小数点以下は削除されます。

std.logがvcl_initで動くようになりました

 

varnishhistにy軸の目盛りが付きました

 

varnishstatの-fオプションがglob(7)の形式が使えるようになりました

 

POSTをキャッシュできるようになりました

正確にはVCLを正しく書けばPOSTもキャッシュできるようになったということです。(もちろんデフォルトではされないです)
内部的な変更としてbodyを常にバックエンドに送るように変更されており、これによりGETとbodyを使うPOSTなどと扱いが変わらなくなったためです。
とはいえGETはbodyがいらないのでbuiltin.vclでは削除するようになっています。


sub vcl_backend_fetch {
    if (bereq.method == "GET") {
        unset bereq.body;
    }
    return (fetch);
}

もし、自身のVCLがbuiltin.vclを通らない(return)している場合はunset bereq.bodyを追加する必要があるでしょう。
また、POSTのキャッシュができるようになったとはいえbuiltin.vclではやはりpass動作になっています。
試していませんがここをhashにすればキャッシュできると思いますが、オブジェクトのハッシュを作る際にbodyを評価していないため
ここらへんはbodyに対してアクセスできるvmodを使って中身を精査してハッシュを作る必要があります。

仕様変更

statsからn_waitinglistがなくなりました

VCL名に指定可能な文字が制限されました
わざわざvcl名を使っている人はそんなにいないと思うんですが
[A-Za-z][A-Za-z0-9_-]*にマッチする名前じゃないとだめになりました。

すべてのVCLオブジェクトは使う前に定義しないといけなくなりました
backendとかaclとかそういうのですね、むしろ今まで意識してなかった(いつも先頭に持ってきてた)ので
え、いけたの?ぐらいです。

pass(uncacheable)されたリクエストではcache-control/ExpiresのTTLはパースされなくなりました
今まではpassした場合でもTTLのパースはしていたのですがされなくなったので、対応するログも出力されなくなりました。

-T/-Sを無効化する場合は明示的にnoneを指定する必要があります

パラメータの変更

追加:ban_lurker_holdoff
効率的でないbanの指定などでban.listが肥大化した場合、ban_lurkerスレッドとの潜在的な競合によりlookup性能が悪化するケースがありました。これを防ぐためにsleepを入れるようになってその値です。
デフォルトは0.1sとban_lurker_sleepと同じです。

追加:session_max
session_maxは削除されました。
もともと4.0のリリースで効果がなくなったものです。
なお、将来的に復活する可能性はあるとのことです。

名前の変更:vcl_dir->vcl_path / vmod_dir->vmod_path
まだどちらも使えますがおそらく将来的に_dirのほうはなくなります。
また、:で分割して複数のpathを指定できるようになりました。
vcl_pathのデフォルトは/etc/varnish:/usr/share/varnish/vclとなっていますが
これはincludeなどでVCLを読もうとした際にまず/etc/varnishを探索し、
存在しない場合は/usr/share/varnish/vclも探すといった動きになります。
また、/usr/share/varnish/vclにはデバイスを判定するためのdevicedetect.vclが入っていています。

builtin.vclの変更点

GET時にbereq.bodyのunset
これは先程「POSTをキャッシュできるようにしました」で説明したとおりです。

hit-for-passの取扱について
今まではreturn(pass)したりそもそもキャッシュできないオブジェクトはhit-for-pass(HFP)としてマークされて
2分間は常にpass扱いとされていましたが、HFPとしてマークしないようにしました。
以下が4.1と5.0のdiffです。


 sub vcl_backend_response {
-    if (beresp.ttl <= 0s ||
+    if (bereq.uncacheable) {
+        return (deliver);
+    } else if (beresp.ttl <= 0s ||
       beresp.http.Set-Cookie ||
       beresp.http.Surrogate-control ~ "no-store" ||
       (!beresp.http.Surrogate-Control &&
         beresp.http.Cache-Control ~ "no-cache|no-store|private") ||
       beresp.http.Vary == "*") {
-        /*
-        * Mark as "Hit-For-Pass" for the next 2 minutes
-        */
+        # Mark as "Hit-For-Pass" for the next 2 minutes
         set beresp.ttl = 120s;
         set beresp.uncacheable = true;
     }

VCLの変更点

backendsの定義に.proxy_headerが追加されました
新機能で触れたものです

vcl_recv:returnにvcl(label)が追加されました
新機能で触れたものです

vcl_recv:rollbackが削除されました

vcl_hit:return(fetch)が削除されました
元々4.1の時点でfetch->missに変更するようにアナウンスが出てたものですが、このタイミングで削除さらえました。

vcl_backend_*:remote.ip, client.ip, local.ip, server.ipにアクセスできるようになりました

vcl_backend_fetch:bereq.bodyが追加されました
新機能で触れたものです

vcl_backend_error:synthetic()がberesp.bodyに変更されました
こんな感じの表記になります


    set beresp.body = {"<!DOCTYPE html>
<html>
  <head>
    <title>"} + beresp.status + " " + beresp.reason + {"</title>
  </head>
  <body>
    <h1>Error "} + beresp.status + " " + beresp.reason + {"</h1>
    <p>"} + beresp.reason + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + bereq.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"};

vcl_synth:synthetic()がresp.bodyに変更されました

vcl_deliver: obj.ttl, obj.age, obj.grace, obj.keepにアクセスできるようになりました(read)

正規表現でmatchした時の結果をBOOLとしてそのままセットできるようになりました


set req.http.foo = req.http.bar ~ "bar";

このような表記ができるようになりました。

アップデート時の注意

パッケージの構成が変更された
元々は

  • Varnish本体
  • libs
  • libs-dev
  • docs

の4種類でパッケージが別れていたのですが

  • Varnish本体+lib+docs
  • dev

の2種類に変更されました。
そのためインストールするときに失敗する場合は一度既存パッケージをアンインストールしてみてください。

VMODのABIが変更されたのでリビルドが必要
VMODのABIが変更されたので使用しているVMODについてはリビルドが必要です。

環境によってはreloadに失敗するかも
これは私が引っ掛かった問題+私のsystemd力が低いのでパッと解決策が思いつかなかった問題なのですが
VCL名が仕様変更(先頭文字はアルファベットである必要がある)されたけど、reloadスクリプトがそれに追随していないように見えます。
Xenial(systemd)でリロードしようとするとこの部分のLOGNAMEが入らないため先頭がuuidになります。
uuidは数字を含むためreload時にVCL名の仕様変更に引っかかってしまう感じです。
とりあえず先頭にreload_とかつけとけば回避はできるのでもし引っかかった場合は


-    vcl_label="${LOGNAME}${LOGNAME:+_}${uuid}"
+    vcl_label="reload_${LOGNAME}${LOGNAME:+_}${uuid}"

こんな感じで修正すると良いかなと思います。
また、私がRHEL/CentOSの環境を持っていないので試していませんが、おそらくRHELでも同様の問題がおきます。(:は使えない)

その他変更

累積バグが直ってたり、細かい改善があったりします。


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));

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

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