その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

tinyorm 1.11.0 is out

tinyormのversion 1.11.0が出ました.

Maven Repository: me.geso » tinyorm » 1.11.0

新しい機能としては

に書いたように

  • Connection を2つ持てるようになった
  • Lazily な connection borrowing に対応した

という2つが挙げられます.
rc1の時点では中々にバギーだったんですが,改善を重ねていった結果,プロダクションでも安定して動作するようになったのでこの度rcを外して正式なバージョンとしてリリースしました *1

今回入った機能についてはConnectionというセクションのドキュメントでも説明しているので併せて参照ください.

*1:その結果rc7までバージョンが育ったという逸話があります

ltsv_exporter書いた

ltsv_exporterというPrometheusのexporterを書いた.

その名の通り,LTSV形式のテキストを読み込んでPrometheusが解釈できる形式に吐き出すexporter.

利用方法としては2つのパターンを想定している.

1つはリモートで配信されているLTSVを読むというスタイル.

$ ltsv_exporter --url https://example.com/sample.ltsv

このようにすると https://example.com/sample.ltsv で配信されているLTSVを読み込んでそれをPrometheus形式で出力するようになる.

もう1つはローカルに設置されているLTSVを読むというスタイル.

$ ltsv_exporter --file /path/to/sample.ltsv

このようにすると /path/to/sample.ltsv に設置されているLTSVを読み込んでPrometheusの形式で出力するようになる.

Exporterの出力としてはこのような感じ.

# HELP ltsv_value LTSV value
# TYPE ltsv_value gauge
ltsv_value{key="duration"} 4.097
ltsv_value{key="size"} 123

key というラベルにLTSVのkeyが入り,値の部分にはそのままLTSVのvalueが入る.なお,LTSVのvalueが数値ではなかった場合は,exporterはその値を出力に含めずに読み飛ばす.
あとはこのexporterのhttp越しの出力をPrometheusで収集すれば良い.


というわけでメトリクスを取りたいものがあったり監視したいものがあった時は,雑にサーバでLTSVとして配信したり,もっと雑にLTSVとしてローカルファイルに吐き出したりするだけでltsv_exporterで読み込んでPrometheus形式で出力できるようになった.
LTSVはシリアライズ(?)する時に特別なシリアライザが不要で,シンプルに文字列組み立てだけで作れるので楽で良いですね.


今後としては,最後の1行だけ読み込んでパーズするモードなんかがあると便利なのかな〜と思っている.

CasperJSを使ってGrafanaのグラフのスクリーンキャプチャを撮る

最近はサーバのメトリクス収集にPrometheus,その可視化の為のフロントエンドとしてGrafanaをハードに使っている.Grafanaは予想よりもはるかに格好良いグラフが生成されるのでやる気が出て良い.デモを触るとなんとなく温度感がわかると思う.
そうしているうちに,これは便利なのでサーバのメトリクス以外の情報も入れてみましょうという発想があり,サービスのKPIに関するような情報も入れ始めている.
とまあここまでは良いのだけれど,そうやって収集したデータも,格好良く可視化したグラフも,人に見られなければ一切の意味が無いのでそうした無意味を避けるためにデイリーでグラフを社内チャットに放流することにした.


という事になるとGrafanaで描画したグラフを画像としてエクスポートする必要が出てくる.
Grafanaにはグラフを画像としてシェアする機能があるのだけれど,これはブラウザをポチポチする必要があるみたいで,APIもぱっと見つからなかった (もしあったら教えて下さい) ので利用を見送った.あとどうやらこの画像データはSQLiteに入るみたいでホイホイ使って良いのかどうかあまり判断が付かなかった (間違ってたら教えて下さい).
というわけで今回はCasperJSを使ってGrafanaのグラフをキャプチャすることにした (結局CasperJS使うんだったらそれでGrafanaの画像シェア機能をポチポチすれば良かったんではないか,という意見もあることだろう.確かに!).
CasperJSはご存知の通りPhantomJS (かSlimerJS) を使ってスクレイピングやテストの実行をするのを助ける便利ライブラリ.CSSセレクタで指定すると,その範囲だけスクリーンキャプチャを撮ってくれるという機能があって大変便利なので使うことにした.

