実践・ウェブアプリ高速化テクニック

os0x(Shogo Ohta), 2011-08-21

slide show

アンケートや情報共有にご協力ください!

Profile

WEB+DB PRESS Vol.64

Theme

ウェブアプリケーション高速化

ウェブアプリの高速化とはなにか

小手先のテクニックに過ぎない!

大切なのは、User Experience!

ユーザーが快適にウェブサービスを利用できること、それがすべて

ユーザーが快適になるための条件

ユーザーが快適になるための条件

迅速なレスポンス

ユーザーのアクションに対するレスポンスは即座に!

高速化を考える時、細かなテクニックに惑わされてはいけない

実際の速さ以上に、
ユーザーが速いと感じることが大事

もちろん、実際に速くなっているか検証することは必要

ただ、数msレベルの速さを語るとき、ユーザーのことを忘れているかも?

以上を踏まえて…

ウェブアプリの高速化テクニック

ウェブアプリの高速化テクニック

通信の高速化:従来のテクニック

概ね、バッドノウハウの集まり(gzip自体はバッドノウハウじゃないけど、IE6で問題があったりしてバッドノウハウがつきまとう)

通信の高速化:HTML5関連のテクニック

ApplicationCache

<!DOCTYPE HTML>
<html manifest="/cache.manifest">

htmlタグでマニフェストファイルを指定し、

# ver.1
# #はコメントアウト
CACHE MANIFEST
index.html
stylesheet.css
images/image.png
scripts/javascript.js

NETWORK:
search.php
login.php
/api

FALLBACK:
images/dynamic.php static_image.png

マニフェストファイルでキャッシュの定義を決める

ApplicationCache

静的なページでは効果絶大

動的なページでは扱い難い


マニフェストファイルを指定したページ自体もキャッシュの対象だったり、静的ファイルの更新に手間がかかるようになる…

localStorage

シンプルなキーバリュー型のデータストアで非常に扱いやすい

ちょっとしたコードで動的なキャッシュが可能

localStorageでAjaxをキャッシュ

function ajax(url, callback) {
  var xhr = new XMLHttpRequest();
  var responseTimer = setTimeout(function() {
    //タイムアウト時の処理
    xhr.abort();
    if (!!localStorage[url]) {
      // localStorageから取り出す
      callback(localStorage[url]);
    }
  }, ajax.MaxWaitTime || 5000);

  xhr.onload = function() {
    //リクエスト成功時
    clearTimeout(responseTimer);
    //localStorageに保存
    localStorage[url] = xhr.responseText;
    callback(xhr.responseText);
  };

  xhr.onerror = function() {
    //リクエスト失敗時
    clearTimeout(responseTimer);
    if (!!localStorage[url]) {
      // localStorageから取り出す
      callback(localStorage[url]);
    }
  };

  xhr.open("GET", url); 
  xhr.send();
};

urlをキーに、Ajaxの結果をキャッシュ

他にもCDNを用意して、Cookieレスの配信用サーバーに静的ファイルを配置する

HTML、CSS、JavaScriptの圧縮と最適化などなど

詳しくは、

プログラムの高速化

プログラムの高速化

ループを展開するとかなんとか?

数msの差ではユーザーは気が付かない

地道な高速化はコードの可読性、保守性を下げてしまい、画期的な高速化を妨げる要因に成り得る

明らかなボトルネックではない限り、うかつに手を出すべきではない

実際の速さよりも、ユーザーが速いと感じることが大事

実例1

例えば、Gmailのスター

  1. アイコンをクリックしたらAjaxでリクエストを投げ、結果を受け取ったらアイコンを更新する
  2. アイコンをクリックしたらアイコンを更新し、Ajaxでリクエストを投げる
  3. アイコンをクリックしたらローディングアイコンを表示しつつ、Ajaxでリクエストを投げ、結果を受け取ったらアイコンを更新する

3が良さそう?

  1. アイコンをクリックしたらAjaxでリクエストを投げ、結果を受け取ったらアイコンを更新する
  2. アイコンをクリックしたらアイコンを更新し、Ajaxでリクエストを投げる
  3. アイコンをクリックしたらローディングアイコンを表示しつつ、Ajaxでリクエストを投げ、結果を受け取ったらアイコンを更新する

Gmailのスター

押した瞬間に星が付く

リクエストの成功を待たずに画面に反映してしまう

ローディングも出さない

ローディングも出さない

結果はわかっているので、わざわざローディングの表示を出さなくてもよい

中途半端なローディング表示はユーザー体験を損ねる

リクエスト自体が失敗する可能性はあるので、その例外処理だけ考える

シンプル イズ ベスト

シンプルさを下げる要因かもしれない

CSSスプライトを簡単に作れるツール、ApplicationCacheのマニフェストを自動で生成するツールなど、システム的に解決はできないことはない

ただ、そのコストに対して十分な見返りが得られるのか考えなくてはいけない

Good is the Enemy of Great

「いいね」は「すごい!」の敵

Greatな例

FacebookのBigPipe

Facebook

世界最大のSNS

SNSということは、ユーザーごとに異なるデータを表示しなければいけない

ほぼすべてのページが動的でキャッシュが効かない(Twitterなども同様)

BigPipe

入れ物となる空のdiv要素を先に表示しておき

その中身をJavaScriptから差し込む

用意できたコンテンツからFlushして書きこむことができるので、遅いコンテンツがあってもそれ以外の部分は先に表示できる

TTI: time to interact

「読み込み開始からレンダリングが完了までの時間」ではなく、「読み込み開始から、主要なコンテンツが表示され、利用できるようになるまでの時間」

通常のウェブアプリの作り方とは大きく異なるので、動いているサービスに導入するのは困難な手法

でも、Facebookは実現した

ただ、BigPipeはシンプルに反するので、Facebook以外には適用しにくい

実践! JavaScript 非同期読み込み

JavaScriptの利用シーンは増え、様々なライブラリを利用することが増えている

ライブラリが増えると読み込み時間がボトルネック(JavaScriptの読み込み中は他の処理をブロックするので)になってくる

全部読み込むと多すぎるし、必要な部分だけ分割読み込み(require.js、yepnope.jsなど)すると次のように直列読み込みになってしまうことが…

(require.js利用時)

同期読み込みは明らかなボトルネックに成り得る

ファイルを結合して非同期化する

さらにlocalStorageでキャッシュすれば完全に通信をなくすこともできる

マスタリング非同期読み込み BPStudy#41も参考にどうぞ

まとめ

SIMPLE is BEST

Good is the Enemy of Great

アンケートや情報共有にご協力ください!