Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

Apache Storm 1.0.0の機能を使ってみる

お久しぶりです@ です。

このブログへの登場はかなり久しぶりです。昨年10月にミャンマーから日本に帰ってきて、今は、IoTやら可視化などに関する仕事をしています

さて、TwitterよりStormが公開されて以降、分散ストリーム処理フレームワークも、FlinkSpark StreamingSamzaBeamGearpumpSensorBee等、さまざまなOSSプロダクトが公開されました。

世はまさに「大ストリーム時代」!?(ワンピース風)

そのような中、4/12にApache Storm から正式メジャーバージョンとなる、1.0.0がリリースされました。このタイミングでどのような機能が盛り込まれるのか、興味を持っていましたが、これまでの課題を解消しつつ、他プロダクトよりも一歩先に行くような内容もリリースされました。

大きな変更点は12個

以下の公式サイトでも公表されていますが、メインとなる変更点は12個のようです。
Storm 1.0.0 released

No タイトル 内容
1 Improved Performance 16倍の処理速度向上と、60%のレイテンシの減少に成功しました。
2 Pacemaker - Heartbeat Server Stormがスケールアップするにつれて生じていたZookeeperのパフォーマンスボトルネックの解消のため、インメモリのKey-Valueストアとして機能するオプションのStormデーモンPacemakerが追加されました。
3 Distributed Cache API Topology毎に共有できるデータストア空間を用意し、Blobで共有データが保持、参照できるようになりました。
4 HA Nimbus Distributed Cache APIの機能を利用することで、高可用性を備えたNimbusが実現可能になりました。
5 Native Streaming Window API Storm Native な Window APIが用意され、いわゆるCEP処理を実装しやすくなりました。
6 State Management - Stateful Bolts with Automatic Checkpointing 自動チェック機構を備えたステートフルなBoltの利用により、状態管理が可能になりました。
7 Automatic Backpressure 上限値、下限値の設定による自動バックプレッシャーが可能になりました。
8 Resource Aware Scheduler Topology毎のリソース(メモリ/CPU)を考慮したタスクスケジューラが実現可能になりました。
9 Dynamic Log Levels Storm UIから動的に出力ログレベルの変更が可能になりました。
10 Tuple Sampling and Debugging Storm UI上でTupleのサンプリングとデバッグが可能になりました。
11 Distributed Log Search Worker毎に分散されてしまうログの検索がStorm UI上で実施可能になりました。
12 Dynamic Worker Profiling WorkerプロセスのプロファイリングがStorm UI上で可能になりました。


今回は上記の変更点の中から、特に面白いだろうと思われる以下の3点を調べてみました。他のものは、なんとなくタイトルから想像できますよね。

  1. Distributed Cache API
  2. Native Streaming Window API
  3. Automatic Backpressure

Distributed Cache APIを使ってみる

前バージョンでは、デプロイしたTopology上で何かファイルのデータなどを使いたい場合、Topologyと一緒にデプロイする必要がありました。そのため、大きなデータをTopology起動後に利用したい場合は、デプロイそのものに時間がかかることがありました。
また、各サーバに共有データを置いたり、データ共有のためにKVSなどのStormとは別のプロダクトを利用するのは、実現したいことに対して重く感じます。
しかし今回のバージョンアップで、Topology上で使いたいファイルを、Stormが持っているデータストアに保持し、Topologyからそのデータを参照することが可能になりました。共有データ保存場所が存在し、そこにデータを配置することでデプロイ時間の削減を可能にした機能です。共有データのサイズが大きければ大きいほど、その恩恵を受けることができます。本家サイトでは「位置情報」や「辞書データ」を保持するとよい、と言われています。

Distributed Cache APIの仕組み

Stormのサイトに素敵な解説図があるので、転載させてもらいます。BlobStoreというインタフェースがあり、このインタフェースを実装したLocalFsBlobStoreとHdfsBlobStoreが提供されています。どちらのStore実装も処理の流れはほぼ同じです。仕組みとしては、Supervisor起動時にBlobStoreのMapを取得し、その後MapにしたがってMap情報(共有データ)を取得する流れのようです。

[LocalFsBlobStore]
f:id:acro-engineer:20160425224701p:plain

[HdfsBlobStore]
f:id:acro-engineer:20160425224721p:plain

使ってみる

早速使ってみます。

  1. 共有データの登録
  2. Topologyの起動

が手順になります。確認のため、Topologyは2つ動作させます。

共有データの登録

README.markdownの登録をします。

# ./bin/storm blobstore create --file README.markdown --acl o::rwa --replication-factor 4 key1

共有用のデータは「storm.local.dir/storm-local/blobs/」に配置されていました。

Topologyの起動

Topologyを2つ起動し、どちらも登録したREADME.markdownをダウンロードすることをログから確認したいと思います。本来はTopologyの中で利用されているところを確認したいのですが、サンプルに適当なものがなかったため、ひとまず起動時に登録した共有データが読み込まれることを確認したいと思います。

# ./bin/storm jar examples/storm-starter/storm-starter-topologies-1.0.0.jar org.apache.storm.starter.clj.word_count test_topo -c topology.blobstore.map='{"key1":{"localname":"blob_file", "uncompress":"false"}}'

test_repoというTopologyを作成し、key1というキーに対して登録したBlobファイルの中身を解凍オプションなしで参照、実行しています。Blobファイルの読み込みに成功すると、以下のようなログが確認できるはずです。

