playframework

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年6月19日 (木)

HerokuでPlay2のコンパイルを速くする

昨日別件(Play起動時にGruntも実行する)で教えてもらったドキュメントです。

http://www.playframework.com/documentation/2.4.x/SBTCookbook

なんと、この文書の下の方に「Disable documentation」として、コンパイル時のScalaDocの生成を止める方法が記載されています。

やり方はbuild.sbtに以下を記述するだけ

 

sources in (Compile, doc) := Seq.empty

publishArtifact in (Compile, packageDoc) := false

 

このドキュメントは未リリースの2.4のものですが2.3でも問題なく動きます。

実測で2分くらい速くなりました。素晴らしい(^^v

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年1月14日 (火)

HerokuでスケーラブルWebSocket

前回の続きです。
前回Play+Redisでスケーラブルに動くWebSocketアプリケーションが完成したので今回はそれを実際にHeroku上で動かしてみます。

といってもCLIで「heroku labs:enable websockets」を叩く以外は特に変わったことをする必要はありません。
この辺は以前にも書いたのでそちらも参照してください。

http://blog.flect.co.jp/labo/2013/12/herokuwebsocket-aad6.html

実際のところはデプロイすれば特に何の問題もなく動きます


★HerokuでWebSocketアプリを動かす場合の留意点

Herokuの特性と照らしてWebSocketアプリを動かす時に気をつけなければならない点がいくつかあります。
ざっと思いつくところは以下です。

  1. 通信が無い状態が30秒続くと接続が切れる
  2. マルチDyno対応
  3. デイリー再起動がある


1番目の30秒ルールへの対応はクライアントまたはサーバから定期的にPing的なメッセージを送信することで回避できます。

2番目のマルチDyno対応は通常のスケールアウトと何ら変わるところがないのでRedisを使うことでクリアです。

やっかいなのは最後、再起動対応です。
HerokuのDynoManagerはWebSocket接続がある場合でも容赦なく再起動をかけてくると思います。(試してませんが。。。)
これはHerokuの内部仕様なのでユーザ側では制御不能です。

以下、これに対してどういう対策が可能かを考えてみます。


★接続遮断時のクライアントの動作

Dynoの再起動をローカルでシュミレートする方法は単純に起動中のPlayframeworkをCTRL+Dで止めるだけです。

この時にクライアント側のJavaScriptがどのような動作になるかをまずは検証します。検証の方法は単純にWebSocketの onopen/onerror/oncloseイベントにconsole.logを仕込むだけです。

検証に使用したブラウザはChrome, Firefox, IE10です。
サーバ強制終了時に発生するイベントとその順序は以下です。

  • Chrome: onclose
  • Firefox: onclose
  • IE10: onerror, onclose


IEのみonerrorが発生していますが、これは多分IEの問題はまたPlayのWebSocket実装の問題です。強制終了に限らずサーバ側からWebSocketを切断(EnumeratorでInput.EOFを送信)した場合、常に発生するので。

ざっと検索した感じ(Playの話ではないですが)この辺りが関連トピックかなと思いますが詳しくは調査していません。

onerrorイベントは発生していますが、例外でスクリプトが停止する等の実害はないのでここでは気にせず話を進めることにします。

重要なのは強制終了による切断であってもクライアント側からは単にサーバからWebSocketを切断されただけに見えるという点です。(PlayはShutdownHookでクリーンアップを行っているのでそれも関係あるかもしれませんが)


★oncloseで再接続してみる

安易ですがoncloseイベントが発生した場合に再接続してみることにします。

ローカルのテストでは手動で再起動を行っている関係で、切断直後はまだサーバが起動していません(※1)。当然接続はエラーになりますが、その場合以下の順序でイベントが発生しました。

  • Chrome: onerror, onclose
  • Firefox: onerror, onclose
  • IE10: onerror, onclose


すべてのブラウザでonerrorとoncloseが発生しています。
onopenとoncloseって必ず対で発生するとは限らないんですね。。。イマイチこれが実装依存なのか正規の仕様なのかがW3Cの仕様見てもわからないんですが、とりあえずoncloseを拾って時間をおいて数回再接続を試みるような実装はできそうです。(※2)


※1 HerokuでマルチDynoで動かしている場合は再接続は別のDynoにつながるのでエラーなしで再接続できます。

※2 console.logは見てませんがiOS、Android(のChrome)でもリトライでの再接続ができているので、やはり接続失敗時にoncloseは発生しています。


★Window#onunloadでのoncloseイベントのクリア

ここまでの内容を実装して動かしてみるとローカル、Herokuともにいい感じに再接続ができています。(Herokuでの再接続のテストはCLIで「heroku ps:restart web.2」のようにDyno単位での再起動を行います。)

あともう一つ注意が必要なのはWebSocket#oncloseイベントはブラウザでページを閉じた場合などにも発生するのでその場合には再接続を行ってはいけないという点です。

ここではWindow#onunloadイベントでoncloseをクリアしていますが、ページ遷移以外のWebSocket切断があるアプリでは他にも制御が必要になるでしょう。

最終的なクライアント側のコードはこうなりました。(タグをつけようか迷いましたがmasterの最新です。)

https://github.com/shunjikonishi/websocketchat-redis/blob/master/app/views/chatRoom.scala.html

★まとめ

Dyno再起動に対応した再接続はがんばればできそうです。
しかし、ここをがんばる位なら素直にHeroku以外のプラットフォームを選んだ方が良いように思います。(^^;;;

しかししかし、EC2を使うとしてその場合にサーバの異常終了による予期しない切断の可能性を考慮しなくて良いかどうかは疑問です。
個人的な感覚としてはWebアプリはエラーが発生したとしてもリロードで回復するなら許せるんですが、かなり高い確率でそれもなんとかせぇと言われそうな気がしますね。。。(--

□□□□


まぁ必要に迫られたら考えますが、ふとこんな言い回しを思いつきました。

パフォーマンスもアベイラビリティも金で買ってください。
パフォーマンスは性能正比例で性能2倍になれば価格も2倍ですが、アベイラビリティは1%向上する毎に価格は10倍です!


昔友人が言っていた言葉の焼き直しですが、あながち間違ってないと思いますね。(^^;

2014年1月10日 (金)

PlayとRedisでスケーラブルWebSocket(実装編)

前回の続きです。

実際にPlayとRedisでWebSocketアプリケーションを作成したコードサンプルを示します。


★題材

今回の調査の途中でドンピシャのサンプルを見つけています。

http://www.ryantanner.org/2013/03/using-play-iteratees-and-enumerators.html

PlayにバンドルされているサンプルのチャットをRedis対応したサンプルアプリです。
接続情報を環境変数から取ってるふりして実はlocalhost固定だったとか、チャットルームのメンバリストをローカル管理してるから厳密にはスケーラブルになってないとか、手を入れ始めるといろいろ気にはなったんですが(^^;、WebSocket/Redisのコードサンプルとしては非常に有益で必要十分な内容でした。
記してここに感謝します。


今回の作業のゴールはこれを改良して汎用的に使えるWebSocket/Redisのベースクラスを作成することです。

https://github.com/shunjikonishi/websocketchat-redis

ただし現時点ではそれを切り出して単体のjar/pluginにすることは考えていません。現実にはWebSocket/Redisを使って何かを作る予定は今のところないしPlay、Redis共に開発サイクルが早いので、実際に必要になった時にはAPIが変わっている可能性も高いからです。

ここでは実際に必要になった時のプロトタイプとなり、その考え方や注意点を示せれば十分と思っています。


そんなわけでRedisService.scalaとChatRoom.scalaはかなり丁寧に書きましたが、それ以外(JavaScriptとか)は割と適当です。(^^;

JavaScript版のWebSocketラッパーもそのうち作りたいとは思うんですけどね。。。


★PlayでのWebSocketアプリの作り方

http://www.playframework.com/documentation/2.2.x/ScalaWebSockets

Controllerでリクエストハンドラを作る時にActionの代わりにWebSocketを使って受信データをハンドルするIterateeと送信を実行するEnumeratorを返すようにすればOKです。

以上、終わり(^^;

。。。なんですがちょっとだけIterateeとEnumeratorについても触れておきます。
これらを解説するサイトは日本語のものだけでも、かなり多数ありますが抽象度の高い概念なのでなかなか理解するのは難しいです。というか抽象度の高い議論は途中からついていけません。。。(^^;;;

そうした概念的な話はさておき、WebSocketの文脈に限定して話をするなら、

- Iteratee
クライアントからデータを受信した時と接続が切れた時に何をするかを定義するもの

- Enumerator
クライアントに発信するデータを供給するもの


とだけ理解しておけば十分です。


これ、よく考えるとアプリで必要な機能以外は何ひとつ作らなくて良いインターフェースになってるんですよね。

今までPlay2を触ってきた中でこのWebSocketのAPIが一番いけてると思いました。(^^;


★RedisService

https://github.com/shunjikonishi/websocketchat-redis/blob/master/app/models/RedisService.scala

プロトなので関連するクラスを全部1ファイルにまとめています。

RedisServiceクラスの機能は

- コネクションプール
- WebSocketで使用するPubSubChannelの作成

の二つだけです。

- コネクションプール
基本的にRedisを使う場合はコネクションプールを併用した方が良いです。RedisClientは基本的にソケット繋ぎっぱなので、接続のコストをカットできます。

ただし、RedisClientをSubscriberとして使用する場合は注意が必要です。Subscribe時のRedisClientは別スレッドでSocketの入力を監視しているのでプールに戻すタイミングが難しくなります。

今回はやや強引な方法でSubscriberもプールに戻してますが、シビアにスレッドセーフに気を使う必要があるので、素直にSubscriberとして使用する場合は新しいインスタンスを作成して使い捨てた方が良いと思います。(使い捨てる場合もunsubscribe時にdisconnectすることを忘れてはいけません。)

このプロトでは試行錯誤の過程を残す意味でプールに戻すコードを残していますが、もしこれを本当にライブラリ化するのであればSubscriberでは常に新しいインスタンスを使用するようにします。(そうするとborrowClient/returnClientメソッドが不要になるのでAPI的にもすっきりします。)


- PubSubChannel
WebSocketにひもづけるIteratee/Enumeratorをラップするクラスです。
コンストラクタの必須引数は購読するチャネル名のみです。
インスタンスを作成してメンバ変数のinとoutをWebSocketに返せばそれだけで単純なEchoアプリが作れます

オプションとして

  • send: クライアントから受信した文字列をRedisに送信する前に加工する関数
  • receive: Redisから受信した文字列をクライアントに送信する前に加工する関数
  • disconnect: クライアント切断時にRedisに送信する文字列を返す関数


などを指定できます。
exception/subscribe/unsubscribeなどのSubscriber関連のイベントハンドリング関数も一応指定できるようになってますが多分使うことはないです。

実装に関して言うと

  • Publisher: Actorになっていて都度プールから取ってきたクライアントで送信
  • Subscriber: ひとつのクライアントでredis-scalaの内部スレッドにおまかせ


となっています。
WebSocketではinとoutは一対ですが、RedisのPub/SubはWebSocketだけで使うものとは限らないので本来的にはは多対多であり、Publish自体はどこから行っても構わない訳です。

こう考えるとPublisherのActorはシングルトンでも構わない気がしますが、Publishのコストは購読者数に比例する(らしい)のでチャネル毎にわけています。

あとこのクラスではsubscribeするチャネルを一つに限定しています。Redisの機能を考えるとマルチチャネル購読を使いたいケースというのは十分に考えられるわけですが、そうすると

  • -> in/outにチャネル名も入れる必要がでてくる
  • -> 入出力の型をStringからTuple(String, String)に変更
  • -> in/outを直接WebSocketに接続できるというメリットが失われる


ので止めました。
まぁマルチチャネル対応版をサブクラスなりTraitに切りだすなりして対応するのはそんなに難しくないので必要に迫られた時に考えたいと思います。


★ChatRoom

https://github.com/shunjikonishi/websocketchat-redis/blob/master/app/models/ChatRoom.scala

ChatRoomに関して具体的なアプリの内容に関する説明はここではしません。
ここではWebSocket/Redisアプリを作る際のポイントとなる部分についてのみ説明します。


- クラスで状態管理をしてはならない
元々のサンプルではチャットルームのメンバ一覧をクラスのvar変数で管理していますが、複数サーバある場合に状態を共有できないのでアウトです。
状態管理の変数は常にRedis上に置く必要があります。

RedisにはListやSetを扱う機能があるのでとても重宝します。(^^;

- WebSocketとPubSubChannelは1対1とは限らない
WebSocket/Redisアプリを作る一番簡単な方法は先に作成したPubSubChannel(のin/out)を直接WebSocketにひもづけることです。

状態管理をすべてクライアント側で行うアプリであれば、サーバ側には一切ロジックを記述する必要がありません。(このサンプルはそうなっていませんが、チャットはそういうやり方でも作成できます。)

ですが、それだと接続毎にRedisのコネクションを消費することになるので無駄が多いわけです。

チャットの場合同じ部屋にいるユーザが購読するRedisチャネルは皆同じなのでホストで一つチャネルを開いてそれを共有できれば大幅にコネクションを節約できます。

実際、PubSubChannelのoutは複数のWebSocketで共有することができます

ですがinはダメです。何故ならこのIterateeではクライアント切断時にチャネルをクローズしているからです。つまり1人のユーザが退出したら全ユーザのWebSocketが切れます

しかし、前述の通りPublishはどこから行っても構わないのでPubSubChannelのinをそのまま使う必要はなく自前のIterateeを使うことができます

この時問題となるのはRedisの接続管理です。

- いつunsubscribeするか?
今でしょ!なわけないですよ。(^^;

論理的にはWebSocket接続数を数えておいて、0になったらunsubscribeすれば良いのですが、どこでcountUp/Downするかも悩ましかったりするわけです。考慮漏れがあると即リソースリークになるので。。。(--

で。。。結論なんですがこれは変に考えすぎず、クライアントからの切断にのみ反応するようにするべきと思います。具体的にはIteratee#Doneの中。

WebSocketの切断が発生するのは多くの場合ブラウザの画面遷移やクライアントの電源断(タブレット等ののスリープ含む)です。

これらはいつ発生するかはわからないので変に状態管理するよりも確実にそれだけを捕まえるのが良いと思います。

直観ですが、WebSocketアプリではサーバーサイドからの切断もあんまりやらない方が良い気がします。

- どこで接続管理を行うか?
これは絶対にActorを建ててそこでのみ行う必要があります。
IterateeはWebリクエストのワーカースレッド内で動くので、スレッドセーフにするためにはcountUp/Downを伴う操作は同じActorの中からしか実行してはいけません。

なのでIteratee#Done内で実行するDisconnet操作もActorにDisconnectメッセージを投げるだけとなっています。

□□□□
ここまでたどり着くのに相当試行錯誤しており、AkkaのActorにも慣れてないのでイマイチ実装に確信を持ててないんですが、概ね抑えるべきところは抑えたつもりです。

何かおかしなところがあれば是非ご連絡を!



★Playの残念なお知らせ

さて最後に今回気がついたPlayframeworkの衝撃の事実を。。。。(--

Playで修正/リコンパイルを繰り返している時には、修正前に使用されていたインスタンスは回収されません。。。マジでかーーーー!!


前回書いた通り、Redisの最大接続数=10の状態で作業しているとほんの数回修正を行っただけで接続エラー。。。。

その度にPlayの再起動を繰り返すのはなかなか苦痛でした。(最初はPlayのせいとは思わず自分かredis-scalaのリソースリークを疑っていたのでなおさら。。。(--)

Global#onStopでcloseすれば大丈夫かと思ったんですが、ここのロジックが実行されるのはリコンパイルが行われた後っぽい。。。意味ねーーーー!!!

シングルトン(object)とか、どうなってるのかと思うけどこれも単なるクラスのインスタンスなのでポインタ変わって新しいインスタンスが作られるだけなのかなと思ったり。

唯一有効だったのはActorのpostStopにcloseを仕込んで置くことだけど、これも常に有効かどうかは疑わしい。。。(上の推測通りシングルトンのアドレスが変わるのであればアウトだと思う。)

修正前のインスタンスが残るのは仕方ない気がするけど、せめてonStopはリコンパイル前に走って欲しい。。。(--

□□□□

以上、こんだけ書いておけば将来ホントにライブラリ化する日が来たとしても、ちょっとこれ作った奴出てこい!とか思わずに済むでしょう。。。多分。。。(^^;


次回、「HerokuでスケーラブルWebSocket」(かもしれない)

2013年12月27日 (金)

PapertrailのWebAPIを使う

前回記事ではHerokuのPlatformAPIを使用してリアルタイムにログを解析してグラフを作ってみたわけですが、やっぱり直近の1500行しかログが取れないとあんまりいい感じのグラフにならないわけです。(--

で、PapertrailにもWebAPIがあったことを思い出してそっちを使ってみることにしました。

 

http://help.papertrailapp.com/kb/how-it-works/http-api

 

ちなみに検索だけだけどラッパーAPIもあります。

 

http://oss.flect.co.jp/libs/ja/papertrailTool.html

 

このラッパー最初に作ったのは多分1年以上前だと思うけど、今見ると色々WebAPI進化してますね。。。

昔は検索以外のAPIなかった気がするけどいつの間にか検索条件の保存とか管理系のAPIもあるっぽい。

まぁ使うことはなさそうだけど、こうしたPaasサービスの提供する機能がなんでもAPIで叩けるようになっているのは良い傾向です。夢が広がります。(^^v

 

★APIでtail的なことを行う

これ非常に簡単です。

まず、検索APIをパラメータなしで実行するとJSONで以下のレスポンスが返ってきます。

  • 含まれるログのIDの最小値
  • 含まれるログのIDの最大値
  • 直近のログ100件(生成日時、プログラム、メッセージなどが構造化されています。)

これに対して指定できるパラメータには以下のものがあります。

  • min_id - 指定された場合このID以降のログを取得する
  • max_id - 指定された場合このIDまでのログを取得する
  • min_time - 指定された場合この日時以降のログを取得する
  • max_time - 指定された場合この日時までのログを取得する
  • q - 任意のクエリ(WebUIと同じ書式)

※min_time, max_timeはUNIX TIMEで指定

 

min/maxの指定でどこから、どこまでを取得するかが指定できるわけですね。ちなみに取得件数は常に最大100件で指定できないようです。

なので、最初にmin_timeを指定していつからのログを取得するかを決めたら、あとはそのレスポンスのmax_idを、次のリクエストのmin_idに指定してループをまわせば連続したログが取得できます。

ここでポイントとなることが2点あります。

 

min_idに指定したIDのログは結果に含まれない

min_idを指定すると指定したIDのログを先頭にしてそれ以降のログが取得されそうな気がしますが、実際には含まれません。

なので、max_idを次のリクエストのmin_idに設定する際には+1するとかの小細工は必要ありません。(PapertrailのログIDは連番ではないので+1しても抜けることはおそらくありませんが。)

 

結果が0件の場合でもレスポンスのmax_idは設定される

直近のログまで読んでしまったら、次のログがまだ存在しないということがありえますが、その場合でもレスポンスには最終行のログIDがmax_idに設定されます。

なので「結果が0件の場合に備えて前回のmax_idを取っておいて。。。」みたいなことをする必要はありません。

 

細かいことですが、この2つのおかげでtailライクなログ取得を組む際のロジックがシンプルになります。

 

★ログ解析アプリケーション

そんなこんなで、ログ解析アプリはPapertrailのAPITokenを入力すれば誰でも使えるようになりました。(^^v

 

http://flect-papertrail.herokuapp.com/

 

Herokuアカウントで使う場合は要求される権限の範囲が広すぎるため、試しに使ってみるのも抵抗がある気がします。開発環境で試してみたいけど本番環境まで一緒に見えちゃうよ!みたいな。

が、PapertrailのAPITokenであれば開発環境で試してみるということもできるので、気が向いた人は使ってみてくださいな。

 

□□□□

どうでもいいけどこのアプリ、最初に作ったのが今年の4月頃。そこからずっと放置してたのがこの数日でグラフ機能を追加。この先もまたしばらく放置の予定です。

これをトラブルドリブン開発(TDD)とでも名づけましょうか。(^^;

それでは良いお年を(^^)/

2013年12月20日 (金)

HerokuとJavaのメモリのお話

Heroku上のJavaアプリでNewRelicで見る限りメモリはまだ余裕がありそうなのにR14(Memory quota exceeded)がログに出力されるようになり、アプリの応答が極端に悪くなる(というより1台のDynoがほぼ無応答になる)という事象が発生したので、原因を調査しました。

結論から言うとエラーの原因は特定できなかった(いや、まぁメモリ不足であることはわかっている(^^;)んですが、なかなか興味深いこともわかったのでここにまとめておきます。

 


★ JavaVMのメモリ管理

Javaで使用中のメモリ量を調べる方法としてはRuntimeのメソッドを使用する方法とMemoryMXBeanを使用する方法があります。

http://docs.oracle.com/javase/jp/7/api/java/lang/Runtime.html
http://docs.oracle.com/javase/jp/7/api/java/lang/management/MemoryMXBean.html

前者はHeapメモリのみを対象とし、後者はHeapとNonHeapの両方を対象としています。
なので、Runtimeからとれる値はMemoryMXBeanからも取得できるわけですが、その関係は以下のようになっています。

  • Runtime#maxMemory == HeapMemorUsage#getMax
  • Runtime#totalMemory == HeapMemoryUsage#getCommitted
  • Runtime#freeMemory == HeapMemoryUsage#getCommitted - HeapMemoryUsage#getUsed


ログ等にメモリ使用量を出力する場合、お手軽なのでRuntimeのメソッドの方が使用されることが多いと思いますが、この方法だとNonHeapの容量が含まれていないことに注意する必要があります。

ちなみに今回テストで使用したアプリ(Play1)ではNonHeapは80MB - 100MB位確保されていました。意外と多いです。

 


★ NewRelicのInstancesページに表示されるメモリ使用量


NewRelic自体もMXBeanを使用して情報収集しているはずなので、ここでの各グラフはMXBeanで取得できる値と対応関係があるはずです。

その対応は多分以下です。

  • Physical = HeapMemoryUsage#getCommitted + NonHeapMemoryUsage#getCommitted
  • CommittedHeap = HeapMemoryUsage#getCommitted
  • UsedHeap = HeapMemoryUsage#getUsed

多分というのは、今回アプリの中からMXBeanの値を定期的にログ出力するようにしてその値をグラフ化するということをやったんですが、そのグラフと自前のグラフでいくらか傾向が異なるからです。

ちなみに自前のグラフはこんな感じ。(1分間隔で出力)

 

Memory

 

見ての通り、UsedHeapがかなりギザギザしたグラフとなっていますが、これは定期的にgcがかかっているためと推測されます。

しかし、NewRelicのグラフではこのようなギザギザは観測されません。
複数Dynoの平均値だからかとも思ったんですが、1Dynoの場合でもこのようなグラフとはならないので謎です。。。(--

ただ全体的には同じようなグラフとなるので、ベースとしている値は上記であっていると思います。。。

が、先にも書いた通り複数Dynoを使用している場合はNewRelicのグラフは平均値になるのでなので各Dynoがどれだけメモリを使用しているかはNewRelicではわかりません

 

あと今回の調査とは直接関係ありませんが、NewRelicのagentのjarファイルはちょいちょいアップデートされているので、たまには見直した方が良いかもしれません。

ほとんどの場合、最初にNewRelicを追加した時にDownloadしたAgentをそのまま使用していると思いますが、2系から3系へのバージョンアップではAgentのオーバーヘッドが減っている、とCHANGELOGに書いてあります。(まぁ実感できるものではありませんが。。。この辺Addonが自動的にやってくれると嬉しんだけれど。)

 


★ heroku labs:enable log-runtime-metrics

labsの機能、log-runtime-metricsを有効にすると20秒に一回各Dynoのメモリ使用量がログに記録されます

 

https://devcenter.heroku.com/articles/log-runtime-metrics

 

ここで出力される「memory_total」がHerokuが監視しているメモリ使用量で、この値が512MBを越えるとR14が出力されます。

ちなみにR14が出力される間隔も約20秒おきなので、このスイッチで切り替わるのはログ出力の有無だけでメモリ監視自体は常に実行されているのでしょう。

ちなみに記録対象はすべてのDynoなので、heroku run bashとかSchedulerで動いたDynoのメモリ使用量も記録されます。

各Dynoのメモリ使用量が個別にわかるし、オーバーヘッドもほとんどないと思うのでlabsだからと敬遠せずに全部のアプリで有効にした方が良いと思います。

 


★ Javaのメモリ使用量とDynoのメモリの関係

さて、今回の調査を始めた元々の動機は「NewRelicで見るメモリグラフではそれほどメモリを使用しているようには見えないのにR14警告が出ることがある(気がする)」というものでした。

先のグラフではわざと載せませんでしたが、log-runtime-metricsの値ももちろんログから拾えるのでグラフ化できます。これらを重ねればJavaのメモリ使用量とDynoのメモリの関係がわかるはずです。

その衝撃の計測結果はテストアプリのでのグラフを公開しているので是非直接ご覧ください。(12月16日以降はグラフが見られます。)

 

https://flect-papertrail.herokuapp.com/app/fexp/metrics/2013-12-19?key=memory_rss,memory_total,HeapUsed,HeapCommitted,Physical,PsRss

 

なにが衝撃だったかって、Herokuの計測しているmemory_totalがJavaのHeapCommitted(+ NonHeapCommitted)よりも小さいことがあるということです。

そんなことってある???

僕の認識ではCommittedというのはJavaによって確保済みのメモリなので、それはプロセスが使用しているメモリとほぼ等しいはずと思っていたんですが、外から見たメモリがそれよりも小さいってどういうこと???

そんなはずはねーと思って、アプリ上にpsを叩いた結果を表示するコントローラを付けてみたところ。。。

。。。マジでかーーー。。。ここでもCommittedよりも小さい値が表示されてるよ。。。(--

誰かこの現象を説明してください。m(_ _)m

 

(12/24 追記)

その後ログに「ps aux」の結果から抜いたメモリ使用量を出力するようにしてグラフに追加したところ、ほぼmemory_rssと重なりました。なので外から見た場合のメモリ使用量はJavaのCommittedメモリよりも大きいことも小さいこともあり、その数字が実際のメモリ使用量(Herokuの監視対象)と考えるのが良いようです。

□□□□

話を戻すとグラフを見るとmemory_totalはJavaから見たメモリ使用量よりも小さい時も大きい時もあります。ただグラフの上がり方を見るとアプリの負荷と相関関係があるのは間違いないです。

アプリ以外にメモリを使っているものというのがイマイチ想像できないんですけど、通信バッファとかもあるんだろうか?psにでてこないプロセスも何かある気がする。(根拠なく言ってます。。。)

これはもうこういうものだと思って飲み込んだ方が良いのかもしれません。。。深入りしても満足のいく結果が得られそうな気がしない。。。(敗北宣言)

ログからDyno毎の正確なメモリ使用量を可視化するツールができただけでもまぁ良しとしますかね。。。(--

 

★なぜか次回予告

さて、今回作成したグラフツールはまぁまぁ便利です。(ドキュメントとかまったく書いてないけど)

仕組みとしてはPapertraiがS3にアーカイブしたログを引っ張ってきて解析しているので、前日以前のログは見られるわけだけどPapertrailのログがアーカイブされるのはだいたいお昼過ぎ(昔はもっと早かった気がするけど)なので、「今R14出てるよ!」みたいな時には使えない訳です。

そこはやっぱりリアルタイムで見たいのが人情!

。。。かどうかは知りませんが。(^^;

 

そういやPlatformAPIで直近のログを取得できたな。。。

 

そういやHerokuってWebSocketサポートしたんだよな。。。

 

じゃリアルタイムグラフも作れるんじゃね?(^^v

 

ということで、次回はHerokuとWebSocketの話です。

2013年9月26日 (木)

Play1のcommons-codecは1.4

commons-codec-1.4以前のバージョンではBase64#encodeBase64Stringはchunked(結果文字列が長い場合改行される)で返されていた。

1.5以降のバージョンでは改行なし。

なかなか気がつかなくて時間無駄にした。。。(--

ていうかPlay1で使うことが想定されているライブラリを書く場合はBase64エンコード時にreplaceAll("\r\n", "")した方が良いのかもとか考え始めるとかなり悩ましい。

http://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64String(byte[])

2013年7月 1日 (月)

FLECT OSS Libraryが公開になりました。

こんにちは

なんと、今日から7月です。つまり2013年ももう半分終わってしまいました。。。(--

そして、小西がFLECTに入社してからちょうど1年半たったということでもあります。
はやっ!年々時が経つのが速くなっているようでかないませんな。(--

ちなみにこのブログを書き始めたのは2012年の5月末です。1年1カ月で記事数31件。
さぼっている時期もありますがだいたい月に2回位書いている計算ですね。もうちょっと頑張ります。

 

★FLECT OSS Libraryとは

これです。

http://oss.flect.co.jp/

この1年半で僕が作ったものがあらかた入っている感じですね。内容はかなり雑多な感じです。なんでこういうものを作ろうと思ったかは以前にも書いたので割愛します。

個人的に汎用性の高いモノ、開発者にとって有益なモノを作りたいという志向性が強いのでこういう形での開発はテンションがあがります。(^^;

 

★Salesforce関連

思い起こせば1年半前、Salesforceを外部からAPI連携するのをやりやすくするライブラリを整備してほしいというお題に対して、いきなりSOAPClientから作り始めたのがFLECTでのキャリアの始まりでした。

それをベースにして作成したSalesforce APIのラッパーがSalesforceClientで、これでどれ位のことができるかを示すために作成したアプリが、Salesforce Explorerです。

完全に上司の想像のナナメ上をいっていたと思います。(^^;

Salesforceを使う開発者ならそれなりに便利に使えると思うので興味ある人は触ってみると良いかもしれません。(多分)

 

★Excel関連

excel2canvasはHTML5Canvasの調査をしている時に「絵心無いし、Canvasにテスト描画する適当なものが思いつかねー(--」と困ったあげくなんとなくExcelをそのまま描画してみようと思い立ったのが最初です。凝り性なのでやり始めると最初のお題からまったく関係ないシロモノになっていきましたがそこはそれ。(^^;R&Dとはそういうものです。(キッパリ)

これ、何気にSlideも結構なViewを稼いでいるし少なくとも日本では絶対うける思うんですけどね。どの位Excelを再現できるかはサービス化を目論んで開発したExcelReportで確認できるので、なんか適当な帳票フォーマットをExcelで持っている人はアップロードしてみてください。

 うまくブラウザで描画されないという場合は連絡くれれば可能な限り直します。(多分)

 

★その他

あとはHeroku上で動かすことを前提として作成したアプリをいくつか公開しています。

ちなみに最近作っているアプリは全部Play2で開発しています。社内のHerokuチームが使用している言語はPlay1なんですが、最近はメンテもされてないようなので次期主力言語として使えるかどうかを評価するという目的も兼ねています。

ちなみにPlay2の評価は。。。うーん、どうなんだろう?(--
まだまだベストプラクティスを模索している状態なので、書いていてももっと良い書き方があるんじゃないの?とか、本当にこんなめんどくさいことを毎回書かないといけないのか?とか思うこともしばしばです。

(↑もっとも、Scalaのおかげで圧倒的に楽になっている部分もあるので、このように不満点だけをあげるのは正当な評価ではありません。Play2については近日資料にまとめるつもりです。)

とにかく最近はHerokuが面白くてしょうがないので、Herokuを便利に使うための小物ツールをもうちょっと作っていこうかなと思っています。まだOSS Libraryにはリストされてませんが現在つくっているものもHerokuユーザーにとってはかなり便利です。(多分)

 

会社としても絶賛Herokuエンジニア募集中なので興味ある人は覗いてみてください。

採用情報

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

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

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

フレクト採用ページへ

会社紹介

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