VarnishのBANのちょっとしたこと
Varnishのキャッシュ無効化の方法の一つとしてBANがありますが、効率の面からBANは直ちに指定キャッシュを削除するのではなく、BANエントリをBANリストに登録するだけになっています。次回アクセス時にBANリストを評価しBANに該当するなら、キャッシュ済みデータを捨て、新たにデータを取得するようになっています。
今回は、BAN関連の(おそらく)Undocumentedな部分について少し解説したいと思います。
BANリストはvarnishadm ban.listコマンドで確認でき、以下のように新しいBANエントリから順番に表示されます。
BANリストの表示例
# varnishadm ban.list Present bans: 1601859642.886582 0 - obj.http.x-url ~ ^/blog 1601859620.136044 2 - obj.http.x-url ~ ^/contact
登録したBANエントリのタイムスタンプとBANの条件が表示されていますが、何だかよくわからない数字(2カラム目)とハイフン(3カラム目)も表示されています。これらは一体なんなのでしょうか?
このうち、2カラム目の数字はBANエントリの参照カウント(ソースコード中の表記に合わせて以下refcountと呼びます)になります。具体的に何がBANエントリを参照しているかというと、キャッシュしたデータを保持しているキャッシュ済みオブジェクト(*1)になります(図1)。

BANエントリを参照して何をするのか
何のためにキャッシュ済みオブジェクトからBANエントリを参照しているのでしょうか?結論から言うと、BANリスト中の各エントリがキャッシュ作成後に登録されたものか否かを判断するのに使われています。
前提としてキャッシュ済みオブジェクトは必ず何れかのBANエントリへのポインタを保持するのですが、キャッシュ済みオブジェクトは新規作成される際、その時点での最新のBANエントリへのポインタを保持するようになっています(図2)。

上述のとおり、このポインタはキャッシュ登録した時間とBANが登録された時間の前後を比較するのに使用されます。次のリクエストが来てキャッシュ済みオブジェクト(A)にマッチした際、評価すべきBANエントリはオブジェクトAが指しているBANエントリ(2)より新しいもののみとなります(それ以外はキャッシュ前に登録されたBANなので評価してはいけない)。図2の状態では、キャッシュ登録後に登録されたBANは存在しないので、リクエスト受信時でもBANの評価は行われません。
図2の状態から新たにBANが登録されると図3のような状態になり、キャッシュ済みオブジェクト(A)に対して、BANエントリ(3)の評価が必要になります(図3)。

実際にキャッシュ済みオブジェクト(A)に対するリクエストを受けた場合は、BANエントリ(3)の評価が行われ、BANエントリへのポインタは最新のBANエントリへと更新されます(図4)。 これにより、次回アクセスではBANエントリ(3)も評価不要になるので、何度も同じBANエントリを無駄に評価することはありません。

このようにアクセスが頻繁にあるキャッシュ済みオブジェクトは、BANエントリのポインタが新しい方へどんどん更新されていきます。varnishadm ban.listの結果で、2カラム目の数字が古いエントリで減って、最新のエントリでどんどん増えていくように見えるのはこのような理由によるものです。BANリスト末尾の参照されなくなったBANエントリは不要とみなされて順次削除されていきます。
BANエントリの評価の要/不要は、わざわざこんなことしなくてもタイムスタンプで比較すればいいのでは?という疑問もあるかもしれません。実際には今回解説した以外にも、BANエントリは自分を参照しているキャッシュ済みオブジェクト達への逆ポインタも保持しており、ban lurker threadが不要なBANエントリを効率良く削除するのに使っています。そのためこのような作りになっているようです。このあたりの解説はまた別の機会に。
ban.list 3カラム目の表示
もう一つ謎だった3カラム目のハイフン表示は'C'と表示される場合があります。'C'表示はCOMPLETED状態であることを示しています。BANエントリとしてはもう不要だが、refcountが残っているため削除できないような場合はCOMPLETED状態になります。COMPLETED状態のエントリはBANリスト上には残っていますが評価に使われることはありません(BANリストをたどる際には無視される)。
なお、COMPLETED状態になった時点でBAN条件も削除されるのでban.listで'C'表示のエントリのBAN条件(4カラム目)が表示されることはありません。
ban.list で残っている最後の1エントリ
BAN登録していない状態でも、ban.listを確認すると以下のような1エントリが表示されます。これを不可解に思ったことはないでしょうか?
BANリストには常に1エントリ以上存在する
# varnishadm ban.list Present bans: 1601859642.886582 0 C
上にも書いたとおり、キャッシュ済みオブジェクトは必ずいずれかのBANエントリを参照するため、BANリストには必ず参照用にBANエントリが一つ必要になります。BAN登録していない状態でvarnishadm ban.listしても1エントリだけ表示されるのはこういった理由です。上記の例ではCOMPLETED状態でもあるので、評価にも使われません。ポインタを張るためだけのダミーのエントリです。
まとめ
Varnishでキャッシュを無効化するにはPURGEよりBANを使うことが多いと思いますが、ban.listで確認すると意味がわからないカウンタがあったり、BANエントリがなぜか一つだけ残っていたりと、いまいち釈然としない点が出てきます。何回か質問されたことがあるのでこれらについてまとめてみました。
BANの動作を理解する上で重要なban lurker threadの動作の方も機会があれば解説してみたいと思います。
(*1) ソースコード中の構造体名はobjcore。本ページではキャッシュ済みオブジェクトと呼ぶことにします。
投稿日:2020/10/05 17:20
タグ: Server