Java

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年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月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年12月11日 (水)

SalesforceとHeroku Postgresの同期

こんにちは。このエントリはSalesforce Advent Calendarの11日目です。


ノリでSalesforce Advent Calendarに手を挙げてしまいましたが、僕自身はSalesforceでアプリを作ったことは一度もありませんVisualForceは1時間で挫折しました。

そんな人間がSalesforceのAdvent Calendarで何を語ろうと言うのかというと、SOAP APIのJavaラッパーを作ったりもしているので、それを使ったSalesforceと外部RDBMSとの同期連携について書いてみようと思います。

※タイトルは半分釣りでHeroku Postgresと書いていますが他のRDBでも多分動きます。


★ところで Heroku Connect

本題に入る前にSalesforceとHeroku Postgresの同期と言えば先日のDreamForceでHeroku Connectが発表されました。

http://blogjp.sforce.com/2013/11/heroku1-.html

今のところ予告編だけで実際に試せるのは来年以降になるらしいですが、某エバンジェリストに聞いたところでは、これはSalesforce上のオブジェクトと同じ構造のテーブルをPostgres上に作成して双方向に同期するもののようです。

ちなみにガバナ制限は受けない某エバンジェリストは力強く言っていましたが、別の筋からはそんなの聞いてないという話もあり、真偽は定かではありません。(^^;

素晴らしいんですが、この枠組みだとまずSalesforceアプリありきなので、既存のHerokuアプリのデータをSalesforceに取り込みたい場合は使いにくいかもしれません。

今時のクラウドデータベースではストレージ容量は十分過ぎる程にあるので、アプリで使用するテーブルとSalesforceと同期するテーブルはきっぱり分離してPostgres内でトリガーで同期するというアプローチはアリだと思いますが、自前のテーブルの方はともかく自動生成される同期テーブルにトリガーをつけて良いかどうかは謎ですね。。。
それにこのやり方で完全双方向は無限ループの回避がやっかいかな?

いずれにせよ使えるようになったら試してみるつもりなのでこの話はまたいずれ。



★ところで flectSalesforce

ちょいちょいあるよー、とはこのブログでも言っていたのですがちゃんと紹介したことはなかったと思うので改めて。

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

flectSalesforceはSalesforceのSOAP APIのJavaフルスクラッチ実装です。

僕は元々某所でSOAPのかなり初期からSOAPクライアント/サーバーを作ったりもしていたのでSOAPには色々と言いたいことがあるんですが、それはまぁ良いでしょう。

とにかくAxisは使いたくなかったので自分で作ることにしたんですが多分正解でした。SalesforceのSOAP実装にも色々と突っ込み所があるんですが、これまた誰の共感も得られそうにないので墓まで持っていきます。(^^;

□□□□
前置きが長くなりましたが。。。
そんなこんなでこれはSalesforce APIの実装のひとつです。必要なものから作っているので全部のメソッドを実装しているわけではありませんが、オブジェクトの更新/削除など主要なものは網羅しています。(describeLayoutやdescribeTabsみたいなメソッドを実装しても誰も使うことないでしょうしね)

元々は社内で使うことしか想定していなかったのでコメントも日本語です。(^^;
(必要最低限しか書いてませんが)

既存のAPIラッパーとの相違点としては単純なAPIのラッパーに留まらず、独自のユーティリティインターフェースを備えている点があげられると思います。

  • SQLライクなINSERT, UPDATE, DELETE構文によるデータ更新
  • Fixtureによるテストデータの更新、削除
  • RDBからのSELECT結果をSalesforceに取り込み
  • SalesforceからのSELECT結果をRDBに取り込み


などなど。
後ろの二つがここから紹介する内容です。
APIの使い方自体は

http://flect-salesforce-sample.herokuapp.com/

でサンプル付きで説明しているのでここでは主に処理の枠組みについて説明します。


★RDBからのSELECT結果をSalesforceに取り込み

http://flect-salesforce-sample.herokuapp.com/sqlsync

RDBからSELECTした結果でCSVを作り、それをBulk APIで投入します。

特徴的なのはSELECTしたフィールドとSaleforceオブジェクトのフィールドの対応をSELECT文内のエイリアスで指定するという点です。

 

SELECT COL1 as Col1__c,
       COL2 as Col2__c
  FROM TABLE1
 WHERE ...

 

この仕様によりJoinや関数の使用などDBMSのサポートするすべてのSQL構文を駆使してSELECT文を書くことができます。

同期処理として運用する場合はWHERE句に更新日付で結果を絞る条件を付加して定期的に実行します。(それに特化したアプリもあります。)
項目に外部IDがあればレコードの更新方法はUPSERTになります。


ちなみにBulkAPIには1万行制限がありますがSELECT結果が1万行を超える場合は内部的にファイル分割されるので、1万行を越えても問題なくデータを取り込むことができます。


★ SalesforceからのSELECT結果をRDBに取り込み

http://flect-salesforce-sample.herokuapp.com/sobjectsync

これもBulkでやりたかったんですが、クエリ自体はSOAP APIで投げています。
何故ならBulkクエリでは参照フィールド(CreatedBy.Nameとか)が取れないからです。。。


使えねー。。。(--

サブクエリは仕方がないと思うけど、参照フィールドくらいはBulkでも取らせて欲しいよ。。。

□□□□

仕組みとしては要するにSalesforceに対してクエリを実行した結果をぐるぐる回しながら指定のキーでUPDATE文を実行し、結果が0件だったらINSERT文を実行しているだけです。
QueryMoreは内部的に処理されるので、結果セットが大きい場合も問題なく実行できます。

こちらの方ではSalesforceオブジェクトのフィールドとRDBのテーブルカラムを一つずつ指定する形式をとっています。

なのでSOQLの関数は使用できません。(あるんだっけ?(^^;;; )

その代わりSELECTした結果をJavaで加工した結果をマッピングすることができます。

 

request.addFunctionMapping("NAME", new SObjectSyncRequest.Function() {
    @Override
    public Object evaluate(SObject obj) {
        String fn = obj.getString("FirstName__c");
        String ln = obj.getString("LastName__c");
        return ln + fn;
    }
});

 

引数のSObjectから値を取得してそれをごにょごにょと演算をした結果をreturnすればOK。データ移行の自由度はむしろこっちの方が高いです。


実行結果の詳細なハンドリングを行いたい場合はListenerクラスを組み込むことで個別にエラーとなったオブジェクトをハンドルすることができます。

□□□□
こんな感じで雰囲気は伝わったでしょうか?
Heroku Connectが完全自動で固定的なのに対して、こちらの方は自由度高くプログラムに組み込めるのが特徴と言えると思います。

実際のところ、これらの機能は社内要件から実装したものであって公開して誰でも使えるようにしているのは、もののついでという感はあるのですが。。。(^^;

ご意見、ご要望等あればGithubのIssue、Twitter等に書いてくれれば可能な範囲で対応するので、定常的な同期に限らずワンショットのデータ移行の場合などでも思いだしたら使ってみてくだされ。(^^)/

2013年12月 6日 (金)

Heroku Platform APIのJavaラッパー

しばらく別の仕事をしていたため一ヵ月以上放置していたんですが、Heroku Platform APIのJavaラッパーをリリースしました。

 

http://oss.flect.co.jp/libs/ja/heroku-platform-api.html

 

Platform API自体がまだベータでどのように変更されるかわからないのでバージョンは0.9としています。(というかいくつかFeatureRequestしようと思ってます。)

元々は全メソッドを実装するつもりはなかったんですが、7割ぐらい実装した段階で全部作ろうと決心しました。でも、どう考えても使うことのなさそうなメソッドも多いです。(^^;

使い方は多分PlatformAPIのJavaDocとHerokuのDevCenterのリファレンスを並べて眺めればだいたいわかるんではないかと。

- http://oss.flect.co.jp/apidocs/heroku-platform-api/jp/co/flect/heroku/platformapi/PlatformApi.html

- https://devcenter.heroku.com/articles/platform-api-reference

基本的にはRESTのAPIとJSONを忠実にJavaに落としていっただけです。ちなみにJavaDocは全クラス、メソッドがGitHubのソースへのリンク付きという親切仕様です。(^^;

以下、いくつかポイントを絞ってわかりにくそうなところと突っ込みどころを解説していきます。

 

★認証

APIを使用するための認証方法には以下の4つがあります。(括弧内は対応するPlatformApiクラスのメソッド名)

  • OAuth(fromOAuth)
  • refresh_tokenをaccess_tokenと交換する方法(fromRefreshToken)
  • ユーザー名とパスワードをaccess_tokenと交換する方法(fromPassword)
  • ユーザー名とAPIキーを使用する方法(fromApiToken)

一番基本的な使い方はOAuthでの認証です。OAuthを使用するためにはあらかじめHerokuダッシュボードのAccount画面でOAuthClientを登録してClientIDとClientSecretを取得する必要があります。

このJavaラッパーではPlatformApi#getOAuthUrlメソッドでHerokuの認可画面のURLがとれるので、そこにブラウザでアクセスしてリダイレクトで返ってきたcodeをPaltformApi#fromOAuthに渡せば有効なPlatformApiクラスが取得できます。

OAuth認証のレスポンスには常にrefresh_tokenが含まれています。つまり一度認可を受けてしまえばあとはずっとオフラインアクセスが可能です。

refresh_tokenはユーザがダッシュボードでそのアプリをRevokeしないかぎりずっと有効です。

あとはユーザ名とパスワードまたはAPIKeyを使用してAPIを叩くこともできます。(APIKeyはコマンドで「heroku auth:token」を叩くと表示されます。)

パスワードでの認証はaccess_tokenとの交換が必要なのに対しAPIKeyは交換なしでいきなり使用できます

これが可能なのでPlatform APIは実は全部以下のようにcurlで1コマンドで実行可能です。

 

curl -i -H "Accept: application/vnd.heroku+json; version=3"
    -u <USER>:`heroku auth:token`
    https://api.heroku.com/apps

 

★Scope

OAuthで認可を求める際に指定できるScopeは以下です。

  • global(全権限)
  • identity(アカウント情報のみ)
  • read(アプリの環境変数以外を読める)
  • write(アプリの環境変数以外を書ける)
  • read-protected(アプリの環境変数も読める)
  • write-protected(アプリの環境変数も読める)

アプリの環境変数が見えてしまうとDATABASE_URLやその他のAddonの設定情報も丸見えなのでデータベースの中身まで見ることが可能になるわけです。

そのためにread/read-protectedのような分け方がされているんだと思うんですが、全アプリに対して同じスコープが適用されるのでほとんど有効なバリアとはなりません。

現状、アプリ毎に個別に権限が指定できないのがPlatform APIの一番の問題点だと思います。

ちなみにglobalスコープでも実行できないメソッドもいくつかあります。(OAuthClientやAuthorizationなど。これらのメソッドを実行するにはパスワードかAPIKeyでの認証が必要。。。なんですけどそもそも使い道が思いつかないですね)

 

★Formation

とはProcfileで定義するWebプロセスやWorkerプロセスのことです。(前は「Process」と呼ばれてた気がするけどこれから変わるのか???)

heroku psコマンド相当のことはFormationまたはDyno関連のメソッドでだいたいできます。

たぶんこの辺が今のところ一番実用的なメソッドです。

 

★ LogDrain

LogDrainはDynoのログを外部のSyslogに流す仕組みです。PapertrailやTreasureDataなどのログを扱うAddonはこの仕組みを利用しています。

一覧を取得するとそこにはAddonが使用しているURLも含まれているので、それを別のアプリでaddすると複数のアプリから一つのAddonに対してログを流すことが可能です。

やっていいのかどうかはわかりませんが。。。(^^;;;

でも多分Addon側からはログを流してきたDynoがどのアプリに属しているかを知る術はないと思うんですよね。。。

 

★LogSession

作成するとhttpsのURLが生成されて、そこにGETでアクセスすると直近のログが取得できます。URLは5分間または接続してから1時間有効です。(とドキュメントに書いてあります。)

接続してから1時間???

どういう意味かと思ってたら作成時のオプションで「tail=true」と指定すると、レスポンスはContent-Lengthなしで繋ぎっぱなしになります。

なんつー乱暴な。。。(--

しかし実際にtailっぽく動作するので便利は便利です。。。でもアリなのか?これ?

 

□□□□

その他、releasesの一覧は取れてもrollbackはできないとか、全体的に突っ込みどころ満載な感じはありますがFeatureRequestやバグレポも割とちゃんと聞いてくれて実際に修正されたりもしているので、皆もっとガシガシ突っ込んであげると良いと思います。(^^;

2013年11月19日 (火)

GitHubにリンクするDocletを作ってみた

こんばんは。

今回タイトルにあることがすべてですが。。。(^^;

ふと思いついてGitHubにリンクするDocletを作ってみました。

StandardDocletから改造したのはたった一箇所、クラス名の横にOctocatをおいてGitHub上のソースにリンクするだけ。

Javadoc

結構便利です。(^^v

仕組み的にはGitHub以外でもJavaのソースが階層構造でアクセスできるサイトなら他でも使えるんですが、リンクアイコンをOctocat埋め込みにしたのでGitHub専用です。

その辺パラメータにしようかとも思ったんですが、あんまりパラメータ増やしたくなかったので追加のパラメータはリンク先のGitHubのURLを指定するだけ。

MavenやAntからももちろん使えるのでOSS界隈で広まってくれるとちょっと嬉しいですね。

採用情報

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

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

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

フレクト採用ページへ

会社紹介

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