うな(。・ε・。)

Android, iOS, AppEngine まわりのめもめも

Facebook の高速ページ表示を支える BigPipe

BigPipe: Pipelining web pages for high performance

非常に面白い内容だと思うので、メモついでに紹介します。

一言でいうと

ページの枠組みと、付随するページフラグメント郡を一つのリクエストでまとめて要求・非同期受信する技術です。

具体的には、トップ画面を表示したときに、始めにトップ画面の枠組みだけをまず表示しておきながら、「フィード」「広告」「通知」「オンラインの友人」を非同期に受信し、表示していくことが出来ます。

利点

最も大きな利点は、多数のデータ取得の結果を待たなくともページの枠組みをさっさと返せることです。

でも、これだけでは「XHR での非同期リクエストでも出来るじゃん」と思ってしまいますね。

BigPipe のキモは XHR で非同期にリクエストすることと違い、最初の HTML を要求するリクエストの際に付随するページフラグメント郡も同時にリクエストされることです。また、最初の HTML リクエストと同じコネクションを用いてこれらのレスポンスが非同期に返却されます。

つまり、全体として一本のコネクションをもって、全てのレスポンスが非同期に返されます。

この性質により、次の観点でスピードを向上させることが出来ます。

  • 全てのデータをデータベースから取得し終えてから一括でサーバサイドレンダリングすることと比べたとき
    • ページの表示のために、データベースのクエリの結果を全て待たなくてよいため、飛躍的な体験的速度の向上が見込まれます
  • XHR に比べたとき
    • 最初の HTML を返してから XHR を行なうまでの時間がカットされます。
    • XHR で生じるリクエストにおいてのレイテンシがカットされます。
    • 多数の XHR リクエスト実行にかかるコストがカットされます。

ちなみに、ユーザの体感速度でみたとき、これくらい速くなるらしいです。(原文: 75th percentile user perceived latency for seeing the most important content in a page)

XHR だと、この指標はむしろ落ちてしまうのではないかと思います。

具体的な実装

前提知識

BigPipe は具体的には Chunked transfer encoding を用いて実装されています。 早い話が HTTP 上で「ストリーミング」が出来るプロトコルです。 これは特殊なプロトコルという訳ではなく、Rails でも普通にページを表示すると Chunked transfer encoding になったりしてました。

実装も簡単です。Header に Transfer-Encoding: chunked を指定して、どんどんレスポンスに内容を書いていくだけ。 ブラウザは内容が書かれたタイミングで、書かれた分の内容をすぐに取得することが出来ます。

BigPipe の実装

ここまで理解すれば BigPipe は簡単です。おおむね、次のような動作をします。

  1. ブラウザはサーバにリクエストを行ないます。
  2. サーバは Chunked transfer encoding を用いて、まずページの枠組みをレスポンスに書き込みます。
    • この段階でブラウザはページを表示出来ます。
    • 同時に、必要なページフラグメントを返すための処理をし始めます。(主にデータベースへのクエリ発行)
  3. サーバはページフラグメントの準備が完了するたびに、順不同でレスポンスに内容を書き込みます。
    • ブラウザはページフラグメントを受け取り、ページに埋め込んでいきます。(JS でハンドリングされます。)
  4. 全てのページフラグメントが返却し終えたら、レスポンスの返却を終了とします。

より詳しくは原文を参照ください。 BigPipe: Pipelining web pages for high performance

参考文献

HTTP/2 (SPDY) とパフォーマンスのめも

主にパフォーマンス関連でじぶんようのメモです

  • HTTP/2 によって解決した問題は、「同時リクエスト制限」によるものが殆どです。
    • HTTP/1.1 では「一本のコネクションで同時に一つのリクエストしか出来ない」だったものが、HTTP/2 では「一本のコネクションでたくさんのリクエストを同時に出来る」と理解して良いと思います。
  • 転送全体としてみれば非常に効率的になりましたが、レイテンシに関する問題はあまり解決されていません。
    • たとえば、TCP の初期接続コスト、TCP スロースタートによる 14KB 問題は依然として存在します。
  • 当たり前ですが、ブラウザ側でレンダリングが遅くなることに対してはノータッチです。
  • HTTP/2 ではドメインシャーディングが逆効果になります。
    • 別のドメインに対しては別のコネクションが張られるため、TCP の初期接続コストが問題になります。
    • 特に、CSSレンダリングをブロックするため、同一ドメインから配信することが推奨されます。(JS は defer にさえしていれば無問題)
  • CSS Sprites や CSS/JS の結合は依然として有効です。
    • いくら同時リクエストし放題といっても、なんだかんだでリクエストにはコストが掛かります。減らすにこした事は無い。
  • その他のパフォーマンステクニックは依然として有効です。