2016-04-23 14:48:05.782 o.a.s.d.supervisor [INFO] Downloading code for storm id test_topo-4-1461390482
2016-04-23 14:48:06.279 o.a.s.d.supervisor [INFO] Successfully downloaded blob resources for storm-id test_topo-4-1461390482
2016-04-23 14:48:06.280 o.a.s.d.supervisor [INFO] Finished downloading code for storm id test_topo-4-1461390482
:
2016-04-23 14:48:06.285 o.a.s.d.supervisor [INFO] Creating symlinks for worker-id: 6d23e0f9-9aa1-43c6-a475-773b0537bdfb storm-id: test_topo-4-1461390482 to its port artifacts directory
2016-04-23 14:48:06.286 o.a.s.d.supervisor [INFO] Creating symlinks for worker-id: 6d23e0f9-9aa1-43c6-a475-773b0537bdfb storm-id: test_topo-4-1461390482 for files(2): ("resources" "blob_file")

同じ操作で別名のTopologyを作成してみてください。上記と同じデータダウンロードが正常に完了するログが確認できるはずです。これでDistributed Cache APIを試すことができました。

Native Streaming Window APIを使ってみる

Stormのネイティブな機能として、スライディングウィンドウが追加されました。どこまでの内容までがStormネイティブとして対応しているのか、サンプルをベースに確認してみたいと思います。まずはメインパートであるSlidingWindowTopology.javaのソースを確認してみます。

    public static void main(String[] args) throws Exception {
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("integer", new RandomIntegerSpout(), 1);
        builder.setBolt("slidingsum", new SlidingWindowSumBolt().withWindow(new Count(30), new Count(10)), 1)
                .shuffleGrouping("integer");
        builder.setBolt("tumblingavg", new TumblingWindowAvgBolt().withTumblingWindow(new Count(3)), 1)
                .shuffleGrouping("slidingsum");
        builder.setBolt("printer", new PrinterBolt(), 1).shuffleGrouping("tumblingavg");
        Config conf = new Config();
        conf.setDebug(true);
        if (args != null && args.length > 0) {
            conf.setNumWorkers(1);
            StormSubmitter.submitTopologyWithProgressBar(args[0], conf, builder.createTopology());
        } else {
            LocalCluster cluster = new LocalCluster();
            cluster.submitTopology("test", conf, builder.createTopology());
            Utils.sleep(40000);
            cluster.killTopology("test");
            cluster.shutdown();
        }
    }

これだけ見ても、以下の3つのBoltが存在します。

  1. SlidingWindowSumBolt
  2. TumblingWindowAvgBolt
  3. PrinterBolt

これらが何をしているのか、またどのように動くのかを確認したいと思います。

[SlidingWindowSumBolt]
とても単純に、受信したTupleの中身を加算していることがわかります。一応ウィンドウから外れたTupleの値は減算するようにも記述されているので、スライディングウィンドウの条件を満たしていることも確認できます。

    @Override
    public void execute(TupleWindow inputWindow) {
            /*
             * The inputWindow gives a view of
             * (a) all the events in the window
             * (b) events that expired since last activation of the window
             * (c) events that newly arrived since last activation of the window
             */
        List<Tuple> tuplesInWindow = inputWindow.get();
        List<Tuple> newTuples = inputWindow.getNew();
        List<Tuple> expiredTuples = inputWindow.getExpired();

        LOG.debug("Events in current window: " + tuplesInWindow.size());
            /*
             * Instead of iterating over all the tuples in the window to compute
             * the sum, the values for the new events are added and old events are
             * subtracted. Similar optimizations might be possible in other
             * windowing computations.
             */
        for (Tuple tuple : newTuples) {
            sum += (int) tuple.getValue(0);
        }
        for (Tuple tuple : expiredTuples) {
            sum -= (int) tuple.getValue(0);
        }
        collector.emit(new Values(sum));
    }

[TumblingWindowAvgBolt]
設定したWindowのサイズで合計値を除算している、シンプルなつくりでした。SlidingWindowTopologyに内包されていますね。「いくつ溜まったら平均値を計算する」という引数には「3」が設定されています。

        @Override
        public void execute(TupleWindow inputWindow) {
            int sum = 0;
            List<Tuple> tuplesInWindow = inputWindow.get();
            LOG.debug("Events in current window: " + tuplesInWindow.size());
            if (tuplesInWindow.size() > 0) {
                /*
                * Since this is a tumbling window calculation,
                * we use all the tuples in the window to compute the avg.
                */
                for (Tuple tuple : tuplesInWindow) {
                    sum += (int) tuple.getValue(0);
                }
                collector.emit(new Values(sum / tuplesInWindow.size()));
            }
        }

[PrinterBolt]
驚くほどシンプルですね。出力するだけ。

  @Override
  public void execute(Tuple tuple, BasicOutputCollector collector) {
    System.out.println(tuple);
  }

まとめると、以下の通りに動くと予想できます。

  1. ランダムに0~999の整数を、合計用のBoltに送付する。
  2. 合計用Boltはデータを受信時、メモリに保持しているSum値に加算していく。
  3. 受信したTuple数が10になったところで、平均計算用Boltに合計値を送付する。
  4. 1~3を、平均計算用Boltの保持Tuple数が3になったら、平均値を計算する。
  5. 1~4を40秒間繰り返す。


これらを基に、動かした際のログを見てみましょう。ソースコードの通り、起動後に40秒で終了するようなので、以下のコマンドを実行してしばらく待ってみます。

