Block Rockin’ Codes

back with another one of those block rockin' codes

SPDY と WebSocket の基礎と SPDY の Push

最近 SPDY対WebSockets? などという記事が出てきたりして、

SPDY と WebSocket が色々ごちゃごちゃになって語られているのかもなぁ、と思います。


SPDY では Akamai の中の人の

Guy's Pod » Blog Archive » Not as SPDY as You Thought

や、それに対するフォロー記事

Followup to “Not as SPDY as You Thought” « Mike's Lookout

なんかも、 ちょっと注目されたりしました。


これらの記事には WebSocket や SPDY の性質やモチベーションを正しく理解するために、
知っておくと良い知識へのキーワードが散りばめられていると思ったので、
そこら辺について、つらつら書いてみようかと思います。

SPDY は「遅い」のか?

Guy's Pod » Blog Archive » Not as SPDY as You Thought では Guy 氏がベンチをとっています。


ベンチは

  • Alexa の top 500 サイト(アダルト含む)を、 HTTP/HTTPS/SPDY に切り替えて比較した。
  • サーバは Cotend でプロキシして、クライアントは Chrome の WebPageTest を使用。
  • 4 つの速度の違うネット環境で 5 回づつテスト。
  • 時間とともに Alexa の結果はかわるので、昼に 2 回、夜 1 回実施して、合計で 9 万回ページロードを試行したらしい。


ここまでキチンと行われているので、結果の信頼性は高いだろうとのこと。

結果は

SPDY は HTTPS より 4.5% 速く、 HTTP より 3.4% 遅かった。


原因としては


であると分析しています。



これに対する補足記事として、数日後に以下の記事があがりました。


Followup to “Not as SPDY as You Thought” « Mike's Lookout


よく、こうしたベンチをめぐる論争があるために、この記事を最初のベンチへのアンチ記事のように思われるかもしれませんが、
よく良く読むとそんなことは一切無くて、 Mike 氏は Guy 氏 の記事の価値(ベンチの信頼性)を評価した上で、こう付け加えています。

Guy 氏 のテストは、 SPDY ページロードではなく、 "部分的な" SPDY ページロードしかテストできてない。


ここでは、 Guy 氏が挙げた 1 つ目の理由の方に着目しています。


まとめると、 Guy 氏 のテストでは、ドメインが違うと SPDY の通信は多重化できないから、 3rd Party Domain が多いサイトでは、
SPDY のメリットが得られにくいと指摘しています。
そして、Guy 氏のテストしたサイトには、 3rd Pary Domain の中に、「自分がコントロールできるドメインが混ざっている」ことを Mike 氏 は問題にしていて、
つまり、 google.com の持ち主なら、 images.google.com を触ることができるんだから、
全てのコンテンツを google.com から取るようにしたら、 SPDY の効果がより大きく出るはずだというもの。


Guy 氏 のテストから cnn, ebay, yahoo の結果だけをサンプリングして、その中で使われているドメインを調べてみても、
「SPDY で読もうと思えば読めるはずだけど、上記理由からそうなってないリソース」が結構多いことを指摘している。
(これが、 Mike 氏 の blog 真ん中の表)


だから、 Mike 氏 は「Guy のテストは素晴らしいけど、これはサイトを SPDY に最適化することで、より効果が得られる可能性を指摘している」 といったまとめをしています。

High Performance WebSite のアップデート

要するに、SPDY によって高速化のための手段は増えたものの、
ただ使うだけではだめで、相応の最適化も必要であるということ。


これは WebSocket についても同じです。


そして、こうした技術をどういった場面に適応するのかは、きちんと見定めないといけないし、
単純に置き換えただけで、なんでもうまくいくというものでもないだろうということです。


SPDY は、確かに既存のインフラやアプリとの親和性/互換性を大事にしているけど、
それでも、それなりに「最適化」をしないと、期待するパフォーマンスは引き出せないことを Guy 氏 の記事は示唆しているととれるのではないでしょうか。


こうした部分を使いこなせれば、今まで行なっていた様々な最適化/高速化手法に、
新たな、強力な選択肢が増えることになります。


SPDY と WebSocket は "vs" で繋げるか?

SPDY と WebSocket については、かなり用途というか、考えるべきレイヤが違うものだと思っています。
このブログでは Socket.IO によりつつも、 WebSocket については何度か何度か書いてきたので、
WebSocket の説明はそこそこに、 SPDY について書きたいと思います。

SPDY のモチベーション

SPDY は Google が「HTTP が遅いから、速くしたい」というモチベーションのもと、
「自分たちで作って、自分たちで使い始めた」プロトコルというような感じで始まりました。


