ぼちぼち日記

おそらくプロトコルネタを書いていることが多いんじゃないかと思います。

GmailがハマったSPDYの落とし穴

1. SPDYブーム到来

おかげさまで、ここ数日 SPDY が私の周りで非常にブームになってきています。
前回案内したSPDY&WS勉強会は既に200名以上の申し込みがあり、今ではSPDYネタでブログを書くと非常に注目されるうれしい状況です。時代はまさに、
SPDYはハイプサイクルを順調に駆け上がっている
状況だと思います。


図1:2012年のハイプサイクル:
図はガートナー社のプレスリリース http://www.gartner.co.jp/press/html/pr20120906-01.html から引用

SPDYが、まだ黎明期に入ったばかりなのか、それとも既にピーク期に入ったのか、それは歴史が証明してくれるでしょう。
ということで勉強会までSPDY熱が冷めないよう、私もいろんなSPDYネタを出していきたいと思います。

2. GmailがハマったSPDYの落とし穴とは

先日、 Google でSPDYの開発の主要担当者をしている William Chan より、 SPDY Prioritization Case Study – Gmail といったブログが公開されました。非常に分量の多いエントリーですが、大きく以下の2つの話が書かれています。

  • ChromeGmailCSSのダウンロードがJSより遅くなっている。なぜだか理由がわからない。」とGmailチームより相談された。→ 調べてみるとSPDY の優先度設定が原因だった。
  • SPDYの優先度設定は、現状の仕様ではまだ不十分なので考え直そう。

今回、前者の話について実際に手元でシミュレーションをした結果とその理由を解説したいと思います。(大元のブログを読んで内容を理解した方は、わざわざこの後を読まれる必要はありません。)

3. Gmailのページ読み込み最適化手法

まずGmailがどのようにしてページ読み込みの最適化が行われているか、その資料のポインターが示されています。
Browser Enhancements to Help Improve Page Load Performance Using Delta Delivery
これ読んで驚きましたね。

Gmailは iframe を使って JS を読み込み、その中の AjaxCSSJSON形式でダウンロードしている。

とのこと。もともとの資料は、W3Cにて DeltaJS について提案したものです。この DeltaJS とは、JSのアップデート差分を送る技術仕様です。

ぶったまげました。しかし、ここに書いてあるGmailの読み込み最適化手法やDeltaJSについては、もう一つや二つブログが書けるネタなので、今回触れるのはこのくらいにしておきます。先のブログでは、Gmailの場合Ajaxで非同期に読み込まれる60KBの圧縮されたCSSのダウンロードが900KBの圧縮されたJSより遅いのは何故だろうか? というのがGmailチームの悩みだったようです。

4. Gmailのページ読み込みをシミュレーションしてみる。

ということで、Gmailのページ読み込みのシミュレーションをしてみましょう。
以下の3種類のファイル(index.html/ifram.html/css.json)を用意して Chrome から SPDY を使って index.html にアクセスしてみます。index.htmlに含まれる iframe.html のサイズは巨大なJSコメントを含んだ1.1Mbyte。一方 css.jon の方は、わずか40byteです。SPDYの情報を取れるようサーバは spdylay の spdyd を使いました。

<!-- index.html -->
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>SPDY Priority Test</title>
  </head>
  <body>
  This is a test page.
  <iframe src="iframe.html"></iframe>
  </body>
</html>
<!-- iframe.html -->
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>IFrame</title>
    <script>
      (function() {
        var request = new XMLHttpRequest();
        request.open("GET", "https://spdy-int.iijplus.jp:8443/css.json");
        request.send();
       })();
    </script>
    <script>
     /* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa */
     /* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa */
     /* 以下コメントがいっぱい 1M byte 程続く */
    </script>
  </head>
  <body>
    Iframe
  </body>
</html>

css.json

{"css" :"#hoge {color: red;}"}

iframe.html の最初で読み込んだSCRIPT文で Ajax が非同期で直ちに実行されるので、1.1MbyteのJSを含む iframe.html のダウンロードが終わる前に、たった 40byte の css.json のダウンロードは当然完了しているだろうと予想します。

結果は、

CSSのダウンロードがJSより遅くなっている!
Ajax全然意味ないじゃん!
ということで、Gmailチームが悩んでいた問題の再現に成功しました。

5. SPDYの優先度とは