結局、レイテンシが問題になりやすいモバイルではあまり速くなってないんじゃ疑惑… 画像を大量に貼ってるページとかは速くなりやすいと思います。

大量の画像を速く表示するために画像を BASE64 して Chunked transfer response する暗黒魔法は必要なくなりました。

でも、ちゃんとチューニングしてるページとかはあまり変わらなくて、恩恵を受けられるのは Server PushCSS, JS を HTML と一緒に送ってあげることくらいでした。

「正しい」技術

正しいものが良いものであるとは限らないし、利用ケースに沿うとは限りません。

正しいものは「もっともらしい」ので、無視する事が非常に気持ちが悪いです。 これは極めて宗教的な圧力です。

しかし、この気持ち悪さを我慢したほうがいいです。YAGNI を我慢する気持ちと一緒です。自分の基準を持つことが大事だと思います。

ということをこのまえ反省しました。まる。

Azure が未来過ぎたので色々調べてみた

登場当時は 「AWS でいいじゃん(笑)」と無視してしまっていたけれど、今見るとめちゃくちゃ気合いを入れてサービスを拡張していた。

AWS と比較して優れていると感じるのは、非常にモジュラー指向なところ。

AWS も SQS や SNS など「モジュール」といえるサービスを出してはいるけれど、依然として EC2 万歳なクラウドであるように感じる。 特に cron とかやろうとすると、生 cron を必ず触る事になるし、冗長性の確保がめちゃくちゃ大変。デプロイの方法論も cron のために特別に考えるハメになったりする。

対して Azure は、クラウド cron であるところの "Scheduler" というサービスが存在する。 機能は非常にシンプル。管理画面上で "Schedule" を作るだけ。あとは勝手に指定した時間間隔で、指定した HTTP Request を発行してくれる。(API もあります)

こういった細かい機能を、冗長性を保った形でインフラ層で吸収してくれています。 アプリはこれらをつまみ食いすればよい。というのは冗長性を確保しながらも、効率の良い開発をするには理想的な形と言えると思います。

まあ、といったわけで、気になるサービスをちょっと調べてみようかと。

API Management Service

azure.microsoft.com

  • API を管理するサービス
  • ドキュメントと、リクエストを試せるコンソール、開発者ポータルを提供出来る
  • ヘルスチェック機能
  • 利用回数等のアナリティクス
  • レスポンスのキャッシュ
  • API コールのログ
  • Rate limit, Throttle が出来る(API Provider として)
  • 様々なバックエンドからデータを取得して一本化、などということが出来る
    • XML -> JSON にしたり、プロパティの名前変えたり、パラメータ付与したりとか
  • ドキュメント機能以外は、要は高性能プロキシです

このへん真面目にやろうと思えば本当に大変なのですごく良い。

「開発者ポータル」という単語から、公開 API を持っているサービスでの用途が想起されますが、通常のサービス内 API で用いても十分使えます。

たとえばの利用ケースとしては、モバイル用の API. REST 厨な API があったとして、クライアント側でたくさんの Entity が必要なとき、困りますね。API Management Service 側でバッチリクエストを行なう API を定義すれば、クライアント側は 1 リクエストで済みます。

Event Hubs

azure.microsoft.com

  • PubSub なメッセージキューです(おわり)

Multi Factor Authentication

azure.microsoft.com

  • MFA as a Service

Media Services

azure.microsoft.com

  • エンコーディングとストリーミングが出来ます
  • iOS への HLS 配信とか一発です
  • ライブストリーミングも対応(!)
  • メディアファイルを検索可能にするための Indexing 機能(!?)
  • 音声や動画を扱う際にめんどうくさいインフラ的なアレコレが提供されています
  • Amazon Elastic Transcoder と違うのはエンコーディングだけではなく、配信系の機能も統合されていることです

DocumentDB

azure.microsoft.com

クエリを下のリンクで試す事が出来ます。基本的なのが色々揃ってる。

DocumentDB Query Playground

他の NoSQL と比べて、制約が緩過ぎて逆に怖い。

App Service