具体的に、例えばあるページが複数のリソース (img, css, js) などをリンクして成り立っている場合を考えると、


HTTP では

  • まず index.html を読む。
  • 中にある <img>, <link>, <script> に書かれたリソースを取得する。この時、それぞれのリクエストは前のレスポンスが返らないと投げられない。(*1)
  • 全てのリクエスト、レスポンスには HTTP ヘッダが付き、それも含めてテキストが基本


SPDY では

  • index.html を読む。(*2)
  • 必要なリソースを、同一のコネクション上で多重化し一気にやり取りできる。
  • ヘッダは圧縮されたバイナリなので、生テキストよりはサイズが小さい。


といった形で、SPDY の方が従来の HTTP より効率の良い(*3)通信を実現することを目的としています。


*1: なので、セッションを複数張って並行にダウンロードしたりする。
*2: SPDY の Push を使うと index.html を読む時点で、必要なリソースも読める。後述。
*3: 一方で、暗号化のコストや、中間キャッシュが効かないなどはある。

SPDY で Push

そんな中で SPDY も WebSocket でよく言うサーバサイド Push が可能だと言う点が、話をもう一つややこしくしているのかもしれません。
これは、同じサーバサイド Push である、 WebSocket と、 似ているようですが、若干用途が違います。


SPDY の Push は、動作イメージとしては、

「リクエストが想定されているリソースを、先にレスポンスしておく」


といった感じの挙動ができます。
(これはあくまでも、ひとつの使い方だと思いますが。)


しかし、これで従来できなかったような高速化が
できるようになる可能性があります。


従来のリソース読み込み

例えば、最初のリクエストで index.html を読み込んで、
ブラウザがそれを解析し始めたとき、以下の様な外部リソースの
埋め込みがあったとします。

<link rel="foo.css">
<script src="bar.js">
<img src="buz.png">


すると、ブラウザは(AMD とかで無い限り)、これらを順番に取得していきます。
取得の際は、単一のセッションではレスポンスが返るまで、リクエストが投げられません。


そこで、ブラウザは複数(2-6くらい) のセッションを同時に張って、
平行にリソースの取得を行うなどしていました。これはサーバやネットワークに大きな負荷になります。


しかし、 spdy は単一のセッションでリクエストを多重化することができるため、
それだけでも、大量の画像の読み込みなどでは、セッションの面で見る通信の効率は
良くなります。


さらに spdy は、サーバ push を利用することで、
ブラウザが index.html を読み込んで、リクエストを投げる前に、
外部リソースを配信することができます。
これは、見方を変えれば「キャッシュの送りつけ」という感じです。


先ほどの例では、ブラウザは index.html を読んで初めて
"foo.css", "bar.js", "buz.png" も必要だということがわかり、
そこで初めてリクエストを投げてリソースを取得します。


しかし、コンテンツを送る側であるサーバは、 index.html が必要とするリソースをあらかじめ知ることができる
(場合が多い) はずなので、
index.html とともに、 ブラウザがリクエストしてくる前に
"foo.css", "bar.js", "buz.png" を「送りつけてしまう」のです。


送りつけられたこの三つは、ブラウザが index.html を読んでいる間に
キャッシュに入り、ブラウザがリクエストを発行する段階では
キャッシュヒットしてすぐにリソースが使用出来るという寸法です。