# bin/storm jar examples/storm-starter/storm-starter-topologies-1.0.0.jar org.apache.storm.starter.SlidingWindowTopology

[合計用Boltに対する出力ログ]
以下のような感じで合計用Boltにはログが出力されていました。

17562 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.executor - Processing received message FOR 4 TUPLE: source: integer:2, stream: default, id: {4017617819859316672=-3880300761259985782}, [899, 1461542140369, 1]
17562 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.executor - Execute done TUPLE source: integer:2, stream: default, id: {4017617819859316672=-3880300761259985782}, [899, 1461542140369, 1] TASK: 4 DELTA: 
17674 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.executor - Processing received message FOR 4 TUPLE: source: integer:2, stream: default, id: {-2465951973599725012=3371764614267379312}, [888, 1461542140475, 2]
17674 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.executor - Execute done TUPLE source: integer:2, stream: default, id: {-2465951973599725012=3371764614267379312}, [888, 1461542140475, 2] TASK: 4 DELTA: 

で、10個たまったところで平均計算用Boltに送付しています。

18516 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.task - Emitting: slidingsum default [6053]
18516 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.executor - TRANSFERING tuple [dest: 5 tuple: source: slidingsum:4, stream: default, id: {-1356407922762824927=-4912942846543592390, 4017617819859316672=-8950777703384980140, -7679610850762262585=5600559632140381686, -217168626838496871=6670643717321357413, -8433729321932816312=-1512990481045386819, -695350376461229364=-6915299522591467528, -8604773776820158944=2085240823323939478, 4818452273885227082=1055563511177261421, -4383830359476279213=-2430226558792731842, -2465951973599725012=-3111554498250395772}, [6053]]
:
:
19527 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.task - Emitting: slidingsum default [11182]
19527 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.executor - TRANSFERING tuple [dest: 5 tuple: source: slidingsum:4, stream: default, id: {4017617819859316672=1956648145851480265, -7679610850762262585=-8990022321548325348, -217168626838496871=8356349476653175499, 6556174365450512594=-2956830898901769282, 4973296703617984132=630324356173502412, -8433729321932816312=7530781138220324522, 8041484834072108391=-1100584463729972475, -695350376461229364=5513770714708145606, -8604773776820158944=-7212706120285088590, -4383830359476279213=5439461521018447939, -1641897178290464600=-510250118691366334, 6730277299577429107=6208397095766677293, -8115189405407159227=1214364586718890587, -1356407922762824927=3843071132908231388, 7588127658633797238=-3035483582895424875, -1600730095770316997=7644364465767360178, -5977653414665598802=6443496393438179244, -4289645355525492039=-7771435519918529374, 4818452273885227082=2145248154554053428, -2465951973599725012=3719500247592761581}, [11182]]
:
:
20536 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.task - Emitting: slidingsum default [16102]
20536 [Thread-25-slidingsum-executor[4 4]] INFO  o.a.s.d.executor - TRANSFERING tuple [dest: 5 tuple: source: slidingsum:4, stream: default, id: {-217168626838496871=-2098183848111262192, 6694109964234120893=-2919364166006116209, -490688806946141211=-115866302962242997, 6556174365450512594=3417840899423850921, 4973296703617984132=-2075234239029720284, -8433729321932816312=3438734146522000751, -695350376461229364=5479035516364205453, -1356407922762824927=536244103058094589, -8759945507516006916=-7691606294364721504, -1600730095770316997=5198643672682538868, 1928584781574233931=3233801634595403530, 4818452273885227082=3613752122075445564, 4017617819859316672=-5965638492780984127, -7679610850762262585=8746099621132998817, 3628468721856542322=3234989506915660599, 8041484834072108391=2473403470958482418, -8604773776820158944=4291163489101357389, 5275805877791886609=2224008364377626542, -4383830359476279213=-1613810029185700041, -1641897178290464600=-7937957462653577021, 6730277299577429107=-5906850224979170611, -8115189405407159227=-6807612857900762546, -492827708169915833=-3985992390535713144, 7588127658633797238=7922452426043726560, -3771417496478433111=-1133769369307004904, -5977653414665598802=2226449212304201933, -4289645355525492039=8279576780572003648, 2457695339746807997=-943386263403830945, 7278045793299574088=-3628544844039353718, -2465951973599725012=5019064850597613642}, [16102]]

最後に、3個合計Tupleが溜まったところで平均値を計算して出力しています。

20540 [Thread-23-tumblingavg-executor[5 5]] INFO  o.a.s.d.task - Emitting: tumblingavg default [11112]

この後PrinterBoltにデータ送付しているのですが、PrinterBoltは非常にシンプルなので、ここで触れるのは割愛したいと思います。
さて、上記のような感じでスライディングウィンドウも使えました。設定した閾値に伴い指定された動作をしてくれるので、簡単な仕組みであれば、Stormだけで動作させられそうです。
具体的な関数の整備はこれからのようですが、SlidingWindowSumBoltやTumblingWindowAvgBoltを見ればわかるように、「BaseWindowedBoltを実装してexecuteで計算させる」というシンプルでオーソドックスなAPIになっています。EsperWSO2 CEP(Siddhi)といったCEPプロダクトの関数を実装したことがある人なら、難なく実装できると思います。

Automatic Backpressure

個人的に一番気になっているところです。そもそもBackpressureって何ぞや、というところですが。

Backpressureとは何ぞや?

