操作方法
- l キー: 次のページへ
- h キー: 前のページへ
- j キー: 目次ページへ
- k キー: 目次ページから戻る
- 目次ページでは j k キーで一覧を移動し、クリックで選択します
FiciaとUIにまつわるエトセトラ (Takana.PM)
Yappo: yappo あっと shibuya.pl
Shibuya Perl Mongers
自己紹介
- 大沢和宏
- Perl Hacker
- CPAN Author
- Farmer
このスライドは
YAPCのJSTAPdの発表の
お蔵入りになった
オマケです
最近は
- OSDC.tw 2010
- Yokohama.PM #6
- YAPC::Asia 2010
- Fukuoka.PMで発表なう
- BPStudy #39で発表予定
- Shibuya.PM #15で発表予定
現在Web+DB PRESSにて
WebUIの連載をやっています
そのほか会社で
Ficiaというフォトストレージ
サービスをやっています
今回のテーマ
LDRやPhotoshop Express等のように、デスクトップアプリケーションをWeb上で提供するサービスが増えていますが、それらのアプリケーションはどう作ったら良いかという話をします。
今回のテーマ
具体的にはFiciaをどんな感じで作っているのかと言うのを話します。
目次
- 目指す方向と問題点
- データ量の増大にどう対応するか
- どうするイベント通知
目指す方向
- 全てのデータはWebへ
- 操作感はキビキビ早く
- ユーザを待たせない
解決すべき問題を見つける
- どのようなデータを取り扱うWebアプリを作るのか明確に
- そのデータをWebにアップロードする方法は、何が一番楽かを考える
回線やサーバ処理速度につい
ては基本すぎなので触れない
Ficiaでは...
- 撮影した写真を全部保存するストレージサービス
- PCにデジカメのカードが繋がった時に、カードの中の写真を全部アップロード
- 一度あげたデータは二度とアップロードしない
専用クライアントを
PCにインストールして
意識せずデータを
Webにあげられる
Webサービスだからといって全部ブラウザの中だけで完結せずに、必要ならば専用クライアントを作るくらいの柔軟さも重要
これが達成できれば何でもいい
- 全てのデータはWebへ
- 操作感はキビキビ早く
- ユーザを待たせない
ここまでは、全てのデータを
Webに上げる良い方法の話
ふつうのWeb
- MVCは全てserver side
- 画面遷移のたびにhtmlをサーバから取ってくる
例えば写真サイト
- ユーザが見たいのは画像のみ
- 普通は次の写真をどんどん見たい
- 極端に言うと画像だけ変われば良い
- 次の写真を見る時にHTMLまで作るのは無駄
例えばこうする
- 次の写真を見るリンクを踏んだら、Ajax等を使って次の写真の画像URLだけを受け取る
- 画像URLを受け取ったら、その時見ていた写真のIMGタグのsrc要素を新しい画像URLに差し替える
サーバ側にModelとController
を置いて、ブラウザ側で
Viewを実装するイメージ
予め次の写真を先読みする
事で、さらに早く表示できる
Pros :)
- サーバ側でHTMLの動的生成をしないので負荷が減る
- データ転送量も大幅に減る
- ブラウザのレンダリング変更する場所も限定的
- 結果的に高速に次の写真が表示される
Cons ;(
- permalinkが無くなる(location.hashを動的に書き換えて対応できる、Twitterがなんかやってるけどブラウザで良い感じに出来るようになるぽい)
- サーチエンジンなどのbotがリンク辿れなくなる
- そもそもJavaScriptが動く環境じゃないと使えない
目次
- 目指す方向と問題点
- データ量の増大にどう対応するか
- どうするイベント通知
サムネイル一覧
一般的な写真サイトでは、上がっている写真を一覧で見る機能がついています。
Ficiaの物を見てみましょう。
画面遷移の無い一覧表示
Ficiaでも、Ajaxを駆使して画面遷移無く写真の閲覧が出来るようになっていますが、1万件も写真があると普通に実装してしまうと困った事になる
ハマりどころ
- 1万件全部が増を表示したらブラウザ重い
- 1万件分のデータをAjaxで取ったら遅い
必要な所だけを表示すべき
- 多量に表示すべきデータがあっても、画面の大きさは有限
- 画面に見えない部分を一生懸命レンダリングしてもリソースの無駄
Ficiaでは...
- どのサムネイルが画面で表示されるかを常に計算して、表示される部分だけのDOMを作っている
- スクロールした瞬間に不要なDOMを消している
- とても縦長な空白のDOMを伸び縮みさせる事で違和感無くしている
Ajaxリクエストにページングを
- 必要な所だけ描画しても、1万件分のJSONを一気にダウンロードしたら遅くなる
- 表示に必要な部分だけの小さなJSONをダウンロードさせるようにすると速い
さらに。。。
- サムネイルリストの場合だと、画面の真ん中に位置する部分から描画してあげたほうが体感速度が上がる
Pros :)
- 必要な部分のサムネイルだけダウンロードされるのでネットワークに優しい
- 描画するDOMの量が圧倒的に少ないのでクライアントにも優しい
Cons ;(
- ページングしたJSONを読み込むタイミングで回線が遅くなったら、ちょっと詰まる
- 激しいスクロールをさせると描画が追いつかない事がある
キャッシュを忘れずに
- JSONや画像データ等は、適切なキャッシュをかけないとスクロールされるたびにダウンロードする事になるので注意が必要
目次
- 目指す方向と問題点
- データ量の増大にどう対応するか
- どうするイベント通知
ここまでのまとめ
ここまでの流れで基本的に画面遷移が発生しないようなアプリケーションの話ですね
画面遷移ないと
- 「あなたの写真にコメントされました」見たいなお知らせをmixiのお知らせみたく出せない
画面遷移ないと
- 「あなたの写真にコメントされました」見たいなお知らせをmixiのお知らせみたく出せない
- なぜならHTMLをサーバからダウンロード出来るのが一回だけ
画面遷移ないと
- 「あなたの写真にコメントされました」見たいなお知らせをmixiのお知らせみたく出せない
- なぜならHTMLをサーバからダウンロード出来るのが一回だけ
- JavaScriptで動的に取ってくるしかない
なぜ通知するのか
- 自分の上げた写真にコメントが付くと嬉しい
- ユーザが嬉しいと思う事は、サービサーとしても早く教えてあげたい
- サービサーとしてはユーザに喜んで欲しいから通知する。それもいち早く
実現手段
- 定期的にserverを叩く
- long pollする
定期的にserverを叩く
- setTimeout等を使って定期的にイベントが読めるAPIを叩いて、新規イベントがあるか無いかをチェックする
- plackup interval.psgi
定期的にserverを叩く Pros/Cons
- 良い所: 実装が簡単、proxyが間に挟まっていても普通は問題起きない
- 悪い所: apiへのアクセスが増える、イベントが発生してからユーザに通知するまでのタイムラグがある
long pollする
- Ajaxリクエストを受け取ったら、サーバ側は新規イベントが発生するまで何も返さずに接続を繋げたままにして、新規イベントが発生したら即座に結果を返す
- plackup longpoll.psgi
long pollする Pros/Cons
- 良い所: イベントが発生したと同時にユーザに通知できる
- 悪い所: 実装が複雑、途中にProxyが挟んでるとうまくいかない時がある
イベントが来たら
- 目立つ形でアピールする
- keireki.jpだと右下がビガビガする
- Ficiaだと右上がビガビガする
目次
- 目指す方向と問題点
- データ量の増大にどう対応するか
- どうするイベント通知
- Perlとか
Ficiaで使ってる主なCPAN
- 昔に開発したので HTTP::Engine(Mouse)
- Data::Model (Driver::DBI, Driver::Queue:Q4M, Driver::Memcached(for kumofs))
- Imager, Imager::Filter::ExifOrientation, Image::JpegCheck, Image::Size
- JSTAPd
例えば一般的なストレージサービスでは
Client - ApplicationServer - StorageServer
といった構造で、ユーザのデータはストレージ用のサーバに保存する
写真とか動画を取り扱う時にSlurp的に一気にロードすると、データの量だけアプリケーションのメモリ消費量が増える
しかも何も考えないでコード書くと、もっと消費メモリ食う
open my $fh, '<', $filename;
my $data = do { local $/; <$fh> };
$foo->bar($data);
...
package Foo;
sub bar {
my($self, $data) = @; # ここでメモリコピー
$self->baz($data);
}
sub baz {
my($self, $data) = @; # ここでメモリコピー
}
デカイデータを扱う時は、チビチビI/Oの読み書きをすると良い
client から App
HTTP::Engine::Request だと
$req->builder_options->{disable_raw_body} = 1
とかとかして、 request body の全読み込みを抑止する
Plack でのやり方はどうするんだっけか
App から Storage
例えば HTTP::Request::StreamingUpload を使う
open my $fh, '<', '/your/upload/requestbody' or die $!;
my $req = HTTP::Request::StreamingUpload->new(
PUT => 'http://example.com/foo.cgi',
fh => $fh,
headers => HTTP::Headers->new(
'Content-Length' => -s $fh,
),
);
my $res = LWP::UserAgent->new->request($req);
Furl でどうやるか知らない
Storage から App
LWP::UserAgent だけで出来るよ
my $ua = LWP::UserAgent->new;
$ua->get($url, ':content_file' => $save_filename);
Furl でどうやるか知らない
App から Client
Plack::Response の body に
io handle 渡せばできるよ
open my $fh, '<', $save_filename;
$res->body( $fh );
return $res;
レスポンスはすぐ返す
- サムネイル作成、動画変換処理は重い
- アップロード時に処理してからレスポンス返すと、ユーザが苛つく
- clientからファイルを受け取ったら、Q4MやJunkやQudoなどのqueueに重い処理のqueueを吐いてすぐレスポンスはこう
- queueでエラーがおきても、なにかしらで通知
FiciaだとFacebookとかへのアップロード処理もqueueでやってて、失敗したら右上で失敗を通知してる
まとめ 1/2
- どんなサービスを提供するかのポリシーを定める
- データが多くなりがちになるときは、必要なデータだけを処理するようにする
- ユーザの操作の邪魔にならないように作る
JPerl Advent Calendar
2010 やります
参加者募集中
- Casual Track
- Hacker Track
- English Track
- Acme Track
- 他にやりたければ随時
関連情報は
#jpac2010@twitter
に流しときました