以下の様な感じでキャプチャが撮れる.

var casper = require('casper').create();

// login処理
casper.start('https://your-grafana.example.com/dashboard/db/your-dashboard?from=now-24h', function () {
    this.fill('div.login-inner-box > form', {
        username: 'Grafana user name',
        password: 'Grafana password',
    }, true);
});
casper.then(function () {
    this.click('div.gf-form-button-row > button');
});

// キャプチャ撮る
var waitMillis = 2000;
casper.viewport(1536, 1536).wait(waitMillis, function () {
    this.captureSelector('capture.png', 'div.grafana-row');
});

casper.run();

ログイン処理の部分は最初にfillしてからログインボタンをclickするように書いてある.シンプルなフォームだとfillの部分だけで認証が通るのだけれど,Grafanaはリッチな感じ (buttonのng-clickで処理が走るっぽい) なのでマニュアルでclickする処理を入れている.
認証が通ったらキャプチャを撮る処理を走らせる.ここで即座にキャプチャを撮ってしまうとグラフが未描画の場合があるので,気持ち待ってからキャプチャを撮る処理を開始している (例だと2秒).


で,これらをやる上で考えるべき点としては

  • グラフの時間スケール
  • viewportのサイズ
  • 対象となるグラフのCSSセレクタ

というのが挙げられる.


グラフの時間スケールと言うのは,どの時間スケール (直近5分とか直近24時間とか) のグラフを取得するかという点.Grafanaは時間スケールの情報はダッシュボード自身と紐付いていて,自分あるいは自分以外のユーザが時間スケールを変更すると全てのユーザに影響する.
そうした状態で時間スケールを特に指定せずにグラフを取得してキャプチャを撮ってしまうとめちゃくちゃになってしまうのでちゃんと指定してやる必要がある,という感じ.
例で言う所の https://your-grafana.example.com/dashboard/db/your-dashboard?from=now-24h?from=now-24h の部分がそれに当たる.


viewpointのサイズというのはCasperJSの話.要は仮想的なブラウザの大きさをどうするかみたいなもので,デフォルトの状態のままだとかなり小さめに設定されている.従ってほぼ確実に調整が必要になる部分だと思う.
Grafanaのグラフの大きさはブラウザの画面サイズに応じて可変なので,良い感じの数値を探っていくという泥臭い作業が要求されるのだけれど,まあ適当に妥協しましょう.


対象となるグラフのCSSセレクタというのは若干厄介.
今回は div.grafana-row というのを指定しているのだけれど,これはGrafanaのダッシュボードのrowそのものを表しているので,そのrowに属するグラフ等のパネルを全てキャプチャできる.
div.panel-container というのを指定すると個別のパネルをキャプチャできるので,用途に応じて使い分けると良い……という感じなのだがそう簡単には済まない.
なぜかというと,例えばrowが2つ有ると div.grafana-row というCSSセレクタも2つhtmlに表れてしまう為,先に出た方が優先されてしまう.つまりどちらか片方しかキャプチャができない.id要素などが付いているわけではない,本当に同一のCSSセレクタが出てきてしまうので面倒くさい……という感じ.もちろんパネルが複数個あればその個数分だけ div.panel-container が出てくるので問題は変わらない.
まあここら辺をなんとかする方法としては

  • 気合
  • キャプチャ用のダッシュボードを新たに作る

などといった解決方法があるので頑張れば問題がない.頑張りましょう.



こんな感じでGrafanaのグラフをキャプチャできるようになった.それを社内Gyazoに投げつけた上で社内チャットに毎日放流するという事が自動行えるようになって便利!
なお自動化はcronでやってます.

そして物語は何度目かのアプリ内通知再実装を迎える

というタイトルでKyoto.なんか #2で発表してきました.


そして物語は何度目かのアプリ内通知再実装を迎える / Reimplement in app notification // Speaker Deck

スライドの内容としては,アプリ内通知 (Twitter appで言うところの「通知」タブにあたる部分) のサーバサイドを実装する際にどういう問題があって,それをどういう風に実装したかという葛藤の記録となっています.

