堀愚霊瑠の指摘で気付いた、はてなスターの静的ファイルとか想像以上にアレな件 - にぽたん研究所

November 27, 2008

このエントリーをはてなブックマークに追加
id:HolyGrail (堀愚霊瑠氏) の「はてなブックマークが重い件について、Page Detailerというツールを使って調べてみる - id:HolyGrailとid:HoryGrailの区別がつかない日記」とか見てて、色々問題点が指摘されてて、うん、まぁそうだねーとか色々と思いつつ、YSlow は、有用なツールである反面、減点基準が必ずしも全てのサイトに適合しないというか、ハッキリ言ってしまえば Yahoo! Inc. 基準すぎるので、鵜呑みにし過ぎるのもどうかなーとか思ってた。

で、気になったのは

13. Configure ETags

ETagsっていうのはサーバ上のファイルとブラウザのキャッシュが一致しているかどうかを検証するためのものなのですが、正しく利用できていないのであれば、ETagsは無駄なだけなので取り除いてやりましょう、という項目です。

http://s.hatena.ne.jp/js/HatenaStar.js

http://s.hatena.ne.jp/images/comment.gif

http://s.hatena.ne.jp/images/add.gif

http://s.hatena.ne.jp/images/star.gif

の4つのファイルに対してETagsヘッダが出ているようなので、必要なければ取り除いてヘッダサイズを減らしましょう。

という箇所。

ETag 自体は「正しく利用していれば」有用だし、ヘッダサイズ云々って言うほどデカくもないし、アレだなーとか思いつつ、なんで「正しく利用できていないのであれば」という前提のものに引っかかったのかなというのが非常に気になった。

ちなみに、ETag ヘッダは、デフォルトではそのファイルの inode 番号、ファイルサイズ、更新時刻 (epoch 秒) の値を 16 進数表記して、ハイフンで繋いだ文字列をダブルクォーテーションで括ったもの。
Perl で同じような文字列を出力するなら、
printf qq/ETag: "%x-%x-%x"\n/, (stat $filename)[1, 7, 9];
こんな感じ。

-- 以下、HTTP とかにさほど詳しくない人向けの説明。

これが、HTTP レスポンスに含まれていたら、次に HTTP リクエストする際に、ブラウザ側でキャッシュした時の ETag の値を If-None-Match ヘッダに渡します。サーバは、今現在サーバ上にあるファイルの ETag の値が一致するかを確認して、一致した場合は 304 (Not Modified) の HTTP ステータスを返し、ブラウザは「以前キャッシュした時から更新されていない」と解釈して、キャッシュされているファイルを再利用します。

ETag の値を返さない場合、Last-Modified ヘッダが HTTP レスポンスに含まれていたら、次に HTTP リクエストする際に、ブラウザ側でキャッシュした時の Last-Modified の値を If-Modified-Since ヘッダに渡します。サーバは、今現在サーバ上にあるファイルの Last-Modified の値がそれ以前であるかを確認して、それ以前だった場合は 304 (Not Modified) の HTTP ステータスを返し、ブラウザは「以前キャッシュした時から更新されていない」と解釈して、キャッシュされているファイルを再利用します。

-- 以上、HTTP とかにさほど詳しくない人向けの説明。

で、思ったのは「はてなスター (だけ?) の静的ファイルが ETag を正しく使えてないから引っ掛かったのかな」と。
よくよく考えたらはてなスターはこのブログにも貼ってあるし、はてなの色々なコンテンツに大量に利用されている事実を考えたら、もし ETag を正しく使えてないのなら、無駄なトラフィックを大量に生み出しているわけで、決して侮れない。
ナニゲに HatenaStar.js はデカいし。

考えられるありがちな落し穴を考えると、「基本的には同じファイル」なんだけど、配置されているサーバが複数台数に分散されているような場合、ほぼ間違いなくそれぞれのサーバで inode 番号が変わるので、ETag に含まれる値の inode の部分 (先頭部分) はリクエストを処理するサーバによって異なってくる。

