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


4月 092018
 

Varnish6.0.0がリリースされました。[changelog] [公式のリリース文] [パッケージDL] [ソースDL]
今回のリリースはメジャーバージョンアップということで機能追加や累積バグFixなど来ています。
目玉機能はUnixDomainSocket(UDS)対応で、個人的に注目しているのがshard directorにおけるlazyモード追加です。
また新しいVCLバージョン(4.1)も追加されていますが、既存が使えなくなったわけではないので影響は少ないでしょう。
なお、アップデートする場合はいくつか注意が必要な点があります。

現状で適用する場合は十分に注意検討する必要がある環境

http/2環境
h2利用時にスレッドがリークしているような動きをしています。(#2623)
最初は自分のテスト用環境だけかなと考えていたんですが、他に踏んでる人を確認したので現状のままでh2を有効にするのはかなり注意が必要です。
h2を使いたいのであれば、きちんと検証して自環境で起きないことを確認しつつ行うか、既存バージョンのままで使いつつFixを待つと良いと思います。
ただしh1のみの場合は現状問題ないため、h2を使わないのであればバージョンアップをしても良いかと思います。

公式パッケージの対象バージョンが少なくなった
公式で提供してるパッケージの対象OSは以下の通りです
– RHEL7(CentOS7)
– Debian9
– Ubuntu16.04
Ubuntu14.04やRHEL6は対象外となりましたので注意が必要です。
ただ、これはあくまで対象バージョンを絞ったというだけなので、自前でpkg-varnish-cacheを利用してパッケージを作って適用は可能です。

VMODを利用している場合
VMOD側で対応しないといけない変更が入っているため、いろんなVMODを利用している場合は対応状況を確認したほうが良いです。
必要な変更はたいしたことないので、メンテされてるものについては対応されるんじゃないかなとか思います。

なお、VSMに直接つなぐアプリについては5.2.0のときのVSM/VUTインタフェース変更に対応できていればほぼ動くと思います。
一部定数と戻り値の型が変わったものがあるためそこを使っていて運が悪いと踏むかもしれません。


VCL

vclを記述する際には、先頭に「vcl 4.0;」と記述していたかと思いますが、今回「vcl 4.1;」が追加されました。
既存の4.0が使えなくなったわけではないため、そのまま動かすことも可能ですし、大抵の場合は先頭行だけ4.1に書き換えても(大抵の場合は)動くでしょう。
あとで紹介するUDSは4.1のみでサポートしている記法が必要なので、もし使う場合は4.1に変更しましょう。
またincludeですが親のVCLバージョンより小さい場合は可能です。
つまり

parent vcl ver child vcl ver result
4.0 4.0 OK
4.0 4.1 NG
4.1 4.0 OK
4.1 4.1 OK

こんな感じです。
VCLの記載の仕方は環境それぞれだとおもうのでなんともいえないのですが、
default.vclを起点として、includeしていくやり方であればdefault.vclを一旦4.1にしておくと良いのかなと思います。
とはいえ、4.0から4.1へのアップデートはそんなに難しくないのでやってしまったほうがいいんじゃないかなとは思います。

VCL変更点

4.0での変更点と4.1での変更点があります。
まずは共通の変更点から

変更点(4.0/4.1共通)

return(fetch)がvcl_hitで使えなくなりました
個人的にはまだ残っていたのかといった感じですが(結構前からdeplicatedになってた)
return(miss)を使いましょう

req.storage / hash_ignore_busy / hash_always_missをclientスレッド側でアクセスできるようになりました。

obj.storageが追加され、vcl_hit/vcl_deliverでアクセスできるようになりました

restart時の動作が変わりました
req.restarts,xid以外のすべてのreq変数は維持されます。
restartを利用している場合は動作の確認をしたほうが良いでしょう。

vcl_recvでもreturn(restart)が呼べるようになりました

変更点(4.1のみ)

backendの定義に.pathが追加
あとで触れるUDSで使うパスです。
.hostと同時に定義することは出来ません。

local.socketの追加
接続に利用したソケットの名前を返します。
-afoo=:81と名前が指定されてる場合はfooを返しますが、もし定義されていない場合は自動的につけられた名前(a0,a1)が返却されます。

local.endpointの追加
接続に利用したソケットの定義を返します。
-afoo=:81の場合は:81が返却されます

sess.xidの追加
セッションのユニークなID(xid)を返します。
今まで使用していたreq.xid/bereq.xidはそのリクエスト、バックエンドリクエストを示すものでした。
しかしKeepaliveやHTTP/2では1セッション中に複数のリクエストが含まれます。


*   << Session  >> 433160262
-   Begin          sess 0 PROXY
-   SessOpen       ***
-   Proxy          ***
-   Link           req 423002616 rxreq
-   Link           req 423002617 rxreq
-   Link           req 423002618 rxreq
-   Link           req 423002619 rxreq
-   Link           req 423002620 rxreq
-   ReqAcct        72 22 94 1251 6 1257
-   End

これはとあるHTTP/2.0でのログの抜粋ですが、これが示しているのは
xid:433160262のセッション(sess.xid)にxid:423002616~423002620のリクエスト(req.xid)がぶら下がっているということです。
今回sess.xidがvcl中から簡単に取れることで、異なるリクエスト中でも同一セッションであることがわかるので、なにかに使えるかもしれないです(ぱっと思いつかないですが・・)

resp.do_esiの追加
beresp.do_esiの指定でフェッチ時にパースした場合でも無効にすることができます。
vcl_deliver/vcl_synthで呼び出し可能です。

(req|bereq|beresp).protoがreadonly
そんなに変更することもなかったと思いますが.protoがreadonlyになりました。

req.esiが削除された
まぁ使う人はそんなにいないかと・・・beresp.do_esiを使いましょう

beresp.storage_hintが削除されました
beresp.storageで代用できます。
例えば今まで


set beresp.storage_hint = "foo";

としていたのであれば


set beresp.storage = storage.foo;

と変更すればよいです。

beresp.backend.ipがが削除されました

IPアドレスが0.0.0.0:0のものはUDS経由のものになります。


Unix Domain Socket(UDS)対応

上下流どちらも対応しています。
これを利用することにより高CPS環境において、特に同一サーバに置いてることがほとんどだと思われるTLS終端を行うミドルウェアとの通信をより効率的に行えるようになります。(上流側)

なお、今回の例ではNginxを組み合わせてやってみます。

LISTEN側でUDSを使う場合
起動パラメータの-aで以下のような指定をします


-a /var/run/varnish-uds.sock,user=vcache,group=varnish,mode=666

今回はproxyプロトコルを指定していませんが指定できます。(PROXYを追加しましょう)
また今回はお手軽にNginxとつなぐために666にしていますが、適切な設定にするべきでしょう。

Nginx側


upstream backend {
    server unix:/var/run/varnish-uds.sock;
}
server {
        listen 80 default_server;
        listen [::]:80 default_server;
        location / {
            proxy_pass http://backend;
        }
...
}

これだけです。
なお、LISTEN側でUDSを使う場合はVCL4.1が必須です。
が、現状試してる限りでは起点となるvcl(default.vcl)が4.1であればinclude先は4.0でも平気な動きをしています。 
しかし、ドキュメント上は

When UDS listeners are in use, VCL >= 4.1 will be required for all VCL programs loaded by Varnish.

と記述されており、もしかしたら塞がれる可能性もありますので、UDSを使う場合はすべてのVCLを4.1にするのが無難でしょう。

なお、UDSは当然ながらIPアドレスはなく、UDS経由のアクセスは0.0.0.0:0として扱われます。
なので、client.identity(デフォルトではclient.ipが使われる)を使って振り分けを行う場合は常に同一backendが選択されるので注意が必要です。
適切に修正を入れるか、そもそも上流はPROXYプロトコルが使えるミドルウェアにするなどの対策が必要でしょう。

バックエンド側でUDSを使う場合
すごく単純です
Nginx側


server {
  listen unix:/var/run/nginx-uds.sock;
  access_log off;
  root /var/www/html;
  index index.html index.htm index.nginx-debian.html;
}

Varnish側


vcl 4.1;
backend default {
    .path = "/var/run/nginx-uds.sock";//絶対パス
}

これだけです。
特に難しくないと思いますが、1点注意なのがbackend内での.pathプロパティはvcl4.1のみなので4.0だと出来ません。
なおListenの時と違いバックエンドのみ使用する場合は4.0混在でも動きます。とはいえ4.1の書き換えは難しくないので書き換えたほうが無難でしょう。


その他新機能・変更

varnishncsa

制御文字・バイナリを含む可能性があるタグの-Fを拒否するようになりました
対象はH2RxHdr H2RxBody H2TxHdr H2TxBody Debug HttpGarbage Hashですが、たぶん使ってる人はいないかなと

起動パラメータの変更

esi_iovsの追加
ESI処理で使うiovec構造体の割り当て数ですが通常の場合はいじる必要がありません。
ESIを使っていてどうしてもtuneしたい場合はsystemcallでwritevの実使用数をみながら調整するとよいでしょう
またこの値を変更する場合はworkspace_threadを同時に調整する必要があるでしょう(割り当て元がworkspace_threadからなので)

h2関連パラメータの追加
h2_header_table_size
h2_max_concurrent_streams
h2_initial_window_size
h2_max_frame_size
h2_max_header_list_size
このあたりはh2のRFCを読んでもらえればわかるかなって感じです。
ちなみにh2_max_concurrent_streamsは100なんですが、chromeのストリームはデフォで1000なので、そのあたりで気になる人もいるかもしれないです。(とはいえそんなに弄る必要ないと思います)

feature_bitの追加(http_dete_postel)

日付の形式はrfc7231#section-7.1.1.2で定義されてるんですが、たまにこれに準拠しないやんちゃなDate,Last-Modified,Expiresを返してくるサーバがいます


Fri, 2 Mar 2018 14:26:02 GMT  (まちがい)
Fri, 02 Mar 2018 14:26:02 GMT (ただしい)

この間違いのパタンを許容するようになります
有効にしたい場合は-p feature=+http_dete_postelにします。
個人的にはオリジン側直したほうがよいかなーとは思いますが・・・

cli_bufferの削除
まぁ以前からアナウンスあったので・・・

カウンタの追加

cache_hit_grace
そのまんまですがgrace状態でレスポンスできた件数になります。
パフォーマンスを稼ぐ場合は一旦古い状態のキャッシュを返しつつ裏で更新リクエストを行うのを重視しますが(うまくいけばキャッシュ更新時に遅くならない)
このカウンタを見てチューニングを行うことが出来るかなとも思います。 

n_lru_limited
nuke_limitに達した件数を示します。
当然ですがストレージがいっぱいの状態で新しいオブジェクトをキャッシュを行おうとする場合は古いものを押し出す必要があります。(LRUです)
Varnishではこの処理をNukeと呼んでいますが、実はこの処理にはnuke_limitというパラメータがあり、一つのオブジェクトを確保するためにNukeして良いオブジェクトのLimitを定義しています。
オブジェクトサイズがだいたい似たようなところならデフォルト値(50)でも問題ないのですが、平均50KBのところに10MBのでかいオブジェクトがたまにやってくるとかだと足りないことがあります。
当然Limitを超えた場合はストレージを確保できないためエラー(503)となるのですが、問題はストリームをしている場合です。
ストリームでレスポンスをしている場合は、当然ながらオリジンから帰ってきたレスポンスコード(200)を返してしまっているので503を返すことができません。
そのため200なのにレスポンスボディが中途半端なサイズになるということがあるのですが(割とmlとか見てるとそれっぽい問い合わせ多い)
このカウンタを見ることでそのような状態に陥ってることがわかります。
とりあえず1でもあがるようであれば、nuke_limitを引き上げる、ストレージの見直しをする(サイズあげるとか、ある程度のサイズ毎に分けてしまうとか)とかすると良いと思います。

vmod_directors

shard directorにかなりの修正が入っています。
shard_param
新しくshard_paramというクラスが定義されています。
基本的に今までのshardの各種パラメータ(rampupとか)が定義されており、単純に外出しにされたものです
使い方は


sub vcl_init{
	new vd = directors.shard();
	vd.add_backend(hoge);
	new p = directors.shard_param();
	p.set(by=URL, warmup=0.7);
	vd.associate(p.use());
}


sub vcl_init{
	new vd = directors.shard();
	vd.add_backend(hoge);
	new p = directors.shard_param();
	p.set(by=URL, warmup=0.7);
}
sub vcl_backend_fetch{
	set bereq.backend=vd.backend(param=p);
}

こんな感じです。

ハッシュアルゴリズムについて
今までハッシュアルゴリズムを選択できたのですが(SHA256(default),CRC32,RS)
これがSHA256だけになりました。これに伴いalgを指定できていたメソッドにおいてalg引数が削除されました。
しかし、わざわざSHA256以外を使ってる人も少ないと思うのでそこまで影響ないと思います。
引数がなくなる影響を受けるのは.reconfigureと.keyになります。

resolve={now,lazy}の追加
個人的に注目している機能です。
lazyモードは実際にバックエンドへの接続を作るタイミングで解決されるものです。
これが何が嬉しいのかというと、Directorを入れ子にすることができます。
例えばfallbackと組み合わせてdirectorが全滅したらfallbackするみたいなのも書けます。


probe healthcheck {
    .url ="/check";
    .timeout           = 1s;
    .window            = 4;
    .threshold         = 2;
    .interval          = 1s;
}
backend ws01  {.port="80";.host = "XX";.probe=healthcheck;}
backend ws02  {.port="80";.host = "YY";.probe=healthcheck;}

sub vcl_init {
	new sdp = directors.shard_param();
	sdp.set(warmup=0.5, healthy=ALL);

	new sd1 = directors.shard();
	sd1.associate(sdp.use());
	sd1.add_backend(ws01);
	sd1.reconfigure();

	new sd2 = directors.shard();
	sd2.associate(sdp.use());
	sd2.add_backend(ws02);
	sd2.reconfigure();

	new fb = directors.fallback();
	fb.add_backend(sd1.backend());//lazy
	fb.add_backend(sd2.backend());//lazy

}
sub vcl_recv {
	set req.backend_hint = fb.backend();
	return(pass);
}

サンプルでshardに登録してるバックエンドはそれぞれ一つですが感覚は掴んでもらえるかと思います。(ついでにshard_paramもサンプル的に入れてみました)

vmod_std

std.log/syslogにおいてworkspace枯渇した場合でもfailしないようになりました

vmod_unix

今回追加されたvmodです
接続してきたプロセスのuser/group/uid/gidを取得できます。
厳密にガードをかけたいときに使うとよいかもしれないです。
man vmod_unixそのままですが


sub vcl_recv {
      # Return "403 Forbidden" if the connected peer is
      # not running as the user "trusteduser".
      if (unix.user() != "trusteduser") {
              return( synth(403) );
      }

      # Require the connected peer to run in the group
      # "trustedgroup".
      if (unix.group() != "trustedgroup") {
              return( synth(403) );
      }

      # Require the connected peer to run under a specific numeric
      # user id.
      if (unix.uid() != 4711) {
              return( synth(403) );
      }

      # Require the connected peer to run under a numeric group id.
      if (unix.gid() != 815) {
              return( synth(403) );
      }
}

こんな感じです

vmod_proxy

今回追加されたvmodです
Proxy Protocol v2にはType-Length-Value(TLV)というフィールドに例えばどんなCipherを使用しているかみたいな情報を埋め込むことが可能です。
残念ながらHitchはalpn(PP2_TYPE_ALPN)しか埋め込んで来ないようなので、フル機能を使いたい場合はHAProxyの比較的新しいバージョンを使う必要がありますが
何かしら情報を取得してログに出力したい場合は有用だと考えています。
また、残念ながら取れる値は固定になっているので、カスタムTLVの範囲へは現状アクセスできません。(NLBのVPCエンドポイントIDとかl)
とはいえコードを見る限りでは対応は容易なのでp-rしてみるのもおもしろいかもしれないです。
使い方は単純で


vcl 4.1;
import proxy;
import std;
sub vcl_recv{
	if(proxy.alpn() == "http/1.1"){
		//プロトコルはまぁreq.protoで取れるんで適切ではないかもですがサンプルとして
		...
	}
}

こんな感じです

VSL

ReqAcctのバイト数が正確になりました
プロトコルのオーバーヘッド分(chunkedのとか)は今まで入ってませんでしたが、OSから報告される値を使うように変更したので正確になりました。

バグ修正

多くのバグ修正がされているのですが、その中でも特筆したほうがよいものに触れます。
max_restartsの取扱い
return(restart)を行った際のチェックの不備で、max_restrtsで指定した回数より1回少ないrestartでlimitになっていましたが、修正されてmax_restartsの指定した数return(restart)ができるようになりました。