CLOVER🍀

That was when it all began.

スタンドアロンでHazelcastを使う時の起動速度を、ちょっと速くする(オマケでPayara Micro)

Hazelcastをふつうに起動すると、例えば以下のようなコードでも
src/main/java/RunHazelcast.java

import java.util.Map;

import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;

public class RunHazelcast {
    public static void main(String... args) {
        HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance();

        Map<String, String> map = hazelcast.getMap("default");
        map.put("key", "value");
        map.get("key");

        hazelcast.shutdown();
    }
}

実行すると、開始から終了までだいたい5秒とかかかります。

10 22, 2016 5:57:05 午後 com.hazelcast.config.XmlConfigLocator
情報: Loading 'hazelcast-default.xml' from classpath.

〜 省略〜

10 22, 2016 5:57:10 午後 com.hazelcast.core.LifecycleService
情報: [172.17.0.1]:5701 [dev] [3.7.2] [172.17.0.1]:5701 is SHUTDOWN

これなんですけど、Hazelcastの初期化処理が重い〜とかというよりは、クラスタに参加する待ち時間があるのが大きな理由だったりします。

ちょっとした動作確認などで、クラスタを構成する必要のない時にはこの時間はちょっと嫌かもしれません。

この待ち時間は、プロパティ「hazelcast.max.join.seconds」で制御することができ、これを絞ることで待ち時間を狭めることができます。設定方法は起動時のシステムプロパティで指定するか(この例では10秒)

-Dhazelcast.max.join.seconds=10

と指定するか、設定ファイルで

<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.7.xsd"
           xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <properties>
        <property name="hazelcast.max.join.seconds">10</property>
    </properties>
</hazelcast>

といった感じです。

とはいえ、これは指定単位が「秒」なので、実際には大して変わりません。待ち時間を0秒とかにしてしまうと起動できなくなります。

10 22, 2016 6:03:42 午後 com.hazelcast.instance.Node
情報: [172.17.0.1]:5701 [dev] [3.7.2] Node is already shutting down... Waiting for shutdown process to complete...
[WARNING] 
java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: Node failed to start!
	at com.hazelcast.instance.HazelcastInstanceImpl.<init>(HazelcastInstanceImpl.java:134)
	at com.hazelcast.instance.HazelcastInstanceFactory.constructHazelcastInstance(HazelcastInstanceFactory.java:218)
	at com.hazelcast.instance.HazelcastInstanceFactory.newHazelcastInstance(HazelcastInstanceFactory.java:176)
	at com.hazelcast.instance.HazelcastInstanceFactory.newHazelcastInstance(HazelcastInstanceFactory.java:126)
	at com.hazelcast.core.Hazelcast.newHazelcastInstance(Hazelcast.java:87)
	at RunHazelcast.main(RunHazelcast.java:8)
	... 6 more

特にクラスタ化せず、スタンドアロンで使いたいようなケースであれば、Node Discovery(Joiner)を無効にするとよいでしょう。

最小設定で用意するなら、こんな感じです。この例では、マルチキャストのJoinerを無効にしています。
src/main/resources/hazelcast.xml

<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.7.xsd"
           xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <network>
        <join>
            <multicast enabled="false"/>
        </join>
    </network>
</hazelcast>

より丁寧に書くなら、TCPも明示的に無効と書きましょうか。

<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.7.xsd"
           xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <network>
        <join>
            <multicast enabled="false"/>
            <tcp-ip enabled="false"/>
        </join>
    </network>
</hazelcast>

この状態で起動すると、かなり速くなります。自分の手元では、5秒かかっていたのが2秒までになりました。スペックによっては、もっと軽快になるかもです。

10 22, 2016 6:07:05 午後 com.hazelcast.config.XmlConfigLocator
情報: Loading 'hazelcast.xml' from classpath.

〜省略〜

10 22, 2016 6:07:07 午後 com.hazelcast.core.LifecycleService
情報: [172.17.0.1]:5701 [dev] [3.7.2] [172.17.0.1]:5701 is SHUTDOWN

この状態では先ほどのようにクラスタ探索のタイムアウトを待つ時間を縮めたのではなく、クラスタへの参加手段を切ったことになります。また、起動時に以下のような警告ログが出力されるようになります。

警告: [172.17.0.1]:5701 [dev] [3.7.2] No join method is enabled! Starting standalone.

Hazelcastには完全なLocal Modeのようなものはないので、このくらいが妥協点でしょうか。
※Local Modeがあるキャッシュライブラリ、グリッドだとクラスタを有効にしなければもっと起動が高速です

オマケ:Payara Micro

Payara MicroではデフォルトでHazelcastが有効になっているため、起動時に同じようにHazelcastの起動時間が上乗せされます。

例えば、以下のようなコードを作成して
src/main/java/org/littlewings/payara/JaxrsActivator.java

package org.littlewings.payara;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class JaxrsActivator extends Application {
}

src/main/java/org/littlewings/payara/CalcResource.java

package org.littlewings.payara;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("calc")
@ApplicationScoped
public class CalcResource {
    @GET
    @Path("add")
    @Produces(MediaType.TEXT_PLAIN)
    public int add(@QueryParam("a") int a, @QueryParam("b") int b) {
        return a + b;
    }
}

ビルドしてパッケージングし、起動するだけでちょっと時間がかかります。
※WARファイル名は、「app.war」とします。

$ java -jar payara-micro-4.1.1.163.jar --deploy target/app.war

うちの環境では、デプロイ完了まで7秒くらい。

[2016-10-22T18:26:41.047+0900] [Payara Micro 4.1] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1477128401047] [levelValue: 800] Payara Micro  4.1.1.163 #badassmicrofish (build 215) ready in 6711 (ms)