そういう場合は、ETag を吐かないようにさえすれば、Last-Modified と If-Modified-Since でいいあんばいにキャッシュ比較とかをしてくれる。
Apache の場合は FileETag ディレクティブを利用して、以下のように設定すれば ETag を吐かなくなる。
FileETag None

あるいは、ファイルサイズ、更新時刻 (epoch 秒) は一緒なのであれば、ETag を吐く時に不要になるのは inode 番号だけ。では、ETag の値から inode 番号を抑制すれば済む問題なので、同様に FileETag ディレクティブを利用して、inode 番号だけ利用しないように、以下のように設定するのがセオリー。
FileETag Size MTime
あるいは
FileETag -INode
こうすることで、複数台数であっても、ファイルサイズと更新時刻によって生成された値によって、ETag と If-None-Match でいいあんばいにキャッシュ比較とかをしてくれる。

じゃあ、はてなスターは、このありがちな落し穴をおかしているのではないだろうか?とアタリをつけて、HTTP HEAD リクエストを送出してみて確認してみた。
% telnet s.hatena.ne.jp 80
Trying 59.106.108.97...
Connected to s.hatena.ne.jp.
Escape character is '^]'.
HEAD /js/HatenaStar.js HTTP/1.1
Host: s.hatena.ne.jp
Cookie: b=hoge
Connection: close

HTTP/1.1 200 OK
Date: Thu, 27 Nov 2008 00:53:51 GMT
Server: Apache
Last-Modified: Tue, 04 Nov 2008 09:25:37 GMT
ETag: "b88995-1697f-45ad9a572a640"
Accept-Ranges: bytes
Content-Length: 92543
Vary: Accept-Encoding
Connection: close
Content-Type: application/x-javascript

Connection closed by foreign host.
* ちなみに、b=hoge とかいう Cookie を送っているのは、session cookie をリクエストの都度吐いてくるので、迷惑にならないように「既に session cookie 持ってるよ」と欺いてます

案の定、ETag は三つの値がハイフンで繋がれています。
恐らく先頭の b88995 は inode 番号なので、複数台のサーバで運用されている場合は、邪魔になる恐れがあります。
続いて、それを検証するために、この ETag の値と、Last-Modified の値を、それぞれ If-None-Match と If-Modified-Since に含んでみたら、ちゃんと 304 (Not Modified) ステータスを返してくれるか検証してみました。
% telnet s.hatena.ne.jp 80
Trying 59.106.108.97...
Connected to s.hatena.ne.jp.
Escape character is '^]'.
HEAD /js/HatenaStar.js HTTP/1.1
Host: s.hatena.ne.jp
Connection: close
Cookie: b=hoge
If-Modified-Since: Tue, 04 Nov 2008 09:25:37 GMT
If-None-Match: "b88995-1697f-45ad9a572a640"

HTTP/1.1 200 OK
Date: Thu, 27 Nov 2008 00:54:48 GMT
Server: Apache
Last-Modified: Tue, 04 Nov 2008 09:20:37 GMT
ETag: "f9665-1697f-45ad993910340"
Accept-Ranges: bytes
Content-Length: 92543
Vary: Accept-Encoding
Connection: close
Content-Type: application/x-javascript

Connection closed by foreign host.
304 (Not Modified) を期待していたら、200 (OK) ステータスが返ってきました。
ETag を見るとやはり先頭が f9665 で先程とは違うので、inode 番号が違います。
でも Content-Length も一緒だし、MD5 の checksum を見ても同じなので、同じ JS ファイルであることは間違いありません。
でも「あなたのとこにキャッシュされているファイルとは、比較するかぎりどうも別のファイルだから、このデッカイ JS ファイルをあらためて読み込んでキャッシュしなおしてよ」とブラウザに訴えてきているのです。
堀愚霊瑠氏の指摘するとおり、正しく使えてない、無駄な ETag を吐いてます。
何度か繰り返してみると、ETag によって返される inode 番号は 2 種類であることから、「はてなスターは 2 台のサーバから静的なファイルを返している」ということがわかります。