もともとは半二重接続のハブやスイッチで用いられるフロー制御方式の一つです。機器内の通信バッファがあふれてフローが止まってしまう前に、データ送付側に通知を投げて、送ってくるデータを止めたり、量を調整したりする仕組みのことを言います。

なぜBackpressureがStormに必要か?

Stormがリリースされた際にずっと言われていた問題点として、「Spoutの処理性能がBoltの処理性能を上回っている場合、キューに処理が溜まり続けてTopologyが止まってしまう/遅くなってしまう」という考慮すべき点がありました。Storm自体の処理性能は良いのですが、「データストア用のプロダクトに書き込むBoltの性能が上がらず、キューに溜まる」という事象は「Stormあるある」と言ってよいくらい見かけます。
そのため、Stormの環境を構築する際には、SpoutとBoltの処理性能に気を付ける必要があったわけです。

しかし、今回のBackpressure機構を利用すれば、この問題点を緩和させることが可能になります。今回のBackpressureはTopology単位で以下の設定が可能です。

  1. high-watermarkとlow-watermarkの指定が可能。
  2. キュー内のメッセージ量がhigh-watermarkで指定した比率を上回ったら、Backpressure機能が発生し、Spoutの処理を自動的に遅くする。
  3. キュー内のメッセージ量がlow-watermarkで指定した比率を下回ったら、通常のSpoutの処理に戻る。

こちらに検討中のBackpressureの図が載っているのですが、閾値を検知した時点でZookeeperに通知を飛ばし、Spoutの処理を抑えるような制御をするようです。ただし下記の図は検討中なので、ここからおそらく何らかの変更が加わっているとは思います。公式の発表待ちですね。
https://github.com/apache/storm/pull/700

ただし、解決するパターンと解決できないパターンがきちんと言及されています。

  • 解決するケース:Boltの処理が遅い
  • 解決できないケース:外部システムにアクセスするBoltで、外部システムが止まった場合(「遅い」ではなく、そもそも「処理できない」。こういうケースは、HystrixのようなCircuit Breakerが欲しくなりますね、とStormのissueでも話が出ているようです)

Backpressure利用のための設定値

Automatic Backpressureに関する具体的な設定値は以下で指定可能です。

topology.backpressure.enable: true
backpressure.disruptor.high.watermark: 0.9
backpressure.disruptor.low.watermark: 0.4

まとめ

いくつか特徴的なStormの変更点を確認してきましたが、DevOps・運用面にかなり注目が集まっている時代の中で、Stormもついにそちらに目を向け始めたように見えました。ますます便利になっていくので、目が離せません!


ストリーム王に俺はなる!


Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、Elasticsearch、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 
データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedlywww.wantedly.com

性能改善10事例

こんにちは、 @ です。

Acroquestでは、Elastic{ON}のような勉強会やカンファレンスに参加するだけでなく、社内向けの勉強会も開催しています。
新しい技術を調べて「使ってみた」的な発表をしたり、実際に開発の中で得られた知見を発表したり。
各自が得意分野で、様々なテーマで発表し、技術の研鑽を行っています。

私の場合、しばらく性能改善に取り組んでいたため、
その際に解決した問題からいくつか事例をフィードバックし、
性能問題を起こさないよう喚起しました。

www.slideshare.net

スライドでも書きましたが、特に大量に繰り返し実行される処理では、性能を意識した実装をしないと問題になりがちです。
問題があちこちに散らばると性能が大きく劣化し、解決も骨が折れます。
こういう問題は起こさないように、各チームメンバが気を付けて実装する必要があります。

ただ、もし、起きてしまった場合は、ENdoSnipeで解決するのがオススメです^^
www.endosnipe.com


それと、性能問題は解決が難しいケースもあります。
自力で解決できない場合は、JaTSサービスも是非ご利用ください!
jats.acroquest.co.jp

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、Elasticsearch、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 
データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedlywww.wantedly.com

Springfox+Swagger+Bootprintによる即席REST API仕様書作成

こんにちは。阪本です。

世の中、Swagger注目を浴びてきていますね。
開発のスピードアップが求められる中、「外部IF仕様書なんて書いてられねぇ!!」なんて言って実装をバリバリ進めてしまいそうですが(アカンアカン)、そうは言っても外部IF、他社との仕様調整も必要。

そんなときに有効な、実装しながら仕様書も作れるSpringfox+Swaggerに加え、ドキュメント生成ツールのBootprintを使って、簡易的な仕様書を作ってみました。

仕様書の動的生成

以下のようなシンプルなSpringBootアプリケーションから、REST APIを生成してみます。

@SpringBootApplication
public class SwaggerExampleMain {
    public static void main(String[] args) {
        SpringApplication.run(SwaggerExampleMain.class, args);
    }
}
@RestController
@RequestMapping("/employee")
public class EmployeeController {
	
    @RequestMapping(method = RequestMethod.GET)
    public List<Employee> list() {
        // Return employee list
        return new ArrayList<>();
    }

