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. 終わり - ご清聴ありがとうございました