Technologies for UI
ma.la
* 1. 自己紹介
2006年2月1日ライブドア入社
メディア事業部 開発部所属 プログラマ
livedoor Readerなんかを作ってます
** 個人的活動
最速インターフェース研究会
主任研究員
** 自己紹介
- 終わり
* 2. 今日のテーマ
- ユーザーインターフェース、
- ユーザー体験のための
- 技術的アプローチ
** ユーザーインターフェースのための技術
- 技術者の視点からの発想
- livedoor Readerでの工夫など
* 3. livedoor Readerの紹介
- 2006年4月リリース(beta版)
- 登録ベースでそろそろ10万ユーザー
- 3日以内ログイン 7000-9000人
** リリース後の機能
- サーバー分散したり
- livedoorクリップ連携
- 登録フィードの公開機能
** livedoor の採用しているテクノロジ
- FreeBSD
- Apache2 / Apache1.3
- MySQL
- mod_perl
- Sledge
** livedoor の採用しているテクノロジ
- 去年やったので省略
** livedoor Readerのバックエンド
- いつもの感じ
- Apache2.2 + mod_proxy_balancer
- Apache1.3 + mod_perl + Sledge
- MySQL 4.0
- クローラーにXango
** livedoor Readerのバックエンド
- 専門外なので省略
** 今回は
- ユーザーインターフェースのお話
* 4. livedoor Readerの仕組み(Ajaxについて)
** livedoor ReaderはAjax採用
- さいきん
- よくきくけど
- Ajaxってなに
** Ajaxってなに?
- Asynchronous
- JavaScript and XML
- の略らしい
** livedoor Readerの仕組み
- サーバーサイドはJSONに変換してデータを渡す
- あとの動作はクライアントサイドにゆだねる
- クライアントサイドはXMLHttpRequestでデータを受信
** livedoor Readerの仕組み
- まるごとPerlって本に詳しく書いてあります
- 省略
** livedoor Reeaderの簡単な紹介
- 終わり
* 5. デスクトップアプリとウェブアプリ
- ウェブアプリが
- デスクトップアプリを
- 上回る可能性について
** ウェブアプリのメリットはいろいろ
- インストールしなくていい
- 勝手にバージョンアップ
- ネットさえあればどこでも使える
- ハイパーリンクで連携できる
- 良いものは爆発的に広まる
** ウェブアプリの欠点
- ブラウザ上で大量のデータを取り扱えない
- OSネイティブのGUI上のアプリケーションと比べると
- 描画パフォーマンスが圧倒的に悪い
** ウェブアプリの最大のメリット
- 必要なデータだけ受信すればいい。
** メールソフト
- どれぐらいのメールを扱えるか?
- 10万件?100万件?
** メールソフト
- Becky? ThunderBird?
- 10万件のメールが入ったフォルダを開いたらフリーズした
-- よくある
** ウェブアプリなら
-- 先頭の10件だけを受け取ればいい。
-- 検索結果だけを受け取ればいい。
** RSSリーダー
- ウェブアプリのメリットが大きい
-- ブラウザでそのまま記事を開ける
-- 自前でクロールしなくていい
-- 更新情報だけ受け取ればいい
** ウェブアプリが勝ちうるためには #1
- 大量のデータをサーバーサイドで高速に処理する
-- 必要なデータだけを逐次、受信、描画する
-- デスクトップアプリでは代替の効かない領域
** ウェブアプリが勝ちうるためには #2
- ネットワーク(ソーシャル)連携で勝負する
-- 集合知、統計情報を利用したフィルタリング
** ウェブアプリが勝ちうるためには #3
- 正しいUIで勝負する
-- デスクトップアプリのUIは間違いだらけ
** 正しいUIってなに?
- 正しさ = 速さ
** 正しいUIってなに?
- ユーザーがタスクを実行する際に
-- 素早く
-- 迷わず
-- 正確に
- 実行できるかどうか
** 正しいUIってなに?
- 結局のところ
- 速さを見れば正しさが分かる
** 常識的な設計がアプリケーションを殺す
- 平均的な企業ユーザーが受け取るメールは1日91通
- http://enterprise.watch.impress.co.jp/cda/topic/2006/06/20/8084.html
- 購読しているフィードの件数: 50%のユーザーが5個以下
- http://japan.internet.com/research/20060929/1.html
** 常識的な設計が最大のタブー
- 平均値をあてにしてはいけない
** 平均値に基づいた設計をしない
- 5件しか読まない前提で設計されたRSSリーダーは
- ユーザーインターフェースが洗練されない
** 使いづらいから使われない
- RSS Bar時代
-- 30 フィード
- Bloglines時代
-- 700フィード
** 使いづらいから使われない
- livedoor Reader
-- 2580フィード(2006年12月現在)
** livedoor Readerの特徴
- ユーザーあたりの購読数が
- べらぼうに多い
** 統計の不一致: ブロガー調査
- アクセス解析を見ると圧倒的なシェア
** 統計の不一致: アンケート調査
- ユーザーに調査するとそんなでもない
-- http://japan.internet.co(省略されました)
** わかりやすくいうと
** ウェブアプリとデスクトップアプリ
- 終わり
* 6. 従来のウェブアプリとAjaxの対比
- 従来のウェブアプリと
- なにが違うか
- Ajaxを使うメリットとは何か
** 米Yahooの調査
- 最近の記事
- http://yuiblog.com/blog/2006/11/28/performance-research-part-1/
** レンダリング時間
- HTMLの転送にかかる時間より
- その他のファイルの受信と
- レンダリングにかかる時間の方が
- はるかに長い
** 80:20の法則
- その他の処理:HTMLの受信 =「80:20」
** 「UIの速さ」は「サーバー速度」ではない
- ページの遷移には雑多な仕事が付きまとう!
- キャッシュ保存、ヒストリの保存
- ローディング中の表示、タイトルバーの書き換え
- 表示回数の記録、セキュリティポリシーの適用
- メモリの開放、JavaScriptの初期化、RSSオートディスカバリ
** Ajaxを使う
- 画面遷移を無くす
- 単純計算で8割の時間を削れる
-- ユーザーの待ち時間が純粋にデータの転送速度だけで済む
** 実例をdankogaiを交えて紹介します
* livedoor Blog
404 Blog Not Found
* はてなダイアリー
404 Hatena::Diary not Found(旧称:はてな弾アリー)
* HTML単体のダウンロード速度
- LWP::Simpleでダウンロード
-- blog.livedoor.jp/dankogai/ : 0.112308秒
-- d.hatena.ne.jp/dankogai/ : 0.301575秒
livedoor Blogの方が3倍速い
* ページの描画が完了するまでの時間
- Firefox2.0 + Fasterfoxで計測
-- blog.livedoor.jp/dankogai/ : 11.67秒
-- d.hatena.ne.jp/dankogai/ : 2.37秒
hatenaの方が5倍速い
* HTMLの受信とその他の速度の割合
- livedoor Blog => 1:99
- はてなダイアリー => 1:8
* HTMLの受信とその他の速度の割合
- 2:8ってレベルじゃない
* アニメーションで解説
* 中身違うのでロード時間が違うのは当然です
- もちろんこのベンチマークはフェアじゃないけれど
-- ユーザーが感じるのは紛れもなく「レンダリング速度」
-- 誰もApacheの応答速度なんか気にしちゃいない
* サーバーの高速化は部分最適化
- サーバーの高速化は重要
-- サーバーだけ速くても「その他の部分」はどうにもならない
-- ユーザーの待ち時間を「減らす」ことにしかならない。
* 待ち時間を「無くす」にはどうすればいいか
- livedoor Readerの場合
-- ユーザーの待ち時間を「無くす」
-- ユーザーのアイドル時間を利用する
-- 見かけ上のレスポンスを向上させる
* 今までのウェブアプリケーション
- ユーザーの待ち時間 =
- サーバーの応答速度 + 回線状況 + 全体のレンダリング速度
* livedoor Readerの場合
- ユーザーの待ち時間 =
- サーバー応答速度 or メモリ速度 + 部分のレンダリング速度
** Ajaxとdankogaiの話
- 終わり
* 7. クライアントサイドのチューニング
livedoor Readerにおける
クライアントサイドの
チューニング
** サーバーの応答速度に頼った設計をしない
- サーバー速度を上げても部分最適化にしかならない
- サーバーが速くてもユーザーの回線が速いとは限らない
** クライアントサイドでキャッシュ
- 一度受信したフィードのデータはキャッシュしておく
- サーバーの負荷を最小限に抑えられる
- 手前のフィードに戻る時にキャッシュを使う
** キャッシュを使って先読みする
- キャッシュクラスを作ったら
- 次に読む記事をあらかじめキャッシュに入れておく
-- 簡単にできます
** ブラウザのキャッシュよりも自前のキャッシュ
- XMLHttpRequestはGETメソッドであればキャッシュしてくれる。
- どのタイミングで消えるか分からない。
- XHRオブジェクト生成のコストがかかる。
** JavaScriptの処理中、ブラウザは停止する
- JavaScriptはシングルスレッド
- タイマーを使った擬似スレッド処理が可能
-- 複雑な機能を分割し、callbackを受け取るように作る
** DOM vs innerHTML
- JSONからHTMLへのレンダリングをどうするか。
- 大雑把に言うと二通り
-- 文字列でHTMLを組み立ててinnerHTMLに代入
-- DOM操作でHTMLを組み立ててappendChild
* 速度に関する多くの誤解
- 結論
-- DOMプロパティの読み書きや
-- DOMメソッドの呼び出しが遅い
-- ケースバイケースで適切な方法を選んで使う。
** innerHTMLを使う
- innerHTMLは元々IEの勝手な拡張
-- 一部の人に嫌われる理由
- 使われまくってるので今更なくなるということはない
** 良くある間違い
- element.innerHTML += "hoge"
-- 追記にはならない。
- element.innerHTML = element.innerHTML + "hoge" と同等。
-- まったく最適化されない。
** JavaScript内で処理する
- innerHTMLに頻繁にアクセスしない。
- 複雑なHTMLを組み立てる場合は「JavaScript」の変数内で作る。
- 最後に一気にまとめてinnerHTMLに代入する。
** ベストプラクティス
- 見た目の変更: styleで。
- 大幅な見た目の変更:classNameで。
- 追加と挿入: appendChild/insertBeforeで。
- 大幅な書き換え:innerHTMLで。
- 間違ったことをしなければ極端に遅くなることはない。
* レンダリングを妨げないための工夫
- 先頭の一件だけ先に描画して
- 次以降はゆっくり描画する
- 記事データ自体はこの段階で全件ロード済み
-- 通信の待ち時間ではない
* ブラウザの負担を下げる
- 読む順番で逐次レンダリング
- 一度に大量の記事をフォーマットするとブラクラになる
-- 「できるけどやらない」
** クライアントサイドチューニング
- 終わり
* 8. サーバーサイドのチューニング
livedoor Readerにおける
サーバーサイドの
チューニング
* 平均値はあてにならない
- レスポンスタイムを見てみる。
- 平均すれば0.2秒
- 最短で0.1秒
- 最長で240秒
- これでは使い物にならない。
* マイフィードの読み込みが遅い
- 記事は先読みしているので気にならない
- 利用可能になるまでの時間が長いのが気になる
- 時間が掛かりすぎるとブラウザがタイムアウトする
-- まったくつかえない状態になる
** クライアントサイドでの対応(2006年9月)
- フィードリストを分割ロード
-- 初回の100件はとりあえず速く返す
-- 次回以降は200件ずつロード
- 利用開始になるまでの時間が短くて済む
- 効果はあった
-- いずれにせよ重い
** サーバーサイドの対応(2006年11月)
- 丁度この時期に突発的にチューニングがしたくなった
** よくよく調べてみたら
- 購読リストを取得するのは速い(SQL発行1回)
-- 未読件数を計算(未読フィードの件数)
-- フィードの情報を取得(購読フィードの件数)
- 1000フィード読んでいたら最大で2001回
- 購読数増えるほど重くなる
- これはよくない
** チューニング
- memcachedを使うように。
** memcached って
- http://www.danga.com/memcached/
- ググれ
** 未読件数計算の最適化 #1
- フィードごとの未読件数はキャッシュしない
-- ユーザー * フィード数だけ必要になる
-- 読むたび更新されるので効率が悪い
** 未読件数計算の最適化 #2
- フィードごとにstored_on(記事の保存時刻)を200件分保存して
- ユーザーがフィードを読んだ時刻と照らし合わせて未読件数を計算するようにした
** どのデータをキャッシュすべきか
- 一度キャッシュした値が全体で共有できるかどうか
- 全ユーザーで共通化できる部分を見つけてキャッシュする
** チューニング結果 #1
- 未読件数算出: SELECT COUNT(*) 200回
-- memcached 1回
- フィード情報: retrieve 200回
-- memcached 1回
** チューニング結果 #1
- キャッシュに入っていない分は追加で取得してキャッシュする
- キャッシュが全てヒットすればSQL発行は1回で済む
** チューニング結果 #2
- レスポンス速度
-- キャッシュがヒットすれば1/10以下。
-- 平均して約3分の1に
*** memcachedの高速化 #1
- CPANモジュールCache::Memcachedを使用
- get_multiメソッドを使うと複数のキーを一度に取得できる
-- 100件の場合で一件ずつ取得するより30%ほど速くなる
*** memcachedの高速化 #2
- 保持期限を長めにしてヒット率を上げる
- 代わりにクローラの更新タイミングで確実にキャッシュを消す
** memcachedのtips
- deleteの際に上書きを禁止する秒数を指定できる
-- 入れ違いで古い内容が書き込まれるのを防ぐ
- レプリケーションの遅延などで古い値が参照されることがある
** 待たせないための工夫 #1
- キャッシュが全く作られていない場合、今までよりも遅い
- キャッシュのヒット率に合わせて処理方法を動的に切り替える
** 待たせないための工夫 #1
- mod_perlのpnotesを使う
-- 一回のリクエスト内のキャッシュのヒット率を計算
-- 全体の処理時間を計測して記録
** 待たせないための工夫 #2
- livedoor Reader Notifier
- タスクトレイに常駐する更新通知アプリケーション
- 10分間隔で未読件数を計算
** バックエンドでキャッシュを作る
- 新着通知が来る段階でキャッシュが作られている
-- 機械的なアクセスでキャッシュを作らせる
-- 人間相手の応答は最速のレスポンスを返す
** UIのための技術
- ボットは待たせてもいいけど
- 人間は待たせちゃダメ
** フィードリスト読み込み高速化
- 以上
** 記事の読み込み速度の高速化
- SQLの発行回数を減らしたことで全体的な高速化
- 今度は記事の読み込み速度が気になってきた
- マウスで操作していると先読みが効かない
- 突発的にチューニング
** やったこと
- MySQLのパラメータを見直す
-- key_bufferを増やす
- 過去記事の保存件数を減らす
-- ほとんど参照されていないので
** さらに高速化するために
- 0.1秒と0.5秒の違いはクライアントサイドで吸収できる
- 遅いクエリはとことん遅い
-- データサイズがでかすぎ
-- key_bufferやディスクキャッシュに乗らない
-- 平均速度よりも安定した速度が必要
** 記事読み込みの高速化
- 根本的な解決
-- 一台あたりのデータサイズ減らす。
- とりいそぎ
-- 記事もmemcachedでキャッシュするように
** memcachedの問題
- でかいと保存できない(デフォルト1MB)
- でかいとそんなに速くない(ようだ)
** キャッシュする記事を選別
- ほとんどのアクセスは先頭の記事
- 読者数がある程度多いフィード
- 先頭記事10件、購読者数50人以上
-- 約37%がキャッシュから読み込まれる。
-- ラッシュ時間帯は70%程度がキャッシュにヒット
** キャッシュの効果
- memcachedに当たれば、ほとんど0.1秒以内
- レスポンスの安定
** UIのための技術
- Plaggerの巡回でキャッシュが作られて
- 人間の待ち時間を減らす
** 記事読み込み高速化
- 以上
** パフォーマンスチューニングのための
- ログ取りの技術
** アクセス時間の視覚化
- フロントでbenchlogを取得
-- Apache2の機能
-- 転送にかかった時間をマイクロセカンドで記録
-- ユーザーの回線速度に影響される
** バックエンド
- Apache1.3なのでbenchlogが使えず
-- Sledgeのフックで対応
- リクエストにかかった時間をリアルタイムで監視
- tail -f でログを垂れ流し
** ビジュアライズ
- 0.0531314秒?
- ログ見ててもわかりづらい
- Perlでワンライナーフィルタ
-- 0.5秒以上かかったら「*」が付くように
** ピンポイントにログを取る
- 特定のフェーズにかかった時間
- キャッシュのヒット率
** ログの話
- 以上
** さらなる高速化に向けて
- 記事テーブルが現在12分割
- 小さめのテーブルをたくさん作った方が良さそう
-- 障害範囲を抑えられる
-- メンテナンスにかかる時間を減らせる
- まだmemcachedに空きがあるので、もっと使う
** まだまだ高速化の余地あり
- 目標
- 0.5秒以上かかるレスポンスを無くす
** サーバーサイドチューニングの話
- 終わり
* 9. まとめ
** サーバーの速さに頼った設計をしない
- サーバーの高速化は部分最適化にしかならない
- ユーザーの回線状況に左右される
** JavaScriptのチューニングは重要
- ちゃんと意識して作れば遅いCPUでもそこそこ動く
- 速いマシンではより快適に使える
- ベンチマーク速度よりユーザーの体感速度
** 両面からのアプローチが重要
- サーバーサイドで
- どうにもならないことが
- クライアントサイドの工夫で
- あっさり解決することもある。
** 両面からのアプローチが重要
- クライアントサイドでごまかすより
- サーバーサイドをチューニングした方が
- 楽なこともある
** UIエンジニアのお仕事
- ユーザーを待たせない
- ためにはなんでもやる
* 10. 今後の方向性とか
** ノンブロッキング
- クライアントサイドは非同期処理をしているが
- サーバーサイドはまだまだ
- 一カ所でも遅いとレスポンスに影響する
** いい加減で速い処理
- ロードに時間がかかるぐらいなら
- 未読件数はいらないんじゃないか?
** 非同期処理のためのアルゴリズムの再設計
- UI中心のアプリケーションはレスポンスが命
- 必要なデータが集まったら即座に返す
** UIに適したアルゴリズムとは?
- 全体が高速に完了するよりも
-- ゆっくりでもキャンセル可能な方が良い
-- 中断、再開が可能なアルゴリズム
-- 処理時間の予想が可能なアルゴリズムが必要
** 例えば
- 10万件のデータをソート?
-- ユーザーが実際に必要としているのは先頭の10件
-- 最初の10件を最速で返せるアルゴリズム
** UI中心に考えるとコードの書き方が変わる
- 従来の速さと
- これからの速さ
* 11. 終わり
- ご清聴ありがとうございました