azure.microsoft.com

  • App は Web App, Mobile App, API App, Logic App に分類され、それぞれに向いた機能が提供されます
  • いわゆる PaaS です
  • Heroku が最も近いと思います。
  • git push してデプロイなどということが出来ます
  • もちろんオートスケールします
  • 対応言語はおおむね ASP.NET (C#), Java, node.js
    • ASP.NET がマネージドインフラで動くのは、嬉しい人多そう
  • Mobile App はモバイルに必要な全て(認証、同期、通知)が同梱されており、もはやカオスでよくわかりません

Scheduler

  • 上述の通り

Notification Hubs

azure.microsoft.com

  • Amazon SNS です
  • ターゲティングの機能(多言語対応とか)が強化されています
  • (どうでもいいけど Safari にも対応しててすごい)
  • Email の配信は出来なくて、SendGrid 推しっぽい。

Managed Cache

azure.microsoft.com

  • Redis を運用しなくて済みます(おわり)

Machine Learning

azure.microsoft.com

  • 「関連するアイテム」とかの計算をイイ感じにやってくれて、結果を API にして公開したり出来ます
  • 機械学習が必要になるアプリ(リコメンドとか)は、どう学習基盤を統合するかで悩むことが多いと思いますが、Azure ML に行動データをぶん投げて、API で予測結果を取得する形にすれば良いと思います。

かんそう

Azure Virtual Machine というサービスが、AWS での EC2 に相当するのですが、あまり目立たない。むしろ、PaaS であるところの App Service 推しです。

Docker とかつべこべ言わずに PaaS に載せとけ!俺たちが面倒みてやるから!ということでしょうか。

とにかくインフラの管理をさせない、という一貫したメッセージを感じました。 AWS は EC2 万歳すぎると思います。インフラ管理がインスタンス管理に置き換わっているだけで、まだまだ運用感は拭いきれません。OS を直接触るのも忌避する時代になってくると思います。

また、API 関係への力の入れよう。API Schema を使って API を管理していこうという姿勢が垣間見えます。 API Schema を用いた透過的なバックエンド間通信が一般人レベルでイイ感じに出来るようになるのも、未来の話ではないかもしれません。

モジュラー指向の強さや、API への関心の高さ、インフラ管理からの解放、あたりのキーワードを見ると、Azure というクラウドプラットフォームはかなり未来を見据えています。

AppEngine らいぶらり書いてる

最近ちょっとライブラリ作るのに凝りだしてしまって、AppEngine/Java の commons ライブラリを書いている。

第一弾は Counter.

github.com

Datastore 上でカウンターを単純に実装すると、遅いし Write オペレーションで高くついてしまいます。 この問題には既に確立された回答があり、PullQueue に一旦情報を貯めておき、Cron Job で Datastore に実際の書き込みを行ないます。このような実装とすることで、速く、安いカウンターを実装する事が出来ます。

この実装をライブラリとして提供したものが Counter になります。

使い勝手としてはこんな感じです。

Counter.increment() で指定したキーに対するカウントを +1 することが出来ます。

Counter.increment("/photos/2343");

使う側はこれで以上です。TaskQueueaddAsync() を使っているので処理をブロックしません。コストも安いので経済的です。

最後に、Cron Job のハンドラーを実装し、実際に書き込みを行ないます。

実際に書き込みを行なう処理は CountWriter interface を実装します。

public class ViewCountWriter implements WriteCountPullTaskDelegate.CountWriter {
    @Override
    public void addCount(String key, int count) {
        // key に対するカウントを、count 分増やして書き込む処理を実装します
        final PageView pageView = PageViewRepository.getOrCreate(key);
        pageView.setViewCount(pageView.getViewCount() + count);
        PageViewRepository.put(pageView);
    }
}

この実装クラスを Cron Job ハンドラーにて WriteCountPullTaskDelegate.doWriteCount() に渡してやることで、処理を完遂出来ます。

// cron job のハンドラー
public void doGet(HttpServletRequest request, HttpServletResponse response) {
    WriteCountPullTaskDelegate.doWriteCount(new ViewCountWriter());
}

他には、次のような小さいライブラリも提供予定です。

  • Key ベースのキャッシュを自動で行なう DatastoreCallbacks
  • アクセスログではなく BigQuery に飛ばすロガー
  • リクエストとレスポンスの payload をログする ServletFilter
  • JOIN なクエリをいい具合に Memcache してくれる DatastoreCallbacks(これは書くか分からない)
  • Entity の変更に対するコールバックを一元的に書くための DatastoreCallbacks
  • Repository クラスの基本的なメソッドを提供するベースクラス
  • stats をメールで送ってくれる Cron Job(未定)

ほぼ自分用です。Java たのしい