Technologies for UI
ma.la
  1. 自己紹介
  2. 今日のテーマ
  3. livedoor Readerの紹介
  4. livedoor Readerの仕組み(Ajaxについて)
  5. デスクトップアプリとウェブアプリ
  6. 従来のウェブアプリとAjaxの対比
  7. クライアントサイドのチューニング
  8. サーバーサイドのチューニング
  9. まとめ
  10. 今後の方向性とか
  11. 終わり

自己紹介


2006年2月1日ライブドア入社
メディア事業部 開発部所属 プログラマ
livedoor Readerなんかを作ってます

個人的活動

最速インターフェース研究会
主任研究員

自己紹介

終わり

今日のテーマ

ユーザーインターフェース、 ユーザー体験のための 技術的アプローチ

ユーザーインターフェースのための技術

技術者の視点からの発想 livedoor Readerでの工夫など

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のバックエンド

専門外なので省略

今回は

ユーザーインターフェースのお話

livedoor Readerの仕組み(Ajaxについて)

livedoor ReaderはAjax採用

さいきん よくきくけど Ajaxってなに

Ajaxってなに?

Asynchronous JavaScript and XML の略らしい

livedoor Readerの仕組み

サーバーサイドはJSONに変換してデータを渡す あとの動作はクライアントサイドにゆだねる クライアントサイドはXMLHttpRequestでデータを受信

livedoor Readerの仕組み

まるごとPerlって本に詳しく書いてあります
省略

livedoor Reeaderの簡単な紹介

終わり

デスクトップアプリとウェブアプリ

ウェブアプリが デスクトップアプリを 上回る可能性について

ウェブアプリのメリットはいろいろ

インストールしなくていい 勝手にバージョンアップ ネットさえあればどこでも使える ハイパーリンクで連携できる 良いものは爆発的に広まる

ウェブアプリの欠点

ブラウザ上で大量のデータを取り扱えない 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(省略されました)

わかりやすくいうと


ウェブアプリとデスクトップアプリ

終わり

従来のウェブアプリと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の話

終わり

クライアントサイドのチューニング

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で。 間違ったことをしなければ極端に遅くなることはない。

レンダリングを妨げないための工夫

先頭の一件だけ先に描画して 次以降はゆっくり描画する 記事データ自体はこの段階で全件ロード済み=> 通信の待ち時間ではない

ブラウザの負担を下げる

読む順番で逐次レンダリング 一度に大量の記事をフォーマットするとブラクラになる=> 「できるけどやらない」

クライアントサイドチューニング

終わり

サーバーサイドのチューニング

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秒以上かかるレスポンスを無くす

サーバーサイドチューニングの話

終わり

まとめ

サーバーの速さに頼った設計をしない

サーバーの高速化は部分最適化にしかならない ユーザーの回線状況に左右される

JavaScriptのチューニングは重要

ちゃんと意識して作れば遅いCPUでもそこそこ動く 速いマシンではより快適に使える ベンチマーク速度よりユーザーの体感速度

両面からのアプローチが重要

サーバーサイドで どうにもならないことが クライアントサイドの工夫で あっさり解決することもある。

両面からのアプローチが重要

クライアントサイドでごまかすより サーバーサイドをチューニングした方が 楽なこともある

UIエンジニアのお仕事

ユーザーを待たせない ためにはなんでもやる

今後の方向性とか

ノンブロッキング

クライアントサイドは非同期処理をしているが サーバーサイドはまだまだ 一カ所でも遅いとレスポンスに影響する

いい加減で速い処理

ロードに時間がかかるぐらいなら 未読件数はいらないんじゃないか?

非同期処理のためのアルゴリズムの再設計

UI中心のアプリケーションはレスポンスが命 必要なデータが集まったら即座に返す

UIに適したアルゴリズムとは?

全体が高速に完了するよりも=> ゆっくりでもキャンセル可能な方が良い=> 中断、再開が可能なアルゴリズム=> 処理時間の予想が可能なアルゴリズムが必要

例えば

10万件のデータをソート?=> ユーザーが実際に必要としているのは先頭の10件=> 最初の10件を最速で返せるアルゴリズム

UI中心に考えるとコードの書き方が変わる

従来の速さと これからの速さ

終わり

ご清聴ありがとうございました