ここで、「--noCluster」オプションを付けると、クラスタを構成しなくなるため起動がかなり速くなります。

$ java -jar payara-micro-4.1.1.163.jar --deploy target/app.war --noCluster

うちの環境で、4秒くらい。

[2016-10-22T18:28:49.718+0900] [Payara Micro 4.1] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1477128529718] [levelValue: 800] Payara Micro  4.1.1.163 #badassmicrofish (build 215) ready in 4319 (ms)

※PCが非力だという話は置いておいて…

これでもいいのですが、ここでJCacheのアノテーションを使うクラスも入れ込んでみます。
src/main/java/org/littlewings/payara/CalcService.java

package org.littlewings.payara;

import java.util.concurrent.TimeUnit;
import javax.cache.annotation.CacheDefaults;
import javax.cache.annotation.CacheResult;
import javax.enterprise.context.ApplicationScoped;

@CacheDefaults(cacheName = "calcCache")
@ApplicationScoped
public class CalcService {
    @CacheResult
    public int add(int a, int b) {
        try {
            TimeUnit.SECONDS.sleep(5L);
        } catch (InterruptedException e) {
            // ignore
        }

        return a + b;
    }
}

JAX-RSリソース側は、これを使うように。
src/main/java/org/littlewings/payara/CalcResource.java

package org.littlewings.payara;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("calc")
@ApplicationScoped
public class CalcResource {
    @Inject
    CalcService calcService;

    @GET
    @Path("add")
    @Produces(MediaType.TEXT_PLAIN)
    public int add(@QueryParam("a") int a, @QueryParam("b") int b) {
        return calcService.add(a, b);
    }
}

この状態でデプロイするとJCacheを使用したコードが動作するようになりますが、「--noCluster」オプションを付与するとちょっとうまく動かなくなります。

[2016-10-22T18:31:38.232+0900] [Payara Micro 4.1] [WARNING] [] [javax.enterprise.web] [tid: _ThreadID=13 _ThreadName=http-thread-pool(1)] [timeMillis: 1477128698232] [levelValue: 900] [[
  StandardWrapperValve[org.littlewings.payara.JaxrsActivator]: Servlet.service() for servlet org.littlewings.payara.JaxrsActivator threw exception
java.lang.NullPointerException
	at fish.payara.cdi.jsr107.impl.PayaraCacheResolverFactory.<init>(PayaraCacheResolverFactory.java:41)
	at fish.payara.cdi.jsr107.impl.PayaraCacheKeyInvocationContext.getFactory(PayaraCacheKeyInvocationContext.java:79)
	at fish.payara.cdi.jsr107.impl.PayaraCacheKeyInvocationContext.<init>(PayaraCacheKeyInvocationContext.java:67)
	at fish.payara.cdi.jsr107.CacheResultInterceptor.cacheResult(CacheResultInterceptor.java:47)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.jboss.weld.interceptor.reader.SimpleInterceptorInvocation$SimpleMethodInvocation.invoke(SimpleInterceptorInvocation.java:74)
	at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeAroundInvoke(InterceptorMethodHandler.java:84)
	at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeInterception(InterceptorMethodHandler.java:72)
	at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.invoke(InterceptorMethodHandler.java:56)
	at org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:79)
	at org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:68)
	at org.littlewings.payara.CalcService$Proxy$_$$_WeldSubclass.add(Unknown Source)
	at org.littlewings.payara.CalcService$Proxy$_$$_WeldClientProxy.add(Unknown Source)
	at org.littlewings.payara.CalcResource.add(CalcResource.java:21)
	at org.littlewings.payara.CalcResource$Proxy$_$$_WeldClientProxy.add(Unknown Source)

どういうことかといいますと、Payara Microで「--noCluster」オプションを使用すると、ことHazelcastについてはHazelcast自体がPayara Micro内部で起動しなくなります。

「--noCluster」オプションを付与すると、読み込む設定ファイルが変わり
https://github.com/payara/Payara/blob/payara-server-4.1.1.163/appserver/extras/payara-micro/payara-micro-core/src/main/java/fish/payara/micro/PayaraMicro.java#L986-L991

Hazelcastが無効に設定されます。
https://github.com/payara/Payara/blob/payara-server-4.1.1.163/appserver/extras/payara-micro/payara-micro-core/src/main/resources/microdomain-nocluster.xml#L160

結果として、Hazelcast自体も起動しなくなる、と。
https://github.com/payara/Payara/blob/payara-server-4.1.1.163/nucleus/payara-modules/hazelcast-bootstrap/src/main/java/fish/payara/nucleus/hazelcast/HazelcastCore.java#L94

見慣れたこういう表示が、なくなっているはずです。

[2016-10-22T18:38:00.942+0900] [Payara Micro 4.1] [INFO] [] [com.hazelcast.cluster.impl.MulticastJoiner] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1477129080942] [levelValue: 800] [[
  [172.17.0.1]:5900 [development] [3.6.4] 


Members [1] {
	Member [172.17.0.1]:5900 this
}
]]

起動した後で、自分でHazelcastの初期化をしてもいいですが、それはもはやPayara Microの管理下にはいないものになります。

このようなHazelcastの機能は使いたいけれどクラスタは無効にしたいケースでは、先に紹介したようにJoinerを無効にした設定ファイルを「--hzConfigFile」オプションで与えるとよいでしょう。

$ java -jar payara-micro-4.1.1.163.jar --deploy target/app.war --hzConfigFile hazelcast.xml

「--noCluster」には及びませんが、多少は速くなります、多少は。うちの環境で5秒くらいには。

[2016-10-22T18:40:27.963+0900] [Payara Micro 4.1] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1477129227963] [levelValue: 800] Payara Micro  4.1.1.163 #badassmicrofish (build 215) ready in 5177 (ms)