INSANELY FAST

Qiitaを読んでる人なら https://dev.to をほとんどの人が見たはず。見てない人は見てきてください、速すぎて驚くはず。またmizchiさんがdev.toに書いた なぜ dev.to がこんなにも速く、こんなにも自分にとって感動的なのか - dev.to を見た人も多いと思う。個人的にHeroku, Railsを採用してここまで爆速なサイトを構築出来ていることは今までの常識を覆す衝撃な出来事だった。こんな新しい発見をもたらしてくれたdev.toには本当に感謝してる。自分もこんなサイト作ってみたいなと思ってdev.toのことを色々調べてて少し知見がたまったので共有してみます。

はじめに

作者の方もdev.toにどうやってdev.toを高速化したのかを幾つかポストしている。

Here's our stack:

Open Source

  • Ruby on Rails
  • VanillaJS more or less
  • Postgres (hosted on Heroku)

Infra/Services

  • Heroku
  • Fastly
  • Airbrake
  • Skylight
  • Sendgrid
  • Amazon S3
  • Google Analytics
  • Stream
  • Algolia
  • Algorithmia
  • Timber
  • Codeship
  • Codeclimate
  • GitHub

設計方針

  • ページのほとんどを静的HTMLとしてキャッシュ出来る状態で返す
  • コンテンツをCDNにより配信することでレイテンシを少なくする
  • HTTP2で通信する。(Fastly, Cloudinalyがhttp2で返してくるのでサーバーサイドでは考える必要ない。)
  • レンダリングブロッキングなCSS, Javascriptを排除してレンダリングまでの時間を短縮する
  • ファーストビューの構築に不要なJavascript, CSS, 画像を全て非同期で取得し実行、描画する
  • 広告やウィジェットなど外部のCSSやJSをリクエストするコンテンツを入れない

キーワード

  • HTTP2
  • CDN
  • ServiceWorker
  • PWA

HTML

originサーバーはGETのページのほとんど全てを静的にキャッシュ出来る状態で配信する

通常WebアプリケーションはRequestに対してユーザーのログイン状態やコンテンツの状態によって動的にページを返すので、document自体をキャッシュさせることはあまりやらないのがいままでの定石だった。しかしdev.toはFastly(CDN)での配信に最適化するためにページのコンテンツを動的な部分と静的(あまり更新されない)な部分に分けて、静的な部分はoriginサーバーで描画後FastlyのCDNにキャッシュする戦略を取っている。動的コンテンツはブラウザで描画後にAjaxで取得する。

描画後にAjaxで取得している部分

  • ログインユーザーに紐づく表示
    • アイコン
    • タグのfollowの有無
    • などなど
  • 通知件数
  • ファーストビューに乗らない後続の記事一覧
  • 記事ごとのいいね数

Fastly

dev.toではHTML, JS, CSSの配信に利用する。たぶんHerokuのaddon。Fastlyの最寄りのCDNからレスポンスを受け取るので、コンテンツのレイテンシーがかなり抑えられている。Herokuを日本で使う場合に一番懸念になるのがアメリカのサーバーにoriginを置くのでレイテンシーが大きい事が問題で、それをCDNを使うことで克服している。記事の更新時やコメントの追加時にinstant purgeさせる仕様っぽい。HTMLの配信にはautomatic gzippingを利用してgzip形式で配信する。

POSTやsearchなどはやはりoriginに問い合わせする必要がありそれらのリクエストはそれなりに時間がかかる。基本的にはほとんどのGETリクエストがFastlyとのやり取りになるのでoriginサーバーにリクエストが来る頻度は普通のweb appに比べてかなり低いはず。

InstantClick

ページ遷移が爆速なのはdev.toに訪れた人なら最初に気づくはず。これはInstantClickにで実現している。Railsを使ったことある人ならTurbolinksを触ったことあるかもで、アイディアとしてはほとんど同じ。リンクの遷移を簡単にSPA風にするjsのライブラリ。ユーザーがリンクにマウスカーソルを合わせるとAjaxでリンク遷移先を先読みする。クリックした時には既に手元にHTMLがあるのでDOMの切り替えのみで遷移が完了する。人がリンクにマウスカーソルを合わせてクリックするまでに平均300~400msかかるので、サーバーはその間にレスポンスを返せば良い。How I Made this Website Hella Fast Without Overcomplicating Things - dev.to である通り、少し改良してるぽい。挙動を見ていると、landing時にトップの5記事ぐらいは先読みしているように見える。あとInstantClickでHTLMLを取得する際には<head>タグを<title>だけにし、<body>ではメインのコンテンツのみでnavやsidebarはHTMLに含めないようにしてる。もともとはpodcastを再生出来てそのタグをDOMから消したくないという目的だった見たいだけど、配信のコンテンツを減らすことでHTMLのサイズが1/3くらいになってる。InstantClick時は?i=iをqueryに含めてrequestしてるので試してみると面白い。この辺りの部分的な入れ替えはTurbolinks3でpartial replacementとして実装される予定だったけどその難しさから?頓挫した辺りなので興味深い。強力だけど扱いが難しくなるのでリスクが伴う、ここはアーキテクチャを一貫して責任持てるからこそ出来る技な気がする。