    @RequestMapping(method = RequestMethod.POST)
    public void create(@RequestBody Employee employee) {
        // Add employee
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public void update(@PathVariable Integer id,
            @RequestBody Employee employee) {
        // Update employee
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public void delete(@PathVariable Integer id) {
        // Delete employee
    }
}

まずはpom.xml。Springfoxを追加します。

<springfox.version>2.2.2</springfox.version>

~~~

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>${springfox.version}</version>
</dependency>
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>${springfox.version}</version>
</dependency>

そして、Swaggerの設定を行うJavaConfigクラスを作成します。
@EnableSwagger2アノテーションを付けて、Docketでいろいろ指定するところがミソです。
今回は、「/error」以外のURIを持つREST APIのドキュメントを一覧化することにします。

import static springfox.documentation.builders.PathSelectors.regex;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket documentation() {
        return new Docket(DocumentationType.SWAGGER_2).select().apis(
                RequestHandlerSelectors.any()).paths(
                        regex("^/(?!error).*$")).build().pathMapping("/").apiInfo(metadata());
    }

    @Bean
    public UiConfiguration uiConfig() {
        return UiConfiguration.DEFAULT;
    }

    private ApiInfo metadata() {
        return new ApiInfoBuilder().title("Employee API").version("1.0").build();
    }
}

これらを書いて、Javaアプリを普通に起動するだけで、APIの一覧とパラメータが見えるようになります。
http://localhost:8080/swagger-ui.html

f:id:acro-engineer:20151202021445p:plain

さらに、この画面でAPIに対してリクエストを送信することもできます。
簡易的な動作確認をするにも便利ですね。

オフライン仕様書生成

上記はアプリを起動しないとAPIの情報が見られませんでしたが、顧客や他チームと共有できる環境がなく恵まれない場合もあります。
そんなときは、オフラインの仕様書を生成しましょう。

オフラインの仕様書はSpringfoxでもMarkdown形式で生成できますが、ここはあえてMarkdownドキュメントが見られない(ツールなんて入れてない)人のことも想定し、HTML形式で出してみます。

Swagger2Markupという選択肢もありましたが、何となく使うのが大変そうだったので、今回はbootprint-swaggerを使ってみます。


node.jsをインストールした環境で以下のコマンドを実行し、bootprint-swaggerをインストールします。

npm install -g bootprint
npm install -g bootprint-swagger

Springfoxの設定を行ったJavaアプリを立ち上げ、bootprint-swaggerを実行します。

bootprint swagger http://localhost:8080/v2/api-docs target

これにより、targetフォルダ配下にHTMLファイルが生成されます。

f:id:acro-engineer:20151202021450p:plain

なんとなく、それっぽいドキュメントができました^^;

おわりに

とりあえず、ソースコードからREST API仕様書を生成できましたが、パラメータの桁数やフォーマットなど、「仕様書」としてはまだまだ情報が不足しています。
次回は、そのあたりの詳細情報の出力について、確認してみたいと思います。

それではー。

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

JJUG主催Pepperハッカソンに参加してきました!

Java Developerの皆さん、こんにちは。
ボクの名前は @IPアドレスは192.168.254.254

・・・というPepperのモノマネはさておき、@です。

6月6日にPepperハッカソンに参加してきました!
【東京】JJUG主催Pepperハッカソン ( https://jjug.doorkeeper.jp/events/25442 )

Pepperハッカソンって?

Pepperハッカソンは、あのヒューマノイドロボットPepperを使って、実際にプログラムを組んで動かしてみようというイベントです。
今回のイベントはPepperJava NAOqi SDKで開発できるようになることを記念されて開催されたものです。
Java NAOqi SDK ( http://doc.aldebaran.com/2-1/dev/java/index_java.html )

そう、JavaPepperのプログラミングができる! というわけですね。

自分のプログラムで動かせると考えると、最初は気味悪かったPepper君の顔も心なしか可愛く見えます。
f:id:acro-engineer:20150609070309j:plain

ハッカソンのようす

さて、会場の風景を・・・といっても、当日はひたすらPepper君をハックしていたのみのため、
Pepper君の雄姿ばかりになりますが、紹介させてもらいますね。

Pepper君達
f:id:acro-engineer:20150609070318j:plain
・兄のNAOと並んだPepper
f:id:acro-engineer:20150609070327j:plain

ハッカソンということもあり、皆さん黙々と開発を進めていました。
ただ、Pepper君が動作確認の時にしゃべるので、それがきっかけで笑いが起こることが多いという、謎な雰囲気でした^^;

イベントの最後には、自分たちで作ったプログラムの発表が行われました。





また、このTwitterの中にはないのですが、JJUGの槙さんによる、SpringBootで作ったPepperの管理画面などは、とても興味深かったです。


え、、、私が作ったもの・・・?
私はPepperのロボットとしての機能(センサー、カメラ、ポーズ、移動など)を駆使して
癒し系のマスコットのようなものを作りたかったのですが、
SDKのドキュメントに無い個所を試しながら掘り下げていって時間切れ。

癒しではない、うざいPepper君になってしまいました^^;

PepperJava SDKはどうだったのか?

今回、参加した理由として、ハッカソンとしてものづくりを楽しみたかった一方で、
PepperJava SDKがどれぐらい使えるのかを知りたいという目的もありました。

実際に使ってみたJava SDKですが、正直なところ、やや使いづらい印象でした。

  1. ドキュメント、サンプルが無い。
    • いまのところ「どんな機能があるのか?」について分かるドキュメントがありません。
    • JavaDocはあるのですが、ほとんど自動生成されたJavaDocのみで、パラメータの意味などもわからないものでした。
    • 分からないからってデコンパイルに走る猛者も
    • ドキュメントはこれから充実すると思いますし、利用者によるブログやまとめも増えそうですよね。
  2. 数百ものイベント検知を1つ1つ設定する必要がある。
    • Pepperには数百ものイベント(頭に触られた、人間が近づいた)があるのですが、それを検知するためには1イベントごとに検知するコードを書く必要があります。
    • イベントは「EngagementZone/PersonEngage」のように階層構造になっているので、MQTTのようにTopicにワイルドカード指定をする形でSubscribe出来ると使いやすくなると思います。
  3. コールバックの型がイベントごとに異なり、扱いにくい。
    • 上記のイベント検知のハンドリングにはコールバックを使用するのですが、そのコールバックの引数型がイベントごとに異なるため、共通的なコールバックのクラス階層を作りにくいです。
    • 今はお試しレベルなのでそう大きな問題にならないのですが、大きなアプリケーションを作る場合にはコードの統制がとりにくいAPIでした。

なんかちょっと細かいところまで書いてしまいましたが。
ただ、ハッカソン後のイベントでスタッフの方と話したところ、
現状のJavaAPIはあくまでβ版であり、今回のイベントでのコメントを受けてブラッシュアップしていくという強い意志を感じました。

何より、これまでのJavaの開発資産を用いることで、
外部サービスとの連携等が容易になるということは、非常に大きな進展だと思います。
その証拠に皆さん、とにかくtwitter4jを使っていましたし(笑)

また今回のような機会があれば是非参加して、PepperJavaで何ができるか、考えながら手を動してみたいと思います。

おまけ

・イベントが終了し、お休みのPepper君達
f:id:acro-engineer:20150609070341j:plain

Pepperステッカー
f:id:acro-engineer:20150609070356j:plain

皆さんも、ぜひPepperJava SDK、試してみてください!

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

オライリー「Javaパフォーマンス」増刷決定! 記念に書評まとめ。

Hello, world! Java界のアイドル CEROMETAL こと、@ DEATH!!
はい、雪が解けても僕の滑りは止まりません!

さて、Acroquestが監訳に協力した「Javaパフォーマンス」ですが、この度めでたく増刷が決定しました!
それもこれも購入して頂いた皆さんのおかげです。そして、まだの方は、ぜひこの機会にご購入ください!

Javaパフォーマンス

Javaパフォーマンス

さて、発売してから2か月弱が経ち、その間にブログに書評なども書いていただきました。
今回はそれらをご紹介したいと思います。

「Java パフォーマンス」を読みました - sos の 作業メモ

著者は、15年間サンマイクロシステムズとオラクルにおいてJavaのパフォーマンスに携わってきた方で、 なかなか読み応えのある内容。 きちんとJavaの流行を追っかけてる人にとっては当たり前のことなのかもしれませんが、Java6の時代でほぼ止まっている私のような 人間にとって、改めて勉強になることが多い本でした。

「Java パフォーマンス」を読みました - sos の 作業メモ

各章について、サマリ・ポイントを掲載してくださっています。
目次だけでは分かりにくかった方にも、本書の内容が掴めると思います。

Javaパフォーマンス - 響雲

性能問題は最後の最後に苦労する事が多いと思います。
経験と知識から事前によけていけるものですが、落とし穴はあるものです。
言語知識だけでは太刀打ち出来ない場面も多く、いろいろな知識が求められます。
本書籍はJavaJVMを主人公に説明されている書籍です。

Javaパフォーマンス - 響雲

ひとつの読み進め方として、1〜3章を前提知識として抑えておき、それ以降は好きなところから読むという流れを紹介されています。僕もその読み方が良いと思います。

書籍「Javaパフォーマンス」を読んで - 見習いプログラミング日記

本書 Javaパフォーマンス のスコープは非常に広く、上記のようなJVMJava SE APIJava EEの広い範囲の性能課題について触れています。本書のような内容が日本語では今までなかったため、トラブル時にはJava SeriesのJava Performanceで苦戦しながら調べていましたが、本書も机の上に置いておこうと思います。

書籍「Javaパフォーマンス」を読んで - 見習いプログラミング日記

仕事としてJavaトラブルシューティングをなさっている @ さんによる書評です。
JITGCログ、解析ツールの解説が嬉しいというあたり、完全に同業者感があります(笑)

「Java パフォーマンス」感想 - sugarlife's blog

今現在 Java で開発している人、特に運用者や試験者は間違いなく買っておくべき本です。Javaに限らない一般的なパフォーマンスチューニングの考え方・観点から、Java アプリケーションにおいてボトルネックになりやすい GCJIT の詳細な確認方法からチューニング方法が解説されている。特にすごいのが Java の世界のみならず、OS の世界まで触れている点。流石に OS の世界はここに書かれているのが全てではないけれど、Java アプリに関わる部分で問題になりやすい点は割と触れている。

JDK8 にも対応しており、今現在手に入る情報としては一番頼もしいと思う。4000 円程度でこの知識量が手に入るなら非常に安い。

「Java パフォーマンス」感想 - sugarlife's blog

OpenJDKコミッタでもある @ さんによる書評です。
1〜3章の後、さらにどこの章を読むと良いか詳しく書かれています。

また、誤訳の訂正もしていただき、これは第二刷に反映させています。ありがとうございます!

Javaでのnullチェックのパフォーマンス - きしだのはてな

こういった最適化の話は、最近出たJavaパフォーマンスに載っています。Javaで書いたプログラムを実行させる人は一度読んでおくといいと思います。

Javaでのnullチェックのパフォーマンス - きしだのはてな

最後は @ さんのエントリー。
nullチェックの話からのJavaパフォーマンス本の紹介、ありがとうございます!w


もし他にも「私も書評を書いてるよ!」という方がいらっしゃれば、ぜひお知らせください。

そして繰り返しになりますが、まだの方は、ぜひこの機会にご購入ください!!
大事なことは2回言う、マニュアル通りの @ でした!

Elasticsearch設定に関する小ネタ

こんにちは、ツカノ(@)です。

この記事はElasticsearch Advent Calendar 2014の21日目です。
20日目はjtodoさんの「Spark on elasticsearch-hadoop トライアル」でした。

さて、以前、「Elasticsearchソースコードリーディング~内部構造把握の第一歩~」と題してElasticsearchのスレッド構成といった内部構造の話を書きましたが、今回もまた、似たような話です。
今回はElasticsearchの設定ファイルについてです。
f:id:acro-engineer:20140131080117p:plain

1.ノード名について

Elasticsearchのデフォルトのログフォーマットでは、ノード名が出力されます。クラスタで運用することも考えると、ノード名って重要ですよね。ノード名の設定方法については、公式ページに記載があります。
ここで面白いのが、設定を省略した場合の動きです。Marvelのキャラクター名からランダムで設定されるそうです。
githubにある設定を実際に見てみましょう。

2826個も名前が書いてあります。
ざっと見ると、Iron ManとかX-Manとかって名前がありますね。運が良ければ(?)あなたのノード名が「Iron Man」になるかもしれません。

この設定ファイルには、以下のような名前も書いてありますね。日本っぽい名前だけれど、私はMarvelは良く分からないので、Marvelの何に由来しているのか分かりません。。。

  • Kimura
  • Mariko Yashida
  • Mikado
  • Shingen Harada
  • Shirow Ishihara
  • Sushi
  • Yukio

こういうノード名が付く可能性があるんですね。

2.設定ファイルのフォーマット、読み込む順序

Elasticsearchの設定ファイルはjson形式でもyaml形式でもproperty形式でも書け、結構便利ですね。
設定ファイルも「elasticsearch.拡張子」と書いてconfigディレクトリに置けば勝手に読んでくれます。
どんな風に読み込んでいるのか気になったので、コードを読んでみました。
ElasticsearchのInternalSettingsPreparerクラスを読むと、以下のようなコードが書いてあります。

                try {
                    settingsBuilder.loadFromUrl(environment.resolveConfig("elasticsearch.yml"));
                } catch (FailedToResolveConfigException e) {
                    // ignore
                } catch (NoClassDefFoundError e) {
                    // ignore, no yaml
                }
                try {
                    settingsBuilder.loadFromUrl(environment.resolveConfig("elasticsearch.json"));
                } catch (FailedToResolveConfigException e) {
                    // ignore
                }
                try {
                    settingsBuilder.loadFromUrl(environment.resolveConfig("elasticsearch.properties"));
                } catch (FailedToResolveConfigException e) {
                    // ignore
                }

「elasticsearch.json→elasticsearch.yml→elasticsearch.properties」の順序で設定ファイルを探して、読み込んでいるんですね。
同じ設定を記述した場合は、後から記載した設定で上書きされるので、設定ファイルの優先順序は以下のようになります。

  1. elasticsearch.properties
  2. elasticsearch.json
  3. elasticsearch.yml

また、yamljsonで階層構造で設定を記述した場合は、階層を「.」でつないだ設定名になります。
例えば、json形式で以下の2つのケースは同じ設定で、Elasticsearch内部では、「node.name」として扱われます。

{
  "node" :
      { "name" : "json" }
}
{
  "node.name" : "json"
}

yaml形式で設定を記述するときも、同様に階層を「.」でつないだ設定名になります。
property形式の場合は「node.name=json」のように記載するしかないですね。

設定ファイルを記述する際の参考になればと思います。ちょっとした小ネタでした。

それではまた~

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Apache版Storm初回リリースの新機能の使い方

こんにちは。kimukimuです。
f:id:acro-engineer:20140709095247p:plain

夏なのか梅雨なのか微妙な気候になっているような感覚を覚える今日この頃です。
いきなり暑くなってきているので、バテないよう気をつける必要がありますね。

さて、前回Apache版Stormの新機能の概要について紹介しましたが、
今回は実際に新機能がどういう風に使えるのか、について確認してみようと思います。

尚、Storm-0.9.2-incubatingもリリースされていますが、それは次回に回すとして、
今回はStorm-0.9.1-incubatingの新機能です。

・・・といっても、ビルドツールの変更などは確認してもあまり嬉しいことはないため、
下記の2つの機能に絞って確認を行ってみることにします。

  1. Storm-UIの各項目にツールチップで解説を表示
  2. NimbusにTopologyをSubmitする際、設定に対するバリデーションが追記

1.Storm-UIの各項目にツールチップで解説を表示

Storm-UIで表示される各種項目に対してマウスオーバーした際にツールチップで解説が表示されるようになった」という新機能です。

Storm-UIを使ってさえいれば常時有効となります。
これは実際にStorm-UIで見てみた方が早いため、実際どんな内容が表示されるかを見てみましょう。

まずはTop画面のバージョンから。こういった形で項目にマウスオーバーすることで項目の説明が表示されます。
f:id:acro-engineer:20140519072554j:plain
同じように、Topology Summaryの画面でも下記のような項目の説明が表示されます。
Capasityをはじめとした「生データから算出される項目」についてはどのように値が算出されているかも記述されています。
f:id:acro-engineer:20140519072902j:plain
f:id:acro-engineer:20140519072906j:plain
Executor SummaryにおいてもHostNameの算出の方法について記述されるなど、かゆい所にも手が届きます。
f:id:acro-engineer:20140519072909j:plain

Storm-UIは基本的にStormがZooKeeper上に保持している性能情報を
Nimbusから取得して表にしているだけのため、パっと見はわかりにくい画面なのですが、
今回各項目に解説が表示されるようになったため、使いやすくなったとは思います。

2.NimbusにTopologyをSubmitする際、設定に対するバリデーションが追記

では、次は「TopologyをSubmitする際、設定に対する定型的なバリデーションを行えるようになった」についてです。

これは内容としては、TopologySubmit時に型として不正な値が設定されている設定値を検出してバリデーションを行うものです。
バリデーション対象となるのはStorm自体が動作するために必要な設定値です。
Topology固有の設定値についてはこれまでと同じくTopologyValidatorを自前で作成し、チェックを行う必要があります。

では、実際にどういう場面で使われるのかを確認してみましょう。

今回ベースとするのはincubator-storm/examples/storm-starter at master · apache/incubator-storm · GitHubです。
この中で一番単純なExclamationTopologyを例にとります。
ExclamationTopologyをStormクラスタにSubmitする際のConfigオブジェクトに
ZooKeeperのポート設定を文字列("Test")として詰めて起動してみます。
当然ながら、ポート設定のため本来数値で設定されている必要があります。

  • ExclamationTopology
  public static void main(String[] args) throws Exception {
    TopologyBuilder builder = new TopologyBuilder();

    builder.setSpout("word", new TestWordSpout(), 10);
    builder.setBolt("exclaim1", new ExclamationBolt(), 3).shuffleGrouping("word");
    builder.setBolt("exclaim2", new ExclamationBolt(), 2).shuffleGrouping("exclaim1");

    Config conf = new Config();
    conf.put(Config.STORM_ZOOKEEPER_PORT, "Test"); // 本来数値でないと動作しない設定に文字列を設定

    if (args != null && args.length > 0) {
      conf.setNumWorkers(3);

      StormSubmitter.submitTopology(args[0], conf, builder.createTopology());
    }
    else {

      LocalCluster cluster = new LocalCluster();
      cluster.submitTopology("test", conf, builder.createTopology());
      Utils.sleep(10000);
      cluster.killTopology("test");
      cluster.shutdown();
    }
  }

この状態でStormクラスタにSubmitを行うと・・?

# bin/storm jar storm-starter-0.9.1-incubating-jar-with-dependencies.jar storm.starter.ExclamationTopology ExclamationTopology-3
(省略)
354  [main] INFO  backtype.storm.StormSubmitter - Jar not uploaded to master yet. Submitting jar...
359  [main] INFO  backtype.storm.StormSubmitter - Uploading topology jar storm-starter-0.9.1-incubating-jar-with-dependencies.jar to assigned location: /opt/storm/nimbus/inbox/stormjar-06306ca5-a1d6-4991-a47a-98b87126186b.jar
409  [main] INFO  backtype.storm.StormSubmitter - Successfully uploaded topology jar to assigned location: /opt/storm/nimbus/inbox/stormjar-06306ca5-a1d6-4991-a47a-98b87126186b.jar
409  [main] INFO  backtype.storm.StormSubmitter - Submitting topology ExclamationTopology-3 in distributed mode with conf {"topology.workers":3,"storm.zookeeper.port":"Test"}
415  [main] WARN  backtype.storm.StormSubmitter - Topology submission exception: field storm.zookeeper.port 'Test' must be a 'java.lang.Number'
Exception in thread "main" InvalidTopologyException(msg:field storm.zookeeper.port 'Test' must be a 'java.lang.Number')
        at backtype.storm.generated.Nimbus$submitTopology_result.read(Nimbus.java:2466)
        at org.apache.thrift7.TServiceClient.receiveBase(TServiceClient.java:78)
        at backtype.storm.generated.Nimbus$Client.recv_submitTopology(Nimbus.java:162)
        at backtype.storm.generated.Nimbus$Client.submitTopology(Nimbus.java:146)
        at backtype.storm.StormSubmitter.submitTopology(StormSubmitter.java:98)
        at backtype.storm.StormSubmitter.submitTopology(StormSubmitter.java:58)
        at storm.starter.ExclamationTopology.main(ExclamationTopology.java:76)

このように、「storm.zookeeper.port」が"Test"という設定になっており、NumberではないからSubmit出来ない、
クラスタに投入する前にはじくことができました。

これは今までだと設定が誤っていることに気付かずにStormクラスタにSubmitしてしまい、
クラスタで起動する際にWorkerプロセスが起動して死ぬを繰り返す・・・という厄介な状態に陥っていました。

特に、実際に使っている方だと
JSONYamlといったファイルに設定値を外だしして読み込ませる方も多いと思いますが、
設定ファイルに記述していた内容が誤っていた場合に予め検出してくれるのでかなり便利に使えると思います。

3.Apache版Stormの初回リリース機能についてのまとめ

  1. Storm-UIに解説が加わり、各画面の項目の意味がわかりやすくなりました。
  2. TopologySubmit時に明らかに誤った設定は事前にはじけるようになりました。

特に新しい機能が追加された・・・というわけではないのですが、使いやすさが確実に増すバージョンアップだったと思います。

尚、別の投稿で紹介しますが、Storm-0.9.2-incubatingでリリースした内容は
使いやすくなる、ではなく実際に使える機能が追加されたリリースになっています。
Apacheに移り、今後も期待していけるStormになった、と言えるでしょう。

それでは。

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