jackson-dataformat-msgpack
数ヶ月前から
というのを細々と作ってました。
これは何かというと、
msgpack/msgpack-java at v07-develop · GitHub で開発が進められているMessagePack Javaのv0.7系の実装と
FasterXML/jackson-databind · GitHub の豊富なデータバインディング機能を連携させるライブラリです。
作った背景ですが、
- 性能面およびメンテナンス性において大きく改善すべく開発が進められているmsgpack-java v07では、v06まで提供していたある程度のデータバインディング機能を自前で提供する予定が今のところ無いので何かしら必要
- そもそものmsgpack-java v06までのデータバインディング機能の機能的な制限が結構辛い
といった感じです。
現状でもv06で扱えない色々なケースに対応しているっぽいので便利っぽい(ぽいぽい)。
- Nested maps and arrays supported? · Issue #131 · msgpack/msgpack-java · GitHub
- Working with enums · Issue #135 · msgpack/msgpack-java · GitHub
- Cross-platform Objects · Issue #136 · msgpack/msgpack-java · GitHub
本家jackson-databind (JSON format)と比較すると(
https://github.com/komamitsu/jackson-dataformat-msgpack/tree/master/src/test/java/org/msgpack/jackson/dataformat/msgpack/benchmark
)性能的には下記のような感じで 1.1 ~ 1.5 倍くらい遅いです。とりあえず手なりで書いて、msgpack-java v07自体で改善した方が良さそうなところを直してもらって、位しかまだやってないので、そろそろjackson-dataformat-msgpack自体の性能を改善する時期なのかもしれないです。
normal object mapper: deserialize(huge_data) => 354 msgpack object mapper: deserialize(huge_data) => 371 normal object mapper: serialize(huge_data) => 130 msgpack object mapper: serialize(huge_data) => 137 normal object mapper: serialize(pojo) => 1288 msgpack object mapper: serialize(pojo) => 1594 normal object mapper: deserialize(pojo) => 2075 msgpack object mapper: deserialize(pojo) => 2998
あと、msgpack-java v07は現状Android (Dalvik VM) では動かないので、結果的に jackson-dataformat-msgpack でも動かない状態なので、後で msgpack-java v07 自体のほうも対応しないとなぁとか思ってます。
とりあえず、割と使い勝手が良い感じがするので使って頂いて、フィードバック等あればissueに突っ込んで頂けると嬉しいです。
chef-client実行時のエラーをHipChatに通知する方法
TreasureDataではエンジニア間のコミュニケーションツールとしてHipChatをがっつり使っているのですが、同じくTDで使われているchefのchef-client実行時のエラーをHipChatの部屋に流すようにしてみました。という小ネタ。
Chefには About Handlers — Chef Docs という機構があって、chef-clientの起動、成功・失敗時に発動する処理を登録できます。特にexception handlerはchef-clientが正常に処理できなかった場合に何かしらの通知をする、といった使い方ができるので有用かと思います。
Chef::Handlerを継承して自作のHandlerを作ればぶっちゃけ何でもできてしまうのですが、今回はできるだけコードを書かずに目的を達成してみます。自分で色々なHandlerを作ってみたい場合は前述のリンクを一読してみることをお勧めします。
まずはじめに、cwjohnston/chef-hipchat · GitHub にHipChat用のcookbookがあるので、これを利用可能にしておきます。
この cwjohnston/chef-hipchat · GitHub は mojotech/hipchat · GitHub で提供されているHipChat用Handlerを、error_handlersに簡単に登録する実装を持っているので、これを有効にします。これによってchef-clientの失敗時にHipChatの任意のroomに通知が飛ぶようになります。対象となるchefのroleに以下のような変更を加えることで、この機能が有効になるかと思います。
run_list( : "recipe[hipchat::handler]", : ) override_attributes( : :hipchat => { 'handler' => { 'token' => '123a456b789c098d765e432f', 'room' => 'ChefAlert', 'enabled' => true, } },
ここで、'token' はHipChatのAPI Token, 'room' はHipChatの通知先を指定してください。
chef-clientの失敗をHipChat等に通知させるようにしておくと、たまに意外なエラーが発生しているのがわかって興味深かったり、致命的な問題を未然に防ぎやすくなるのでおすすめです。
コンパイル時に特定のAnnotationが付いているClassのmodifierをチェックする方法
先日、
@ Messageのついたunpack対象のclassの定義に public staticとつけていないのが原因だった。
— Kumazaki Hiroki (@kumagi) 2014, 2月 28
@komamitsu_tw これ、コンパイル時に怒らせる事ってできないんでしょうか。できないんですよねリフレクションだから…
— Kumazaki Hiroki (@kumagi) 2014, 2月 28
という事例を聞いたので何とかコンパイル時に検知出来ないかぼーっと考えてて、プリプロセス的にチェックできないか思いついたので試してみました。
#ちなみにTweet上ではリフレクションとあったけど、多分実際はJavassistを使ってる気がする
今回サンプルとして動かしてみたのは、@Helloというannotationがついたクラスはstaticじゃないとエラーにするやつ。
org.komamitsu.pptest.annotation.Hello
package org.komamitsu.pptest.annotation; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) public @interface Hello {}
org.komamitsu.pptest.Main
package org.komamitsu.pptest; import org.komamitsu.pptest.annotation.Hello; public class Main { @Hello static class A { } @Hello class B { } public static void main(String argv[]) { System.out.println("helloworld"); } }
こんな感じになっている場合に、コンパイル時にclass Bをエラーとしたい。
で、プリプロセス的なタイミングでチェックするやつがこれ
org.komamitsu.pptest.preprocessor.Hello
package org.komamitsu.pptest.preprocessor; import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.annotation.processing.*; import javax.tools.Diagnostic.*; @SupportedAnnotationTypes(value= {"*"}) public class Hello extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element elm : roundEnv.getElementsAnnotatedWith(org.komamitsu.pptest.annotation.Hello.class)) { Set<Modifier> modifiers = elm.getModifiers(); if (!modifiers.contains(Modifier.STATIC)) { processingEnv.getMessager().printMessage(Kind.ERROR, "@Hello: Only accepts static class. " + elm); } } return true; } }
一応、これも。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.komamitsu</groupId> <artifactId>pptest</artifactId> <version>0.0.1-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
Main classとかをコンパイルするときにorg.komamitsu.pptest.preprocessor.Helloを挟んであげれば良さそうなのだけど、スマートな方法が思いつかず以下のような手順で...
$ pwd /Users/komamitsu/Dev/workspace/pptest $ mvn clean package : $ cd src/main/java/ $ javac -cp ../../../target/pptest-0.0.1-SNAPSHOT.jar -processor error: @Hello: Only accepts static class. org.komamitsu.pptest.Main.B 1 error
本当はちょこっとwarningが出てるんだけど、見辛いし本筋と関係ないので省略。
とまあ、こんな感じでコンパイル時にチェックできそうなんだけども、これをどうmsgpackに組み込むか(ユーザーが特に何もせずに当該チェックが作動するか)が思いついてない...
Raspberry PiでFluentdを立ち上げてTDにデータを流してみた話
一ヶ月程前に同僚である @doryokujin 先生からRaspberry Pi(面倒なので以下Pi)を頂いたのだけど、Piに必要なSDカードが手元に無くてすっかり放置していたのですが、満を持してSDカードを買ってきたのでちょっとPiを立ち上げてみた。という話を備忘録的に。
必要なもの(というかこれで間に合った、的なもの)
- USBケーブル
- RCAケーブル
- USBキーボード
- LANケーブル
- SDカード(SDHD 8GB)
startxとか叩かなければUSBマウスは要らない。
Piの立ち上げ
- 準備に使った環境はMac OX 10.8.5
- SDカードは挿してある状態
- SDカードの/dev/disk番号を確認してunmount
$ diskutil list /dev/disk0 : /dev/disk4 #: TYPE NAME SIZE IDENTIFIER 0: FDisk_partition_scheme *7.8 GB disk4 1: DOS_FAT_32 NO NAME 7.8 GB disk4s1 $ diskutil unmountDisk /dev/disk4 Unmount of all volumes on disk4 was successful
- OSにはRaspbianを選択。2014-01-07-wheezy-raspbian.zip を落としてきて展開したimgファイルをddで書き込み。かなり時間がかかる
$ sudo dd bs=1m if=~/Downloads/2014-01-07-wheezy-raspbian.img of=/dev/disk4 Password: 2825+0 records in 2825+0 records out 2962227200 bytes transferred in 1071.833726 secs (2763700 bytes/sec)
- ddでイメージが書き込まれたSDカードをPiに挿してMBPとか適当なやつからUSBケーブルをつなげて電源を供給して上げれば起動
- 自宅にディスプレイが無いので、あまり使われていないアナログテレビを用いて動作確認。RCAケーブルの黄色いやつ(映像用)をつなげれば滲んだ雰囲気の画面で確認出来る
- Setting画面が表示されてユーザーのパスワードの変更とか色々できるので、やりたい場合はやる感じで。SSHの設定に関しては失敗してたけどひとまず先に進む
- Settingを終えるとlogin promptが表示されるのでlogin。sshdも起動していたのでログインしてみたら普通に入れた。
- Rubyは1.9.3, Javaは1.7が入っていて良い感じ
Fluentdとかを入れてTDにデータを流し込んでみる
- Fluentdの安定版であるtd-agent(http://docs.fluentd.org/articles/install-by-deb)入れようかと思ったけど、何となくgemから入れてみた。FluentdはC拡張のビルドが発生するので、ruby-dev的なものを入れておく
$ sudo aptitude install ruby-dev $ sudo gem install fluentd
- TreasureDataにデータを入れるのでfluent-flugin-tdも。というか事前にTDのアカウントが必要なので適当に作っておくのが前提
$ sudo fluent-gem install fluent-plugin-td
- fluentdのconfファイルを作成(/home/pi/fluentd/fluent.confとか)。入力はin_forwardだけの超シンプルな設定。出力はout_td。
<match td.*.*> type tdlog apikey <replace this with your TD API key!> auto_create_table buffer_type file buffer_path /home/pi/fluentd/td </match> <source> type forward </source>
- fluentd起動
$ fluentd -c /home/pi/fluentd/fluent.conf
fluentdデーモンとして起動したい場合は以下のようにしても良いかも。
$ export FLUENTD_ROOT=/home/pi/fluentd $ fluentd -c $FLUENTD_ROOT/fluent.conf --log $FLUENTD_ROOT/fluentd.log --daemon $FLUENTD_ROOT/fluentd.pid
- fluent-catとかfluent-postコマンドなどでレコードを放り込んでみる
$ echo '{"name":"pi", "age":49}' | fluent-cat td.pidb.test
- td tablesコマンドで件数を確認
$ td tables pidb +----------+-------+------+-------+--------+---------------------------+---------------------------+-----------------------+ | Database | Table | Type | Count | Size | Last import | Last log timestamp | Schema | +----------+-------+------+-------+--------+---------------------------+---------------------------+-----------------------+ | pidb | test | log | 1 | 0.0 GB | 2014-02-15 23:17:20 +0900 | 2014-02-15 23:14:15 +0900 | age:long, name:string | +----------+-------+------+-------+--------+---------------------------+---------------------------+-----------------------+ 1 row in set
- td query コマンドでクエリーを投げてみる
$ td query -w -d pidb 'select name, age from test' : Status : success Result : +------+-----+ | name | age | +------+-----+ | pi | 49 | +------+-----+ 1 row in set
見返してみると、記事にするほどの内容でも無い気がしてきたけど、勢いで書いたのでまぁ良いのではと。
PiにはGPIOとか付いているのでセンサー的なものをくっつけて、そこからの情報をTDに送り続けるというのは割と簡単に出来そう。データ量をあまり気にせずどんどん放り込んで後で集計の切り口を試行錯誤しやすいのは、1TDユーザーとしても使っていてかなり楽なので相性が良さげ。
fluent-logger について
こんにちは。Fluentd Advent Calendar 11日目担当の @komamitsu です。ワイワイ。
Fluentd Advent Calendarにノリで登録したもののfluentd本体やプラグインに余り関わっていないので途方に暮れていましたが、fluent/fluent-logger-java のメンテナンスをしていることもあり、fluent-logger周りのことを書いてやりすごしたいと思います。
fluent-logger-javaの一年
地味な見出しですね。自分でも書いてみて素でちょっと引きましたが、とりあえずfluent-logger-javaに関連した出来事などを...
いくつか細かいバグの修正や改善を入れていますが、その中でもTCP切断後の再接続処理やタイムアウトの問題が改善されているので、地味に使いやすくなっていると思います。
MLより。複数のfluentdに対して障害時のfailover、またはメモリバッファが一杯になったらファイル書き出し出来ないの?fluentdのメンテ中にメモリバッファがあふれちゃうよ的な話。確かに一理ある要望(まだ対応できてませんが...)
実のところ、TDでは全てのサーバーにfluentdがいて、アプリからは自サーバーのfluentdに投げているのであまり接続周りで困ったことがないんですよね... なので違う構成で利用されている方で困っているケースがあれば色々と知りたいなぁと思ってる次第です。
あとJavaのlog frameworkとの連携に関する要望とかもたまに目にしますねぇ。需要がどの程度あるのか気になります。
他のfluent-loggerとの比較
fluent-logger-javaは万一送信先のfluentdに送信出来なくてもメモリ上でバッファリングを行い、次回以降の送信成功時にまとめて送るようになっています。しかし、前述の "じゃあメモリバッファに書ききれなくなったらどうするの?ディスクに書き出すの?fluentdの接続先を変えてくれるの?" という要望には答えきれてないように思いました。そこで、他のfluent-loggerはどんな感じなのか気になったのでざっとコードを眺めてみました。
切断後の再接続の有無 | 再接続がexponential backoff? | 送信失敗時のデータ保持 | socket domain | ||
---|---|---|---|---|---|
Java | fluent/fluent-logger-java | yes | yes | yes | INET |
Ruby | fluent/fluent-logger-ruby | yes | yes | yes | INET |
Python | fluent/fluent-logger-python | yes | no | yes | INET,UNIX |
Perl | fluent/fluent-logger-perl | yes | yes | yes | INET,UNIX |
PHP | fluent/fluent-logger-php | yes | yes | no | INET,UNIX |
Node.js | fluent/fluent-logger-node | yes | no | yes | INET |
D | fluent/fluent-logger-d | yes | yes | yes | INET |
Scala | oza/fluent-logger-scala | yes | yes | yes | INET |
Haskell | notogawa/fluent-logger-haskell | yes | no | yes | INET |
OCaml | fluent/fluent-logger-ocaml | yes | yes | yes | INET,UNIX |
C | roadman/fluent-logger-c | no | - | no | INET |
C++ | todayman/fluent-logger-cpp | no | - | yes | INET |
C# | zoetrope/fluent-logger-csharp | yes | no | yes | INET |
Lua | ngigroup-developer/fluent-logger-lua | no | - | no | INET |
# Haskellもあったけど眠気の限界。気力があったら後で追加します...notogawaさんから教えて頂きHaskell版追記しました。thanks!
# 全体的にざっと眺めただけ&上記全ての言語についてちゃんと理解しているかというとそうではないので、間違っていたらご指摘頂けると幸いです。
全てのfluent-loggerでfluentdとの接続を保持しており、毎回接続しなおさないようになっていますが、それ以外は結構違いがあって面白いです。fluent-loggerのほうでどこまで頑張るか、呼び元のほうで頑張るかなど思想の違いもありそうです。(2013-12-11 思い出したので追記 ->) それ以外にも、Perl版ではfork()されたプロセスではsocketを共有せずに自分で開き直すような処理があったりして、そういう渋い工夫を見るのも楽しいですね。
ちなみに、複数のfluentdに接続してfailoverしたり、メモリバッファが溢れたらディスクに書き込む機能はどれもありませんでした。意外とそれで困るケースはすくないのかな。
一点気になったのが、いくつかのfluent-loggerではsocketへの書き込みのみを続けているっぽいので、fluentdが万一落ちたときにFINパケットが飛んで来ても気がつかないケースがありそうかなと。select()的なことをしてread()して0byteであったら切断を検知, またはnon-blockingでread()してEAGAINを期待する、ということが必要な気がしました。fluent-logger-rubyだと毎回別socketで接続しているようで、これであればFINを送る間もなく切断した場合でも検知出来るなー、でもちょっとコストが心配だなーと色々面白いです。
あと、自分では計測していないのですが、UNIX domainソケットのほうが性能が出るっぽい話も聞くので、優位な差がありそうであればJava版も対応してみても良いかな、などなど。
OCatraをLwt使うようにした
OCatraをいろいろ直したり、ベンチマークとってみたり - komamitsu.log で作っていたOCamlのおもちゃ的Webサーバー OCatraをLwtを使うように修正してみた。
1st | 2nd | 3rd | 4th | Ave. | |
---|---|---|---|---|---|
OCatra (byte) | 4238 | 4424 | 4299 | 4285 | 4311 |
OCatra (opt) | 11806 | 12384 | 12381 | 12063 | 12158 |
bytecode版で1.5倍程度、native版で3倍程度高速になってうれしい。
nginxもついでに測ってみたのだけど、nginxのほうが倍近く速くて悔しい...
Androidでのnew String(byte[])とCharsetDecoder.decode()の違い
byte[] -> String 変換のパフォーマンス、とAndroidでCharsetDecoder#decode(ByteBuffer in).toString()が遅い件 - komamitsu.log でAndroidではnew String(byte)の方が速い結果が出たので[https://github.com/OESF/OHA-Android-4.0.3_r1.0:title=コード]を見てみた。
すると、CharsetDecoder.decode()ではICU Library(IBMの人が作ったunicode用ライブラリ)を使っているが、new String(byte, "UTF-8")の方は自前で手抜きしつつガリガリconvertしていた。なので、new String(byte)の方が高速で低機能なのではないかと思われ。
追記:
OpenJDK6のコードを見たら、CharsetDecoderの方はICUを使わずに自前でガリガリ。new String(byte)の方はCharsetDecoder#decodeを使ってた。