先読みで無駄にリクエスト飛ばしてるの気になるところなんだけどそれぞれ30KBくらいで多くのユーザーは10リクエストするかどうか、300KBくらいなら気にならないか。それよりもUX優先。

CSS

共通のスタイルはHTMLのの中に<script>を直接埋め込みしている

外部のCSSファイルへのリクエストはレンダリングブロッキングなので、CSSの読み込みが終わるまでブラウザはレンダリングを開始することが出来ない。dev.toでは共通部分のCSSをHTMLの中に直接埋め込むことで外部リクエストを発生させいないようにしている。そのためブラウザはHTMLのレスポンスが返るとレンダリングブロッキングな外部ファイルアクセスが無いため即座にレンダリングに入る。そうすることでDOMContentLoadedまでの時間を短縮し画面が真っ白な状態を限りなく短くすることでユーザーは瞬時にページが表示される体験を得ることが出来る。

追加のCSS読み込みを非同期で行う

ファーストビューの表示に必要のないその他のCSSはonloadイベント以降に<link>タグを<head>タグにappendすることでレンダリング後に取得するように記述されている。この辺りは今後<link ref="preload">が来るとさらに実装はシンプルになりそうだけど、今のところFirefoxは未実装だったりするので標準的に使えるのはまだ先になりそう。

Javascript

  • 全てのjsをasyncでダウンロードする
  • JSのライブラリを極力使わずpure jsで記述してる(読むとすごい勉強になる)
  • ファーストビュー以外のコンテンツの非同期取得・描画
  • service workerを利用する
    • ここらへんあまり理解できていない。以下のような認識
    • ブラウザのJSのランタイムとは別のランタイムで動くのでレンダリングや処理の中断を発生させない
    • Cache APIを使うことで柔軟なCache機構を使う事ができる
  • その他いろいろ

Image

Cloudinaly

画像の配信はCloudinalyを仕様している。これもたぶんHerokuのaddon。Cloudinalyではアップロードした画像のリサイズ、http2での配信、ブラウザごとに最適な拡張子(webp/jpeg)の選択などを行ってくれる。

Service Workerによる非同期通信

Service Workerで非同期通信を行うことでメインのランタイムで取得するよりも効率的に受信を行うことが出来る(らしい)。SafariではService Workerの実装はまだ行われていない(in developmentらしい)のでChromeやFirefoxだと恩恵を受けることが出来る。Safariでも十分速いのでここはボトルネックとしてはあとの方か。

やらないこと

向いてるサイト

作者の人も書いてるけど、リードヘビーなサイト。動的に変化するコンテンツが少なめのサイトほどHTMLのキャッシングが有効になる。ブログ、メディア、ECサイトとか。SNSは向いてない。動的に変化するコンテンツが大量にある場合はSPA、静的な部分が多い場合はこういった高速化と棲み分け出来ていくと良さそう。dev.toでも作者の方が投稿でpreactかVue.js使うかもみたいな投稿があるし、ある程度進むとpure jsだけでリッチなサイト作るのが難しいはずなので操作性も取るなら軽めなフレームワークを取り入れると良さそう。

まとめ

最初にも書いたけどHerokuでRailsでここまで爆速なサイトを作れることに感銘を受けた。そして調べて見ると真似できそうなアイディアも多いのがまた良い。一部やり過ぎなところもあるけどInsanely Fastはそれぐらいやらないとという感じか。CDNサービスの進化とNative Javascriptの進化によってWebも順当に進化しているんだなと改めて感じた。

今回の件でフロントエンドのパフォーマンスチューニングについてかなり勉強になった。Service WorkerのユースケースやPAW, PRPLデザインパターンなんかは今回の件で知ったので調べて行きたい。

またInstantClickにみんなが驚いている箇所はTurbolinksも同じようなことを目指していて、その差は大雑把に言うとonclickonhoverかの違いしかなくて、その差でTurbolinksは効果が薄くデメリットが目立ってしまい嫌われた背景を見ると興味深い。。と思ってたところTurbolinksにもInstantClick化したらどうかというissueが上がっていたのでめっちゃ期待してる。

見逃してる点、間違ってる点、補足などあればお願いします。

あとパフォーマンスチューニングの仕事めっちゃやりたいので案件お待ちしております :)

1473685167
I'm Ruby on Rails engineer.