Webアプリケーションやスマートフォンアプリケーションを書いていると,そこそこの確率でアプリ内通知を書くことになると思うんですが,ところがどっこい「実際にどういう風に実装しているか」みたいな知見が共有されている感じがあまりありません.みんな実装しているはずなのに,ググってもあまり情報が出てこなくて寂しい.地味な機能だから?
という思いがあり,そこら辺アプリ内通知周辺の技術交流・意見交換などしたいなあというモチベーションでこの度発表してみた次第です.賽は投げられた.


ところで,この発表の最後で言おうと思っていたのに言っていない事があったのでこの場に書きますが,実際は「アプリ内通知」というか「通知タブ」のようなものはアプリケーションが個別に自作すべきものではないのでは? と思っています.
iOSAndroidには受け取ったpush通知を貯めて表示する通知センター的な領域が用意されているので,そうしたOSが提供してくれている機能に乗るようにアプリケーションを作成すればこんなに悩まなくても済みます (例えば今回の場合だと,push通知を送るだけ送るようにしておいて,クライアントが通知を貯めておくようにしておけばサーバサイドで通知情報を保持する必要がなくなる).


通知に限らず,他にもそうしたコンポーネントと言うのは様々あると思っていて,OSが提供してくれている機能で要件を満足できるのであればそれを活用するのが良いんじゃないかな,と思っています.
とはいえアプリケーション・サービスの特性によってはそうも言っていられないので,今回のように自作することになります.ケースバイケースと言えるでしょう.OSの通知センター使いたかった……




Kyoto.なんかは色々な技術的な話を聞けてとても有益でした.
特にid:r7kamuraさんのamakanのシリーズ判定の話がすこぶる面白かった.

r7kamura.hatenablog.com

主催のid:hakobe932さん,id:hitode909さんありがとうございました.また参加します.

Guice で独自の scope 切って inject したいって時

これを見ると良い.要は CDI のようなものをやるという話である.
CustomScopes · google/guice Wiki · GitHub


ここに書かれているように,

1. 任意の Scope を表す annotation を作る
2. Scope を実装する
3. Scope の実装と annotation を紐付ける (これは Module 内で行なう)
4. Scope#enter()Scope#exit() で Scope のライフサイクルを管理する

という流れに従う.


ここで少し難しく感じるのは2の「Scope を実装する」だと思う.
Wiki に書いてあるように基本的に Wiki に書いてある実装をコピペして使えという感じなのだが,まあ中身は読めばそこまで難しいことはしていない.
enter() する時に Scope が保持する objectを管理する Map を作成し,exit() する時にその Map を破棄するという感じ.あとは seed() で scope が持つ Map に直で object を登録したり,scope() で scope が保持している object Map から対応する object を取ってくるという処理を書いたら良い.
基本的に自分で Scope の実装を書く時はデフォルトの実装をそのまま使い,どこかで独自の処理をしたくなったらその部分を書き換えるという方法で良いと思う.元は thread safe な実装になっているし「大抵は」満足できる.


んでもって,あとは4のライフサイクル管理が若干面倒,一方で直感的ではあるかなあとは思う.
しかし,Module 内で injectee が bind されている時の挙動が個人的にわかりにくかったので書いておく.

例えば Module が以下のようになっていて,

final SimpleScope batchScope = new SimpleScope();
bindScope(BatchScoped.class, batchScope);
bind(SimpleScope.class)
    .annotatedWith(Names.named("batchScope"))
    .toInstance(batchScope);

bind(SomeObject.class)
    .toProvider(SomeObjectProvider.class)
    .in(BatchScoped.class);

Scope を管理する部分が以下のようになっている時,

final SimpleScope scope = injector.getInstance(Key.get(SimpleScope.class, Names.named("batchScope")));
try {
    scope.enter();

    injector.injectMembers(instance);

    // do something
} finally {
    scope.exit();
}