「俺は index.html を読んでリクエストを発行しようとしたら、
そのリソースは既に"キャッシュの中に入ってた"。。
何を言ってるかわからねーと思うが(ry
cdn とか css-sprite とかそんなちゃちなもんじゃあ 断じてねぇ
もっと恐ろしいものの片鱗を味わったぜ。。」

node-spdy で push

node-spdy でサーバ push をやるのは、そこまで難しくないです。


indutny/node-spdy · GitHub を npm でインストールしたら、
下記のように通常の https の createserver と同じインタフェースでサーバを立てるだけです。

push については、

  • req.issdy
  • res.push()

を用います。


主要部分はこんな感じ。

var path = '/images/icon0.png',
    header = { 'content-type': 'image/png' },
    file = fs.readFileSync(__dirname + '/public' + path);

app.get('/', function(req, res) {
  var title = 'HTTP :(';
  if (res.isSpdy) {
    title = 'SPDY :)';

    res.push(path, header, function(err, stream) {
      if (err) return console.error(err);
      stream.on('error', function(){});
      stream.end(file);
    });
  }

  res.render('index', {
    title: title
  });
});


上記の全体は、こちら。 spdy-push/simple.js at master · Jxck/spdy-push · GitHub


サーバを起動して、 https://localhost:3000 にアクセスします。
(HTTPS であることに注意!! いや、俺は別にそんなところで 15 分もハマったりしてないですよ、やだなぁホントww)


Chrome でみると、 Push したリソースは Cache から読まれていることがわかります。
(もちろん Disable Cache しておきましょう。)



SPDY が動いていることは、 SPDY-indicator を使うと URL エリアに印が出ます。
(ちなみに作ってるのは、 Socket.IO の作者 Guillermo です)

ベンチマーク

では、多数のリソースファイル(img, css, js)を読み込むだけの、
単純な html を返して、Push した場合としない場合で、全体の読み込みにかかる時間を調べてみようと思います。


ベンチを取ったこれの全体はこちらです。(spdy.js が本体)


https://github.com/Jxck/spdy-push


リソースは、img が自分の icon、 css, js については、一般的なものとして、
jquery.js と bootstrap.css の非圧縮のものを採用しました。
それぞれとりあえず 10 個づつ読み込んでいます。


chrome を用いて、同じリソースを読み込む速度を
Push した場合と、しなかった場合で比べてみました。


三回測定した結果は

push なし
2.09, 2.14, 2.02:
push あり
1.69, 1.69, 1.57:


これだけでも結構早くなってますね。


ただ、同じスクリプトを HTTP で読んでみたところ、

HTTP
1.28, 1.42, 1.30:


と、こちらの方が速い結果になりました。
これは Guy 氏の言っていたような結果と近いかもしれません。


この場合は、並行するセッションが多いから、そっちの方が速いのかもしれません。
10個は少ないかなとも思ったけど、現実的な値が欲しかったのでそうしました。
HTML が読み込むリソースがもっと多いと差が出るのかもしれません。


SPDY と WebSocket の Push の違い


SPDY の Push を見てきましたが、では WebSocket の Push とは何が違うかを考えてみます。


まず SPDY で Push をしましたが、これはあくまで SPDY の通信なので、
通信にはヘッダ情報などがつきます。


また、実際はリクエストより先にレスポンスを返すという感じなので、
SPDY サーバのコンテキスト上でやり取りします。


一方 WebSocket は、メッセージにヘッダ情報などはつかないため、
メッセージの容量は非常に小さく済みます。


そして、例えば HTTP サーバなどとは独立した形で通信が可能なので、
非常に柔軟な扱いができます。


頑張れば、 SPDY だけでチャットなども出来るかもしれませんが、
そうしたガリガリプログラムで制御するような用途なら WebSocket の方が楽です。
(あ、でも Push が来たことブラウザで取れるのかな?)


WebSocket はそもそもインタフェース自体が、 W3C などを中心に、
アプリケーションを実装しやすいように仕様策定しているので、
当たり前と言えば当たり前ですが。


ここまで見ていただいてわかったように、 SPDY での Push は WebSocket の Push と若干違います。
使い分けるとすれば

spdy
コンテンツのプッシュ、サイトの高速化が主な目的。
WebSocket
アプリに必要な、インタラクティブな表現を可能にするのが主な目的。


という形で使い分ける感じで落ち着くんじゃないかと思います。

まとめ

両方とも、次世代の Web を担うことを期待されたプロトコルだし、
出てきた時期も、 Push できるなど似たような性質を持っている点で、
一緒くたに語られることが増えそうではありますが、
何度も言うように、2つが担う点は別だし、考えないといけないことも、
使うべき場面も違うということは抑えておいたほうがいいと思います。



また、 infoQ では、WebSocket にヘッダが無いことが、
セキュリティやインフラへ大きな影響を及ぼすことを指摘しているリンクがあります。


これは確かにそうで、ヘッダが無いこと以前に、upgrade が通らず LoadBalanser で使えない とか、
パーソナルファイアーウォールでつながらない とか、
いう事例が起こったりします。


その点、 TLS の拡張である Next Protocol Negotiation を用いて昇格している点
SPDY は既存のインフラとの親和性は高いかもしれないけど、
暗号化されている分中間キャッシュが効かない、ドメインが違うと多重化できないなどもあります。



どっちも、「取り入れて終わり」とか「とにかく使えばいい」ってほど簡単でも無い点は変わりません。


いずれも出たばかりのプロトコルですが、
WebSocket は対応ブラウザも増えてきたし、
SPDY は次期 HTTP2.0 のベースとしても採用されそうな動きもあるので、
それぞれの特性をキチンと掴んだ上で、
両方をうまく取り入れるのが、今後求められるのかなと思います。