多段HTTPプロキシでのAgeヘッダ

はじめに

RFC 9111: HTTP Caching4.2.3. Calculating AgeではAgeの計算式は以下のように定義されています。

A response's age can be calculated in two entirely independent ways:

the "apparent_age": response_time minus date_value, if the implementation's clock is reasonably well synchronized to the origin server's clock. If the result is negative, the result is replaced by zero.

the "corrected_age_value", if all of the caches along the response path implement HTTP/1.1 or greater. A cache MUST interpret this value relative to the time the request was initiated, not the time that the response was received.

  apparent_age = max(0, response_time - date_value);

  response_delay = response_time - request_time;
  corrected_age_value = age_value + response_delay;

The corrected_age_value MAY be used as the corrected_initial_age. In circumstances where very old cache implementations that might not correctly insert Age are present, corrected_initial_age can be calculated more conservatively as

  corrected_initial_age = max(apparent_age, corrected_age_value);

The current_age of a stored response can then be calculated by adding the time (in seconds) since the stored response was last validated by the origin server to the corrected_initial_age.

  resident_time = now - response_time;
  current_age = corrected_initial_age + resident_time;

nginxでAgeヘッダをこの仕様に沿って更新するようにパッチを作った後、テストをしているうちにいろいろ気付いたのでメモです。

request_timeresponse_timeの単位

まず、date_valueDateage_valueもupstreamのAgeに対応する値なので秒単位です。

corrected_age_value = age_value + response_delay;という式でcorrected_age_valueage_valueも秒単位なので、response_delayも必然的に秒単位になります。

一方、request_timeresponse_timeは以下の説明となっていますが、時間の単位は明記されていません。

"request_time"
  The value of the clock at the time of the request that resulted in the stored response.
"response_time"
  The value of the clock at the time the response was received.

例えば、request_timeが00:00:00.999、response_timeが00:00:01.001の場合(日付部分は省略)を考えます。

ミリ秒単位であれば、response_delayは0.002秒ですが、秒単位に切り捨てると0秒になります。 秒単位であれば、request_timeは00:00:00、response_timeは00:00:01となりresponse_delayは1秒となります。

このようにrequest_timeresponse_timeの単位を秒単位にするかもっと細かい単位にするかで、response_delayの値が変わってくる場合があるわけです。

Apache Traffic Serverではrequest_timeresponse_timeは秒単位

HttpTransactHeaders::calculate_document_ageメソッドの引数を見るとrequest_timeresponse_timeの型はともにink_time_tでこれはtime_t#defineされているので秒単位です。

多段プロキシの場合はresponse_delayが多重に効いてくる

client -> child proxy -> parent proxy -> origin serverという2段構成を考えます(->はリクエストの流れの向き)。

child proxyとparent proxyにともにキャッシュがない初期状態で、以下のようになったとします。

00:00:00 chlid proxyの`request_time`
00:00:00 parent proxyの`request_time`
00:00:00 origin serverの`date_value`
00:00:01 parent proxyの`response_time`と`date_value`
00:00:01 child proxyの`response_time`

parent proxyでは以下のようになります。

  apparent_age = max(0, response_time - date_value) = 1

  response_delay = response_time - request_time = 1
  corrected_age_value = age_value + response_delay = 0 + 1 = 1

  corrected_initial_age = max(apparent_age, corrected_age_value) = 1

child proxyでは以下のようになります。

  apparent_age = max(0, response_time - date_value) = 0

  response_delay = response_time - request_time = 1
  corrected_age_value = age_value + response_delay = 1 + 1 = 2

  corrected_initial_age = max(apparent_age, corrected_age_value) = 2

実際はオリジンからchild proxyまでのレスポンスの転送に1秒しかかかってないのに、Ageは2になってしまいます。

response_delayがラウンドトリップの時間になっていて、child proxyでの値はparent proxyでの値を含んでいる上に、corrected_age_valueではresponse_delayが各hopごとに足されていくので、こういう結果になるわけです。

改めて考えてみると、以下の計算式が自然な気がします。

  response_receiving_delay = response_time - date_value;
  corrected_initial_age2 = age_value + response_receiving_delay;

冒頭に貼った4.2.3. Calculating Ageの説明を読むと、originやproxyがすべて正しくAgeヘッダを設定する場合はcorrected_age_valuecorrected_initial_ageとして使ってもよい(MAY)とあります。そうだとしても

  response_delay = response_time - request_time;
  corrected_age_value = age_value + response_delay;

response_delayがupstreamからのレスポンス転送時間ではなくラウンドトリップの時間なので、上の例ではchild proxyのAgeはやはり2になります。

なぜこういう定義になっているのかは私にはよくわかりません。しかし

the "corrected_age_value", if all of the caches along the response path implement HTTP/1.1 or greater. A cache MUST interpret this value relative to the time the request was initiated, not the time that the response was received.

とリクエスト開始日時からの相対値でなければならない(MUST)と書いてあるので意図的なものであることは間違いなさそうです。

一つありそうな理由としてはdownstreamとupstreamでサーバのシステム日時がずれている場合でも問題ないように、request_timeを起点にしているのかもしれません。こうするとresponse_timerequest_timeが同一のサーバ上のシステム日時なのでサーバ間のずれの問題は回避できるので。