xcir

\いわなちゃん/

9月 042022
 

カプリチョーザのトマトとにんにくのスパゲティ好きですか?自分は好きです。

割と好きな人が多いためか、ネット上で再現レシピを検索してみるといろいろでてきます。

自分もいくつか試してみて、近いけどなんか違うなぁとかやってたんですが、先日公式で冷凍品を売っているのを見かけました。

商品画像を見ていると原材料や(一部の)配合比率が書いてあり再現できるんじゃ?と思い立ち試行錯誤したレシピになります。

また、このレシピを書くにあたり、様々な情報(例えばトマト缶をどうするかとか)とそれをどう解釈したのかみたいなのを書いておきますので、だれか追試して、ここが違うんじゃみたいな感じでやってほしなと思います。

レシピ

トマトソースとパスタソースは別で作っています。理由は後述。

トマトソースレシピ(4食分)

材料

  • トマトホール缶 1缶
    • 100gあたりのカロリーが25kcalを超える高糖度なもの
  • EXVオリーブオイル おおさじ1
    • 安いものでいいです
  • 玉ねぎみじん切り 30g
  • 塩/砂糖 必要に応じて

手順

  1. 弱火でみじん切りした玉ねぎをオリーブオイルで透き通ったあたりまで炒める
  2. 火を止めてトマト缶を入れた後に手で荒くつぶす
  3. 火をつけ沸騰後10~15分程度コトコト煮込む
  4. 味を見て塩・砂糖で調整

写真

玉ねぎの量はかなり少なくてOK、これをトマト1缶と合わせる
トマト缶を入れて手潰しした後
トマトソース完成、トマト缶を入れた直後よりちょっと赤くなってる

注意事項など

パスタソースレシピ(1食分)

材料

  • パスタ 70~80g
  • トマトソース90~100g
    • 上記で作ったものの1/4
  • (安い)EXVオリーブオイル おおさじ1
  • スライスにんにく 約20g
    • 皮むき前で20g程度で向いて数gなくなる分には問題ないです
  • スキムミルク おおさじ1
  • 粉チーズ おおさじ1
    • チーズはエダムなどさっぱりしているものが良いが普通の粉チーズでOK
  • マギーブイヨン 無添加アレルゲン対応 2g(袋の半量)
  • 砂糖 適量(小さじ1/2~1を目安に調整)
  • 水 適量
  • 香辛料
    • 唐辛子ホール 適量
    • 黒胡椒 適量
    • 白胡椒 適量
    • オレガノ 適量

手順

  1. オリーブオイルにスライスのにんにくと唐辛子を入れてきつね色になる入口ぐらいでまで炒める
  2. トマトソースとブイヨンを入れて軽く煮込む
    1. 煮詰まりすぎたら水で伸ばす
    2. トマトソース自体の煮込み時間が短いので、多少ここで火入れすることで酸味を飛ばす
  3. オレガノ・白黒胡椒・砂糖を入れて煮込む
    1. 煮詰まりすぎたら水で伸ばす
  4. ゆであがったパスタをあまり水を切らずに入れ、かき混ぜた後にスキムミルクとチーズを入れて泡立てるようにかき混ぜる(パスタを入れてから30秒ぐらいで終わらせる)
  5. 完成

写真

加熱前 にんにくは厚めに
ほぼ出来上がり状態 色づき始めたかなぐらいで止める
トマトソースと合わせて香辛料と砂糖を入れたあたり
パスタを投入する直前(チーズ・スキムミルクは未投入)
完成品

注意事項など

  • 香辛料の多さは唐辛子>黒胡椒>オレガノ>白胡椒です(単純なグラム数で考えた場合)
    • もちろんあくまで目安です
  • バーズアイ唐辛子を2本使っています。その際に1本は手で荒く砕いて、もう一本はそのまま入れてます
  • オレガノは一気に風味が変わります。まず一振りから試してみて途中で足りなければもう一振りみたいな感じで増やすのがお勧めです
  • スライスにんにくは厚めです。2.5mmは欲しいです。
    • 店のにんにくは割とほくほくしている(=カリカリではない)ので普段がっつりチップ状態まで火入れするような人は気をつけましょう
  • スキムミルクはどうあがいてもダマになりますので、ダマは気にしなくてOKです。
    • というか店でも白いつぶつぶあります
  • パスタはつるつるした太目のがおすすめです(自分はバリラのNo5)
  • パスタ湯は塩分があるので入れすぎるとしょっぱくなります。基本は水で伸ばしつつパスタ湯で味の調整って感じです
  • 店のトマトソースはかなり甘いのでいつも塩を入れるようなところを砂糖で調整したほうがそれっぽいです
  • ニンニク感が足りない場合はニンニクを増やしたりスライスを薄めにしてもいいですが、ローストガーリックオイルを足すとよいかも
  • 粉チーズはそんなに凝る必要はないかなと思いますが、癖が少ないチーズなのでパルメザンというよりエダムと思っています(もしくは両者の混合)

再現レシピを作るまで

最初に触れましたが、実はAmazonで売ってます。

驚いたのが原材料の表記で、香辛料でまとめればいいものを細かく書いてありました。

これを見てレシピの再現するかーといろいろネットで情報を集め出しました。

以下に自分が集めた情報、考察とかを置いときますので他に再現される方の参考になればと思います

カプリチョーザ販売の冷凍ソース

カプリチョーザが売っている冷凍ソースで入数が多いので買ったことはないのですが、おそらく店舗の材料と近いと考えています。

原材料

  • トマト・ジュース漬け(要はトマト缶)
  • 植物油脂(まぁオリーブオイルでしょう)
  • にんにくスライスロースト
  • 乳等を主要原料とする食品
  • 玉ねぎ
  • 食塩・砂糖
  • 酵母エキス
  • 唐辛子・ブラックペッパー・オレガノ・ホワイトペッパー
  • 香料
  • (一部に大豆・乳成分を含む)
栄養成分表100gあたり一食換算(130g)
カロリー177kcal230.1kcal
タンパク質4.4g5.72g
脂質13.8g17.94g
炭水化物8.9g11.57g
食塩相当量1.5g1.95g

仕込み配合

  • トマト 59.5%
  • にんにく 9.3%

仕込み配合なので、調理時に水分が飛ぶことを考えるとかなりにんにくが多いというのにびっくりします。

カプリチョーザ実店舗のアレルゲン情報

  • 小麦
  • 大豆

意外だったのが肉が入っていなかったことです。

セブンイレブンのトマトとにんにく

カプリチョーザ監修トマトとニンニク

少し前にセブンでカプリチョーザ監修のトマトとニンニクが販売開始されました。

こちらは監修とありますので完全に同一レシピではなく、あくまで同じ味を目指したものと考えてよいでしょう(とはいえカプリチョーザのチェックは入ってる)

実際に食べてみたところよくできているなと思いますが、本家では感じなかった油っぽさと粘度がちょっと高い気がします。

味については店でもあるブレの範囲と思います。

で、材料を見てみると先ほどの公式冷凍品とはかなり違います。

にんにくのチップは正直少ないですが、それでもにんにくの香りはすごいです。

これはローストガーリックオイルを使って足してるんだと思います。

また、チキンペーストも入っていますが、これはブイヨン替わりかなと考えられます。

トマトペーストはトマト缶で足りない濃度を上げようとしているもので、大筋で材料の納得感があります。

そして気になるのがナチュラルチーズと乳などを主要原料とする食品が分かれてることです。感覚的に脂質が高いので脱脂粉乳ではなくクリープのようなものかもしれません。

一口食べたときに植物系ではない油っぽさを感じたのは、店ではダマになってるような成分が溶け切っているとか、もしかしたら乳脂肪分が多いのかもしれません。

レシピの考察

様々な再現レシピを見てたのですが、気になったのがトマトソースの作り方です。

カプリチョーザのメニューを見てみましょう。

トマトベースのパスタは

  • トマトとニンニク
  • ペスカトーレ
  • 渡り蟹のトマトクリーム
  • ミートソース
  • ペンネアラビアータ

の5種あります。

これのミートソースを除いて大体にんにくを使いますが、それでもそんなにニンニクは強くありません。

このようなレストランでわざわざ各メニュー個別にトマトソースを作っているとは考えにくく、割とシンプルなトマトソースがベースだろうと考えました。(ミートソースは煮込みなので別かもですが)

そのためトマトソースに最初からニンニクを入れてるものは違うんじゃないかなーと思っています。

まぁ全種類食べてみれば何かしらの感じられるとは見えると思うんですが、行くとトマトとニンニクを頼むので難しい・・

そのため調理ステップとしては割とオーソドックスで

  1. ニンニク+唐辛子+オリーブオイルでオイルを作る
  2. ブイヨン・香辛料・トマトソースと合わせる
  3. パスタを投入
  4. チーズ・脱脂粉乳を投入

かなと考えました。

また、チーズ・脱脂粉乳を投入するタイミングは悩みました。

特に脱脂粉乳はダマになりやすいのでパスタ投入前にすべきかとか悩んでのですが、

カプリチョーザのパスタ専門店の渋スパの動画において、先にパスタを投入した後に投入しているので倣っています。

なお、動画で最初に水を入れていますが、割と重要です。

固めに茹でたパスタは水を吸うので投入前にある程度水分量が無いとという感じです。

シャバくても、30秒程度はトマトソースで煮てるのでそこでとろみがつきます。(後はチーズで)

ただ、トマトソース自体が濃くないと味も薄くなります。そういう点でトマト缶は濃いものがよいでしょう。

またパスタは、少し固めに上げるのもそうですが、水分を吸いずらいテフロンダイスが良いでしょう。

あと重要なのは割と豪快にかき混ぜることです。リゾットを作ったことがある人ならわかると思うのですが、空気を含ませるように豪快にかき混ぜないと(モンテする)味に一体感が出ません。あれと同じです。

また、乳製品を入れているレードルですが、15cc(大さじ1)としては大きすぎるのでおそらく30ccだと考えています。(フライパンは24cmだと思うので)粉チーズ・脱脂粉乳はそれぞれ大さじ1で約6gなので合計12g前後で考えればよいかなと思っています

パスタソースの量について

お店で食べると割とソースが多く、少なくともパスタを先に食べた場合でもソースが皿に残ってます。

そのうえカプリチョーザの量はそもそも多く、家で作るとあのソース量にはならないです。

ネットで検索してみると、店でのカロリーは880kcalらしいのですが、先ほどの冷凍ソースは230.1kcalしかありません。

冷凍ソースの推奨パスタ量が100gで、これで379kcalなので合わせて609kcalです。(茹でることで多少減るんですが一旦考慮しない)

カプリチョーザの麺の量は130gといわれているので麺量を100gで換算すると677kcalと多いです(+68kcal)

この増分をソースでカバーするとなるとだいたい1.3倍に増やす必要があります。

ただそれだと最初に書いたレシピのグラム数がちょっと面倒(特にブイヨン、まぁトマトソース作るときに入れてもいいと思いますが)なので一旦今回はパスタが少な目(70~80g)ぐらいで、計量しやすい量にしています。

麺の量をカプリチョーザと同じ130gぐらいまで増やすならパスタソースを倍量にするとちょうどよくなりそうですが、いったんは70gぐらいで考えてみてもよいでしょう。

パスタ100g+パスタソースレシピ通り
パスタ70g+パスタソースレシピ通り

どちらも味は同じなので、100gで作ってもいいと思いますが、100gで作ってソースが残らないからと言って水分を余計足すと(当たり前ですが)すごいスッカスカになりますのでやめたほうがいいです。

使う材料について

トマト・ジュース漬け(トマト缶)

実はどのトマト缶を使っているかの情報は料理王国の記事で公開されています。

ロッソガルガーノのトマト缶(サンマルツァーノ)で自分は見たことないですが、輸入食材店にたまにあるみたいです。

ただ、トマト缶のホールは大体サンマルツァーノなので最初はオーケーのを使っていたのですが味が決まらず悩んでいろいろ調べてたら思った以上にトマト缶にも個性があることにきづきました。

ロッソガルガーノオーケーストア
フィオリータ
ピノキオ
プレミアム
カルディ
ラ・プレッツィオーザ
トマト(参考)
カロリー25kcal20kcal31kcal27kcal20kcal
タンパク質1.3g0.9g1.4g1.3g0.7g
脂質0g0.2g0.1g0.2g0.1g
炭水化物5.0g4.4g6.2g5.3g4.7g
食塩相当量0.03g0.01g0.02g0.03g0g

これはいくつかのトマト缶の成分表なんですが、案外違うのがわかります。

だいたいトマト缶は完熟トマトを使っている(のを謳っている)のでそこまでぶれないように思うのですが、結構変わってきています。

じゃぁこの違いは何かというとジュース漬けするときのジュース(ピューレ)の濃度によるところです。

大体トマトでカロリーが上がるところは炭水化物なんでまぁカロリーが高いトマト缶は糖度が高いと考えてよさそうです。(要はhigh brix)

なので、トマト缶を選ぶ際は25kcalを超えるものを選べばいいでしょう。

多分手に入りやすいのはカルディかなと思います。

自分はピノキオ プレミアムホールトマト缶を利用しており、銀座のヴェンティピノで買っています。会員制ですがその場で会員になれますし、正直よくわからない安さなのでお勧めです。

植物油脂

まぁオリーブオイルですね、やっすいEXVでいいです。

にんにくスライスロースト

正直いまだによくわかってないのですが、おそらく乾燥にんにくのスライスだろうと考えています。

いわゆるロースト(こういうやつ)はどちらかというと出来上がりにかけるものだと思うんでまぁそうかなと。

そもそも普通に売ってるにんにくは乾燥したもので、だいたい水分を30%以上飛ばしたものとなるのでこれで考えています。

自分はスペインの白にんにくを使っていますが、紫じゃない理由は店で食べたときに分厚くてホクホク感があったなぁと思ったので白を使っていますが、まぁどっちでもいいと思います。

