HTML5

2014年12月24日 (水)

祝 Salesforceハッカソン入賞

ども、22日は有給もらって4連休でした。(^^v
久しぶりに休日まったくPCを開かずに遊び倒していたので、気づいてませんでしたが、その間にSalesforce1のAdvent Calendarにこんな記事が!!!

プログラミングコンテストで良い結果を出すために重要な(プログラム以外の)3つのこと

はい、私このハッカソンでHeroku部門の3位いただきました。
そのエントリー投稿はこちら

http://challengepost.com/software/salesforce-vs-heroku


で、先の記事にあるプログラミングコンテストで大事な3つのことですが、このアプリなんと全部外してます!!!(--

ていうかこの記事僕のために書かれたような気がするなぁ。。。(--
なんだか申し訳ない。。。


以下、懺悔です。


★ 1. 主催者側のニーズを読む

あいすいません。m(_ _)m

ビジネスアプリと言うお題があることは当日に知りましたが、それをガン無視してゲームを作りました。

何を作るかは前日の夜に考えたんですが、その時点では何でも良いと思ってたんですよね。。。

当日ビジネスアプリ縛りがあることを知りましたが、別のネタを考えるのも面倒だったのでそれをそのまま出しちゃいました。


★ 2. 簡潔かつ明確に、かつ視覚に訴える説明をする

今回のハッキングチャレンジでも12の入賞チームのうち、実に11チームが紹介ビデオを作成しています。(こちらで推奨したというのもありますが)

あいすいません。m(_ _)m

ビデオを作らなかった唯一の入賞者が僕です。。。

ていうか、ビデオを作ろうとか1ミリも考えませんでしたよ。。。(--
フタを開けてみたらほとんどの参加者がビデオも登録してあってかなりビックリしました。

こうしたハッカソンイベントではビデオが必須になっていることもあるので、以前には作ったこともあるんですが、正直プログラム書くよりも100倍面倒で仕上がりも残念な感じにしかならないので、あんまり気が進まないんですよね。。。

本当に入賞はなかやま絵のおかげだと思います。それが無ければ箸にも棒にもかからなかったんじゃないかと。。。(^^;;;

ちょっと修行します。


★ 3. ビルド手順を明記し、デモ環境を用意する


あいすいません。m(_ _)m

わりとギリギリまで作ってたのでビルド手順書くの忘れました。。。(--

しかもローカルではSBTだけあればRedisはなくても動くように作っていたつもりが、提出時点のコードはバグっていてローカルでもRedisが必須になっていたという。。。(--

審査では対象の全アプリを実際にビルドしたそうですが、よくこれのビルドを通せたものです。

一応現在はビルド方法を追記し、Redis無しでも動くように修正しました。


いや本当に、このアプリを推してくれた審査員の方々の努力と忍耐に心よりお礼申し上げます。


★ その他感想など


参加作品を見ると実際にサービス化することを目的としたアプリが思った以上に多いことにも驚きました。

僕の場合、完全にこのハッカソンのためだけに作ったジョークアプリなのでなんというかもう全然本気度が違う感じ。(^^;;;

ドメインもなんか取ろうかと考えはしましたが、キャラクタをSalesforceとHerokuにすることに方針転換した後に「salesforce-vs.herokuapp.com」というアプリ名が取れたところで、もうこれでいいやと思っちゃっいました。(というわけでこのアプリのランニング費用は1円もかかってません。本当にすみません。。。m(_ _)m)

ただし、アプリ自体は使い捨てるつもりでもその中で使っている個々の要素技術は実際に使い物になるかどうかの評価をしつつ、再利用する前提で作っています

今回の場合テーマは以下のものがありました。

  • WebSocketのさらなる可能性の追求
  • CodeMirror(HTML5のテキストエディタコンポーネント)の評価
  • ブラウザ上での複数人の同時コーディングの可能性評価(コードレビュー的な)
  • ブラウザ上で書いたものがその場で動く簡易インタープリタの実験
  • 実践的なCSS3アニメーションの実験


結論としてCodeMirrorは思った以上に使いやすかったですし、changeイベントを逐一相手に送りつけて、相手側のブラウザ上で自分がコードを書いている様を再現するという試みも成功したのでそれなりに満足です。(^^v

これらが将来的にどういう形で活かされるのか、あるいは闇に消えるのかはまだまったくの未定ですが。。。(^^;


しかし、このようなちょっと作ってはみたいけれど、いまいちモチベーションが無いみたいな場合にハッカソンを目標設定のために使うというのは割とアリな手段だと思います。(賞金もらえるかもしれませんし)


今回のアプリに何かしら機能を足していくということはもうあまり考えていませんが、同じようなプログラミング要素を使ったゲームみたいなものはこの先も何か作るかもしれません。(イメージ的にはIncredible MachineやElectric Boxのもっとプログラミング要素の強いモノという感じ。Incredible Machineはもはや入手できないと思うけど、どこかで再販して欲しい。。。。)

□□□□
以上、祝ってタイトルに付けたわりには謝ってばかりの謎エントリでした。

推定最後です。良いお年を。(^^)/

2014年11月 7日 (金)

AngularJSのValidationで、maxlength, minlengthのサロゲートペア対応

ども。

最近は某案件に突っ込まれているのでわりとAngularJSを使っています。

Angular、便利は便利なんですがちょっと癖がありますね~。

しかし、これの思想を理解しておくことは(特に設計等を考える立場の人にとっては)大変有益だと思います。

一度はがっつり使ってみておくのも良いんじゃないでしょうか。(^^;

(↑ これは個人的にはあんまり積極的には使わないという意味でもあるんですが。。。)

 

 

それはさておき、Validationです。

Angularではinput要素にng-requiredとかng-maxlengthとかをつけておくとFormのモデルに自動的にエラー情報が付加されます。

非常に便利です。(^^v

。。。が、このng-maxlength。割と後半になってからサロゲートペア未対応であることが判明!

えーーー。。。。(--;;;;

 

 

まぁ、言うてもJavaScriptはもともとサロゲートペアはまともに扱えないので予測はできてもおかしくなかったんですが、こういうのは必要に迫られるまでは忘れてますよね。。。(--

もう2010年代も半ばなんだから、フレームワーク側でサロゲートペアもよしなに扱ってほしいとは思うんですが、既にいっぱい使っちゃってるng-maxlengthをどう対処していくべきか。。。

 

 

★ 対処法

あんまりブログに時間かけてる場合でもないんで、さっくり答えを書いちゃうと自前でdirectiveを定義することでほとんど改修コストをかけずに修正することができました。

 

https://gist.github.com/shunjikonishi/2dbe5e8eab1ebd08e434

 

ここではsp-maxlength、sp-minlengthという2つのdirectiveを定義していますが、エラー情報の設定名はそれぞれ「maxlength」、「minlength」としています。(ng-maxlength, ng-minlengthが使用している名前と同じです。)

このため、input要素での宣言だけを「sp-maxlength」に置き換えてやれば、エラー表示の制御等で使用しているであろう

 

form.inputName.$error.maxlength

 

の部分は変更不要です。

ちゅーことで、単純なGREPと置換で対処することができました。

ちなみに同時に行った対処としてPlayframeworkのValidationでmaxlength, minlengthのサロゲートペア対応というのもあるんだけど、こっちはソースをコピってきて一部書き変えただけだから割愛。

めでたしめでたし(なのか?)

2014年7月15日 (火)

Herokuで帳票(PDF)

ふと思い立ってPDF出力のAddonの調査していたら予想外に大変な便利Addonにぶちあたりました(^^;
多分これは今年一番のお役立ちエントリです。一度でもPDF出力に悩まされたことのある人なら読まないと後悔するレベル(かも)


★HerokuのPDF Addon

Addonを探すとDocRaptorHyPDFという二つのAddonが見つかります。
どちらもHTMLを渡せばそれをPDFに変換してくれるというサービスのようです。

多分、DocRaptorの方が有名なので、最初こちらから試そうと思ったんですが。。。
料金プランを見てビックリ

  • Starter(Free) - 月に5件まで
  • Basic($19) - 月に125件まで
  • Professional($37) - 月に325件まで
  • Premium($95) - 月に1250件まで
  • Max($189) - 月に5000件まで


無料プランがあるのは良いんだけど、月に5件ってなんだよ。。。(--
そんなのテストしてたら一瞬で消費するっちゅーの。

一番上のプランでも月に5000件、一日換算だと166件しか出せないって。。。
なんでこんな料金プランにしてるんだろ。。。

一方のHyPDFの料金プランは以下

  • Nano(Free) - 日に5件まで
  • Milli($15) - 日に15件まで
  • Kilo($30) - 日に45件まで
  • Mega($60) - 日に135件まで
  • Giga($120) - 無制限


安っ!
制限のリセットも1日単位なので、ちょっと試すだけでもこちらの方が扱いやすそうです。
何より$120で使い放題というのは太っ腹。(^^;

ていうか、あんまり商売っ気がないのかも。
ブラウザで「www.hypdf.com」にアクセスすると、いきなりHeroku AddonsのページにリダイレクトされるのでどうもHyPDF単体ではサービス提供しておらず、Heroku Addonとしてしか提供されてないみたいなんですよね。
そういうのもアリなのか。。。(^^;

そんなこんなでまずはHyPDFから試してみることにしました。

(後から気がつきましたがDocRaptorにもHyPDFにもテストモードがあって、その場合は制限回数は更新されません。と言ってもDocRaptorの方はもはや試していませんが。。。)


★HyPDFの使い方

https://devcenter.heroku.com/articles/hypdf#api-reference

基本的な使い方としてはJSONで出力対象のHTMLと各種オプションを渡すだけです。
そうするとレスポンスのHTTPボディでPDFのバイナリが返ってきます。
HTMLの渡し方には文字列でHTMLを丸ごと渡す方法と、URLで外部サイトへの参照を渡す方法の両方がサポートされています。

オプションとしては

  • ヘッダ、フッタ
  • 上下左右の余白
  • グレイスケール
  • コールバックURL(指定した場合PDF生成が非同期となり生成完了時にこのURLがキックされる)
  • 生成したPDFをS3にアップロードするためのオプション情報


などが指定できます。
何回か試したところ、PDF生成には最大10秒くらいかかることがあったのでHerokuから使う場合は非同期にした方が良いかも。

使い方は難しくないですけど唯一の注意点はHyPDFの使っている証明書がStartCom(StartSSL)の証明書だという点です。
知らなかったんですがこの証明書、デフォルトではOracle JDKのキーストアには入ってないんですね。なので、接続するためには自分で証明書をインストールするかHostNameの検証をスキップするかする必要があります。
(この辺を調べてる過程でさらに憂鬱な衝撃の事実を知ったんだけど、それはまた別に書きます。)

一応JavaのAPIラッパーも作ったのでサクッと試したい場合は使ってみてください。

https://github.com/shunjikonishi/hypdf4j

ちなみにHeroku上ではOpenJDKだからなのか、キーストアをHerokuが独自で用意しているからかわかりませんが証明書のインストールは不要です。

ちなみにちなみにPDF生成以外にも

  • PDFのプロパティ情報取得(pdfinfo)
  • PDFのテキスト取得(pdftotext)
  • PDFの連結(pdfunite)
  • 複数ページのPDFから指定ページの抜き出し(pdfextract)


といったことが(APIラッパーでも)できますが、多分そんなに使うことはないです。(^^;


★PDFの日本語出力は必ずはまることになっている

そして、とりあえず試しに作ってみたPDFがこんな。

Hypdf1


。。。
あー、うん。。。。この豆腐見慣れてるなぁ。。。(--


思い出したけど、自力でPDF生成を試みた時も日本語フォントで挫折したんだった。。。。
PDFを生成するホストに日本語フォントをインストールすれば出力することはできるんだけど、ライセンス関係がどうなっているのかがイマイチ確信が持てなくて結局棚上げにしたような気が。

これはアカンかもなぁ。。。と思いつつ、一応サポートに日本語出ないんだけどと投げてみました。
で、その間にDocRaptorの方も試してみるかとドキュメントを読んでたらなんと20分後には返事が来ました。

早っ!!


You can use any custom font by including it with @font-face in your styles:

@font-face {
    font-family: MyFont;
    src: url(www.somecdn.com/my_font.ttf);
}

body {
    font-family: MyFont;
}

 

な・る・ほ・ど!!!
ここでWebフォントなのか!!!!

Webフォントって図形文字や凝ったデザインの文字を使う場合に使用するものというイメージでしたが、探してみるとIPAフォントとか標準的なものもあるんですね。

IPAフォントを組み込んで出力してみると日本語も正しく出力されるようになりました。素晴らしい!(^^v

PDFのフォント問題って世界中の開発者が頭を抱えてる問題のような気がしますけど、これはかなり見事な解決方法だと思います。
心の底から感心しました。


★excel2canvasと組み合わせてみる

ところで僕はexcel2canvasというHTMLのCanvasを使ってExcelをブラウザ上で表示するというライブラリを作ってたりするんですが、それとHyPDFを組み合わせた場合どうなるかということが気になってちょっと試してみました。

Canvasへの描画はちょっと厳しいんじゃないかと事前には思ってたんですが、試してみると結果はこんな感じ

Hypdf2




凄っ!!!
思った以上にイケてるやんけ!

何パターンか試してみたところ、HyPDFはHTMLの横幅に合わせてスケールを調整するらしく、幅がExcelの標準レイアウトで1ページに収まらないようなものはレイアウトが崩れますが、印刷前提でレイアウトが調整されているようなExcelファイルはだいたい良い感じに出力されました。(^^v

今時のExcelはファイルをHTML形式で保存できたりもしますが、Webフォントへの変更とかそれなりに手間だったりもするので、これはこれでアリなソリューションだと思います。

勢い余ってデモサイトも作ってみたのでご興味ある方はお試しあれ。

http://hypdf-excel.herokuapp.com/

ていうか、これこのままHerokuアドオン化しても需要ありそうじゃね?(^^;

2014年4月24日 (木)

WebSocket通信のメリットを考える

何故だか昨日のエントリが意外なほど読まれている。(^^;;;


Twitterでの言及も過去最大級かも。特に、


サーバーとの通信はAjax併用でも良いんですが、多分全部WebSocketでやる方がシンプルだし速度面やセキュリティ面でも優位があります。

 

この文章に引っ掛かった人が多いようで、これについてもうちょっと掘り下げてみます。


★ Ajaxの代替としてのWebSocket

そもそもの話として、素のWebSocketではリクエスト(この場合クライアント→サーバのメッセージの意)とレスポンス(サーバ→クライアント)を対応付けることができません。これを実現するためには自力でサーバ/クライアントの双方にメッセージをハンドリングするための仕組みを実装しならず、それは結構めんどうな作業です。

今回、その部分をフレームワーク化しようとしているので、それが実現できた場合のメリットを考えてみます。
ざっと思いつくのは以下です。

  • KeepAliveかつ、httpヘッダのパースも不要になるので速い
  • 特に断続的なリクエストが大量にある場合大きな優位となる(実験結果)
  • 冗長構成でサーバが複数台ある場合も単一クライアントからのリクエストは常に同一サーバに送信される
  • 単一接続からのリクエストは常に同一クライアントからのリクエストであることが保証される


速度メリットは実はおまけみたいなもので、重要なのは最後の2つです。これ、言い換えればWebSocketをAjaxの代替として使うことでステートフルな通信が可能になるということなんです。

既存のWebサーバーではリクエストルーティング機構のインスタンスはサーバー単位で1つなので、リクエスト処理の流れは

  1. リクエスト受信
  2. Cookieを使用してユーザーを同定
  3. 必要ならMemcache等からステート情報を取得
  4. リクエストを処理


のようになりますが、WebSocketの場合接続毎にリクエストルーティングのインスタンスを作ることができるので、2、3の手順がまるまる不要になります。

リクエスト処理のパフォーマンスを出すためになんらかのキャッシュを行う場合もオンメモリだけで間に合いますし、Cookieやhiddenパラメータを使って情報を引き回すことも不要になります。

# ただし、意図しない切断はありえるので長時間ステートを保持することや常にステートが維持されていることを前提としたプログラミングをすることはお勧めしません。この辺のベストプラクティスはまだ確立されていないので当面は自分のバランス感覚だけが頼りになります。(^^;


★ WebSocketとセキュリティ

前述の通り、WebSocketでは単一接続からのリクエストは同一クライアントからのものであることが保証されます。

近年のJavaScriptプログラミングでは処理全体をdocument#readyのクロージャで括るのが一般的ですが、その場合仮にXSSの脆弱性があったとしても外部から接続済みのWebSocketインスタンスにアクセスすることは不可能です。

つまり、最初の接続時にクライアントの確認をしっかりと行っておけば以降のリクエストは攻撃ではないと見なすことができます。なのでCSRFのチェック等は不要です。

問題は接続時の確認を、どのように行うかですがこれはこの資料が参考になります。

http://www.slideshare.net/muneakinishimura/webhtml5-31749532

ほとんどの場合はくログイン認証自体は従来のhttp(s)接続で行ってその後はCookie併用でクライアントを同定することになると思います。
http接続とws接続が同じオリジンであればこれで十分なはずです。


次節で紹介するWebSocketにはSame Origin Policyがないことを利用したスケールアウトを行う場合はもう少し考慮が必要になります。


★ WebSocketとスケール

とりあえずルームモデルでも単純な水平スケールが可能であることは確認済みです。


ただ、この方法には効率の悪い部分があります。

というのは、サーバ100台に対して100人のユーザが1つのルームに入室した場合、最悪100人全員が異なるサーバに接続する可能性があるからです。
ユーザがばらけるとバックエンドのRedisの負荷が上がるので同じルームのユーザは1台とは言わないまでも極力小数のサーバに集中してほしいのです。

HerokuやELBなどフロントにロードバランサがいる場合、こうしたリクエストを適切にふりわけることはできません。(パス毎にリクエストを振り分けるロードバランサがあれば、可能ですがあまり筋の良い解決方法とは思えません。)

が、実はもっと簡単な解決方法があります
WebSocketにはSame Origin Policyがないので単純にサーバを分けてしまえば良いのです。

ページをリクエストするホストが「http://www.quizar.info/...」だったとしても、その中でws接続するホストは「ws://room1.quizar.info/...」であっても良いわけですね。

この場合、接続時のクライアント認証をどうするかが課題になりますが、Cookieのドメインを明示するとかバックエンドキャッシュを使うとかいくつか方法はあると思います。(必要に迫られたらちゃんと考えます。(^^;)

このアーキテクチャを採用する場合、同一のコードベースから簡単に複数のアプリケーションを作成できるHerokuは最強のWebSocketプラットフォームだと思いますが、実際にやって良いかどうかはまた別の話です。(少なくとも課金を減らす目的でやるのはアウトだと思います。実際に必要になったら問い合わせますが、パッケージ買いでその範囲で行う分には問題ないだろうと思います。)


★ 課題

最大の課題はやはり切断時の対処です。HerokuもELBも無通信状態が長く続くと切断されますし、スマホの場合スリープやブラウザから別アプリに切り替えることも当たり前の操作なのでそれも考慮しなければなりません。また、Herokuを使う場合はデイリー再起動への考慮も必要になります。

この部分もフレームワークで吸収できればと思っていますが、もうちょっと試行錯誤が必要なのでそれはまたおいおい。(^^;

□□□□
以上、現時点でのWebSocketに対する考察でした。
WebSocket。既存のhttp通信の延長で考えている人が多いと思いますが、うまく使えば劇的なパラダイムシフトが発生するかもしれません。

どこに辿りつくのかわかりませんが、もうちょっとこの道を進んでみたいと思います。(^^;;;

2014年4月22日 (火)

WebSocketアプリのモデルを考える

ども。  
Salesforce Hackチャレンジに参加していたQuizarは安定の選外でした。。。 
ま、Salesforce1まったく関係なかったから無理もない。(^^;  

さて、それはさておきQuizarの製作を通してWebSocketを使えば今まで世の中に存在しなかったようなアプリを作れる可能性があるということを実感したわけですが、現実にはほとんどWebSocketアプリは開発されていません。

こうした状況の最大の原因は世の中にWebSocketアプリ開発のためのモデルやフレームワークがほとんど存在しないためだと思っています。

現状では低レベルのAPIだけはあるものの、それしかないからその上のモノは全部自分で作らないといけないわけです。
Quizarの開発過程でやったことだけでも、メッセージのルーティング、リクエスト/レスポンスの対応付けなど通常のWeb開発ではやらないようなプログラミングをかなり行っています。

状況としてはhttp/1.0の頃にServlet APIだけで開発していた極初期のWebアプリ開発に近いかな?
あるいは、ひと固まりのメッセージがあるだけでそこに何の付帯情報もないということを考えると生ソケットで直接メッセージを読み書きしている状況に近いという気も。(^^;

いずれにせよこの状態から一本のアプリを作り上げるのは結構な労力なわけで、逆に言えば、になんらかのモデルとフレームワークがあればWebSocketアプリ開発のハードルは一気に下がる気がします。

いくつかのアプリ開発を通してなんとなくその形が見えてきはじめているので、今回はそれをざっくりまとめてみます。


★ モデル

多分WebSocketアプリのモデルはほとんどすべてルームモデルに収まります。

「ルームモデル」という言葉は小西の造語ですが、とりあえず以下のように定義します

  • アプリ空間に複数のルームがあり、
  • ルームには複数の人が入室でき、
  • ルーム内にいる人同士がリアルタイムにコラボレーションできるアプリケーション

シンプルな定義ですが、それだけにほとんどのWebSocketアプリはこのモデルにあてはまるはずです。

Quizarは典型的なルームモデルアプリケーションですが、ルームの中にいるのはクイズの出題者と回答者という役割の違う2種類の人達です。
このようにルームに入室した各人の役割は複数あり得るので

  • ルーム内にはロールの異なる複数の人が存在して良い


というのも定義に加えて良いかもしれません。


★ フレームワークに必要な機能

次にルームモデルアプリケーションを作成するために必要な機能を考えてみます。
サーバー側にルームを管理する機能が必要なのはもちろんですが、最低限メッセージのフォーマットを決めておかないとリクエストのハンドリングもままなりません。
また、クライアント側も必然的にSPA(Single Page Application)となるのでWebSocket以外にも多くの機能が欲しくなります。

ここではとりあえずメッセージ、サーバー、クライアントの3つにわけて必要と思われる機能を列挙してみます。(細かく説明し始めるとあまりにも長くなりそうなので。。。)

ちなみにこのリストもQuizarの開発経験からの逆引きです。

● メッセージ

  • JSON形式のエンベロープとする
  • リクエスト毎にIDを持つ
  • リクエストのコマンドが識別できる
  • 任意複数のパラメータが渡せる
  • レスポンスのエラーが識別できる
  • JSON、HTML、Textなど複数の形式のレスポンスを返すことができる


● サーバー

  • 複数のWebSocket接続を束ねるルームを持つ
  • ルームの永続化(RDB等へのルーム情報の保存)機能は範囲外
  • スケールアウトするための外部のPub/Sub機能を利用できる
  • ルーム内ユーザーへのメッセージブロードキャスト
  • ブロードキャストメッセージのフィルタリング
  • メッセージのルーティング
  • メッセージのロギング


外部のPub/Subは今のところRedis以外に知りませんが、差し替えられるように抽象化はした方が良さそうです。

● クライアント
クライアント側ではルームモデルに限らずSPA全般で必要となる機能も含みますが、それぞれの機能は独立して使えるモノとします。

  • WebSocketをAjaxライクに使用できる(リクエストに対応するレスポンスを取得できる)
  • メッセージのエラーハンドリング
  • リクエストにひもづかないメッセージのルーティング
  • サーバーサイドからの切断時の再接続
  • アイドル状態の識別とイベントハンドリング
  • プラグイン可能なデバッグログ
  • ポーリング
  • WebStorageでのテンプレート管理
  • PushState対応


サーバーとの通信はAjax併用でも良いんですが、多分全部WebSocketでやる方がシンプルだし速度面やセキュリティ面でも優位があります。(これはそのうち別に書きます。)

逆に面倒な部分としては切断時の対応とデバッグ(ブラウザのWebコンソールでメッセージを見ることができない)があるんですが、これらをうまくフレームワーク側で吸収できればメリットだけを享受することもできるはずです。

□□□□
とりあえず、こんなところでしょうか。
なんか忘れてる気もしますが、まぁそれはおいおい(^^;

実際にはQuizar開発当初からフレームワーク化を意識していたので、上に書いた内容は概ね実装イメージがあります。(もちろんブラッシュアップや再考が必要な部分も多々ありますが。。。)

サーバーサイドはWebSocketとRedisのPub/Subが使えれば何でも良いんですが、とりあえずPlayで。(他でも作るとしたら多分次はNode.js)

というわけで、6月末を目途にフレームワークとなんか適当なルームモデルアプリを作ります。(^^v



2014年4月 4日 (金)

HerokuのWebSocketでC10Kに挑戦(前篇)

前回SalesforceハッカソンにWebSocketのクイズアプリを出してきたよ~という話をしたわけだが今のところ身内以外からはほとんどアクセスされてないっぽい。
まぁほとんど宣伝してないからそれは別に構わないんだけど、審査されている形跡もないのは大丈夫なのか。。。(^^;


さて、それはさておきWebSocketアプリを作ったら是非試してみたいと思っていたことのひとつにC10K問題の検証と言うのがあります。
C10Kとはクライアント1万台問題の略で平たく言うと「WebSocketってクライアントとずっとソケット繋ぎっぱにするわけだよね。そんなのクライアントの数がちょっと増えたらあっという間に破綻するんじゃね?」という問題のことです。


★検証シナリオ

今回作ったアプリにはルーム毎にチャットの機能があるのでそれを利用することにします。
具体的な目標数値としてはとりあえず以下のように設定しました。

  • ルームを100個用意する
  • 各部屋に3秒に1人ずつ、100人(100クライアント)入室する(最大時同時接続1万クライアント)
  • 各クライアントは5秒に1回チャットメッセージを投げる(合計100万メッセージ)


とりあえず1万クライアント。全員を同じルームにいれちゃうとチャットのブロードキャストがえらいことになってしまうので、ルームだけは分けることにします。

3秒とか5秒とか段階的に負荷を増やしているのはWebSocket接続はだいたい1接続が1人の人間に対応するので、人間の操作としてそこまでの連続リクエストはこないだろうと思われるからです。(先にDevSumiで行ったデモは特殊な例です。(^^;)

この数字をもう少し細かく見ていくと以下のような負荷になります。

  • 最大時秒間2000メッセージ(1万人が5秒に1回メッセージ送信するから)
  • その時のブロードキャストメッセージが20万件(1メッセージはルーム内の100人にブロードキャストされるから)
  • 1クライアントが1秒間に受信するメッセージは20件(ルーム内の100人が5秒に1回メッセージ送信するから)


。。。やりすぎ?。。。

しかし、普段負荷テストをする時はJMeterでぶんぶんリクエスト投げてるわけだから、3秒に1人追加とか5秒に1回のメッセージとかもの凄く手加減してる気分なんだが。。。。(--

それに1クライアントあたり秒間20件のダウンロードがあると言っても、チャットのメッセージなんか1000個あってもそこらのHTMLページのサイズよりも小さいだろうから、通信のコスト自体はどう考えてもhttpアプリのJMeterテストよりも低いはず。
そもそもWebSocketアプリの負荷テストのセオリーがわからないし、テストツールも自作するんだからとりあえずやっとけやっとけ。

Herokuはこの程度の負荷ではおかしくならない!(多分)


★とりあえず1dynoでテスト

いきなり高負荷のテストをしようにもどの位のDynoを用意すれば良いのかまったく見当がつかないのでまずは1dynoの臨界点を見極めることにします。

手始めに

- 1ルームに100人入室、100メッセージ送信

というテストをしてみたところ、何の問題もなく正常終了しました。
まぁNon-Blocking I/Oを使っているんだからこれくらいはやってもらわないと、という感じです。(^^;
ちなみにこの時点でテスト環境で使っているRedisはRedisCloudの無償版です。無料枠でもこの程度のリクエストは捌けるってことですね。

ここから人数とメッセージ数はそのままでルームを増やしていきます。



- 2ルームに100人入室、100メッセージ送信

まだ何のエラーも起こりません。
メモリ使用量も200MB未満なのでかなり優秀です。
Play(というよりもNettyとAkkaのActor)はかなり筋が良いんだろうと感じました。


- 3ルームに100人入室、100メッセージ送信

接続できないクライアントがでてきました。
が。。。サーバー側にエラーログが出ていないところを見るとどうやら限界を迎えたのはクライアント側っぽい。
テストクライアントはjava_websocketを使ったPlayアプリケーションとして作っているんですが、このライブラリが内部的に使っているのは通常のSocketクラスなので、この辺がNon-Blocking I/OとNormal I/Oの差でしょうか。またjava_websocketはSocketの監視のために接続毎にスレッドを作っているのでその辺もメモリ使っちゃって臨界が早い原因になっていると思います。

ここから先はテストクライアントもHeroku上で複数dynoを立ててのテストとなります。テストクライアントをHerokuにあげて実行したらこれもクリアしました。

本当に優秀。。。ていうか予想ではそろそろエラーになるはずなのになんで動いてるの?って感じ。。。(^^;;;


- 4ルームに100人入室、100メッセージ送信

ここにきてようやく2種類のエラーが発生しました。

まずそろそろ来ると思っていたRedisの最大接続数到達エラーがでました。
無料枠のRedisの接続数はたったの10個しかありません。RedisClientはコネクションプールから取るようにしていますが同時接続数が増えると当然並列で使われる件数が増えます。また、それ以上に影響が大きいと思われるのはルームの数だけRedisのSubscriberが必要になる点です。4つのルームがアクティブなら4つのコネクションがそれぞれのルームに占有されます。つまり同時接続数は増えているにも関わらず使えるコネクション数は減っているわけでむしろよくここまで持ったと思います。
本番環境ではRedisGreenを使ってますが、この先のテストでは接続数無制限のRedisCloud(2.5GB)を使うことにします。


またHerokuのRouterがH11を返すようになりました。このエラーの意味はDevCenterのドキュメント、Request queueingのあたりに書いてあります。


HerokuのRouterは1dynoに対して50同時接続しか行わずそれ以降はキューに入ります。そのキューがあふれた場合にこのエラーが発生するわけです。
しかし実際には400近くの接続が1dynoで行われています。これが何でかというとルーター自体が複数台あるからです。結果からの推測では1アプリで少なくとも8台ルーター使ってる計算になります。
なので、H11が発生した場合でもリトライすると別のルーターに繋がってあっさりと接続できたりします。(はっきりと裏を取ったわけではありませんが高負荷時にブラウザからアクセスした際にそれらしき現象を確認しました。)

この動作はWebSocketに限らずhttpアクセスの場合も同じはずです。Httpクライアントからのアクセスがエラーとなった場合に直後にリトライするのって意味あるんだろうか?と思ってましたが、少なくともHerokuでは意味があると言えます。(まぁH11が発生しているなら素直にdynoを増やせっていう話ではありますけど。)


★1dynoの限界

ここまでの結果からH11が発生する400前後が限界となります。
ただしサーバー側のメモリ使用量を見ると300MB前後なので、ルーターさえリクエストを回してくれればもっと多くのリクエストを1dynoで捌けるだろうとは思います。

ちなみに2X dynoの場合でもこのルーターの制限値は変わらないようで、これはもうちょっとなんとかならんのか?あるいはWebSocket有効のルーターでは制限値を引き上げられないか?ということをサポートに問い合わせ中です。

どうやら現在ちょうどルーター周りの改修を行っているらしく、うまくいけば近々対応されるかもしれません。(みたいな返事が来ているが実際のところはどうなのか謎。。。)

この結果を受けて1万クライアントを捌くには25dynoくらいだろうと辺りを付けて実際試してもいるんですが続きはまた今度。
これ書きながら追試したいことやサーバーサイドを直したいところも出てきているので。(Herokuに問い合わせ中のモノもあるし)

ちなみに現在のところ、完全にノーエラーで接続できていると確認できたクライアント数は5600台位です。

傾向としては1度接続が確立したクライアントの動作は安定しておりパフォーマンスもほとんど悪くならないようです。(何故ならソケット繋ぎっぱだから。)

負荷がかかってくると接続時にエラーになることが多いようで、そこをなんとかすれば7,8000まではすぐに行くんじゃないかという気もしてます。(1回のテストで2GB以上のログが出るんで雑な検証しかしてませんが。。。(^^;;;)

後篇がいつになるか(あるいは本当に書かれるのか?)は未定です。(^^;;;

2014年3月 5日 (水)

JavaScriptのWebSocket API

ブラウザのJavaScriptはシングルスレッドで動いている。

そんなことは知っている。

いや、知ってはいてもちょいちょい忘れてていけてないコードを書いちゃうこともあるのだ。

JavaScriptでのWebSocketの使い方は以下のようなコードになる

 

var ws = new WebSocket("ws:...");
ws.onopen = function(e) { ...};
ws.onmessage = function(e) { ...};

 

このコードを見て「onopenをバインドするよりも前に接続が確立しちゃったらどうなるんだろう?」と疑問に思わないだろうか?

でも、実際にはそんなことは起こらない。何故ならWebSocketの接続が行われるのはコンストラクタを実行している関数の処理を抜けた後だから。

このことは以下のようなコードで検証できる。

 

function init() {
    console.log("Start init: " + new Date().getTime());
    var ws = new WebSocket("ws:...");
    ws.onopen = function(e) {
        console.log("WebSocket open: " + new Date().getTime());
    }
    for (var i=0; i<1000000; i++) {
        var dummy = new Date();
    }
    console.log("End init: " + new Date().getTime())
}

 

空ループを回さなければ20ms以下で接続が確立するが、このコードではループが終了するまで接続は行われないので数秒のタイムラグがある。

言い換えれば「End init」のログは常に「WebSocket open」よりも先に出力される

(ただしFirefoxでFireBugを動かしていると途中で「スクリプトが応答していません。」のようなダイアログが表示されることがあり、その場合はそこに割り込んでWebSocketの接続が行われる。)

何が言いたいかと言うと、

 

function init() {
    var ws = new WebSocket("ws:...");
    //なんか重たい処理
    ws.send("hoge");
}

 

のようなコードは途中にどれだけ重たい処理があっても必ず失敗するのでちゃんとonopenを使おうという話でした。

2014年3月 3日 (月)

SessionStorageのスコープ

先週のHTML5カンファレンスで@albatrosaryさんのセッションを聞いて自分がSessionStorageのスコープについて誤解していることに気が付いたのでちゃんと調べました。

SessionStorageのスコープってウィンドウ + ドメインなんですね。。。
半端知識でCookieと同じだろうと思ってたら全然違いました。。。(--

 

★誤解その1、同一ブラウザでもウィンドウ(あるいはタブ)が違うとスコープが異なる

同一ブラウザ上でタブを二つ開いて、両方で同じページを開いた場合それぞれのSessionStorageは別になり値は共有されません。

Cookieやサーバーサイドセッションでは同一ブラウザ、別ウィンドウは区別できないのでこれはSessionStorageだけが持つ大きな特徴と言えます。

 

★誤解その2、パスがちがってもドメインが同じであればスコープも同じ

ウィンドウが同じであれば「http://SERVERNAME/test1」と「http://SERVERNAME/test2」のSessionStorageは値を共有します。

Cookieの場合はPathでスコープを制限できますがSessionStorageにはそのような機能はないので同一ドメインであれば常に同じスコープになります。

ちなみにプロトコルが異なる場合(httpとhttps)は別スコープになります。

 

★疑問その1、別のサイトに移動して戻ってきた場合はどうなるの?

対象のページから全く無関係なページ(例えばyahoo.co.jp)に移動して、また同じページ(あるいは対象ページとドメインが同じな別ページ)に戻ってきても、SessionStorageの値はクリアされず維持されます。

要するにウィンドウを閉じない限りSessionStorageの値も残り続けるということです。

 

★疑問その2、モバイル端末がスリープした場合どうなるの?

ウィンドウを閉じさえしなければスリープしたりブラウザから別のアプリに移動した場合でも値は維持されます。

動作確認したのはAndroid 4.1.2(標準ブラウザ、Chrome)とiPad2です。

ちなみにモバイル以外はIE10 IE11, Chrome, Firefoxで動作確認しましたがすべて同じ動きになりました。

 

□□□□

なんというか思ったより都合の良い動作しているのでSinglePageAppを作る場合には割と使い勝手が良さそうです。(^^;

採用情報

株式会社フレクトでは、事業拡大のため、
Salesforce/Force.comのアプリケーション
開発
HerokuやAWSなどのクラウドプラッ
トフォーム上でのWebアプリケーション開発

エンジニア、マネージャーを募集中です。

未経験でも、これからクラウドをやってみた
い方、是非ご応募下さい。

フレクト採用ページへ

会社紹介

株式会社フレクトは、
認定コンサルタント
認定上級デベロッパー
認定デベロッパー
が在籍している、
セールスフォースパートナーです。
heroku partnersにも登録されています。
herokuパートナー
株式会社フレクトのSalesforce/Force.com
導入支援サービス
弊社の認定プロフェッショナルが支援致します。
・Visualforce/Apexによるアプリ開発
・Salesforceと連携するWebアプリ開発
も承っております。
セールスフォースご検討の際は、
お気軽にお問合せください。
Powered by Six Apart