たごもりすメモ

コードとかその他の話とか。

mrbgemをビルドしてテストを手元で走らせる(2017年版)

mrubyを使っていたところ、あるmrbgemが思った通りに動かなかったとする。あなたはまず真っ先に自分のコードを疑い、次にmrbgemの挙動を疑うであろう。
しかしmrbgemにはテストが十分に書かれていないかもしれない。ので、自分でテストケースを追加して手元で走らせ、様子を見たいと思うのが人として自然な成り行きである。

ところでmrbgemのリポジトリなどを見てもどのようにビルドしテストを実行させているのかが全く不明なケースが多く、なにこれどうやるの? という疑問を当然のように持つと思うので、そのような人のためのメモがこのエントリ。

ちょっと調べると以下のようなエントリがひっかかりますが、これは2年前のもので、この通りに build_config.rb を書いて上書きして走らせてもmrbgemのテストは実行されません。注意。
mrubyのモジュールmrbgemを開発した際に簡単にTravis CIを設定する方法 - 人間とウェブの未来

どうするか

現代(2017年10月)においては mrbgem のリポジトリのクローンからテスト実行までは以下のようにします。例として mruby-foo というmrbgemをどこかからcloneしてきて、その中のテスト(おそらく test/*.rb に書いてある)を実行するものとします。

手順としてはまず対象のmrbgemのcloneから。

$ git clone https://github.com/foobar/mruby-foo.git
$ cd mruby-foo

その中でさらにmrubyをcloneする(テスト実行用のmrubyバイナリをここから作る)。

$ git clone https://github.com/mruby/mruby.git
$ cd mruby

そのmrubyのビルド設定において、テスト実行時に有効になる設定があるのでそこに1行を加える。
この "conf.gem '..'" によりmrubyの親ディレクトリ(つまりmruby-foo)が依存関係に含まれ、かつテストも実行されるようになる。

@@ -111,20 +111,21 @@ end
 
 MRuby::Build.new('test') do |conf|
   # Gets set by the VS command prompts.
   if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
     toolchain :visualcpp
   else
     toolchain :gcc
   end
 
   enable_debug
+  conf.gem '..'
   conf.enable_bintest
   conf.enable_test
 
   conf.gembox 'default'
 end

あとは rake test すればよい。末尾にテスト実行結果が出てくるはず。おしまい。

>>> Test test <<<
mrbtest - Embeddable Ruby Test

...................................................................................................?.........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................?..........................................................................................................................................................................................
Skip: Struct.new removes existing constant  redefining Struct with same name cause warnings
Skip: Module#prepend super in alias  super does not currently work in aliased methods
Total: 982
   OK: 982
   KO: 0
Crash: 0
 Time: 0.58 seconds

................
Total: 16
   OK: 16
   KO: 0
Crash: 0
 Time: 0.56 seconds

余談

ところでこの mruby のテストで使える assert がわからん。以下のように書いても OK と言われてしまう。どういうこと?

assert('Must fail') do
  1 == 0
end

追記(assertについて)

上述の余談の件、わかった。最近に mruby/test/assert.rb における assert の挙動が変更されていた。
github.com

ということで上記のような(falseを返したらfailすると期待しているような)テストケースは全部動かなくなっている。以下のように明示的に assert_* メソッドを書くべき。

assert('Must fail') do
  assert_equal 1, 0
end

これ、今までいくつかのmrbgemを見た記憶によると(特に大量にmrbgemを書いている何人かがauthorの)mrbgemのテストが軒並みぶっこわれてるんじゃないなあ。きびしい。

ISUCON7予選で敗退した

あー、負けたー。「Asakusaの方から来ました」というチーム名で、Asakusa.rbでよくいっしょする @joker1007 さん、および @yancya さんと出た。最終結果は111400くらい? ただし20時*1を過ぎても4〜6万くらいをうろうろしてて、最終的には20:10頃に入れた変更でスコアが倍になり、なんだこれー? と言ってる間にタイムアップした。
去年のISUCON6決勝はやるべきことをやれなくて負けたので素直に悔しかったけど、今年はなんか問題設定の意図がまったくわからなくてそのまま不完全燃焼で手が停まって4時間経過で死んだという感じなので、なんだかなー、という気分。あんま疲れないまま終わってしまった。

たぶん出題者にはお礼の言葉が今週のblogエントリ等では並ぶと思うので、負けた人間としてはここで一発だけdisっておきたい。結果論として、問題設定としてはISUCON4決勝の問題で Cache-Control をみんなが有効にした後の状況を作りたかったんだろうなー、という気がする。けど、ハイコンテキストすぎるだろ。あと Cache-Control: public を明示的に有効にしないとキャッシュしてくれないっていうのは明らかに特定のCDNを意識していると思うんだけど、参加者に複数ホストを最初から与えておいて、最終的にやらせたかったのは外部サービスにいかにキャッシュさせるか*2ってことですか?

いやまあ、そういう問題だったし、そこをクリアした上で勝負を争えという問題だったんでしょうね。そういう問題だと気付いた時点で残り1時間もなくて、潜在的に抱えこんでいた問題に対処しきれず負けたのは、もう普段Webサービスから遠ざかった自分の実力なんだろうと思います。
最終的にはこれCDNかということに気付いたけど、そのときに周囲のチームのようにはスコアが上がりきらなかったのは、完全に自分のオペミスのせいだった。これだけは本当に言い訳ができないミスで、チームメイトに申し訳なかったなと思う。あの1手で本選出場を逃したと言っても過言ではない(詳細は後述)。

予選環境は快適でした*3ベンチマークもエンキューした直後には始まる状態がずっと続いていて、1チームに複数サーバを与えるということも含めて、さくらインターネットさんの担当側は大変だっただろうなと思います。お疲れさまでした&ありがとうございました。
環境構築であれこれあったためかスケジュール上もあれこれあって、運営の941さんも物凄く大変だっただろうなと思います。お疲れさまでした。いつもありがとうございます。

経過

joker1007さんのエントリに細かい経過がある。

https://cdn-ak.f.st-hatena.com/images/fotolife/j/joker1007/20171023/20171023022330.jpg

SSHログイン含めてちょっと最初の環境整備でバタついて、結局この図を描いたときにはスタートから1時間半くらい経ってたと思う。負荷とトラフィックをうまく3台にバラす、ということをいつもの決勝の通りメインで考えていた。WebDAVと言ったときはまたWebDAVかと自分でも思ったけど、あとで考えたらS3的なオブジェクトストレージが欲しいときに簡単に実現するための自分の発想がWebDAVなんだなと。

メッセージまわりのコードの変更はあとの2人にお願いして、自分は画像をうまく配信するためにhttp_dav_moduleを有効にしたOpenRestyのビルドと設定、あとは手元でもちゃんと動くようにRubyのアプリケーションでもPUTを受け付けてローカルに書き出すリクエストハンドラを追加したりしてた*4

で、多少ミスったりしつつ*5もしつつ、3ノード全部で画像をnginxから直接返せるようになったのがこのcommit、17:25。けっこう時間かけちゃってるなあ。

fix to upload icons to servers · tagomoris/isucon7-elimination@b380245 · GitHub

で、ネットワークが詰まって、なんだこりゃーとなった。今にして思えば画像をDBから出して直接nginxから返すのだけを初手にやって、それを複数台構成に直す、としていればたぶん今回のミスは踏まずにすんで、普通に戦えてたんだろうなという気はする。いきなり最初からベストな最終形を描いてそこを目指して進むのは自分の仕事の進めかたのよくない所だろうかという気はちょっとする。……んだけど、うーん、普段はそっちの方が効率いいんだよなあ。

この作業のとき、以下のような手順を踏んだ。これが後にして思えば決定的なミスだった。

  1. スクリプトを書いてDBからファイルシステムに画像を書き出す (OK)
  2. 書き出した画像をgitリポジトリに突っ込む (どちらかと言えばNG、ただし決定的ではない)
  3. サーバ3台でそれぞれ git pull で画像をfetch (OK)
  4. WebDAV用の領域に画像を cp -r で移す、というコマンドを3台でそれぞれ叩く (完全NG)

最後の手順により3台のサーバでそれぞれ初期の画像セット(67ファイル)のタイムスタンプがズレて、最終的に Cache-Control: public がついた状況でCDN(と想定されるクライアント)側にうまくキャッシュが保持されなかった。
これ、単に tar.gz で画像ファイルを固めて各3台のサーバのWebDAV領域でそれぞれ展開していれば全く問題なく処理されてただろうし、あるいは cp するときに -a つけていればそれでもよかったはずで、今となってはそのあたりの手順を当然のようにとってても不思議ではなかったと思うんだけど、なんか、そうしてしまった。本当に悔やまれる。

その後、ネットワークが詰まった状態になったのはすぐに分かったので Cache-Control のことはすぐに思い付き nginx で expires の指定を入れるものの、まったく状況が改善せず、なんで? と不可解な状況に陥った。
tcpdumpをとって調べたんだけど、Last-Modified の入ったレスポンスを返していれば普通のWebブラウザ等のクライアントであれば当然入ってるはずの If-Modified-Since の指定が入っておらず、でも周囲のスコアを見てみると画像のバイナリを返していれば不可能なスコアを達成しているのは明らかで、どういうことじゃこれは、と悩み続けてた。

特に悩ましかったのがこれ*6で、同じ静的ファイルなのに違う傾向のリクエストを送ってくるCDNがあるなんて想定してなかったから、完全に思考が空転してた。ずっと「このクライアントはなんなの?」って言い続けてた気がする。

ずーっと煮詰まってたんだけど、20:10のこのcommit、半笑いで「まっさかねー」と言いつつ Cache-Control: public を入れた。ベンチ回してトイレいって、戻ってみたらスコアが倍増しててうぎゃあコイツCDNだ! とか言ったような気がする。

add Cache-Control: public explicitly · tagomoris/isucon7-elimination@079a30a · GitHub

ここで11万点まで上がったんだけど、しかしそれでもネットワーク帯域は使い切った状態で、なんで? と言い続けてた。
これは自分のミスでハマったところ。最初に撒いたファイルの Last-Modified および ETag が各サーバで異なってたため、CDNを想定したクライアントのエッジキャッシュヒット率が 1/3 に落ちていたんだと思う。んでそのたびに画像をダウンロードしに来ていたから初期データへのアクセスでネットワークをまず使い潰した。これでベンチマーカーの負荷も上がらず、結局初期データへのアクセスだけが続くようになってドツボにはまったっぽい。

その後にアップロードされてくるデータをサーバ3台に渡すのは、まあちゃんと気を使ってなかったとはいえ、1秒をまたぐこともほとんどなかったはずなので、概ね大丈夫だったはずと思っている。もちろんもっとちゃんとやりようはあったけど。
とはいえ「このクライアントなんなの?」という疑念が強過ぎて、Last-ModifiedおよびETagをちゃんと返せているかというところまで頭が回らなかった。CDNがエッジキャッシュを作るときには当然そこを厳密に見ているはず、という発想もなかったので、これは、うーん……もうちょい時間があればなあという気もするけど。うう。

他の2人がちゃんとメッセージ回りのロジック改善をやってくれていたので、あとは /icons の初期データさえどうにかなっていたらだいぶ上位のスコアが出てたんじゃないかなあという気がする。心残りは大きい。

結論

頑張って仕事しよう、と思ったものの、正直こっちの方向にISUCONが行くといくら自分の仕事してても弱くなる一方なので、引退かなあ、とか考えつつある。
去年の予選は*7はてなのサービスだ! というのがわかったので面白がれたところはあったけど、今年のはもう、なんなの、ちょっと無理、みたいな気分。

*1:今回は13時〜21時というスケジュール

*2:しかもそんなものが存在するという前提は教えない

*3:……fail2banを除いて。メンバーのひとりでもちょっとログイン試行に失敗を繰り返すと全員がbanされてたので、最初にちゃんと全員のvalidな公開鍵を入れ終わるまでちょっとトラブってたのはきつかった

*4:ISUCON3の決勝のときにもほぼ似たような構成だったけど、このときは何も考えずにWebDAVが有効なnginxが無いと動作確認もできない状態にしてしまって難儀した覚えがあったので、その教訓から

*5:OpenRestyビルド時に --with-ipv6 つけ忘れてアーッってなったり。IPv6がちゃんと最初から有効なの、さくらインターネットさすがだな! って感心しながらビルドしなおした。

*6:あとでfujiwaraさんもなんかtweetしてたので確認ミスではなかったようで、ちょっと安心している

*7:競プロ勢向きだなー! と思いはしたものの

Javaのリリースサイクル変更により Oracle JDK が一般ユーザに提供されなくなるのではという話があったけど誤読ではないかという話

雑に書くぞ!

Faster and Easier Use and Redistribution of Java SE | Oracle Java Platform Group, Product Management Blog

このエントリを読む限り、Oracle JDKのダウンロードが商用サポート契約者にのみ限られる、という話は書いていない。

The Oracle JDK will continue as a commercial long term support offering
* The Oracle JDK will primarily be for commercial and support customers once OpenJDK binaries are interchangeable with the Oracle JDK (target late 2018)
* Oracle will continue to enhance the packaging and distributing of complete ready-to-run applications

"The Oracle JDK will *primarily* be for commercial and support customers ..." とあるけど、わざわざ primarily とついてるってことは secondarily があるってことで、それは通常の(今までどおり)テキトーにダウンロードしてたユーザのことじゃないの?

the Oracle JDK will remain as a long term support (LTS) offering for Oracle’s commercial and support customers.

Oracle JDK はLTSの提供のために残る、とあるが、サポートサービス提供の対象が Oracle JDK であるというだけで Oracle JDK の利用はOracle顧客に限られる、とは書いていない。

Commercial Features packaged separately from the Oracle JDK, such as the Advanced Management Console, will continue to be provided separately through Oracle’s “Java SE Advanced” commercial offering.

商用専用の機能は Oracle JDK とは別にパッケージされ、"Java SE Advanced" の顧客に、Oracle JDKとは別に("separately")、提供される。これはわざわざバイナリを別パッケージでクローズドチャネル経由で提供すると言ってる。Oracle JDK自体には言及していない。

Oracle Java SE サポート・ロードマップ

たこちらの日本語の公式情報を読んでも、Oracle JDKは商用チャネルのみでのダウンロード、とは書いていない、ように読める。

以下で説明される Oracle JDK サポート・ロードマップの要点を述べるとすれば、2018 年 9 月を過ぎると商用利用目的の Java SE 8 の更新版は公式ダウンロード・サイトに掲載されなくなることです。Java SE 8 及びそれ以前のバージョンに対する重大なバグ修正、セキュリティ修正及び一般的なメンテナンスへの継続的なアクセスが必要なお客様は、Oracle Java SE Advanced, Oracle Java SE Advanced Desktop または Oracle Java SE Suite によって長期サポートを入手することができます。その他のユーザは、Oracle JDK または OpenJDK の最新のメジャーバージョンにアップグレードすることを推奨しています。

Java SE 8への継続サポート(が適用されたバイナリ)が必要な人はサポート契約が必要、それ以外の人は "Oracle JDK または OpenJDK の最新メジャーバージョンにアップグレードすることを推奨" している。ということは普通に Java 9 の Oracle JDK はダウンロード可能なんじゃないの? ということ。
Java SE 8 への長期サポートが必要なら契約してねっていうのはまあ普通ですよね。(嫌な人は古いバイナリを使い続けるなりOpenJDKを使うなりすればよい……よね?)

ということで

まあ実施されてみないと実際のところわからない*1けど、たぶん、騒ぐようなことじゃないと思いますよ。

追記

えー。うーん。まあでも、わかんないな。

*1:あるいはOracleの人がなんか追加で言うかも

Rubyで try-with-resources (あるいは with/using)をやりたいという話

前のエントリで書いたうちに入っていたが、Rubyでも try-with-resources (あるいはPythonで言う with やC#で言う using)が使いたいなー、という話。

Feature #13923: Idiom to release resources safely, with less indentations - Ruby trunk - Ruby Issue Tracking System

Ruby本体にFeature request出してみたんだけど、ライブラリでやればいいんじゃないの、という結論になってしまって、ええーと思っている。ライブラリだといくらでもある方法のひとつになってしまって、他のプログラマ(他所のライブラリ作者とか)に「これを使え」という話がしづらいんだよねえ。あとgemにして広く使われたとしても、この機能のバージョン指定がgemどうしで衝突してトラブルとか、死んでも死にきれない感があるし。
どうせやりかたの選択肢がほとんどないんだから、こういうものは言語側で方法を提供してほしい、と思ってはいる。

どういう用途でこれが必要なのかというと複数のリソースを確保しつつ確実に(逆順で)解放してほしいというケースで、最近思い付いた良い例としては (1) データベースへの接続を作る (2) その接続を用いて PreparedStatement を作る、みたいなものが当たる。

begin
  conn = Database.new(addr, port, dbname)
  begin
    stmt = conn.prepare(sql)
    # stmt.execute(...)
  ensure
    stmt.close
  end
ensure
  conn.close
end

Rubyであればopenとブロックを使ってこのリソース確保と解放をする例が多いと思うが、これはリソースひとつにつきインデントがひとつ深くなるので簡単にネストの深いコードになり、全体として簡潔とは言いがたい見た目になる。

Database.new(addr, port, dbname) do |conn|
  conn.prepare(sql) do |stmt|
    # stmt.execute(...)
  end
end

たこういったブロックを渡せるメソッドがそれぞれのクラスで実装されている必要がある、が、現実としてそうなっていないケースは多い。例えば上記のコード例では conn.prepare にブロックを渡せる必要があるが、mysql2 の prepared statement を作るためのメソッドには実際にはブロック渡しはできない。

ところでライブラリとして実装してみる

とはいえ、とりあえずライブラリとしてでも実装がないと話にならない、みたいな面もあるのかなと思ったので、自分でエイヤと実装してみた。bugsのやりとりではdeferでやるという方向に倒れかけてたけど、自分の好みじゃない*1ので、実装するなら自分が書きたいように書けるようにしてみた。

github.com

これで上記の例は以下のように書ける。

require "with_resources/toplevel"
using WithResources::TopLevel # once per file

with(->(){ conn = Database.new(addr, port, dbname); stmt = conn.prepare(sql) }) do |conn, stmt|
  # stmt.execute(...)
end

簡潔! 何よりネストが深くならない!

見た目としてはJavaのtry-with-resourcesに似ていて、動作もそっくりそのまま……のはず。リソース解放時などに複数の例外が起きたときはふたつめ以降の例外は `e.suppressed` で参照できる配列*2に格納されるようになっているのも try-with-resources のまま。

キモとしては with に渡した lambda に複数の文が書け、そこのローカル変数にセットした値が後のブロックにも渡ってくるというところ。これはキモい。
TracePointとbindingを使ってあれこれやった。ちょいマジカルで、かつ実装としてはちょっと不安定だと思う。リソース解放用のブロックに存在するローカル変数だけをキャプチャしたいんだけど、TracePointを使ってあるブロック内に存在する記述だけを正確に追いかけることが現状不可能なことによる。変な書き方したら動作がおかしくなるかも。

ということで

作りました。こんなんどうですかね、という提案に近いですが。

*1:golangのdeferはその関数の終わりで自動的に解放されてインデントも増えないのに対して、Rubyで自分で実装する方向だとブロックをひとつ作らないといけなくてgolangみたいに簡潔な記述にならないのが気にくわない

*2:extendで動的に足している、まじべんり

RubyKaigi 2017で広島に行ってきた&しゃべってきた

いやー、よかったですね。旅行としても楽しかったし、いいカンファレンスでしたね。

f:id:tagomoris:20170926132255j:plain

rubykaigi.org

自分が出したTalk proposalも通ったことだし、わいわいと広島まで行ってきた。非常によくオーガナイズされて会場では快適に面白い話ばかりえんえん聞けるし、そのへんにいる人達といろんな議論をしていろいろ良いことがあった。すばらしいカンファレンスでした。主催者ならびに運営協力のかたがた、ありがとうございました。前日から最終日まで、夜のパーティーも切れ目なくたくさんあってすごかった。

しゃべってきた

f:id:tagomoris:20170926132812j:plain
(photo by @koichiroo)

自分のトークのスライドはこちら。

動画ももう上がってた、すごい。内容で聞きたければこちらをどうぞ。YoutubeのサムネイルはなぜかDukeがデカデカと映っていますが、これは何かの間違いです。ちゃんとRubyKaigiのトークです。だ、だいじょうぶ……。

内容としては最近仕事でやっているプロジェクトにからめて、その仕事でのRubyの使い方と、ついでにRubyで分散ストレージシステム(のクローン)を書いてみたんだけどそこで感じたRubyに足りない機能、みたいなのをあれこれ話した。
使われかたがやっぱりWebに偏っているせいか、いざストレージミドルウェアを書いてみようとするとあれこれ面倒な書き方をしないといけない部分が多くて、これはもうちょっと言語機能としてどうにかしてほしい、みたいなのをあれこれ挙げたつもり。このへんが良くなるとTreasure DataとしてはRubyで書ける部分がさらに増えてすごく助かる。

で、ちょうどRubyコミッタの人達が周囲にゴロゴロしているので、これ幸いとそのあたりの機能のアイデアについてどう思うか聞いたりしてた。TDにもRubyコミッタは(何人も)いるが、どうしても同僚だとユースケースをよくわかっている分、意見に偏りが出る気はする……ので、外部のコミッタとあれこれ議論できる機会はすごく大事だと思う。
結果的にあれこれチケット登録したりパッチ書いたりしつつ、その内容を議論の結果をフィードバックさせてアップデートさせたりできてすごくよかった。

以下が自分が関わってて、Kaigi中に報告したり作成したり(過去に報告してて)コミットされて修正が完了したチケットとpull requestのリスト

ということで、RubyKaigi中とその前後だけでRuby trunkがだいぶミドルウェア書きやすい言語に向かって進んでいる。自分にとってはすばらしい会期だった。Yay!

行ってきた

参加者側としては、自分のトークが3日目だったのもあったし、周囲の人と話すのを優先していたといえばそう、なので、各トークは聞いたり聞かなかったり。しかし今年はとにかく型の話が多くて、自分も上記リストに含まれてる通りちょっと興味のあるトピックだったから、面白く聞いていた。
全体的にコードの話が多くて、というかコードの話しかなくて、いいなあと思う。ふらっと入ると面白い内容の話をしてるもんね。あとLTで @joker1007 さんと @k0kubun さんが勢いのある変態コードの話をしてて最高。その話、ふつうにメインのトークにproposal出そうよとは思うけど。w

ということで、おおむねふらふらとしつつ楽しんでいた。

で、まあお昼ごはんとかね、当然広島だからお好み焼きも食べるし、汁なし担々麺が名物らしいと聞いたらそれも食べるし。おいしかった。

f:id:tagomoris:20170926134140j:plain

夜はパーティーがあるとはいえ、当然その後もよさそうな店を見付けて飲みに行くわけじゃないですか。なんか*1異常に安いし、すごいおいしいんですよ。天国。

f:id:tagomoris:20170926123733j:plain

特にこの松茸と鱧の土瓶蒸しがもう大変にけしからん味で、この汁だけで日本酒がいくらでも飲める。けしからん。最高。最高でした。

f:id:tagomoris:20170917221142j:plain

Kaigi後の観光

せっかく広島まで行ったので、1日帰りを遅らせて2日間つくってちょっと観光してきた。広島城とか宮島とか呉とか。広島城はコンクリ城だけど、まあ、まあ。どっちかというと平城のくせに堀のあたりに風情あってよかった。

f:id:tagomoris:20170926134227j:plain

宮島は厳島神社*2修学旅行で行ったのでほどほどにして、裏にある弥山をガッと登った。思ってたよりハードだったけど、なんとか。こちらも巨大な岩の下をくぐって山頂に到達するなど、独特の趣があってよい。

f:id:tagomoris:20170926131708j:plain

呉は雨が降ってたので、資料館をぶらぶらするのを中心に。街中の空中に突然潜水艦が登場するのとか、東京ではちょっとありえない趣がいいねえ。海上自衛隊の資料館をぶらぶらしたり、大和ミュージアムを回ったり、埠頭の展望台から馬鹿デカいコンテナ船が整備中(?)だったり建造中だったりするのを眺めて一人で盛り上がったり。いやー、港の街はいいなー。

f:id:tagomoris:20170926131720j:plain

で、観光したらおなかが減るじゃん?

f:id:tagomoris:20170926131729j:plain

もうね。

f:id:tagomoris:20170926131743j:plain

来年の仙台も楽しみですね!!!

*1:東京と較べると

*2:小学校の頃だけど……

Stream Processing Casual Talks 2 にいってきた & しゃべってきた

開催されたので参加してきた。
ついでにちょっと前に社内でのやりとりで思い付いてそのまま温めてたアイデアがあったので形にしてしゃべってみた。

connpass.com

しゃべってきた

社内でしゃべってたのは、ストリーム処理クエリって結局自分がもってるデータしか参照できなくて、じゃあ3年前のデータとJOINしたかったらクエリを3年走らせつづけないといけないの? 無理じゃね? みたいなあたり。過去データを参照したいケースは(特にビジネス要件のクエリで)普通に存在するいっぽう、現行のストリーム処理エンジンはクエリを開始した時点から受け取ったデータそのものしか参照できない*1

んで、いやよく考えたら過去データからストリーム処理クエリの計算中の状態を再現できれば、クエリ開始時に3年前からのデータをどこかからもってきて処理を起動できる、ストリーム処理系の脇にあるバッチ処理系を前提にしてこれができたらどんな過去データを扱うストリーム処理クエリも書き放題で、しかも好きなタイミングで再起動できるじゃん!!!! すげえ!!!! みたいなアイデアがある。SQLならできる。

これはいろいろ制約があって汎用の分散処理エンジンではなかなか難しかったりするのと、そもそもストリーム処理系とバッチ処理系で同じデータと同じ処理を前提に……これはラムダアーキテクチャというやつだ! ということでがーっとアイデアをまとめた。このアイデアの副産物として、同じ処理を内包する複数のストリーム処理クエリがオペレータ(とその内部状態)を共有できるというメリットもおまけについてきて、これが実現できればさらに基盤全体のスケーラビリティが上昇する。これはきた!!!!

まあ、分散処理版のNorikra(通称Perfect Norikra)はまだ1バイトも存在しないんだけど。スライド最後にちょっと書いたが、実は名前は既に決まっている。英語で普通の単語に聞こえ、かつユニークネスがある程度ありそうな単語を探すのは骨なので、プロダクト名が既にあるということはプロダクトを書き始めるまでの最大の問題が解決しているということだ。すばらしい。

いってきた

NiFiねー、とか、Hortonworks Streaming Analytics ManagerはUIよくできてんなー、とか。しかし全体としては面白技術という感じでもなくなって、みんな細かい改善がんばってんなあ、という気分。Apache Beamに乗るのはこれだから嫌なんだという感じがする。
ともあれ最近リサーチもサボってるので界隈の話題をひとさらいできてよかった。

*1:もちろんコードを何でも書ける処理エンジンであれば独自に外部データを参照してそれとJOINすることはできる、が、それはストリーム処理クエリがサポートしているとは言えないと思う。何でも書けるコードの中で勝手にやっているだけで、つまりそれはクエリ処理エンジンが分散をよろしくやれる範囲の外ということ。

System.nanoTime() をJMockitで乗っ取る

Javaミドルウェアなど書いていると必然的に内部でタイマーを作ったりすることになってその制御に System.nanoTime() とかを使うことになると思うんだけど、じゃあテストどうしよっかと思うと、テストでは、まあ好きなタイミングで時間を進めたり好きなところで時間を止めたりってことが普通したいじゃないですか。どうすんのという話。

JMockitを使う

とりあえずJMockitを使っとけという話っぽい。static methodをモックできるのはこれしかない*1

JMockitは理想的なモックフレームワーク - じゅんいち☆かとうの技術日誌

ちょいちょい調べると例が見付かるので真似る。

import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;

import mockit.Expectations;
import mockit.Mocked;

public class FooTest
{
    @Test
    public void testTimer(@Mocked System unused)
    {
        long current = System.nanoTime();
        new Expectations() {{
            System.nanoTime(); result = current;
        }};
        assertThat(System.nanoTime(), is(current));
    }
}

JUnit4に怒られる

コンパイル通ったぜー、と思うとなんかテストでコケる。

java.lang.Exception: Method testTimer should have no parameters

調べてみたらこんなのが見付かった。要はJUnitよりも前にJMockitを書いておけと。えー、と思いながらとりあえず build.gradle を変更してみた。

dependencies {
    // jmockit should be written before junit
    // http://robertmarkbramprogrammer.blogspot.jp/2013/03/eclipse-and-jmockit-method-should-have.html
    testCompile group: 'org.jmockit', name: 'jmockit', version: '1.33'
    testCompile group: 'junit', name: 'junit', version: '4.11'
    testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'

    // ...
}

そしたら期待通り動いてしまった。えー。ジャバむずかしいな。

(追記) なんか期待通りに動かない

複数のテストケースでそれぞれ違う値でモックしようとしたら結果がおかしくなる……みたいなのが多発してこれはやばいということになった。調べるとJITが走ったらダメになるみたいな話もあるし、ここで苦労するべきではないと判断。撤退。

*1:今でもそうなのかな? まあ、たぶんそう