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 は簡単です。おおむね、次のような動作をします。
- ブラウザはサーバにリクエストを行ないます。
- サーバは Chunked transfer encoding を用いて、まずページの枠組みをレスポンスに書き込みます。
- この段階でブラウザはページを表示出来ます。
- 同時に、必要なページフラグメントを返すための処理をし始めます。(主にデータベースへのクエリ発行)
- サーバはページフラグメントの準備が完了するたびに、順不同でレスポンスに内容を書き込みます。
- ブラウザはページフラグメントを受け取り、ページに埋め込んでいきます。(JS でハンドリングされます。)
- 全てのページフラグメントが返却し終えたら、レスポンスの返却を終了とします。
より詳しくは原文を参照ください。 BigPipe: Pipelining web pages for high performance
参考文献
- BigPipe: Pipelining web pages for high performance : https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919
- Chunked transfer encoding - Wikipedia, the free encyclopedia : http://en.wikipedia.org/wiki/Chunked_transfer_encoding
HTTP/2 (SPDY) とパフォーマンスのめも
主にパフォーマンス関連でじぶんようのメモです
- HTTP/2 によって解決した問題は、「同時リクエスト制限」によるものが殆どです。
- HTTP/1.1 では「一本のコネクションで同時に一つのリクエストしか出来ない」だったものが、HTTP/2 では「一本のコネクションでたくさんのリクエストを同時に出来る」と理解して良いと思います。
- 転送全体としてみれば非常に効率的になりましたが、レイテンシに関する問題はあまり解決されていません。
- 当たり前ですが、ブラウザ側でレンダリングが遅くなることに対してはノータッチです。
- たとえば、CSS や JavaScript でレンダリングをブロックしているとき。HTTP/2 になってもあまり速くなりません。
- HTTP/2 ではドメインシャーディングが逆効果になります。
- CSS Sprites や CSS/JS の結合は依然として有効です。
- いくら同時リクエストし放題といっても、なんだかんだでリクエストにはコストが掛かります。減らすにこした事は無い。
- その他のパフォーマンステクニックは依然として有効です。
結局、レイテンシが問題になりやすいモバイルではあまり速くなってないんじゃ疑惑… 画像を大量に貼ってるページとかは速くなりやすいと思います。
大量の画像を速く表示するために画像を BASE64 して Chunked transfer response する暗黒魔法は必要なくなりました。
でも、ちゃんとチューニングしてるページとかはあまり変わらなくて、恩恵を受けられるのは Server Push で CSS, JS を HTML と一緒に送ってあげることくらいでした。
Azure が未来過ぎたので色々調べてみた
登場当時は 「AWS でいいじゃん(笑)」と無視してしまっていたけれど、今見るとめちゃくちゃ気合いを入れてサービスを拡張していた。
AWS と比較して優れていると感じるのは、非常にモジュラー指向なところ。
AWS も SQS や SNS など「モジュール」といえるサービスを出してはいるけれど、依然として EC2 万歳なクラウドであるように感じる。 特に cron とかやろうとすると、生 cron を必ず触る事になるし、冗長性の確保がめちゃくちゃ大変。デプロイの方法論も cron のために特別に考えるハメになったりする。
対して Azure は、クラウド cron であるところの "Scheduler" というサービスが存在する。 機能は非常にシンプル。管理画面上で "Schedule" を作るだけ。あとは勝手に指定した時間間隔で、指定した HTTP Request を発行してくれる。(API もあります)
こういった細かい機能を、冗長性を保った形でインフラ層で吸収してくれています。 アプリはこれらをつまみ食いすればよい。というのは冗長性を確保しながらも、効率の良い開発をするには理想的な形と言えると思います。
まあ、といったわけで、気になるサービスをちょっと調べてみようかと。
API Management Service
- API を管理するサービス
- ドキュメントと、リクエストを試せるコンソール、開発者ポータルを提供出来る
- ヘルスチェック機能
- 利用回数等のアナリティクス
- レスポンスのキャッシュ
- API コールのログ
- Rate limit, Throttle が出来る(API Provider として)
- 様々なバックエンドからデータを取得して一本化、などということが出来る
- ドキュメント機能以外は、要は高性能プロキシです
- API の話題的には、「オーケストレーション層」と呼ばれることが多いと思います
このへん真面目にやろうと思えば本当に大変なのですごく良い。
「開発者ポータル」という単語から、公開 API を持っているサービスでの用途が想起されますが、通常のサービス内 API で用いても十分使えます。
たとえばの利用ケースとしては、モバイル用の API. REST 厨な API があったとして、クライアント側でたくさんの Entity が必要なとき、困りますね。API Management Service 側でバッチリクエストを行なう API を定義すれば、クライアント側は 1 リクエストで済みます。
Event Hubs
- PubSub なメッセージキューです(おわり)
Multi Factor Authentication
- MFA as a Service
Media Services
- エンコーディングとストリーミングが出来ます
- iOS への HLS 配信とか一発です
- ライブストリーミングも対応(!)
- メディアファイルを検索可能にするための Indexing 機能(!?)
- 音声や動画を扱う際にめんどうくさいインフラ的なアレコレが提供されています
- Amazon Elastic Transcoder と違うのはエンコーディングだけではなく、配信系の機能も統合されていることです
DocumentDB
- NoSQL as a Service
- フルマネージドでイイ感じにスケールする DB を使えます
- 課金は容量とスループットで計算されます
- DynamoDB より扱いやすそうな NoSQL と考えるとよいかと
- トランザクションサポート
- SQL シンタックスのサポート
- 他強力なクエリ機能多数(調べてない)
- 整合性レベルを選べます(!)
クエリを下のリンクで試す事が出来ます。基本的なのが色々揃ってる。
他の NoSQL と比べて、制約が緩過ぎて逆に怖い。
App Service
- 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
- Amazon SNS です
- ターゲティングの機能(多言語対応とか)が強化されています
- (どうでもいいけど Safari にも対応しててすごい)
- Email の配信は出来なくて、SendGrid 推しっぽい。
Managed Cache
- Redis を運用しなくて済みます(おわり)
Machine Learning
- 「関連するアイテム」とかの計算をイイ感じにやってくれて、結果を 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.
Datastore 上でカウンターを単純に実装すると、遅いし Write オペレーションで高くついてしまいます。 この問題には既に確立された回答があり、PullQueue に一旦情報を貯めておき、Cron Job で Datastore に実際の書き込みを行ないます。このような実装とすることで、速く、安いカウンターを実装する事が出来ます。
この実装をライブラリとして提供したものが Counter になります。
使い勝手としてはこんな感じです。
Counter.increment()
で指定したキーに対するカウントを +1 することが出来ます。
Counter.increment("/photos/2343");
使う側はこれで以上です。TaskQueue
の addAsync()
を使っているので処理をブロックしません。コストも安いので経済的です。
最後に、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 たのしい
Android で雑に GCM するやつ
前書いたので
RegistrationIdSender
(registrationId: String をサーバに送るメソッドだけ) を実装して渡すことでよしなにやる Helper です。
Amazon の「注文を表示」のように、Gmail のメールにアクションを表示する
表題通りの事を調査しました。せっかくなので貼っておきます。