店で食べたトマトとにんにく にんにくスライスが分厚いことがわかる

もし、ニンニク感が足りないのであればオリーブオイルを少し減らしてその分ローストガーリックオイルを入れるのも手です。
が、おそらくこの量のにんにくを使っている以上足りない場合は、火入れが足りないか、火力が強すぎて香りが移ってないかだと思うんで、もうちょっとじっくりオイルへ香りを移すようにすればいいかなと思います。

他にもスライスを薄くしたり、一部をみじん切りにすればより強く出ると思いますのでいろいろ試すのもありかと思います。

乳等を主要原料とする食品はなにか

単純にチーズならチーズと書くでしょうし、脱脂粉乳ならそうと書くと考えています。

ところが表記が乳などを主要原料とする食品となっており、要はこれはカテゴライズできないものです。(参考サイト

とはいえ、実際に店で食べるとクリーミーで、油っぽさはありません。

またチーズの風味を感じるわけで、チーズが入ってるのは確かです。(食べた後の皿にチーズがついていることもある)

しかし、先ほどの冷凍ソースの成分表を見ると案外脂質が少ないです。

1食の17.94gの脂質はオリーブオイルでも使うのでおそらく低脂肪なチーズっぽいなにかだと考えられます。(油が大さじ1で大体14g)

ちなみに、よく家庭で使われるクラフトのパルメザンチーズは脂質が100gあたり32gとなっておりそれなりに多いです。

あとそもそもクリーミーさはあるので生クリームかもとも思ったのですが、それならにんにくのトマトクリームパスタとかいいそうし、脂質もオーバーしそうです。

そこで、思いついた材料は脱脂粉乳とチーズの組み合わせです。

ちなみに、お店で食べると謎の白いつぶつぶがあると思いますが、それだけ食べてもチーズっぽくなくでもにんにくでもないし何だろうと思ってたんですが、きっと脱脂粉乳が溶け切ってないやつでしょう。

公式の画像でもやっぱダマっぽい何かが見える

チーズは熱で溶けますが、脱脂粉乳は溶けづらくダマになりやすいということからも多分そういうのが入っていると考えています

また、先ほどパルメザンチーズで例をだしましたが、個人的にはパルメザンではないか複数のチーズの混合だろうと思っています。

単純にパルメザンは高いというのもあるのですが、チーズの癖が少なく低脂肪であるためエダムの粉チーズじゃないかなと。

強いニンニクで打ち消されているというのもあるかもですが、店で食べたときにパルメザンチーズを感じたかといわれると自信をもってそうとは言えないんですよね

実際エダムチーズとパルメザンで作り比べたところエダムのほうが近いんじゃないかなと感じました。

あくまで感覚で微妙なところではあるので、いろいろ試してみたりするといいかなとか(混合比とか)

玉ねぎ

普通の玉ねぎを使っています(新玉ではない)。注意がいるとすると原材料の順序は仕込み時の重量順なので、にんにくより少ないです。

食塩・砂糖

まぁ塩は使うのですが、ポイントは砂糖です。

店に実際に食べに行くと酸味が少なく、想像以上にトマトソースが甘いことに気づくと思います。

通常トマトソース作るときに塩と砂糖で味の調整をしますが、塩はできるだけ避けておいて、パスタソースとして仕上げるときに調整するほうがよいでしょう。

酵母エキス(+香料)

この原材料とアレルゲンを見たときに思ったのが動物系のブイヨン/ブロード(要は出汁)を使ってないということです。(セブンのには含まれてますが)

で、野菜系かというと玉ねぎとしか表記がありませんのでそうでもなさそうです。

要は酵母エキスで旨味を足してるのですが、それに近いものがないかなーと探したところ

マギー アレルゲン28品目不使用 ブイヨンでした。

まぁ普通のブイヨンで良いですが、近づける場合はこれかなーと思います。

減塩タイプでない場合は大体半分は塩分なので、塩分はこれで決めるぐらいでやってよいかと思います。

香辛料(唐辛子・ブラックペッパー・オレガノ・ホワイトペッパー)

香辛料はこれだけです。特にオレガノと唐辛子は省かないでほしいのと唐辛子はホール/クラッシュで入れましょう。

自分はオレガノはS&B、唐辛子はカンナメーラのものを使っていますが、まぁ何でもいいと思います。

また、胡椒は熱を加えると割と香りが飛ぶので出来上がり直前かなと思いつつ、動画を見るとパスタと合わせた後に乳製品を加えてそのあとに胡椒を入れるといったことはしてないので(編集で切ってるかもなのですが)オレガノと同じタイミングでいれてます。
どちらにせよ胡椒特有の香りは店でもしないのでいいのかなとは思ってます。

材料の比率を考える

原材料の並び順は重量順なのはよく知られています。

ここに仕込み配合の比率を見てみると

トマト・ジュース漬け(59.5%) > 植物油脂 > にんにくスライスロースト(9.3%) > 乳等を主要原料とする食品 > 玉ねぎ

となります。

とはいえ注意がいるのがこれは仕込み時の重量で、トマトソースを作る際に飛んだ水分のことは考えられていません。

この中で調理過程で水分が飛びそうなのはトマトとにんにくと玉ねぎです。

とかそんなことを考えつつざっくり量を考えてたんですが

もちろんここに砂糖足したりするのでもう少し変動しますが、割と近いところまで持っていけたかなと考えています。

材料自体は多分そうズレはない(乳製品だけ謎ですが)とおもうので自分はこれをベースに調整しています。

最後に

試作と店に食べ行くを何回か繰り返しました。

同じ店なのに、やたらトマトソースが甘かったり逆に薄かったりとか結構ブレが多いのもカプリチョーザです。

トマトソースがやたら甘いのは砂糖を多めに入れつつパスタソースでの火入れの時間が長かったのかなとか(トマトの風味がなかった)、薄いのはトマトソースの量が少なすぎたかなとか、まぁいろいろ考えられますが、割と幅が広いと思います。

今回のレシピはあくまで自分の感覚ですが、店のブレの範囲に収まってるんじゃないかなと思っていますが、ここから調整して好きな味を探そうと思います。(バジルちょっと入れてみたいなとか、フレッシュトマト足したらどうなるだろとかいろいろ)
あと、やっぱ飽きないですね、カプリチョーザのトマトとにんにくおいしいですよね


 Posted by at 12:59 PM
7月 252022
 

はじめに

少し前にHTTP関連のRFCが改訂されましたね!(RFC91XX)

このあたりの全般的な話は既に詳しい方々が触れていますので(HTTP 関連 RFC が大量に出た話と 3 行まとめ)、基本的にはそっちを参照すればよいかなと思うのですが、何が変わったかというと大規模なリファクタリングがされたといっていいでしょう。(もちろん同時にHTTP/3やCache-Statusなどの新しい仕様もふえています)

とはいえHTTPのリファクタリングってなに?と思うことも多いと思うのでHTTP/2の例示をしてみます。

皆さんご存じの通りHTTP/2、HTTP/1.1のどちらにおいてもAccept-Encodingなどのフィールド(改訂でヘッダからフィールドに用語が変わりました)は同様に使えます。

要はセマンティクスは同じなわけですが、RFC7231ではHypertext Transfer Protocol (HTTP/1.1): Semantics and ContentとあるとおりHTTP/1.1と深く紐づいていました。

今回の改訂ではセマンティクスはRFC9110にまとめられましたが、このことによりHTTP/2などのRFCが非常にすっきりしました。

どれぐらいすっきりしたかがわかるようにHTTP/2のSection 8を比較してみましょう。
どちらもセマンティクスについての記述です。

HTTP/2 is intended to be as compatible as possible with current uses of HTTP. This means that, from the application perspective, the features of the protocol are largely unchanged. To achieve this, all request and response semantics are preserved, although the syntax of conveying those semantics has changed.
Thus, the specification and requirements of HTTP/1.1 Semantics and Content [RFC7231], Conditional Requests [RFC7232], Range Requests [RFC7233], Caching [RFC7234], and Authentication [RFC7235] are applicable to HTTP/2. Selected portions of HTTP/1.1 Message Syntax and Routing [RFC7230], such as the HTTP and HTTPS URI schemes, are also applicable in HTTP/2, but the expression of those semantics for this protocol are defined in the sections below.

8. HTTP Message Exchanges RFC7540

これが

HTTP/2 is an instantiation of the HTTP message abstraction (Section 6 of [HTTP]).

8. Expressing HTTP Semantics in HTTP/2 RFC9113

こんな感じです。

もちろんリファクタリングだけではありません。

RFC9111になったHTTP Cachingでは新要素(must-understand)や使われなくなった部分や曖昧な部分の整理が行われています。

つまりは今までキャッシュを知っている人からするとまぁそんな変わらないよとなるわけですが、ここで重要なのは「曖昧な部分の整理」となります。

RFC7234を読むとわかると思うのですが、この場合はどう解釈すればいいのだろう・・と詰まるところは多数あると思います。

no-cacheとmax-ageが同時に指定されていた場合はどちらが優先されるのでしょうか?

no-cacheが指定されている場合は再検証が必要ですが、有効期限内のキャッシュは果たして再検証の対象となるのでしょうか?

とか考えだすともにょもにょしだすわけです(個人的にはRFC7234でもno-cacheが優先されると考えています)

今回の改訂ではこのような部分がだいぶ解消されていますので、なんとなく引っかかったポイントを解説します。

なお、紹介する順序は細かいところからとしているのと、ある程度キャッシュの知識がある人向けに書いています。(Web配信の技術をざっと読んでれば大丈夫だと思うので買ってね!(唐突な宣伝))

改訂の(おすすめな)読み方

今回触れるのは変更の一部ですので、是非皆さんも読んでほしいのですが

その際には単純に新旧のRFCを読むだけではなく、Github上で行われた議論を読んでみるとなんでこの文面が変わったのかというのがわかって納得感が違いますのでお勧めです。

https://github.com/httpwg/http-core/issues?q=is%3Aissue+is%3Aclosed+label%3Acaching+sort%3Acreated-asc

例えば今まではmax-age=”3600″(quoteされてる)といったものはSHOULD NOTだったのですが、より強いMUST NOTに変わりました。

個人的にはquoteするような実装があるのかと思ってたんですが、議論を見ると白熱しており、なるほどなぁと思いました。

議論:Quoted cache-control directives #128

4.2 有効期限の計算に使うのはGMTのみに変更

元々RFC2616#19.3では有効期限の計算はGMTで行う必要があります(MUST) GMT以外のタイムゾーンを誤って指定された場合は最も保守的な変換を使いGMTに変換する必要があります(MUST) とありました。(意訳)

それがRFC7234#4.2では有効期限の計算においてGMTもしくはUTC以外のタイムゾーン指定は無効とみなすべきです(SHOULD)となりました(意訳)

ところがUTCをサポートしている実装は少なく、基本はGMTですのでGMTのみとなりました。そう混乱するような変更ではないと思いますが、まぁ時刻はGMTでと覚えておけばよいかと思います。

議論:UTC in dates #472

5.4 Pragma: no-cacheが非推奨

Pragma: no-cacheはそもそもHTTP/1.0時代のもので、既に広くCache-Controlが普及したので非推奨となりました。

議論: Pragma #140

5.5 Warningの廃止

余り知られてないフィールドにWarningというのがありました。 要は何か伝えたいことがあるときに使うもので、

Warning: 112 - "network down" "Sat, 25 Aug 2012 23:34:45 GMT"

こんな感じのものですが使われてないですし、他をみればわかるし不要ですよねってことで廃止となりました。

議論:Warning #139

4.3.3 検証中に5xxが返ってきた場合はリトライできることを記載

タイトルまんまですが、検証時にオリジンからのレスポンスがサーバーエラー(5xx)の場合の動きとして

  • (OR)リクエスト元にこの応答(5xx)を転送する
  • (OR)サーバが応答できなかったものとして動作を行う
    • この場合は以前保存した応答を制約のもとにレスポンスするか(要はStaleキャッシュのレスポンス4.2.4参照)、検証要求のリトライをすることができる。

となりました。

議論:Validation failures #462

4.2.1 重複定義・競合定義の取り扱い

例えばExpiresが複数存在していたり、Cache-Control内にmax-ageが複数存在していた場合はどうすればよいでしょうか?

RFC7234#4.2.1ではディレクティブの値は無効であるとみなされる。無効な情報を持つキャッシュはStaleとして扱うことを推奨するとあります。 (この記述は特にMUST/SHOULD/MAYでもなくencouraged)

ではno-storeが複数定義されている場合はどうでしょうか?

そのまま捉えてしまうと、Staleとして扱うことは再検証すれば利用ができる状態なため、no-storeの期待している動作より緩い指定です。

そこで今回この辺りの取り扱いが整理されました。

  • 重複定義(複数のExpiresやmax-age)は最初に出現したものを利用するか、応答自体をStaleとみなします。
  • 競合定義(max-ageとno-cacheの同時指定など)は最も制限的なディレクティブを尊重する必要があります。(この場合はno-cache)
  • キャッシュは無効な鮮度情報(max-ageに非整数など)をもつ場合はStaleとして扱うことを推奨する。

わかりやすくてよいかと思います。

議論:Handling duplicate directives (part II) #460

5.1 Ageヘッダの解釈を整理

Ageヘッダは基本的に1つのみの定義です。

ところがDevToolを開きながら見ているとたまにAgeが重複定義されていたり、リスト(X, XX)の形で折りたたまれているケースがあります。

この場合の解釈では最初のものを利用し、それ以降のものは破棄する必要がある(SHOULD)と記載されました。

議論を見てもらえばわかるのですが、これは既存実装を参考にして明確にしただけですので、そう混乱はないかと思います。

議論:Revisiting Age error handling #471

5.2.1 要求ディレクティブ全体がMAYに変更

Cache-Controlは応答時によく見かけますが、要求時にも発行されています。

実際ブラウザでリロードを行うとmax-age=0no-cacheが発行されますが、CDNやProxyでは解釈されないことがほとんどです。(HTTPキャッシュ入門の入門)

ですが、単純に仕様を読む限りではMUSTであったりするので非常に解釈が難しい部分でした。 今回、ここに文言が追加されました。

This section defines cache request directives. They are advisory; caches MAY implement them, but are not required to.
(意訳)このセクションでは要求ディレクティブを定義します。これらの実装は必須ではありません。(実装してもよい(MAY))

要はまるっと要求ディレクティブが実装は必須でもないよとなったわけです。

これに伴い他の箇所で(具体的には後で触れるキャッシュ保存のフローなど)要求ディレクティブに関する記述が消えているところがあります。

ですが、考慮する必要がなくなったというわけではなく、要求ディレクティブに対応する場合は当然対応が必要となるので気を付ける必要があります。

議論: Request Cache-Control directives #129

5.2.2.3 must-understandディレクティブの追加

詳しくはjxckさんが書かれていることと同じでより詳しいのでそちらを見てほしいのですが(Cache-Control: must-understand ディレクティブとは何か 

まずRFC7234#3のキャッシュする条件においてthe response status code is understood by the cache(ステータスコードを理解していないとキャッシュできない)があります。

例えばステータスコード999が爆誕して、このステータスコードはデフォルトでキャッシュが可能(ステータスコード200のように)とします。

この状態でcache-control: max-age=60と指定したとしても、999を解釈できない実装(CDNやProxy)はキャッシュ条件に従ってキャッシュをしません。

この動きは安全ではあるものの、保守的です。

一般的に新しい仕様が広く実装されるまでには時間がかかります。

適切なcache-controlの指定(max-age指定)があったとしても、未知のステータスコードというだけでキャッシュができないというのはちょっと厳しすぎないか?というところから始まってます。

とはいえ、単純に「ステータスコードを理解していないとキャッシュできない」という制限を外してしまうと未知のステータスコードが何らかの条件の元にキャッシュが可能な場合、誤ってキャッシュしてしまい事故につながる可能性があります。

既にあるステータスコードで何か例があるのかというと、Rangeリクエストの結果で返ってくる206やIMS/INMリクエストで返ってくる304です。

どちらもCache-Controlでmax-ageを指定される可能性はありますが、どちらも正しく解釈できないとキャッシュすることができません。

例えばrangeで0~1KBまでリクエストして、206で返ってきた一部のコンテンツをキャッシュしたとします。次に1~2KBのrangeリクエストがあったときに、0~1KBの範囲の結果を返したらコンテンツが壊れてしまいます。

つまり理解できないとキャッシュができないという状態です。

そこでできたのがmust-understandです。

must-understandが指定されている場合はそのキャッシュ要件を理解していないといけないという指定になります。

つまりは先ほどの206や304のようにキャッシュするための要件があるから、例えmax-ageが指定されていたとしてもキャッシュしてはいけないといった指定になります。

とはいえ、must-understandは新しいディレクティブです。このディレクティブ自体が普及してない状態でそんなこと言われても・・・となります。

そこで、同時にno-storeを指定する必要がある(SHOULD)と記載されています。

cache-control: max-age=60, must-understand, no-store

たとえばこのような指定をした場合で、must-understandを理解しない実装の場合は拡張ディレクティブ扱いとなります(RFC7234#5.2.3, RFC9111#5.2.3) 理解できない拡張ディレクティブの場合は単に無視するだけですので

cache-control: max-age=60, no-store

といった指定になります。 この場合no-storeが指定されているため、キャッシュされないので安全です。

じゃあmust-understandもステータスコードのキャッシュ要件を理解して実装している場合はどうなるのかというと

きちんと仕様上にno-storeを無視する必要がある(SHOULD)と書かれています。

議論: Status codes and caching #120

3.1 ヘッダ・トレイラーの取り扱いについて

これはどちらかというとセマンティクス側(RFC9110#6.5)の項目なんですがこちらにも記載があるので注意事項として取り上げます。

フィールドにはレスポンスのはじめに送られてくるヘッダ(Header)フィールドとボディを送った後にトレイラー(Trailer)フィールドがあります。(詳しくはflano_yukiさんの“HTTPヘッダ”が指すものとは参照)

トレイラーが送られてきた際のキャッシュの動きとして

  • 別々に保管(ヘッダとトレイラーを別物として扱う)するか破棄してもよい(MAY)
  • ヘッダとトレイラーを組み合わせてはいけません(MUST NOT)

となります。

要はどこで送られたかというのが重要なユースケースもあるんで勝手にマージしてそれがヘッダ由来なのかトレイラー由来なのかわからない状態にすんなということだと思います。

この辺りはRFC9110#6.5あたりを読んでみるとよいかと思います。

3 キャッシュを保存するときの条件

当然ですが特に破壊的な変更はありません。

ポイントとしては、must-understandとリクエスト時のcache-controlが条件から消えている(先ほど触れたようにMAYに変更されたので)ところかなと思います。

後はRFC7234においても他の箇所を読むと書いてあるけど記載されてないものを明記したというところです。(特にprivateの追記がポイントかなと)

RFC7234の時は

  • リクエストメソッドが解釈できるもので、かつキャッシュ可能なメソッドとして定義されている(キャッシュ可能なメソッドを参照)
  • ステータスコードが解釈できるもの
  • リクエスト・レスポンスのCache-Controlno-storeが含まれていない
  • 経路上のキャッシュ(shared)として格納しようとしている際、レスポンスのCache-Controlprivateの指定がないこと
  • 経路上のキャッシュ(shared)として格納しようとしている際、リクエストにAuthorizationヘッダが含まれていないこと、ただし明示的に許可している場合は除く
  • レスポンスで以下の条件のうちどれかを満たす
    • Expiresヘッダを含む
    • Cache-Control内にmax-ageを含む
    • 経路上のキャッシュ(shared)として格納しようとしている際、Cache-Controls-maxageを含む
    • 拡張ディレクティブで明示的に許可されているもの
    • ステータスコードがデフォルトでキャッシュ可能なもの
    • Cache-Control内にpublicを含む

以上の条件を満たす場合はキャッシュされます。

でした。

これがRFC9111では以下のように変更されています。

  • リクエストメソッドが解釈できるもの
  • ステータスコードは最終応答である
  • ステータスコードが206304、もしくはCache-Controlmust-understandを含む場合、ステータスコードが解釈できるもの
  • レスポンスのCache-Controlno-storeが含まれていない
  • 経路上のキャッシュ(shared)として格納しようとしている際、Cache-Controlprivateの指定がない。もしくはprivateでフィールド名の指定があり、それに従い改変されたレスポンスである
  • 経路上のキャッシュ(shared)として格納しようとしている際、リクエストにAuthorizationヘッダが含まれていないこと、ただし明示的に経路上のキャッシュに格納することを許可している場合は除く
  • レスポンスで以下の条件のうちどれかを満たす
    • Cache-Control内にpublicを含む
    • 格納先が経路上のキャッシュ(shared)でない場合、Cache-Control内にprivateを含む
    • Expiresヘッダを含む
    • Cache-Control内にmax-ageを含む
    • 経路上のキャッシュ(shared)として格納しようとしている際、Cache-Controls-maxageを含む
    • 拡張ディレクティブで明示的に許可されているもの
    • ヒューリスティックにキャッシュ可能なステータスコード

以上の条件を満たす場合はキャッシュされます。

なお最終応答についてはRFC9110#15を参照してほしいのですが、非最終応答が1xxでそれ以外が最終応答となります。

また、no-storeが含まれていないことの条件ですが、must-understandと同時に指定されていてキャッシュ条件を満たしているのであれば無視できます(先ほど触れたmust-understand参照)

個人的にはこれもここに含めておけばよかったんじゃ・・とも思います。

4 キャッシュからの応答の構築法

こちらはほぼ変更がありません。

せいぜいリクエスト時のcache-controlやPragmaの記述が消えたぐらいです。

  • リクエストとキャッシュのURIが一致すること
  • キャッシュ格納時のメソッドがリクエストのメソッドで使えることを許容していること
  • キャッシュ中にVaryでヘッダが指定されている場合は、キャッシュ中のセカンダリキーとリクエストの指定ヘッダの値が一致すること
  • リクエスト中のCache-ControlPragma内にno-cacheを含む場合はキャッシュの検証が成功していること(RFC9111ではこの条件が消えた)
  • キャッシュ中のCache-Control内にno-cacheを含む場合はキャッシュの検証が成功していること
  • キャッシュの状態が以下のどれかである
    • Fresh
    • Staleでの利用が許容されている
    • 検証が成功している

最後に

他にも変更要素がありますので是非Appendix B. Changes from RFC 7234を参照してみてください。


 Posted by at 10:55 PM
4月 182022
 

先日とらのあなラボ様の勉強会に参加していたところ「強いキャッシュ」「弱いキャッシュ」とキーワードが出てきました。
初めて聞く表現だったので質問したところやはり知らない定義だったため、少し調べてまとめてみたものです。
なお、強いキャッシュ・弱いキャッシュという説明を否定するものではなく、補完したいと考えています。

強いキャッシュ・弱いキャッシュの定義

ネット上を調べると日本語・中国語・英語で説明が出てきますが、調べた限りでは強いキャッシュ・弱いキャッシュの初出はWebフロントエンド ハイパフォーマンスで、定義は以下の通りです。

ExpiresヘッダーとCache-Controlヘッダーでは強いキャッシュを設定できます。
ETagヘッダーとLast-Modifiedヘッダーでは弱いキャッシュを設定できます。

Webフロントエンド ハイパフォーマンス p124

こちらの文書の前後に詳しい定義がありますが、要約すると

  • 明示的にTTLを設定Cache-Control: max-age / Expiresすることで、キャッシュ利用時にオリジンにリクエストが飛ばないキャッシュを強いキャッシュ
  • キャッシュ利用時にオリジンに条件付き問い合わせを行う必要があるものを弱いキャッシュ

で、著者に質問をしたところ

  • 条件付きリクエストで検証が必要なキャッシュを弱いキャッシュ
  • それ以外は強いキャッシュ

で、またこの「強いキャッシュ・弱いキャッシュ」は本書で便宜的につけた名前でRFCなどには定義はないと思うとのことでした。

検証が必要なものを弱いキャッシュ・それ以外は強いキャッシュと呼ぶのは
直観的でわかりやすいなとは思うのですが、より詳しくキャッシュについて知ろうと思った場合にキャッシュの仕様(RFC7234)おける概念とどう紐づければいいのかというのを説明したいと思います。

キャッシュのRFCには強いキャッシュ・弱いキャッシュという表現はない

著者の回答通りなのですが RFC7234 Caching そろっと新しいRFC出ると思いますがにおいては強いStrongキャッシュ・弱いWeakキャッシュという表現はありません。

Strong/Weakの表現があるのは検証子Validatorで、今回の本筋ではないので詳しくは説明しませんが一般的にはLast-ModifiedとETagを指します。RFC7232#2

ETagを見ていると「etag: W/”6226c295-200f3″」のようにW/から始まるものと「etag: “6226c295-200f3″」W/がついていないものがあることに気づくと思います。ついてると弱い検証子です。(Weak validator)

強いキャッシュ・弱いキャッシュはキャッシュのRFCで言うところのなんなのか

まずは(HTTP)キャッシュの動きについてちょっと考えてみましょう。

サイトでCSSなどの静的リソースをキャッシュしたいということで、1日のTTLを設定するとします。

このケースでオリジンからレスポンスされるヘッダに含まれるキャッシュに関わるヘッダは以下の通りとします

  • Cache-Control: max-age=86400
  • ETagヘッダ
  • Last-Modifiedヘッダ

このようなヘッダであれば、サイトへの初回訪問時にキャッシュされ、少なくともその日にサイトを回遊してる間はキャッシュから利用できるでしょう。

そして、数日たってからそのサイトに再訪した時には当然TTLは切れています。

ですが、キャッシュというものはTTLが切れたからといって使い道がなくなって即時で消えるものではありません。

TTLが切れてはいるものの、そのキャッシュが最新のものと同一であることが確認できれば再利用できます。
この再利用を行うためにクライアントがオリジンに対して行うのが条件付きリクエストIf-None-Matchなどです。
この時に最新のものと同一であることを確認するのに使うのが検証子でETag/Last-Modifiedです。
要は今持ってるキャッシュはこの検証子だけど、最新の検証子と一致してる?と聞いてるわけです。
もし検証子が一致しないのであればオリジンは200を返却し、同時に本文を返します。
一致していれば304を返し、本文は返しません。本文の転送が発生しない分低コストなわけです。

もちろん条件付きリクエストはこの動作をするものがすべてではない(If-Matchなど)のですが、ブラウザでキャッシュ更新で使うIf-Modified-Since/If-None-Matchはこの動きをします。

一般的なキャッシュのざっくりとした動きはこんな感じですが、これを強い・弱いキャッシュの表現で考えてみましょう。

  • TTL期間中は強いキャッシュで、リクエストのタイミングでTTLが切れていれば検証が必要な弱いキャッシュ
  • 検証を行いTTLが延長されればまたしばらくは検証が不要となるため強いキャッシュとなる。

おそらくこんな感じの説明ができると思います。

次にRFC7234における表現で考えてみましょう。

RFC7234ではキャッシュの状態はFresh(新鮮)とStale(陳腐化してる)があります。

それぞれ

  • TTLが切れていない=Fresh
  • TTLが切れている=Stale

となります。

なお、Freshの場合は必ずキャッシュが使われるかというとそうでもなく、またStaleだとキャッシュが使われないかというとそうでもないのが厄介なところです。RFC7324#4

格納しているキャッシュを使えるかどうかを判定する一つの条件にFreshがあるだけです。

また、オリジンにリクエストを行ったセッションでのオブジェクトはFreshなのかというと難しいです。
そもそもそのオブジェクトはオリジンから持ってきたもので、キャッシュから持ってきたわけではないむしろ様々な条件でキャッシュしようとしているので別枠と考えるのが良いでしょう。(そのセッションのみではFreshとも考えられますがキャッシュを使ってるわけではないのでちょっと違和感がある)

これらを踏まえて同じように説明すると

  • TTL期間中はFreshで、リクエストのタイミングでTTLが切れていれば検証が必要なStale
  • 検証を行いTTLが延長されればまたしばらくは検証が不要となるためFreshとなる。

若干先ほどにあわせるために強引な説明正確でないにはなってますが、だいたいこんな感じです。

つまり、強い・弱いキャッシュはFresh・Staleの関係に近いというわけです。

もちろん厳密にいうと異なります。
ハイパフォーマンス本の強いキャッシュの定義には「Cache-Controlのmax-ageやExpiresで明示的にTTLを指定する」とありますが、TTLはこれらヘッダが未指定でもDateとLast-Modifiedヘッダがあれば有効になるヒューリスティックなTTLというものがあります。HTTPキャッシュ入門の入門参照

いうならば「強いキャッシュ」は「Fresh」に含まれるでしょうか

また、強いキャッシュと弱いキャッシュは相反するものではなく、今回の例のように明示的にTTLを指定しつつ(強いキャッシュ)、Etagを指定(弱いキャッシュ)することでTTL切れの時に弱いキャッシュとして動作することができると考えられます。

ここまでで、強いキャッシュ・弱いキャッシュをベースにキャッシュの仕様を読む際のとっかかり部分ができたかなと思っています。

より詳しくキャッシュを知りたい時に注意しておきたいところ

基本的には以前書いたHTTPキャッシュ入門の入門や自著のWeb配信の技術を読んでほしいのですが(今回の内容は全部3章にあります)
キャッシュというのは時間経過で状態がかわる(TTLがある)ので必ずしも格納したタイミングではそれがFreshともStaleのどちらかといったことはわかりません。

じゃぁどこでわかるのかというと、キャッシュを利用するタイミングです。

案外抜けがちだと思うのですが、キャッシュは「格納」「利用する」の2つのステージがあり、それぞれ別のロジックで動きます。

この「格納」「利用する」を分けて押さえておくと一気にいろいろ理解ができると思います。


 Posted by at 10:39 PM
11月 242021
 

前の記事で紹介したパラレルESI(pesi)など、様々なVMODがあるのですが、残念なことにパッケージで提供されているVMODはほとんどありません。

自分がメンテしているカスタムでカウンタを作るもの(xcounter)とかは、勉強がてらGithub actionsでdebを作るようにしてみたのですが、なかなかめんどくさいのが実情でこれは提供したがらないよなぁと思ってました。

なんなら公式のvarnish-modulesも公式ではパッケージ提供されていません。

では、VMODを使いたい場合はどうしてたのかというと、自分でパッケージを作るか.so配布するかとか、利用者・作者共ににも辛い状態だったわけです。

誰かが作るんじゃないかなと思ってたんですが、だれも作らなかったのと急に思い立ったのでえいやでdebとかrpmが作れるツールを作りました。

https://github.com/xcir/vmod-packager

使いかたは簡単で、docker, curl, jqが使える環境で

xcir@build01:~/git/vmod-packager$ cd src
xcir@build01:~/git/vmod-packager/src$ git clone https://github.com/varnish/varnish-modules.git
xcir@build01:~/git/vmod-packager/src$ cd ..
xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -e 0.19 varnish-modules
...
##################################################
        docker image: vmod-packager/focal:7.0.0-1
                Dist: focal
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: varnish-modules
        VMOD Version: 140.0.19
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ ls pkgs/debs/varnish-modules/
varnish-modules_140.0.19~focal-1_amd64.build      varnish-modules_140.0.19~focal-1_amd64.changes  varnish-modules-dbgsym_140.0.19~focal-1_amd64.ddeb
varnish-modules_140.0.19~focal-1_amd64.buildinfo  varnish-modules_140.0.19~focal-1_amd64.deb

こんな感じでパッケージを作れます。

もちろんrpmも

xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -d centos8 -e 0.19 varnish-modules
...
##################################################
        docker image: vmod-packager/centos8:7.0.0-1
                Dist: centos8
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: varnish-modules
        VMOD Version: 140.0.19
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ ls pkgs/rpms/varnish-modules/
varnish-modules-140.0.19-1.el8.src.rpm  varnish-modules-140.0.19-1.el8.x86_64.rpm

作れます。

もちろん、このように簡単にビルドできるのは

  • ./autogen.sh(もしくは./bootstrap) + ./configure + makeで作れるもの
  • 依存パッケージがないもの

上記を満たす必要がありますが、追加パッケージが必要だったり、ちょっと特殊なビルドが必要なものでも
カスタムスクリプトでパッケージにすることが可能です。

実際に先の記事で紹介したlibvdp-pesiをパッケージにしてみましょう。

xcir@build01:~/git/vmod-packager$ cd src/
xcir@build01:~/git/vmod-packager/src$ git clone https://gitlab.com/uplex/varnish/libvdp-pesi.git

xcir@build01:~/git/vmod-packager/src$ cd libvdp-pesi/
xcir@build01:~/git/vmod-packager/src/libvdp-pesi$ git checkout -b 7.0 remotes/origin/7.0
xcir@build01:~/git/vmod-packager/src$ cat libvdp-pesi_init.sh 
#!/bin/sh

cp -rp ${VMP_ROOT_DIR}/src/m4 ${VMP_WORK_DIR}/src/m4

xcir@build01:~/git/vmod-packager/src$ cat libvdp-pesi_config.sh 
#!/bin/sh

./autogen.sh
./configure VARNISHSRC=/tmp/varnish/src

xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -f  src/libvdp-pesi
...

##################################################
        docker image: vmod-packager/focal:7.0.0-1
                Dist: focal
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: libvdp-pesi
        VMOD Version: 140.0.1
   Enable fixed mode
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ dpkg --info pkgs/debs/libvdp-pesi/libvdp-pesi_140.0.1~focal-1_amd64.deb 
 new Debian package, version 2.0.
 size 56760 bytes: control archive=816 bytes.
     309 bytes,    11 lines      control              
     711 bytes,    10 lines      md5sums              
 Package: libvdp-pesi
 Version: 140.0.1~focal-1
 Architecture: amd64
 Maintainer: libvdp-pesi <example@localhost>
 Installed-Size: 141
 Depends: libc6 (>= 2.14), varnish (= 7.0.0)
 Replaces: libvdp-pesi (<< 140)
 Section: web
 Priority: optional
 Homepage: https://github.com/xcir/
 Description: packed by vmod-packager

ビルド時に事前準備が必要な場合は[VMODの名前]_init.shにその内容を書きます。
今回はm4をコピーしていますが、ここでビルドに必要なパッケージをインストールすることも可能です。

この例では使っていませんが、dependsに追加が必要な場合は[VMODの名前]_env.shという別のファイルに入れる形になります。

また、libvdp-pesiはconfigureでvarnishのソースパスを指定する必要があるので [VMODの名前]_config.shで記述できるようにしています。

ちなみに-fオプションというのを使っていますが、これはlibvdp-pesiがVarnishのバージョンが厳密に一致する必要があるためでバージョンを固定するオプションになります。

こんな感じで割と複雑な手順が必要なVMODでも簡単にパッケージ化が可能です。

とはいえ、細かい指定はまぁいろいろできるんですが、自分は今のところsrc以下に使ってるvmodをcloneした後に

for i in `find src/  -mindepth 1 -maxdepth 1 -type d` ; do echo $i;cd $i ;git pull; cd ..;cd .. ; done

./vmod-packager.sh -v 7.0.1 -t -e `date +%Y%m%d` `find src/  -mindepth 1 -maxdepth 1 -type d`

find pkgs/debs/ -type f -name *.deb|grep `date +%Y%m%d`| xargs -i cp -p {} [コピー先]

みたいな感じで、更新した後に一気にバージョンを日付でビルドしてあとはよしなにするといった感じです。

何はともあれ、使いかたはREADMEを見てほしいです。
今のところよく使われてそうなものはビルドを試しており、大体いけるんじゃないかなと考えています。
が、もしビルド出来ないVMODや気になる点があったら指摘してもらえると嬉しかったりします。

なお、このツールでパッケージにしたものは外部に配布することを想定していません。

これはcopyrightとかdescriptionとかまでカスタマイズできるするのが面倒だったというのがあるので、そのうちなんとかするかもしれないです。
が、現時点では自身の環境にインストールするパッケージを作るのに使うぐらいが良いでしょう。


9月 252021
 

VarnishのEnterprise版で利用できるパラレルESIがcommunity版でも使えるものをUPLEXが公開してくれました。

ヘビーにESIを利用する人はそこまでいない気もするのですが、これがまたなかなか便利なので紹介します。

ビルド方法

このvmodはVarnishの内部関数に深く依存しているため、ビルド時にはVarnishのコードが必要でビルドに癖があります。

また、動作時にはVarnishとビルドにつかったVarnishのコミットハッシュ値が一致している必要があります。

なお以下の環境で確認しています

OSUbuntu 20.04 LTS
Varnish7.0.0 (454733b82a3279a1603516b4f0a07f8bad4bcd55)
vmod_pesimaster (52cd44e3d8944825d1d84a01c70002a509048c74)

pesiは最新のコードを使えばよいかなと思いますが、もしビルドができない場合はもしブランチで7.0があればそちらを(現時点ではないですが)、もしくはコミットログを眺めて適度に巻き戻るとよいでしょう。

とりあえずVarnishとpesiのコードをダウンロードします。

ubuntu@proxy:~/tmp$ ll
total 16
drwxrwxr-x  4 ubuntu ubuntu 4096 Sep 25 21:38 ./
drwxr-xr-x 12 ubuntu ubuntu 4096 Sep 25 21:37 ../
drwxrwxr-x  3 ubuntu ubuntu 4096 Sep 20 23:33 libvdp-pesi-master/
drwxrwxr-x 15 ubuntu ubuntu 4096 Sep 24 02:18 varnish-cache-master/

一旦こんな感じのパスで説明します。

事前準備

まずはVarnishをautogen.sh / configure / makeしておきます。

ubuntu@proxy:~/tmp$ cd varnish-cache-master/
ubuntu@proxy:~/tmp/varnish-cache-master$ ./autogen.sh
ubuntu@proxy:~/tmp/varnish-cache-master$ ./configure
ubuntu@proxy:~/tmp/varnish-cache-master$ make

なお、make installは不要です。

次にpesiの直下にvarnishのソースツリーにあるm4フォルダをコピーします。

ubuntu@proxy:~/tmp$ cd libvdp-pesi-master/
ubuntu@proxy:~/tmp/libvdp-pesi-master$ cp -rp ../varnish-cache-master/m4 ./

configure.acにあるVarnishのバージョン指定をtrunkからインストールしているVarnishのバージョンに変更します。(今回は7.0.0)

ubuntu@proxy:~/tmp/libvdp-pesi-master$ diff configure.ac ../org/configure.ac
56c56
< VARNISH_PREREQ([7.0.0])
---
> VARNISH_PREREQ([trunk])

次にsrc/Makefile.amに1行追加します。(こちらは今PR投げてるのでもしかしたら直るかもしれないです)

ubuntu@proxy:~/tmp/libvdp-pesi-master$ diff src/Makefile.am ../org/src/Makefile.am
72,73d71
< vmod_pesi_debug.lo: vcc_pesi_debug_if.h
<

とりあえずここまででビルド準備ができました。

ビルド/インストール

ここで注意がいるのはVarnishのソースの場所をフルパスで指定することです。

ubuntu@proxy:~/tmp/libvdp-pesi-master$ ./autogen.sh
ubuntu@proxy:~/tmp/libvdp-pesi-master$ ./configure VARNISHSRC=/home/ubuntu/tmp/varnish-cache-master

ubuntu@proxy:~/tmp/libvdp-pesi-master$ make
ubuntu@proxy:~/tmp/libvdp-pesi-master$ make check
ubuntu@proxy:~/tmp/libvdp-pesi-master$ sudo make install

これでインストール完了しました。

使ってみる

使いかたは簡単で

import pesi;

sub vcl_backend_response {
  set beresp.do_esi=true;
}

sub vcl_deliver{
  pesi.activate();
}

これだけです。(細かい使いかたは README に)

とりあえずこの状態で通常のESIとパラレルESIを比較してみましょう。

$ cat esi1.html
<html>
<body>
        <esi:include src="./time.php?t=1&s=1"/>
        <esi:include src="./time.php?t=2&s=2"/>
        <esi:include src="./time.php?t=3&s=3"/>

</body>
</html>


$ cat time.php
<?php
$s=(int)$_GET['s'];
if($s >10)$s=10;
if($s==0) $s=1;
echo $_GET['t'];
echo "&nbsp;";
echo "start:".date(DateTime::ISO8601);
echo "&nbsp;";
sleep($s);
echo "end:".date(DateTime::ISO8601);
echo "<br>\r\n";

コードはこんな感じで1,2,3秒スリープするPHPをincludeします。

通常のESI

シーケンシャルに処理しているため1+2+3で6秒かかっています。

パラレルESI

パラレルで処理しているため、ベースページ(esi1.html)が読み込まれた後に子ESIのfetchが一気に走っています。

ちなみにESIをネストしても結果は期待通りです。

ESIネスト

<html>
<body>
        <esi:include src="./time.php?t=C%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D1%26s%3D1%22%2F%3EC%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D1%26s%3D1%22%2F%3E&s=1"/>
        <esi:include src="./time.php?t=C%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D2%26s%3D2%22%2F%3EC%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D2%26s%3D2%22%2F%3E&s=2"/>
        <esi:include src="./time.php?t=C%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D3%26s%3D3%22%2F%3EC%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D3%26s%3D3%22%2F%3E&s=3"/>
</body>
</html>

urlエンコードしてる部分はこんな感じで。

C<esi:include src="./time.php?t=1&s=1"/>C<esi:include src="./time.php?t=1&s=1"/>
C<esi:include src="./time.php?t=2&s=2"/>C<esi:include src="./time.php?t=2&s=2"/>
C<esi:include src="./time.php?t=3&s=3"/>C<esi:include src="./time.php?t=3&s=3"/>

要は子ESIでさらに2つincludeしています。イメージは以下の通りです。

+ sleep=1
|+ sleep=1
|+ sleep=1
+ sleep=2
|+ sleep=2
|+ sleep=2
+ sleep=3
|+ sleep=3
|+ sleep=3

通常ESI

パラレルESI

子ESIにおいてもパラレルで動いていることがわかります。

使うときの注意点

きわめて魅力的なパラレルESIですが、注意点があります。

ESIをネストしていて、パラレルESIを有効にした場合は子要素もパラレルESIを有効(pesi.activate)する必要があります。

パラレルESIを有効にしたリクエストにおいて子要素で通常のESIを行うとpanicを起こしてVarnishがrestartします。

ここら辺のことはREADMEに書いてますのでしっかり読んでおいてしっかり検証したほうがいいでしょう。

あ、あとVarnish自体のアップデート記事はそろっと書きます(本書いててサボってたけどもう書き終わってしまったので)


4月 292021
 

キャッシュなどのクソ分厚い本は書いたのですが、流石にあれだけ分厚いと読んでくれる人も少なそうな気がするので、キャッシュを使う上で知っておきたい基本的なポイントを解説したいと思います。
目的としてはとりあえずこの辺押さえておけば、MDNのキャッシュCache-Controlなどの記事が読みやすくなるんじゃないかなというのと(記事読み終わったらMDN読んでみてほしい)、興味を持ってもらえれば本を手に取ってもらえるかなぁといった感じです(割と本から内容持ってきてる)。

なお、キャッシュといっても様々なものがありますが、この記事ではHTTPにおけるキャッシュをキャッシュとしています。また、同様にProxyはReverseProxyのことを指します。

そもそもキャッシュとは何をどうしようとしているのか

そもそも一口にキャッシュといっても文脈によって異なります。
一般的にわかりやすいところだとブラウザCDNだと思いますが、この両者はキャッシュは行いますが特性はまったく違います

インターネット上の記事、本、雑誌どれでもそうなのですが、その記事が指しているのはブラウザなのか、CDNなのか、はたまた両者なのかを理解したうえで読まないと混乱しますし、キャッシュってクソ難しいよねと感じてしまいます。

ひとつずつ整理していきましょう。

キャッシュの性質と置き場所

キャッシュの性質

キャッシュはいくつかの軸で考える必要があり、その一つに再利用を誰がするのかという性質があります。

  • 特定のクライアントのみが参照可能なキャッシュ(private cache)
  • 複数のクライアントから参照可能なキャッシュ(shared cache)

この2つの性質はきちんと区別して考える必要があり、ごっちゃにすると事故の元となります。
(ちなみにcache-controlのpublic/privateとは全く別物です。それとごっちゃにしないようにしましょう)

例えばAPIのレスポンスをキャッシュするとします。
都道府県のリストを返すようなAPIであればsharedとして問題ないでしょう。
ところが、ログイン後のユーザの情報が含まれるものは複数のクライアントで共有してはいけないのでprivateです。

sharedのものをprivateとして扱うのは範囲が狭まるわけですから問題ないのですが、逆を行うと事故るのが目に見えるでしょう。
多くのキャッシュ事故はここを意図せず・適切なケアをせずに跨いでしまって起きています。

また、private/sharedですが、厳密にこの2種に収束するかというとそうではありません。

若干応用的な考え方になるので簡単にですが、例えばユーザの住んでる地域(都道府県)という属性によってAPIの結果が変わる場合について考えてみましょう。
これは複数のクライアント向けではあるものの、すべてのクライアントが対象ではないということが言えます。
特定の属性(追加のキー)の集合体(クラスタ)向けのキャッシュでprivateとsharedの中間といえるでしょう。
これはキャッシュキーがとかそういう話にはなり、キャッシュを高度に使うための話になるのですが、これについてはCDNとの付き合い方を読んでみてください。
あくまで、ここではprivate/sharedで分かれて、場合によってはその垣根を意図して跨ぐことで高度に使うことができるといったことを知っておけばよいでしょう。

キャッシュの置き場所

先ほどブラウザとCDNを例にあげましたが、Webサイトを見る上でキャッシュはどこにあるのだろうかというと、ローカル・ゲートウェイ・経路上の3か所にあります。

キャッシュの置き場所(ローカル[ブラウザ]・ゲートウェイ[Proxy]・経路上[CDN])

ゲートウェイと経路上というのはあまりキャッシュでは聞きなれないとは思います。

ゲートウェイキャッシュはオリジンとインターネットをつなぐ部分においてあるProxyで行うキャッシュで、
経路上のキャッシュはインターネットの経路上にあるCDNのエッジサーバーで行うキャッシュです。

なお、これらの区分け(ローカル・ゲートウェイ・経路上)はあまり一般的ではなく、定義でも揺れてる感があるんで整理したものです。(参照

また、これらのキャッシュはすべてのサイトで使われているわけではありません。
CDNを未導入のサイトもありますし、ブラウザキャッシュをうまく使えていないサイトも多数あります。
とりあえず、3か所あるということは知っておきましょう。

ローカルキャッシュ(ブラウザキャッシュ)

ローカルキャッシュはいわゆるブラウザキャッシュで非常にわかりやすいものです。

そのクライアントのローカルにあるので、当然他のクライアントから参照することができません。
そのため特定のクライアント向けのprivate cacheと複数のクライアント向けのshared cacheのどちらも格納することが可能です。
複数のクライアント向けだからといって必ず他クライアントに共有しなくてはいけないというものではなく、あくまで性質であるということを覚えておきましょう。

ゲートウェイキャッシュ(Proxy)・経路上のキャッシュ(Proxy/CDN)

ProxyとCDNですが、どちらも複数のクライアントからリクエストを受けます。

そのため、格納できるのはshared cacheのみです。

Proxyもインターネットの経路上にあるわけなので経路上のキャッシュではと感じた方は鋭いです。
そのとおりでProxyも経路上のキャッシュではあるのですが、CDNと一緒にするには性質が異なるため分けています。
ここでは詳しく触れませんが、いったんはProxyとCDNの違いはいくつかあり、両者を併用することでメリットもあるのですが、いったんはsharedを格納する場所と覚えておきましょう。

まとめるとこんな感じです

種類クライアントとの対応格納できる場所
private1:1ローカル
shared1:nローカル・経路上・ゲートウェイ

ローカル・経路上のキャッシュを併用しよう

キャッシュは再利用されるほどいいものです。

サイトの規模にもよるのですが、ローカルと経路上のキャッシュはそれぞれ性質が異なるため、ブラウザキャッシュだけ適切に設定しておけば経路上では不要というわけではありません。


ローカルキャッシュはキャッシュを持つクライアント自身がサイトを再訪する場合は有効ですが、キャッシュを持っていない新規クライアントには無効です。

経路上のキャッシュは新規クライアントに対してもキャッシュを返すことができるため、例えばサイトへの流入が突然増えるといった事態でも対処がしやすいです。

そのためコンテンツ次第ではありますが、ブラウザキャッシュのように特定のクライアントでしか使えないprivate cacheにするよりも、 効率を考えてローカル・経路上のどちらでもキャッシュができ、多数のクライアントで共有できるshared cacheに積極的にすべきでしょう。

ここまでのまとめ

  • 一口にキャッシュといってもその性質でprivateとsharedの2種類がある
  • キャッシュの格納する場所としてローカルと経路上(+ゲートウェイ)がある
    • ローカルにはprivateとsharedが格納できる
    • 経路上にはsharedが格納できる

とりあえずこの辺りを抑えたうえでキャッシュの各記事を読んでみると、CDN(経路上)の話をしているのか、ブラウザ(ローカル)の話をしているのかとわかりやすくなると思います。

Cache-Controlとはなにを行うものか

そろっと新しいRFCが出そうですが、現状のキャッシュの仕様はRFC7234で、そのセクション2において

Although caching is an entirely OPTIONAL feature of HTTP,
it can be assumed that reusing a cached response is desirable 
and that such reuse is the default behavior when no requirement
or local configuration prevents it.

キャッシュはHTTPのオプション機能ではあるが、キャッシュの再利用は望ましいものであり、それを防止するような要件・設定がない場合は「デフォルトの動作」である。

RFC7234#2

と触れられています。

まず最初に知っておきたいのは条件さえそろえばCache-Controlを指定しなくてもキャッシュされるということです。
条件は様々なものがありますが、重要な条件にTTLが計算できるということがあります。
TTLはCache-Controlのmax-ageで指定するものでは?と思われるかもしれませんが、ヒューリスティックなTTL計算(RFC7234#4.2.2)というのがあります。

Last-Modifiedというコンテンツの最終更新日と現在時刻を表すDateヘッダがありますが、この2つのヘッダを使うことで、ヒューリスティックなTTLの計算を行います。

すごい乱暴に言えば最終更新日から1日経過してるんでまぁ2時間ぐらい(10%程度)キャッシュしてもいいでしょといった感じです。

ヒューリスティックなTTL = (Date – Last-Modified) / 10

つまり、キャッシュされたくないコンテンツはCache-Controlを正しく設定する必要がありますし、キャッシュをするにしても意図した動作を行うように正しく設定する必要があります。

キャッシュを格納する、そして使う

ローカル・経路上のどちらの場合でもキャッシュというものは格納する使うという2つのステージがあります。

例えばTTLの範囲内かといった計算は利用するタイミングで行うわけですから、当たり前といえば当たり前なのですが、案外この辺りの意識が抜けているとことが多いです。

Cache-Controlには様々なディレクティブがありますが、それがどのステージに効くのかというのを考えるとわかりやすいことがあります。

勘違いされやすさで有名なno-cacheとno-storeですが、no-cacheはキャッシュを利用するときのディレクティブでno-storeはキャッシュを格納するときのディレクティブとなります。

no-cacheは利用するときのディレクティブなので、キャッシュは格納されます。で、利用時に再検証を行い最新であることを確認して利用します。

no-storeは格納するときのディレクティブなので、そもそもキャッシュに格納させないわけです。

レスポンスだけではないCache-Control

これはexample.netに対してF5でリロードをしたときのリクエストおよびレスポンスヘッダとなります。

レスポンスヘッダにCache-Control: max-age=604800とある以外にリクエストヘッダにCache-Control: max-age=0とあることに気づきます。(ちなみにctrl+F5だとリクエスト側がno-cacheに変わります)

リクエストを行う際にもCache-Controlがつきますがこれは何を意味しているのでしょうか?

レスポンスヘッダのmax-ageはそのコンテンツのTTLを指定しますが、リクエストヘッダは指定したTTL以上のコンテンツを受け入れる気がない、つまりmax-age=0なので0秒経過したコンテンツは欲しくないので新しいのくださいってことですね。

これだとちょっとわかりづらいと思うので雑に言うとキャッシュに格納されているレスポンス由来のmax-ageをリクエスト由来のmax-ageに一時的に書き換えて評価するといった感じでしょう。

さて、これを踏まえて先ほどのno-cache/no-storeをもう一度考えてみましょう。

no-storeは格納するときの指定なわけですから、リクエスト中に含まれていた場合はキャッシュに格納しないでねといった動きになります。
あまりレスポンスの時の動きと変わらないような気がしますが、既にキャッシュがある状態を考えてみましょう。
no-storeは利用時には評価されないため、キャッシュがレスポンスされます。

とはいえ、CDNやProxyの経路上のキャッシュに対してF5でリロードしてもオリジンに毎回検証してくれるわけではありません。
当然といえば当然なのですが、律儀に全部通していたらあまりCDNの意味がありません。

じゃあどこに効くのかというとブラウザなどのローカルキャッシュです。

ローカルキャッシュ自体をサーバーと考えてそこに対してCache-Controlを通じて問い合わせを行っていると考えるとしっくりくるかもしれません。

なのでxhrでの問い合わせの時にキャッシュされるとか困ってる場合はこの辺りを思い出してみるとよいかと思います。

Cache-Controlの解釈は実装によって異なる

リクエスト・レスポンスに含まれるCache-Controlはあくまで指定でしかなく、その解釈は受け取った側に委ねられています。

例えばno-storeを指定したとしても本当にキャッシュを格納しないのかというと受け取った実装依存となるわけなので、CDNを使う場合はこの辺りもきちんと調べておくとよいでしょう。

ちなみに今まで見た中で一番思い切りがいいなとおもったのはステータス200ならCache-Control一切見ないでX時間キャッシュというものです。こういうのもあります

TTLは経路上とローカルで共通

たとえばsharedキャッシュでmax-age=3600と指定した場合、経路上・ローカルで1時間キャッシュできます。
この1時間はそれぞれではなく合算となります。

たとえば経路上で30分キャッシュ(Age: 1800)したものはローカルでは30分しかキャッシュできません。

案外この辺りを失念しやすいのか、サイトを眺めているとAgeヘッダ > max-ageとなっていて毎回検証リクエストが飛んでるといったことがあります。

先ほどローカル・経路上のキャッシュを併用しと触れましたが、併用すれば当然TTLの割り振りも考える必要があります。

ここまでのまとめ

  • キャッシュは条件を満たしていればCache-Controlを指定しなくてもされる
  • Cache-Controlの解釈は実装によって異なる可能性があるのでCDNを使う場合はきちんと調べよう
  • キャッシュは格納と利用の2ステージがある

最後に

この辺りの話は重要なのですが、案外触れている記事がない(もしくは知っているものとして書かれている)ので、
押さえておくと、様々な記事についても読みやすくなると思います。

また、この辺りの話も含めてめっちゃ詳しく書いてますので、よかったら読んでもらえるとうれしいです。


 Posted by at 9:32 PM
3月 032021
 

twitterでなんどもつぶやいてるので多分知られているとは思うんですが、Web配信の技術という本を書きました。
せっかくなんで、なんでまたこんな本を書いたのかとかどういう流れだったのかみたいなのを簡単に書いてみようかなと

 そもそもどういう本なのか

非常にタイトルを決めるのが難しい本でした。
サブタイトルに「HTTPキャッシュ・リバースプロキシ・CDNを活用する」とあるようにいわゆるHTTPキャッシュの本なわけですが、コンテンツ配信の技術といえばCDNの印象が強く出ますし(本書はCDNの使いかたというわけではないです)、Web配信といえば動画ストリーム配信(VTuberの配信とか)を思い浮かべる人も多いと思います。
今考えればWebコンテンツ配信の技術とすればよかったかもと思いつつ、今度は長くなりすぎるのでなかなか難しいです。

ということでHTTPキャッシュを使ってWebサイトを高速化したり安定化させようといった趣旨の本です。
以前と比べるとキャッシュを使ってWebサイトの負荷対策を行うというのは広く広まってきているんじゃないかなーと思います。
これが日本で広まったのは日経電子版のリニューアルでの全面的なCDNを利用が大きく寄与していると考えています。
あくまで自分の体感ですが、日経電子版のリニューアル以降に雑誌でもCDNの特集が組まれることが増え、また書籍においてもCDNに触れるの増えた気がします。

ところが雑誌の特集は長くても数十ページ、書籍ではそもそもキャッシュの本は(少なくとも自分は)見たことなく、数~数十ページでCDNを使ってパフォーマンスを向上しましょうと触れられている程度です。
では、インターネットの記事で見られる事例はどうなのかというと、あくまでもキャッシュの知識があることを前提とした記事であって、なぜキャッシュが効果的なのか、キャッシュといってもどういう種類があるのかといった基本的な事から触れらている本や記事は自分はあまり見たことがありませんでした。

キャッシュというのは非常に便利なのですが、適切に扱わないと牙をむくものです。

このコロナ渦で急遽負荷対策を行うためにCDNを導入し、設定ミスで個人情報を漏洩といったニュースになったサイトもありましたが、1年に1度はこのようなキャッシュ関連の事故と思われるニュースを見かけます。
このようなニュースは、キャッシュに対して二の足を踏む人を増やす結果となると思っています。

キャッシュはたしかに難しいものではありますし、事故ると非常に怖いものですが、本来の難しさ以上に恐れられていると考えています。
そもそも、正しく理解して設定すればキャッシュは安全に利用することができるものです。
ところが、そのために必要なドキュメントがあるのかというと存在せず、キャッシュについての入門書が必要だと考えました。

だからこそ、キャッシュと関連技術だけで456ページという気が狂ったようなページ数の本が出来上がりました。
本を書く上で似たような本がないかもamazon.comも含めて調べたのですが、無かったので恐らく世界初に近いキャッシュの入門書ではないかと考えています。

あれ、タイトルは入門ではなく技術としているじゃないかと思った人もいると思いますが、個人的には「Web配信入門」のつもりでも書いています。
ではなぜ入門ではなく技術なのかというと表紙のデザイナーさんから「入門」というよりも、現場で活躍する実践的な知恵というニュアンスが強いので「技術」とかそういうフレーズのほうがマッチするのではという提案があったからだったりします。

もちろんどっちつかずにならないようには書いたつもりで、本の最初のほうに

本書は主に中小規模サイトを例に、配信に従事したことのないエンジニアでもわかるよう基礎から解説しています。もちろん、大規模サイトでもやるべきことの基本は変わりません。
配信を業務で行っているエンジニアにとっても基礎の再確認や見落としのチェックなどに役立つ内容になっています。

第一章より引用

と触れています。

配信に触れたことがないエンジニアにはなぜ必要なのかという基礎から、配信を既に意識しているエンジニアでも物足りなりなさを感じないように自分が今まで踏んできた様々な障害やミスも多数書きました。むしろ書いてる途中に踏んで追加したコラムもいくつかあります。

また本を書く時の工夫として、参照したRFCはなるだけ書くようにしました(これは他の本も追随してほしいなとか)
そもそもHTTPキャッシュの多くの誤解はRFCを読んでいないというところから来ていると考えており、実際にRFCの参照部分を示すことで突き合わせてみることができるのかなと考えています。

なので章を進めるごとに実践的な知識を得られるようになっています。
章立ては

  • 配信の基礎知識(2章)
  • Proxy/CDNでキャッシュを行うために必要なオリジン側の地ならしを行う(3章)
  • Proxy/CDNでのキャッシュの行い方(4~6章)
  • 実際に自前で配信環境をつくってみる(7章)

となっています。
とりあえず目次も読みながら以降を読んでほしいのですが

2章はネットワークの話をメインにここにキャッシュを置けたら効果あるよねだったり、キャッシュの種類の話をしています。
割と多くの人がWebサイトでキャッシュといえばCDNという印象があると思いますが、それよりも速いブラウザキャッシュもあるよね、とかどう使い分けるのかといった知識が得られると思います。

3章はかなり分厚い章です。ここではHTTPそのものの話やHTTPキャッシュの仕様を多く触れている章です。
RFCの参照も一番多い章で、この章までを読むことでブラウザキャッシュをうまく使うことができるはずです。
またちょっと面白いところで画像のフォーマットについても触れています。Web配信において重要な要素はいくつかありますが、その一つに配信コンテンツのサイズを小さくすることです。
ちらほらjs/cssなどのテキストコンテンツの圧縮漏れやサムネイルなのになぜか原寸大の画像をスタイルシートで小さくしてるだけだったりするサイトを見かけます。
恐らく配信にかかわったことがあるインフラエンジニアであれば首がもげるほど同意してもらえると思うのですが、CDNを使って配信する以前に対策ができることが多い(サムネ使って・・・とか)と思います。そのようなことを詰め込んだ章です。

4章はProxyやCDNでキャッシュを行い負荷対策をする際の注意事項を多数入れています。
3章とうって変わってこの章はRFCの参照が少ないです(これ以降の章もですが)要は実践的な内容といった感じです。
とはいっても4章は5章のより実践的な内容に移るまでの基礎的な内容とも言えます。特に冒頭に実際にキャッシュで助かったサイトの例を取り上げており、結構楽しいんじゃないかなと思います。

そして分厚い5章で、恐らくこの本で一番難しい章となっています。
恐らく実際に読んだ人だとこれはProxy向けの話が多いんじゃ?と思う人も多いとおもいますが、決してそうではありません。
例えば多段Proxyの話はCDNにおいても多段エッジの話とも通じてきます。特に細かい制御が可能なFastlyの場合はこの辺りを理解しておく必要があるでしょう。

そして6章はCDNの話です。本書を買った人は割とCDNの使いかたを目的としていると思います。
サンプルとしてFastlyを取り上げていますが、恐らくそのボリュームにはがっかりする人も多いと思います。
CDNの業者はたくさんありますし、共通する設定はあるものの多くの設定は各CDNで異なるため、正直なところそういう操作は各CDNのマニュアルを読んだほうが良いでしょう。(特定CDNに特化してがっつり記事書いても面白い気もしますがまぁ・・)
そもそもCDNを使うために必要な知識は5章までで完成できていると考えています。
では、この章で目的としているのはなにかというと、CDNで障害が起きたらどうやって切り分けるのかとか、プロダクトからなんかキャッシュ壊れてるとか言われたりなんか遅いけどと言われた時にどう考えるのかといったあまり見ない切り口書いています。

そして最後の7章は個人的に賛否両論がある章だと考えています(正直すごくどうするか悩んだ章です)
自作CDNと銘打っていることもあり、当初は中規模程度の配信環境について書こうかなと考えてはいたのですが、恐らく中規模・大規模トラフィックを扱う人はだいたいCDNを使うはずで、自前で用意するのは(失礼ながら)お金がないサイトのはずだからそういうサイトに向けて書きました。
純粋に中規模サイトに触れる場合は細かいチューニングや監視に触れる必要があり、その内容は何かしら特化した内容となるためニッチになるだろうと判断したというのもあります。
個人的には実を取り実際に手を出しやすいようになったとは考えていますが、これを自作CDNと呼ぶとまぁなんか言われそうだなとドキドキしています。

最後の付録はVarnishについて書いています。付録というには割とコアなところも触れてると思います。残念ながらVarnish本ではないのでかなり端折っていますが重要だったりハマりポイントは触れられたと思います。

(追記2022/04/13) なお、3章はその場ですぐに理解するには難しいところもあると思います。これは3章はこの後の章の実践的な内容に対する辞書的部分があるからです。
なので突然何を言ってるんだ?と思ってもひとまずわからないところは飛ばして、のちの章でキーワードが出たらまた3章に戻って読んでみるといった方法もよいと思います。

さて、各章について説明したのですが、なんとなく途中の章から読んでもダメそうというのを感じ取ってもらえたかと思います。

例えばCDNを今すぐ使いたいという理由で6章だけを読んだとしても恐らく役に立たないでしょう。

この本の特徴としては知識を積み上げていっています。
そのためCDNを使うのであれば例えば3章のCache-Controlなどの知識が、5章の各イベントの知識など、それまでの章の知識があること前提に、CDNを使うのであれば追加でこの知識が必要であるといった感じで書いてます。
なので、もし買って最初から読んだ人はちょっと後ろから読んでみてもらえるとスカスカで内容が全く足りない頭が悪そうな本だと思えて面白いと思います。

あと、対象読者はエンジニアなのですが、キャッシュを行うというのはエンジニアだけで済む話ではないことも多々あります。

5章で動的コンテンツはよく見ると複数のコンテンツの集合体であり、キャッシュするには異なるライフサイクルを持つコンテンツを混ぜない(=分割する)といいったことに触れたのですがページを分割するには例えばデザインレベルで検討する必要があるかもしれません。
他にもCDNを使うとなればその導入メリットを説明する必要もあるかもしれません。ただ、もしこの本を読んでもらえればなぜ重要なのか、というのを制作に携わる人で共有できると考えています。ですので、エンジニアだけではなくWeb制作にかかわる全ての人に読んでほしいです。

幸いなことに本屋にも割と並んでるみたいですので、飛ばし読みにはなるのですがまず4章の冒頭をさらっと読んでみてこういう効果があるぞと理解した後に最初からさらっと読んでもらえれば立ち読みでもきっとキャッシュのニュアンスは伝わるかと思います。
もちろん買ってはほしいですが、まずキャッシュというものを頭の片隅に入れてもらえればと思うわけです。

 本を読まないにせよ知っておくと便利なキャッシュの知識

せっかくなので、あまり他では見かけないけど意識しておくとキャッシュの考え方が変わる知識を一つ紹介します。
no-cacheはその名前からハマる人が多く、キャッシュをしないという意味ではないというのがだいぶ浸透してきたのかなと思います。

正確にはキャッシュを使うには検証が必要というディレクティブです。

よくセットで説明されるno-storeはキャッシュを格納してはいけないというディレクティブです。

このno-cacheのキャッシュを使うには検証が必要というのとno-storeのキャッシュを格納してはいけないというニュアンスの違いはどこからくるのでしょうか?

これは両者が評価するステージが異なるところから来ています。
そもそもキャッシュというのは「格納する」というのと「利用する」というのは異なるステージで行われます。

no-storeは「格納する」際に格納させないという指定であり、no-cacheは「利用する」際にオリジンに検証を行い最新であれば利用するという指定です。

「利用する」というステージはあまり普段意識されることはないのですが、TTLを評価するタイミングは当然「利用する」時に行うものなので、あーなるほどと納得いく人も多いと思います。

さて、ここで面白いのがno-cacheやno-storeはサーバーからのレスポンスヘッダ(cache-control)に含まれるだけではなく、クライアントからのリクエストヘッダ(cache-control)に含めることも可能です。

両者とも意味は同じなのですが(max-ageはリクエストとレスポンスにある場合で意味合いが異なってきますが)、CDNやProxyに対してno-cacheを指定したところで無視されます。
もし律儀に解釈してオリジンに問い合わせたらF5のやりたい放題です。ただ、これはブラウザキャッシュはこれを解釈します。no-storeを指定してリクエストすれば、ブラウザキャッシュには格納されません。

では、既にキャッシュがある状態でno-storeを指定した場合はどうなるのでしょうか?答えはキャッシュがレスポンスされます。

これはno-storeが「格納する」際に評価されるためで、この場合に指定すべきなのはno-cache、もしくはmax-age=0です。ちなみにmax-age=0はリクエストで含まれた場合は指定秒数以上のキャッシュを受け入れる気がないという意味となります。

これは考えてみればなるほどと思うんですが、割と気づかない人おおいんじゃないかなぁとか思います

本を書く経緯

この本を書くまではそもそも技術書を書くとかどういう経緯から行うかとか全くしりませんでした。もしかしたら興味ある人もいると思うのでちょっと書いてみます。

自分とあったことがある人は「エイプリルフールで落ちるサイトを減らしたい」みたいなことを勉強会やうどん会で聞いたことあるかもしれないです。他にもこんな感じでエイプリルフール当日つぶやいてました。

この本を書く前から、このような問題意識を持っていたのですが、2017年にとあるサイトのキャッシュ事故でCDNが話題になった際にCDNとの付き合い方という記事を書きました。

実はこれは元々社内向けに書いていて満足していたのですが(結構何か起きたりすると社内向けに記事を書いたりすることが多くて他にも割と出したら面白そうなネタがいくつか)TwitterかQiitaで、動的コンテンツをキャッシュするなんて・・・みたいな投稿を見かけていやそうじゃないとカッとなって書いた記憶があります。

カッとなってやったことなのですが、翌年に本書きませんかというメールが来たわけです。

はじめまして。出版社、技術評論社の野田と申します。
書籍企画へのご協力、執筆をお願いしたく連絡差し上げました。

現時点ではまだVarnish-Cache入門のような書籍になるか、
あるいはCDN含め配信全体を俯瞰的に解説する本になるかも未定なのですが、
Webサービスやシステムのための「リバースプロキシ」や「キャッシュ」「配信最適化」の入門書を企画しております。

PDF版のVarnish
Cache入門や貴ブログのCDNに関する記事(****)を拝読し、
ぜひ執筆お願いできないかと今回連絡差し上げました。

もしご興味お持ちいただけるようなら、
企画の狙い、最終的にはどういう形の書籍になるかの複数のプランなどお伝えにお訪ね(もしくは企画書の送付)いたします。


ご検討よろしくお願いいたします

メール来た時めっちゃビビったと思います。(というかどこからメアド知ったんだろうというのがまずありましたw)
このあと打ち合わせをした際の議事録がこんな感じです

議事録
 大まかな方向性
 進めるならWebサイトのコンテンツやアプリケーション向けアセット「配信本」。目的意識に近い。
 キャッシュ本は説明しきれないし、varnish-cacheは商業書籍にするほど人気があるものではない。
 ユーザーに近いところから徐々に配信を最適化する手法を解説していく。
 CDNを使えば万事解決というものではなく、むしろその前の段階が重要なのでCDN以前についてもしっかり解説し最終的にCDNを使う方法について解説するような構成。
 ブラウザー、リバースプロキシ、アプリ、CDN…というような構成で進める。
 内容補足
 CDNをちゃんと使う前の最低限から始める 1:nのキャッシュ
 CDNは最後に…
 どのCDNを紹介するか GREEはAkamaiを使っているが読者向けにはFastlyだろう Akamaiは想定読者がそもそも使えない
 Akamai/Fastly/CloudFront
 レビューしてくれるか、内容をヒアリングできるか。
 大規模ならではという話があると面白いかも varnishのマイグレーションとか大変な話は大規模
 キャッシュさせるサーバーについて UbuntuとRHEL系(CentOS)
 varnish, nginx, apacheの三択。書籍としてはたぶんvarnish中心。 
 読者の課題とこの本の意義。
 小規模、中規模だと適切にキャッシュを使えていないことがある。そういうところが適切に使えるようになるために、配信やキャッシュについて一冊にまとめる本を出すことには意義があるだろう。大規模は概ね適切に使えているはず(例外もあるが…)。
 eg. 4/1、エイプリルフールで落ちる中小サイト…。
 eg.
 メディア掲載などでPV増が起きた肝心なときに落ちてしまい機を逃すサイト…。
 こういった代表的な例が考えられるが、適切に配信できていない中小サイト(の運営企業の担当者)が読者対象。大企業は配信を見る人がいるが、そういう人がいないところ。
 本のつくり
 192p ~ 256pぐらい。技術書としては短め。

元々~256pぐらいだったのが倍ぐらいまで膨れるとか驚きますねこんな感じのやり取りをしつつ、後は原稿を書いていったわけです。ちなみに執筆はmarkdownで、図はcacooで、やり取りはgithubのissueで行っています。

締め切りとは何だったのか

2018年末には原稿は仕上がるはずだったのですが、締め切りを2年ぶっちぎってしまって本当に申し訳ないという・・書きたいことはかなり早期に固まっていたのですが、実際に書いてみると恐ろしく時間がかかる。

いくつかの理由はあるのですが、キャッシュ自体が順を追って説明するのが難しい要素が多々あるからです。
例えばVaryがその典型なのですが、これは3,4,5章で説明をしています。3章までで触れた内容で解説できるVaryと5章までで触れた内容で解説できるVaryは違うのです。
こういうのが結構あったことや、自分の中で整理しきれてない部分を調べなおしたりで時間がかかりました。

あと、原稿が一気に加速したのは2019年末ぐらいからで、それまでは数日なにも書かずにやり取りしてるgithubのissueを見るのが怖いというのもざらだったんですが、とにかく毎日何かコミットをする(句読点の修正でもいいので)というのをルーチンにしたところ一気に進みました。
多分これやってなかったらまだ書き終わってないでしょうし、もし最初からやっていたら半年~1年ぐらいは速く仕上げることができたんじゃないかなと思います。
何か本を書く人はまじでルーチン化するのをお勧めします。
ありがたいことに催促はほぼされなかったので、割と納得いくところまでかけたかなと思います。

少し期待していた「進捗どうですか?」「進捗だめです」のやりとりはしていないはずです(というかだめですでふざけられるレベルではないので申し訳ない)

出版社で出すという意義

最近では技術書典のような個人で技術書を出すということもやりやすくなったため、以前に比べると気軽にまとまった形で技術の情報発信ができるようになってきています。

以前何かで見かけたのですが(twitterだった気がします)技術書を出版社で出す意味はあるのだろうかです。
あくまで今回書いてみての感想ですが、目的によっては意味はあるです。

この目的ですが、自分の場合は「HTTPキャッシュの入門書を書いて、少しでも落ちるサイトを減らしたい」というまぁ大それた目標だったりします。

後者はまだわかりませんが、前者については出版社で出したからこそ達成できたと考えています。

正直なところこんなニッチな本売れないだろうと思ってましたが、ありがたいことに漫画でもないのに発売日前に出版社の在庫切れ+増刷と非常に売れているのでお金もついてくるとは思うのですが、打合せで聞いた際の見込み額は自分のかけるコストに対して低いなと正直思いました。
技術書は漫画のように売れるわけでもないですし、仕方ないところはあるとは思います。
なので自分はこれは分厚い同人誌であってお金を目的としていないと考えながら書きました。なので目的次第なのかなといった感じです。

じゃあ何が出版社でのメリットなのかというと編集がつくということです。
あまりこういうのを書くと自意識強すぎではと思われるかもしれないんですが、

一般的に著者は書こうとしている分野においてそれなりに知識を持っているはずです。
その中であくまで著者の考えでわかりやすく書いていくのですが、結局のところ、それはできる人の考えるわかりやすいでその知識を持ってない人から見て本当にわかりやすいかといえばそうではないです。

そのため原稿を書いてる途中に何度もこれはわかりづらいという指摘を受けて修正しました(特にVaryとか本当になんど直したんだろうと)
指摘を受けてもなんでこれでわかりづらいのかすぐには理解できないこともあり、もし編集抜きで一人で書いていたのであれば間違いなくキャッシュを元々知っている人ならわかるすごいニッチな本になったと思います。
間違いなく編集がいなければこの本は別のものとなっていたでしょう。

後は読みやすさも改善されます。この記事を見てもらえば分かるように割と地の文は読みづらいよねとは思うわけですが(流石にもう少し意識して書いてますが)、本は綺麗に直してもらっており、恐らく編集で相当なコストを費やしており、本当に有難うございますとしかいえないわけです。

最初の読者は編集者であるというのは聞いた事があると思いますが本当にそうだと思います。

ですので、技術評論社の野田様には非常に感謝しています。

後はまぁ自分の本がISBNついて紀伊国屋とか並んでるのを見るとなんかすごいなと思ったりもすることもありますが(発売日に眺めに行きました)、それはおまけかなとも思います。

追記

編集すごいと再認識したのと、この本ができたのは自分と編集の見ている問題意識が一致していたからだろうなと思いました(めっちゃ驚いた

さいごに

ということでキャッシュと関連技術のクソ分厚い本書きました。
ぜひぜひ買ってくださいね!あと感想とかレビューもらえるとすっごくうれしいです。


 Posted by at 11:40 AM
9月 042019
 

Varnish6.0.4/6.2.1が公開されました。
今回の更新はセキュリティ上の問題の修正(VSV00003/CVE-2019-15892)です。
Varnishに存在するヘッダのパース処理の問題により細工されたHTTPのリクエスト(keep-alive)によりassertを引き起こさせ結果としてVarnishを再起動させることが可能です。

影響を受けるバージョンについてと修正

  • 6.1.0~6.1.1および6.2.0
    • 6.2.1で修正
  • 6.0.0~6.0.3
    • 6.0.4で修正

なお6.0.0未満のバージョン(以前のLTSだった4.1も含む)についてもパース処理に同様の問題はあるもののassertがなかったため影響を受けません。

VCLによる緩和策について

すぐにアップデートできない環境向けにVCLでの緩和が可能で公式で紹介されています。
実際のコードは公式を参照してください。

単純な緩和策

今回の攻撃の成立条件としてkeepaliveが必須なので、単純な方法としてはクライアントにレスポンスする際にConnection: Closeを返却する方法がありますが、当然パフォーマンスにインパクトがあります。

複雑な緩和策

なるだけパフォーマンスに影響を与えない形で緩和するためのVCLも提供されています。
ただしこれはインラインCを使っている関係でサポートしているバージョンが

  • 6.2.0
  • 6.1.1
  • 6.0.3

と限られています。

その他変更について(6.0.4)

6.2.1の変更は今回のセキュリティ修正のみですが、6.0.4についてはもともと積んでいて6.0ブランチにコミットされていたバグ修正や改善も同時に入っています。(かなり少ないですが)
その中で一部を紹介します

std.ipのデフォルトポートを指定可能に

std.ipは文字列をIPアドレスに変換するメソッドですが、もともとポート番号は80固定となっていましたが、pというパラメータが追加されて指定可能となりました。
デフォルト値は今まで通り80なのは変更ありませんので今まで使っていても新規に指定が必要というわけではありません。

そのほか

また、今回の更新については以前紹介したVML購入者向けのVIVU-listに事前に通知されました。そう高くもないですし使っている人は検討してもよいかと思います。
あともともとの次のリリース予定日が9/15だったんですが、どうなるんだろ・・


5月 092019
 

Vanrish6.2.0がリリースされました
マイナーバージョンアップにとどまることからわかる通り大きな機能の追加はなく、公式のリリース2019年4月3日メールでも触れられていますが、HTTP/3が見えてきているのでその前段階の改善といったところでしょうか(最近のbugwashでもh3の話題を見かけます)
割と重要なバグ修正や機能改善が含まれています。

開発者向けの仕様変更

インストールやビルドに必要なPythonが2.7から3.4以上に変更されました

今回の公式パッケージの提供が遅れた原因です。
6.2.0のリリース自体は3/15にあったのですが、公式パッケージは3/29まで提供されていませんでした。(で、記事が今頃になったのは書きかけで放置してた感じです・・
なおPython2.7.xは来年EOLになるみたいです。

vmodの仕様変更

対応していないvmodはほぼ間違いなく動作しません。
変更ポイントは多いのですが、多くのvmodで引っかかるのはvccで定義した各種func/eventは必ず先頭にvmod_がつくようになりました。
今までは

$Event event_function

という定義であれば

int v_matchproto_(vmod_event_f)
event_function(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)

でよかったのですがこれが

int v_matchproto_(vmod_event_f)
vmod_event_function(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)

とする必要があります。
また次に引っかかるのが多そうなのがVCL_BLOBで使う構造体が変わったことです。
今まではvmod_privだったのですが専用のvrt_blob構造体が用意されました。
ほぼrenameで対応できるのでこれも容易だと思います(あとは->privを->blobに変えれば大体動くはず)
VRT_APIが9に変更されてるので

int v_matchproto_(vmod_event_f)
#if VRT_MAJOR_VERSION > 8U
  vmod_event_function(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
#else
  event_function(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
#endif

こんな感じで両対応も可能です。

VUTの仕様変更

通常の利用者は問題ないと思うのですが、自前で作ったスクリプトとかでVUTをたたいている人はおそらく動かなくなります。
VUT構造体が変更されていますので追随する必要があります。
また、signalのハンドリングも変わっているので気を付ける必要があります。

仕様変更

タイマー系のログ出力はミリ秒精度に統一されました。

ExpKillとExpRearmは元々ナノ秒だったんですがこれがミリ秒に変わりましたが
これを見てる人ほぼいないと思うので影響する人はいないでしょう。

非推奨のパラメータが削除されました。

shm_reclen/vcl_dir/vmod_dirが削除されています。
それぞれvcl_reclen/vcl_path/vmod_pathを利用してください。

パラメータの追加/変更

thread_pool_stackのデフォルトサイズが大きくなりました。

VCLの変更

req.is_hitmiss/req.is_hitpassが追加されました

vcl_hit内でreturn(miss)が削除されました。
そもそも期待通りうごかなかったみたいです

機能追加/改善

起動パラメータのdebugにvcl_keepが追加されました。

VCLをCコードに変換した結果とsoを保存するようになっています
出力は/var/lib/varnish/[instance-name]/[vcl-name]/以下にvgc.cで出ます。
varnishtestを行う際に便利かもしれません。

ban時にttlなどの有効期限での比較が使えるようになりました。

obj.age/ttl/grace/keepは時間の経過とともに変わる値ですがこの値を使ってbanができるようになりました
なので例えば5時間経過しているオブジェクトを消したいといったときは

ban obj.age > 5h

みたいな指定が可能になりました。

パラメータをデフォルトに戻るparam.resetが追加されました(varnishadm/varnish-cli)

varnishadmでのjson出力サポート

6.0.2と6.1.0からサポートするようになったjson出力ですが
6.1.1の記事でも触れた通り実は6.0.2のほうがjson出力に対応しているコマンドが多かったのですが、6.2.0では違いがなくなっています。
また同時にいくつかのコマンドの列幅がターミナルのサイズに合わせて可変長となったので
もしツールなどでcliの結果を処理しているのであればjsonを使うことを検討すべきでしょう。

statの強化

これも6.0.2のみで対応していたstatが6.2.0でも出るようになりました。

lookup処理の改善(#2854)

lookup時のmutexを減らしています。
高RPSな環境では割と効果があると思います。

resp.filtersの追加

6.1.0の際に増えたberesp.filtersのresp版です。
最終的なレスポンスを行う際に使用できるVDP filterの並び替えができるようになりました。
ただ、バグがあるのですぐに使う人もいないと思いますが一旦次バージョンを待ったほうがいいです(masterは修正済み

vmod_stdの型変換関数の改善

型変換は今までreal->integerの時はreal2integer、time->integerの時はtime2integerと分かれていてめんどくさく、fallbackの指定も必要でしたがこれがすっきりしました。
例えばほしいものがintegerであればこんな形です。

std.integer(real=1.23)

とりあえずduration/bytes/integer/real/timeが用意されており、
以前のreal2integer/real2time/time2integer/time2realは非推奨となり、将来削除されます。

vmod_directorsにlookupが追加されました。

vcl_init/finiで呼び出し可能で、個人的にはいまいち使い道が思いつかないのですが

new l2_cache = directors.hash();
l2_cache.add_backend(cache01, 1.0);

みたいな指定を

new l2_cache = directors.hash();
l2_cache.add_backend(directors.lookup("cache01"), 1.0);

といった感じで書けます。

varnishncsa/varnishlogにratelimitオプション(R)を追加しました。

-R 10/2m

こんな指定が可能です。

varnishstatのリフレッシュレートが変更できるようになりました。
表示中に+/-を入れると0.1秒単位で増減します。

バグ修正

http/2のactive streamのカウント周りのバグが修正されました(#2916,2923)

一つはコントロール用のstream=0をactiveなストリームとしてカウントしてたのでmax_concurrent_streamsの判定が1ずれてたものと
もう一つはストリームのカウントの増減をスレッドの割り当て時・解放時に行っていたので、解放時に後処理してたら次のstream openの要求が来てrefuseするといった感じです。
とりあえずこれでストリームを多く使うようなサイトでも問題なく使えるようになったと思います。


10月 282018
 

6.1.0が出たときにすぐ記事を書かなかったのは、リリース後に一部デグレ(varnishhist表示)がIRCで話題になって6.1.1をリリースする?
みたいな話がでていたので実際に6.1.0を動かしながらはまりポイントがないかを探しながら6.1.1のリリースをまってました。
思ったより期間があいたので、先に書いとけばよかったなと反省しています。すいません。
また、すでにPRが用意されていることからさほど時期を開けずにLTS(多分)である6.0.2もリリースされると思われます。

6.0.0/6.0.1から6.1.1の変更は新機能もあるのですが、それ以上にバグ修正が多いので基本的にバージョンアップをおすすめします(6.0.2でもいいですが)

特に以下の機能を使っている場合は関連するバグ修正がいくつかあるのでバージョンアップを強くお勧めします。
・HTTP/2 (6.1.1/6.0.2のみの重要な修正があるので6.1.0/6.0.1を利用している場合もバージョンアップ必要)
・proxy protocol
・UDS
・pipe

ダウンロードはこちらから

6.1.xと6.0.xの違い

6.1.xはいくつかの新機能やコア部分の変更はありますが、バージョンアップに苦労するような変更はほぼありません。
例えばVCLについては削除された変数などがないため、6.0で動いていたものをそのまま動かすことができます。
ただしvmodについてはdirector APIの変更があったため、一部については再コンパイルだけでは動作させることができません。(要対応)

では、今回はどのような大きな変更があったかというと
backendをフェッチしてstorageに格納する際に呼ばれるVFPと呼ばれるスタックをVCLから入れ替えなどの操作ができることになったことです。
今までもVFPスタックに処理を追加することはできたのですが(Varnishでテストコードを書こう!~実践編~+Bodyを読もう!)、デフォルトの順番を動かすことや差し替えを行う事が難しかったのです。
例えば今回の変更で、デフォルトのgzip処理をZopfliに変更するようなこともできるといえば可能です。(他にもESI処理の置き換えも)
ちなみにVarnishにはクライアントにレスポンスする直前に呼ばれるVDPスタックもあるのですが、ここの同様な操作は6.1.xでは実装されていません。
しかしtrunkではVDP関連のコミットがあるのでおそらく次期バージョン(2019/03)では来ると思います。

VarnishのLTS

Varnishは古いバージョンのサポートを早々に打ち切ります。
しかし一部のバージョンについては長くサポートされています。
例えば4.1.xは5.xがサポートが打ち切られているのにも関わらずサポートされ続けています。
Releases & Downloads

特に明言されているわけではないのですが(MLとかIRCでなんどかそのような発言は見かけましたが)
VarnishSoftware(VS)がリリースしているVarnishCachePlusのベースとしているバージョンはLTSとしてサポートされるようです。
(用はスポンサーがついてる Please backport #2077 to 5.1.x
現在のところPlusは6.0.1r2と4.1.10r5がリリースされていますので6.0.xと4.1.xはLTSです。
とはいえ6.0ベースのPlusが出たので、さすがに4.1.xは収束していくのではと考えています。
もし、現在4.1.xを使っている方は6.0.xまで上げるのも検討してもよいかと思います。

6.1.1と6.0.2の位置づけについて

今回のリリースで6.0.2だけに追加されている機能があります。(もちろんVFPスタック操作については6.1限定です)
理由を聞いたところ6.1.1はbugfixリリースで新機能を入れることで新しいバグが入り込むのを避けたいということでした。

動作変更について

HostヘッダなしのHTTP/1.1のリクエストを400で返すように変更(#2631)
そもそもHTTP/1.1はHostヘッダが必須なので特に気にすることはないでしょう。
builtin.vclでの実装なので、どうしても困ることがあるのであれば処理を上書きすればよいかと思います。(望ましくないですが)
For HTTP/1.1 requests, Host is mandatory

パラメータの追加

max_vcl/max_vcl_handling
varnishの設定を適用する際にreloadをしていると以前のvclが残ります。
当然これらはディスク容量を圧迫しますし、また一部の操作(vcl.listなど)は当然遅くなります。
max_vclはvcl数の閾値でデフォルトは100です。で、閾値を超えた場合の動作を定義するのがmax_vcl_handlingです。
0は警告も何もしない、1でwarning表示(デフォルト)、2でVCLの追加を拒否します。
デフォルトの状態で使い続けても特にVCLの追加ができなくなることもないので(warnはでる)
warnが出たらvcl.discardしていくのもよいかと思います。

backend_local_error_holddown/backend_remote_error_holddown
バックエンドに接続を行う際に、自身もしくは接続先のリソース不足などで接続失敗することがあります。
この場合で即時再試行しても結局リソース不足で再度失敗することが多いので、少し待ってみるのも効果的です。
このパラメータはその秒数を指定するものです。
backend_local_error_holddownはEADDRNOTAVAIL・EACCESS・EPERMが起きた際に待機する秒数で
backend_remote_error_holddownはECONNREFUSED・ENETUNREACHが起きた際に待機する秒数です。
どちらもデフォルトでよいかなと思います。
また、これらのエラーが起きた際は後述するカウンタが上がりますので、その際はチューニングや構成見直しを行うと良いでしょう。

thread_pool_watchdog
h2の場合依存関係や制御情報をやり取りするだけのストリームがあるのですが、Varnishの場合はそれもスレッドを割り当てます。
ですので、スレッド不足で実際のリクエストに対してスレッド割り当てができない場合、そこでリクエストの処理を待ってしまいデッドロックする可能性があります。
これらのデッドロックを検知した場合に子プロセスを再起動するのですが、その設定時間がこれです。
基本的にデフォルト値で問題ないでしょう。
もし、これ起因でrestartが走るようであれば、スレッド関連の設定を見直すのがよいでしょう(thread_pool_maxなど)
また、h2起因で追加されたパラメータですが、別にh2以外でもデッドロックが起きた場合は発動します。

VCL変更

削除されたいくつかの変数が復活
req.ttl・req.ttl・req.graceが復活しています。

すでにimportしたvmodを再度importしようとしてもエラーにならないように変更
地味にうれしい変更です。
例えばvmod_stdはよく使われているvmodなのですが、複数のvclをincludeしている際にimport std;を複数のvclに記載してエラーになることがあります。(きちんとdefine.vclみたいなものにまとめておけばいいのですが)
この変更でエラーにならずに2回目以降のimportは無視されるようになります。

vcl_init中のreturn failでメッセージを入れられるようになりました
vcl_initで何かしらのエラーが起きて終了させたいときに使えるreturn(fail)ですが
なんのエラーで落ちたかとメッセージで出す方法がありませんでした。
今回return(fail(“foo”))といった指定が可能になったことで失敗した理由を通知することが可能です。


//vcl
sub vcl_init{
  return(fail("foo"));
}

//output
VCL "boot" Failed initialization
Message:
        foo

このような形で出力されます。

beresp.filtersの追加
後述します

新機能について

vmod_stdにfnmatchを追加


BOOL fnmatch(STRING pattern, STRING subject, BOOL pathname=1, BOOL noescape=0, BOOL period=0)

shellでパスを指定するような(ex:/var/log/*)形で指定することが可能です。
pathname/noescape/periodはそれぞれfnmatchのflagsの該当するものと同じ意味です。
FNMATCH

サンプルにもありますが、


if (std.fnmatch("/foo/*", req.url)) { ... }

といった感じでurlマッチに使えると思います。
これの便利なところとしては、VCL中で使う正規表現と違いpre-compileが必要無いのでマッチパタンを変数にするといったことも可能です。

VFPスタックの呼び出し順をVCLから変更可能に(6.1.x)

先ほども述べた通り現状ですぐに役に立つものではありません。
VFPを提供するVMODが増えてくれば有用でしょう。
とりあえず現状どのようなものが適用されているかを確認するのであれば


varnishlog -graw -i filters

で見ることができます。

また、レスポンスヘッダにどのようなフィルタが使われているかをだすには


sub vcl_backend_response {
  set beresp.http.x-filters = beresp.filters;
}

このようにすればヘッダに出力されます。

また設定するには


sub vcl_backend_response {
  set beresp.filters = "esi" // gzipじゃない時のberesp.do_esi = true;と同等;
}

とすればよいでしょう。
とはいえ、理解せずに弄るといろいろ崩れたりしますので注意しましょう。(多分VMODで指定されると思うのでその通り使えばOKかと)
かなり上級者向けの設定です。

varnishadm(varnish-cli)でのjson出力(6.0.2/6.1.0)

json出力がサポートするようになりました。


# sudo varnishadm backend.list -j

[ 2, ["backend.list", "-j"], 1540196843.165,
  {
    "reload_20181022_074741_10060.***": {
      "type": "backend",
      "admin_health": "probe",
      "probe_message": "5/5 good",
      "last_change": 1540194463.057
    },

...

    "reload_20181022_074741_10060.***": {
      "type": "hash",
      "admin_health": "probe",
      "probe_message": "healthy",
      "last_change": 1540194463.054
    }
  }
]

6.1.1では一部、6.0.2は大部分のコマンドで出力をサポートしています。
varnishadmの結果をスクリプトで処理する際には非常に便利だと思います。

6.1.0/6.0.2の両方で対応しているもの
– ping
– backend.list
– help

6.0.2のみで対応しているもの
– status
– vcl.list
– param.show
– ban.list
– storage.list
– panic.show

statの強化
varnishstatで表示されるstatがかなり追加されています。
こちらも一部は6.0.2のみ対応の項目があります

6.1.0/6.0.2の両方で対応しているもの
– MAIN.sess_fail_econnaborted
– MAIN.sess_fail_eintr
– MAIN.sess_fail_emfile
– MAIN.sess_fail_ebadf
– MAIN.sess_fail_enomem
– MAIN.sess_fail_other
– [backend名].unhealthy
– [backend名].busy
– [backend名].fail
– [backend名].fail_eacces
– [backend名].fail_eaddrnotavail
– [backend名].fail_econnrefused
– [backend名].fail_enetunreach
– [backend名].fail_etimedout
– [backend名].fail_other
– [backend名].helddown

以下6.0.2のみ対応
– wrk.client_resp_500
– wrk.ws_backend_overflow
– wrk.ws_client_overflow
– wrk.ws_thread_overflow
– wrk.ws_session_overflow

例えばfail_eaddrnotavail(EADDRNOTAVAIL)であればエフェメラルポートの確保に失敗してるんだろうということで設定の見直しなどができると思います。
また、overflowのカウンタも便利です。
これらはそれぞれ起動パラメータのworkspace_clientなどと一致するので、カウンタが上がっていたらチューニングを行うと良いでしょう。

その他変更について

director周りにAPIが変更されているため一部vmodは対応が必要
通常のVMODであれば再コンパイルだけで動くと思いますが、director周りを触るvmodの場合は対応が必須です。
アップデートする前に対応しているか確認すると良いでしょう。

バグ修正について

6.0.2/6.1.1は大量のバグ修正が含まれます。
また、毎回言ってるのですがHTTP/2で重要なバグフィックスが2件あります(他にもあるんですが特に重要・・・)

HTTP/2利用時にスレッドリークが起こるの修正(#2623)

閉じられたストリーム宛てにPRIORITYフレームが届くとプロトコルエラーとして扱っていたのを修正(#2775)

http/2のexperimentalはまだ外れてないのですが、さすがにもう強烈なバグは出尽くしたんじゃないかなとか・・・考えてます。

そのほか

公式パッケージでUbuntu18.04(bionic)向けのパッケージが追加
varnishtestでhaproxyを扱うことが可能に
おそらくvarnishtestは将来的にプロジェクトが分離され汎用的になるでしょう(Divorcing varnishtest from varnish-cache)
既にレポジトリもあります
VTest