ここでW. Chan は、これを SPDY の問題であると見つけ出すのですね。さすがです。実は Chrome のSPDY優先度設定が原因でした。
SPDYでは、リクエスト毎に0-7の8段階の優先度設定ができます。SPDY の優先度フィールドどう設定されているのかは、以下のスライドの p13、 SYN_STREAM のプロトコルフォーマット表を参照して下さい。

で、Chrome側の実装では、リソースのタイプによって優先度を HIGHESTからLOWESTの5段階に分けていたんですね。それを表にすると以下の通りになります。

表1:ChromeのSPDY優先度設定
優先度カテゴリ 優先度 リクエストタイプ 説明
HIGHEST pri=0 MAIN_FRAME メインページ
SUB_FRAME iframe
MEDIUM pri=1 STYLESHEET スタイルシート
SCRIPT JavaScriptファイル
FONT_RESOURCE フォント
LOW pri=2 SUB_RESOURCE MEDIUM以外のリソース
OBJECT オブジェクト要素
MEDIA メディア要素
WORKER ワーカ
SHARED_WORKER 共有ワーカ
XHR Ajax
LOWEST pri=3 IMAGE 画像
FAVICON favicon
IDLE pri=4 PREFETCH プリフェッチ

実際に spdyd の出力で確認してみましょう。

iframe.html

[id=3] [ 19.616] recv SYN_STREAM frame <version=3, flags=1, length=118>
          (stream_id=3, assoc_stream_id=0, pri=0)
          :host: spdy-int.iijplus.jp:8443
          :method: GET
          :path: /iframe.html
          :scheme: https
          :version: HTTP/1.1
          (以下略)

css.json

[id=3] [ 19.735] recv SYN_STREAM frame <version=3, flags=1, length=128>
          (stream_id=5, assoc_stream_id=0, pri=2)
          :host: spdy-int.iijplus.jp:8443
          :method: GET
          :path: /css.json
          :scheme: https
          :version: HTTP/1.1
           (以下略)

表1の通りiframe.html は SUB_FRMAME なので優先度はHIGHEST(pri=0)です。一方CSSの方は Ajax による json 形式で取得しているので LOW(pri=2)なんですね。

6. Gmail がハマった理由

この優先度設定を先ほどのシミュレーション結果に当てはめてみます。

これで問題の原因があきらかです。

AjaxによるJSON形式のCSSデータの取得は、iframeで取得するJavaScriptよりSPDYの優先度が低いためCSSのダウンロードの完了がJSより遅い

ということです。このため、いくらAjaxで非同期処理にしても、わずかCSSのサイズが40byteしかなくても、CSSのダウンロードの完了がiframe中のJSのダウンロードより遅れてしまうということが常に発生しているのです。結局、

JSをiframeでダウンロードし、cssAjax経由の json でダウンロードするといった Gmail の(ある意味特殊な)最適化手法がChromeのSPDY優先度設定に影響されてしまった

というオチだったんですね。ここで試しに、iframe.html を以下のように変更してみます。

<!-- iframe.html -->
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>IFrame</title>
    <script src="iframe.js"></script>
    <link rel="stylesheet" href="css.css" type="text/css">
  </head>
  <body>
    Iframe
  </body>
</html>

iframe.jsは、1Mbyte近くのJSコメントのみ、css.css は、先程の css.jsonCSSだけ入っています。いわゆる通常のページ(script要素で JS、 link要素で CSS をダウンロード)の場合です。

その結果は、

です。(SPDY優先度値も加えています。)

おぉ、これは予想通り JSとCSSが並んでダウンロードが完了しています。 普通のページは、こんな感じになるんでしょう。
しかし、小さなCSSがJSに引きずられている感はあります。こういった部分を改良することが今後必要だというのがブログの後半部分です。一昨日からGoogle Japan で開催されていた HTTP/2.0 の interim meeting でこの点について議論がされたようですので、その様子の報告を楽しみに待ちたいと思います。(spdy/4では、 Stream Dependencies という仕様が現在提案されています。)

そして、昨日 Google で開催された Chrome Tech Talk Night #5 の後、講演者で Chrome Developer Advocate の Ilya Grigorik さんと直接SPDYの話をする機会がありました。GoogleのSPDYの取り組み状況やGmailチームの大変な苦労などお聞きして非常に参考になりました。(Private Comminication なので詳細は書きませんが。)既に2年以上SPDYをサービス提供しているGoogleでもこのような落とし穴にハマることがあるんだなぁ、と意外に思うと同時に、

やはりSPDYを本当に使いこなすには、しっかりした運用ノウハウの蓄積が欠かせないものだ

と強く感じた次第です。