結論: はてなスターは ETag を吐かないようにするか、inode 番号を利用しないように正して、きちんとブラウザのキャッシュを有効活用させるようにして欲しいです!











って、気持ちよくしめようとしたら、現実はもっとひどいことに気付いた。

目を凝らしてよく見れば ETag だけの問題ではない。
Last-Modified が 2 台それぞれのサーバによって違う。
5 分も違う。
当然、ETag も inode 番号の部分だけじゃなく、更新時刻の部分も違う。
ETag を抑止しても、Last-Modified がバラバラだから、古いほうを先に GET してしまうと結局新しいほうを GET しに行った時に 304 ステータスは返らない。

はてなスターは、あれだけのパーツ画像とデッカイ JS をバラまいておいて、ブラウザにキャッシュさせる気はないのだろうか。。

ファイルの属性は転送しないようなよっぽど変なデプロイツールを使っているのか、あるいはインターンで来た学生に JS を精密に写経させていたのだろうか。
ちなみに、上記であがっていた 4 つのファイルのそれぞれが、ETag と Last-Modified 共にサーバごとに合致しない。
GIF 画像ファイルまでもがそういう状況なのだから、インターンで来た学生にバイナリエディタで GIF を精密に写経させていたのだろうか。

ちなみに、今日の午前中の時点での各ファイルの ETag、Last-Modified の組み合わせを列挙してみた。

http://s.hatena.ne.jp/js/HatenaStar.js
Last-Modified: Tue, 04 Nov 2008 09:25:37 GMT
ETag: "b88995-1697f-45ad9a572a640"
Last-Modified: Tue, 04 Nov 2008 09:20:37 GMT
ETag: "f9665-1697f-45ad993910340"

http://s.hatena.ne.jp/images/comment.gif
Last-Modified: Tue, 13 May 2008 13:04:52 GMT
ETag: "f8ff2-362-44d1c4f516500"
Last-Modified: Tue, 13 May 2008 13:04:53 GMT
ETag: "b88361-362-44d1c4f60a740"

http://s.hatena.ne.jp/images/add.gif
Last-Modified: Tue, 13 May 2008 13:04:52 GMT
ETag: "f8fe8-51-44d1c4f516500"
Last-Modified: Tue, 13 May 2008 13:04:53 GMT
ETag: "b88357-51-44d1c4f60a740"

http://s.hatena.ne.jp/images/star.gif
Last-Modified: Tue, 13 May 2008 13:04:52 GMT
ETag: "f9044-b2-44d1c4f516500"
Last-Modified: Tue, 13 May 2008 13:04:53 GMT
ETag: "b883b3-b2-44d1c4f60a740"


これらを直して欲しいと切に願うと同時に、それぞれのファイルのヘッダがいつ正しく修正されるかを、今後継続して生暖かく見つめていきたいと思いました。

nipotan at 11:53 | Comments(1) | TrackBack(1) | 技術 
このエントリーをはてなブックマークに追加

Trackback URL for this entry

Trackbacks

1. クラウド環境でのApacheの設定  [ cloudrop ]   September 12, 2009 18:42
クラウドのホスティングサービスは、一定リソースの時間極課金+通信トラフィックの従量課金が一般的です。 CPUやメモリなどのリソースは、1%しか使わなくても100%使っても時間内の料金は同じです。 一方で通信料は使った分だけGB単位などで段階的に課金される仕組みです。 ...

Comments

1. Posted by じゅんちゃん   December 02, 2008 14:18
僕もブログもやってます、毎日は更新できないんですよねー・・・
けどこれからもがんばっていきましょう。

風邪などひかないようにしてくださいねー、応援します♪

Post a comment

Name:
URL:
  Remember info?: Rate: Face    Star