scope 内で injectMembers() しないと SomeObject は inject されない.
scope 内で injectMembers() された時は Module で指定されたように,SomeObject のライフサイクルは scope 内に固定される.ここは直感的.
なお Wiki の注意書きにも書かれているが,finally でちゃんと Scope#exit() しないと scope が閉じられず,ずっと生きながらえてしまうという問題があるので注意が必要.try-catch-resources が使えれば良いような気はしているが……


と,ここまで散々書いたのだけれど,Wiki の冒頭にあるように Custom Scope は自前で積極的に書くべきものではない.
基本的に,Singleton ServletModule が提供する RequestScopedSessionScoped で事足りるはずで,それで足りないケースというのはあまり存在しない.足りないケースの時だけ書くべきであるという事を留意されたし (今回はやんごとなき事情で自前で書きました).

tinyorm 1.11.0.rc1 is out

tinyorm 1.11.0.rc1 が出ました.以下の様な変更が入っています.

Connection を2つ持てるようになった

「Read/Write できる connection」と「Read だけができる connection」の2つの connection を持てるようになりました.tinyorm は内部でクエリのタイプ (read or write) に応じてこれらの connection を使い分けるようになっています.
これにより,write 系のクエリは master へ向いた「Read/Write できる connection」を使いつつ,read 系のクエリは slave へ向いた「Read だけができる connection」を利用するというような事が可能になりました.

今までは「Write 用の tinyorm」と「Read 用の tinyorm」をそれぞれ用意して利用するという感じで運用していたのですが,「Read 用の tinyorm」でひいてきた Row object に対して update をうっかり発行すると例外が上がってしまうという使い勝手の悪さがあり,今回このような改善を入れたという次第になります.

もちろん従来通り,単一の connection を利用することも出来ます.この場合はその1つの connection を全てのクエリで利用します.

Lazily な connection borrowing に対応した

Lazily な connection borrowing,つまり実際に connection が必要になったタイミングで connection を取ってくる (都度 establish するなり,connection pool から取ってくるなり) よう指定することが可能となりました.

方法としては,tinyorm を instantiate する際に javax.inject.Provider<Connection> を渡してやることで,connection が必要になったときに Provider<>#get() を利用して connection を borrow してくるようになります.なお,一度 borrow してからはその tinyorm instance 内では borrow した connection を使いまわします.

今後

内部的な変更が大きかったので,ひとまず rc 版ということで maven central にリリースしてあります.既存のテストケースは全て通っていますが,機能のすべてを保証できるとは言い切れないというステータスです.
安定して利用出来ることが検証でき次第,正式版としてリリースする予定です (rc が外れるタイミングでドキュメントが書かれる予定です).

以上です.よろしくお願いします.

2016-08-15 19:44 追記

rc1 は全く話にならず,現在は rc4 がお目見えしています.これはちゃんと動いてるのでもし試されるのであればこちらが良いです.

thin-cacheというJava向けのキャッシュライブラリ書いた

thin-cacheというキャッシュライブラリ書いた.いつものようにmaven centralにも置いてある.そしていつものようにJava 8以降が対象となっている.

maven central

Microservicesみたいな感じでサービスを作っていると,他のサーバにrequestを飛ばして得られた値を加工して,あるいはそのまま利用するみたいなコードを書く事が多くなってくる.毎回毎回他サーバにrequestを飛ばして良いのであればまあ飛ばせば良いのだけれど,そうもいかない場合も多い (トラフィックなり応答速度なりの理由で).そういう場合に対策として考えうる手段のひとつとしてキャッシュが挙げられると思う.
今まではそういったキャッシュを良い感じでコントロールするコードを都度都度書いていたのだけれど,それにもそろそろ飽きてきたのでいい感じに抽出してライブラリにした.

Javaでキャッシュ回りを司るライブラリは色々あると認識していて,例えばguava のcacheなどが挙げられる.最初からguavaが入っているのであれば良いんだけどそうではない場合にキャッシュの用途のみでguavaを入れるのもオーバーキルな感じを抱いており,シンプルなキャッシュ機能だけを提供するライブラリが欲しくなったのでthin-cacheを書いた次第.
シン・ゴジラが流行っているからこういう名前にしたわけではなく,薄くてシンプルなライブラリということでこういう名前にした.

機能

シンプルな使い方としては以下の通り.

final AutoRefreshCache<Long> autoRefreshCache = new AutoRefreshCache<>(10, false, new Supplier<Long>() {
    private long i = 0;

    @Override
    public Long get() {
        return ++i;
    }
});

autoRefreshCache.get(); // => 1L (initialize cache)
autoRefreshCache.get(); // => 1L (hit cache)

// 10 seconds spent...

autoRefreshCache.get(); // => 2L (expired, refresh cache)
autoRefreshCache.get(); // => 2L (hit cache)

第三引数として渡したSupplier<T>の実装により得られた値を,第一引数で指定した秒数分だけ保持するという,見ての通りの感じ (第二引数については後述).
キャッシュが効いている間にAutoRefreshCache#get()が呼ばれるとキャッシュ内容がそのまま返却され,キャッシュが期限切れになってから (つまり保持期限が過ぎてから) AutoRefreshCache#get()が呼ばれると supplier が実行されてその値を結果として返すと同時に,キャッシュに supplier の結果を格納しつつ保持期限の更新をしている.シンプル.

キャッシュを操作するメソッドとしては以下の4種類がある.

  • get()
  • forceGet()
  • getWithRefreshAheadAsync()
  • forceGetWithRefreshAheadAsync()
get()

Read throughにキャッシュを取得してくるメソッド.
キャッシュが存在しない (初期化されていないとか,expireしたとか) 時に,キャッシュコンテンツを生成しそれをキャッシュすると同時に返却する.

forceGet()

Write throughにキャッシュを取得してくるメソッド.
キャッシュが存在しているか否かに関わらず,常にキャッシュコンテンツを生成しつつそれをキャッシュすると同時に返却する.

getWithRefreshAheadAsync()

Refresh aheadにキャッシュを取得してくるメソッド.
キャッシュが存在しない場合,即座に古い (一世代前の) キャッシュコンテンツを取得・返却しつつ,そのバックグラウンドでキャッシュコンテンツの生成及びストアを非同期に行なう.戻り値としてはキャッシュオブジェクトとFutureのタプルを返しており,キャッシュコンテンツの生成とストアが完了するとそのFutureは完了状態になる.

なおキャッシュが初期化されていない場合はget()と同じ動き,つまり同期的に動作する.

forceGetWithRefreshAheadAsync()

Refresh aheadにキャッシュを取得してくるメソッド.
キャッシュが存在していようがいまいが,即座に古い (一世代前の) キャッシュコンテンツを取得・返却しつつ,そのバックグラウンドでキャッシュコンテンツの生成及びストアを非同期に行なう.以下略.

なおキャッシュが初期化されていない場合はforceGet()と同じ動き,つまり同期的に動作する.

工夫した点

工夫した点 (というか欲しかった機能) として,キャッシュ生成時に例外が発生した場合の動作を指定出来るようにしている.「例外が発生した時にそのまま例外を上流に放る」か,「例外内容をエラーログに出しながら (slf4jを使っている) キャッシュ済みの古いオブジェクトを返す」かを制御できるようになっている.例えば,他のサーバから値を取ってこようとする際に相手が500を返してくるケースとかは事実あると思っていて,そういう状況でこちらまで巻き込まれていては良くない.そういう場合でもサービスを継続できるようにすべく,このような状況下での動作をコントロールしたかったのでこういう機能を付けた.

所感

こういう小粒なライブラリでも,非同期性とかスレッドセーフとかを意識しつつコードを書くと少し複雑になることが分かった.毎度毎度こういうコードを書いていては身体や精神が持たないので,ライブラリにすることで再利用できるようにした.最近こういう小さなライブラリばかり書いている気がするのだけれど,どんどん小さなライブラリを作りつつ組み合わせて作っていくぞという精神世界になっている.

ところで,read through とか write through とか refresh ahead とか,そういう概念は学生時代に勉強したりパタヘネ本で勉強したりしていたはずなのにすっかり忘れていて厳しい気持ちになった.一方でこういうライブラリを実装しつつドキュメントを書くことで知識を定着させることが出来て良かったと思う.実感を伴わせることで知識が身につくという事を久々に実感できて体験があった.