CLOVER🍀

That was when it all began.

WildFlyのHTTPセッションの保存先をInfinispan Serverに変更する

これは、なにをしたくて書いたもの?

WildFlyでのHTTPセッションの保存先として、以下のパターンがあるようです。

このブログでは、どれも扱ったことがないのですが。

最初のInfinispan(Embedded)を扱う場合はstandalone-ha.xml等のHigh Availability向けの設定ファイルを使うとすぐにできるので、
今回は3つ目のInfinispan Server(Hot Rod)をHTTPセッションの保存先にしてみたいと思います。

HotRod session management

WildFlyのHTTPセッションの保存先をInfinispan Server(Hot Rod)にする方法については、以下に記載があります。

High Availability Guide / Distributable Web Applications / Distributable Web Subsystem / HotRod session management

ざっくり言うと、WildFlyに対して以下の設定を行うことで利用できます。

  • Remote Cache Containerの定義
    • Infinispan Serverへの接続先の定義(socket-binding-groupへの追加)
    • InfinispanサブシステムへRemote Cache Containerを追加(remote-cache-containerを追加)
  • Distributable Webの設定
    • Distributable Webサブシステムへ、Hot Rod用のHTTPセッション管理設定を追加(hotrod-session-managementを追加)
  • Webアプリケーションの設定
    • web.xml<distributable/>を設定する
    • (Distributable WebサブシステムでデフォルトのHTTPセッション保存先をHot Rodにしていない場合は)WEB-INF/distributable-web.xmlでHTTPセッション保存先をHot Rodに指定

では、確認していきましょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 17.0.3 2022-04-19
OpenJDK Runtime Environment (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.3, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-117-generic", arch: "amd64", family: "unix"

WildFlyは26.1.1.Finalを使用します。

$ curl -OL https://github.com/wildfly/wildfly/releases/download/26.1.1.Final/wildfly-26.1.1.Final.zip

zipファイルを2回展開して、ローカルにWildFlyを2つのNodeを起動させることにします。

## WildFly-1
$ unzip wildfly-26.1.1.Final.zip
$ mv wildfly-26.1.1.Final wildfly-26.1.1.Final-1
$ cd wildfly-26.1.1.Final-1

## WildFly-2
$ unzip wildfly-26.1.1.Final.zip
$ mv wildfly-26.1.1.Final wildfly-26.1.1.Final-2
$ cd wildfly-26.1.1.Final-2

WildFly-1はそのまま起動し、

$ bin/standalone.sh

WildFly-2はポートを100ずらして起動しておきます。

$ bin/standalone.sh -Djboss.socket.binding.port-offset=100

Infinispan Serverは、WildFly 26.1.1.Finalに含まれているものがInfinispan 13.0.10.Finalだったので、こちらの合わせたものを使用。

Infinispan Serverは3つのNode(172.18.0.2〜172.18.0.4)を用意して、それぞれ以下のコマンドで起動しておきます。

$ bin/server.sh \
    -b 0.0.0.0 \
    -Djgroups.tcp.address=`hostname -i`

また、各Infinispan Serverには管理者権限を持ったユーザーを作成しておきます。以下のコマンドは、各Infinispan Serverで実行します。

$ bin/cli.sh user create -g admin -p password web-session-user

Cacheは作成しません。

WildFlyの設定を行う

ところで、WildFlyの起動コマンドは以下にしていました。

## WildFly-1
$ bin/standalone.sh
## WildFly-2
$ bin/standalone.sh -Djboss.socket.binding.port-offset=100

Standaloneモードであり、WildFly自体はクラスターを構成しない方針でいきます。
WildFly自体もクラスター構成にしてもいいのですが、今回はInfinispan Serverを介してHTTPセッションを共有していることを確認する主旨とします

WildFly-1に管理CLIで接続。

$ bin/jboss-cli.sh -c
[standalone@localhost:9990 /] 

手順に従って、WildFlyを設定していきます。

まずはSocket Binding Groupの追加と、Remote Cache Containerの追加です。

High Availability Guide / Infinispan Subsystem / Remote Cache Container / Configuration

/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server-1:add(host=172.18.0.2, port=11222)
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server-2:add(host=172.18.0.3, port=11222)

2つのNodeしか登録していませんが、あとひとつはHot Rod Client自身に見つけてもらうようにしましょう(Hot Rod Client自身がクラスターを
構成しているInfinispan Serverの各Nodeを検出することができます)。

次に、Remote Cache Containerを追加します。

batch
/subsystem=infinispan/remote-cache-container=remote-infinispan:add(default-remote-cluster=remote-infinispan-cluster)
/subsystem=infinispan/remote-cache-container=remote-infinispan/remote-cluster=remote-infinispan-cluster:add(socket-bindings=[infinispan-server-1, infinispan-server-2])
run-batch

少なくとも初回は、上記2つの操作はバッチ処理で実行する必要があります。Remote Cache ContainerとRemote Clusterの設定が相互に
依存したものになっているからです。かつ、Remote Cache Containerの作成時にはdefault-remote-cluster(デフォルトのRemote Clusterの設定)は
必須になっています。

個々に登録しようとすると、以下のようにエラーになります。

/subsystem=infinispan/remote-cache-container=remote-infinispan:add(default-remote-cluster=remote-infinispan-cluster)
{
    "outcome" => "failed",
    "failure-description" => "WFLYCTL0369: Required capabilities are not available:
    org.wildfly.clustering.infinispan.remote-cache-container.remote-cluster.remote-infinispan.remote-infinispan-cluster; Possible registration points for this capability:
                /subsystem=infinispan/remote-cache-container=*
                /subsystem=infinispan/remote-cache-container=*/remote-cluster=*",
    "rolled-back" => true
}


/subsystem=infinispan/remote-cache-container=remote-infinispan/remote-cluster=remote-infinispan-cluster:add(socket-bindings=[infinispan-server-1, infinispan-server-2])
{
    "outcome" => "failed",
    "failure-description" => "WFLYCTL0175: Resource [
    (\"subsystem\" => \"infinispan\"),
    (\"remote-cache-container\" => \"remote-infinispan\")
] does not exist; a resource at address [
    (\"subsystem\" => \"infinispan\"),
    (\"remote-cache-container\" => \"remote-infinispan\"),
    (\"remote-cluster\" => \"remote-infinispan-cluster\")
] cannot be created until all ancestor resources have been added",
    "rolled-back" => true
}

次に、Remote Cache Containerにorg.wildfly.clustering.web.hotrodをモジュールとして追加し、propertiesで認証情報を追加します。
認証情報は、Infinispan Serverで最初に作成した管理者権限を持ったユーザーを指定します。

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=modules,value=[org.wildfly.clustering.web.hotrod])
/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=properties,value={infinispan.client.hotrod.auth_username=web-session-user,infinispan.client.hotrod.auth_password=password})

org.wildfly.clustering.web.hotrodが必要な点はドキュメントに記載がなかったので、まあまあハマりました…。

ドキュメントに習って、統計情報も有効にしておきましょう。

High Availability Guide / Infinispan Subsystem / Remote Cache Container / Configuration / Statistics

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=statistics-enabled, value=true)

Remote Cache Containerの定義が終わったので、今度はHTTPセッションの保存先としてInfinispan Server(Hot Rod)が利用できるように
設定します。

High Availability Guide / Distributable Web Applications / Distributable Web Subsystem / HotRod session management

Distributable Webサブシステムのhotrod-session-managementとして、先ほど作成したRemote Cache Containerを指定。

/subsystem=distributable-web/hotrod-session-management=remote-session:add(remote-cache-container=remote-infinispan, cache-configuration=org.infinispan.DIST_SYNC, granularity=SESSION)

指定している各要素の意味は、それぞれ以下です。

  • remote-cache-container … HTTPセッションの保存先となるRemote Cache Container
  • cache-configuration … Infinispan Server側でCacheを作成する時のテンプレートとなるCacheの設定
    • Deploymentの名前を同じCacheがInfinispan Server側に存在しない場合、この設定を元にしてCacheを作成する
  • granularity … Session Managerが個々のセッションをどのように扱うかを指定する(SESSIONATTRIBUTEの2つから選択)
    • SESSION … すべてのセッションの属性を単一のCacheエントリー内に格納する。ATTRIBUTEよりもコストがかかるが、オブジェクト間の参照は保持される
    • ATTRIBUTE … 各セッションの属性を個々のCacheエントリーとして格納する。SESSIONよりも効率的だが、オブジェクト間の参照は保持されない

granularityは、standalone-ha.xmlに定義されているデフォルトのInfinispan(Embedded)のHTTPセッション管理だとSESSION
だったので、今回はそれに習いました。

granularityについては、こちらにも記載があります。

High Availability Guide / Distributable Web Applications / Optimizing performance of distributed web applications / Session granularity

affinityの設定も必要になります。affinityはHot Rodだとnonelocalのいずれかから選択します。今回はnoneとしました。

/subsystem=distributable-web/hotrod-session-management=remote-session/affinity=none:add()

noneはWebのセッションがアプリケーションごとに維持されないユースケースを想定しており、特定のNodeには紐付かない指定になります。
localは特定のリクエストを最後に処理したNodeとの親和性を持たせます。いわゆるスティッキーセッションに対応するオプションです。

デフォルトのHTTPセッション管理(default-session-management)は、今回作成したhotrod-session-managementとしておきましょう。

High Availability Guide / Distributable Web Applications / Distributable Web Subsystem

/subsystem=distributable-web:write-attribute(name=default-session-management,value=remote-session)

これで、web.xml<distributable/>として指定されたWebアプリケーションは、HTTPセッションの管理がInfinispan Server(Hot Rod)と
なります。

ここまでやったら、reloadWildFlyを再起動。

reload

WildFly-2にも、同様に反映しておきます。

$ bin/jboss-cli.sh -c --controller=localhost:10090

設定。

/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server-1:add(host=172.18.0.2, port=11222)
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server-2:add(host=172.18.0.3, port=11222)

batch
/subsystem=infinispan/remote-cache-container=remote-infinispan:add(default-remote-cluster=remote-infinispan-cluster,modules=[org.wildfly.clustering.web.hotrod],properties={infinispan.client.hotrod.auth_username=web-session-user,infinispan.client.hotrod.auth_password=password},statistics-enabled=true)
/subsystem=infinispan/remote-cache-container=remote-infinispan/remote-cluster=remote-infinispan-cluster:add(socket-bindings=[infinispan-server-1, infinispan-server-2])
run-batch

/subsystem=distributable-web/hotrod-session-management=remote-session:add(remote-cache-container=remote-infinispan, cache-configuration=org.infinispan.DIST_SYNC, granularity=SESSION)

/subsystem=distributable-web/hotrod-session-management=remote-session/affinity=none:add()
/subsystem=distributable-web:write-attribute(name=default-session-management,value=remote-session)

reload

Remote Cache Containerの設定は、こちらでは1行にまとめました。

/subsystem=infinispan/remote-cache-container=remote-infinispan:add(default-remote-cluster=remote-infinispan-cluster,modules=[org.wildfly.clustering.web.hotrod],properties={infinispan.client.hotrod.auth_username=web-session-user,infinispan.client.hotrod.auth_password=password},statistics-enabled=true)

サンプルアプリケーションを作成する

では、動作確認用のサンプルアプリケーションを作成します。

Mavenの依存関係等。

    <groupId>org.littlewings</groupId>
    <artifactId>remote-wildfly-hotrod-session</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-web-api</artifactId>
            <version>8.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>

HTTPセッションに保存するクラス。

src/main/java/org/littlewings/infinispan/session/Counter.java

package org.littlewings.infinispan.session;

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;

public class Counter implements Serializable {
    private static final long serialVersionUID = 1L;

    AtomicInteger value = new AtomicInteger();

    public int increment() {
        return value.incrementAndGet();
    }

    public int getValue() {
        return value.get();
    }
}

HTTPセッションを操作するクラス。お題は、カウンターということで。

src/main/java/org/littlewings/infinispan/session/CounterServlet.java

package org.littlewings.infinispan.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/counter")
public class CounterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();

        Counter counter = (Counter) session.getAttribute("counter");

        if (counter == null) {
            counter = new Counter();
            session.setAttribute("counter", counter);
        }

        int current = counter.increment();

        resp.getWriter().write(String.format("current value = %d%n", current));
    }
}

web.xml。ポイントは、<distributable/>の指定です。

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee webapp_4_0.xsd"
         version="4.0">
    <distributable/>
</web-app>

パッケージングして準備完了です。

$ mvn package

動作確認してみる

それでは、動作確認をしてみましょう。

まずはWildFly-1にデプロイ。

$ cp target/ROOT.war /path/to/wildfly-26.1.1.Final-1/standalone/deployments

すると、WildFly側ではInfinispan ServerにROOT.warというCacheが存在しないというWARNログが出力され

19:45:20,390 WARN  [org.infinispan.HOTROD] (HotRod-client-async-pool-0) ISPN004005: Error received from the server: org.infinispan.server.hotrod.CacheNotFoundException: Cache with name 'ROOT.war' not found amongst the configured caches

Infinispan Server側ではCacheが作成されます。

$ bin/cli.sh -c http://web-session-user:password@localhost:11222


[infinispan-server-18273@cluster//containers/default]> describe caches/ROOT.war
{
  "distributed-cache" : {
    "mode" : "SYNC",
    "remote-timeout" : "17500",
    "statistics" : true,
    "locking" : {
      "concurrency-level" : "1000",
      "acquire-timeout" : "15000",
      "striping" : false
    },
    "state-transfer" : {
      "timeout" : "60000"
    }
  }
}

また、WildFly側では、Infinispan Serverが3つのNodeで構成されていることを検出しています(登録していたのは、もともと2つのNodeでした)。

19:45:20,625 INFO  [org.infinispan.HOTROD] (HotRod-client-async-pool-1) ISPN004006: Server sent new topology view (id=9, age=0) containing 3 addresses: [172.18.0.4/<unresolved>:11222, 172.18.0.3/<unresolved>:11222, 172.18.0.2/<unresolved>:11222]

何回かアクセスしてみます。

$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 1


$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 2


$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 3

ここで、WildFly-2にもデプロイ。

$ cp target/ROOT.war /path/to/wildfly-26.1.1.Final-2/standalone/deployments

WildFly-2にアクセスしてみます。

$ curl -c cookie.txt -b cookie.txt localhost:8180/counter
current value = 4


$ curl -c cookie.txt -b cookie.txt localhost:8180/counter
current value = 5

WildFly-1で進めたカウンターの値を引き継いでいます。

もう1度、WildFly-1にアクセス。

$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 6


$ curl -c cookie.txt -b cookie.txt localhost:8080/counter
current value = 7

OKそうですね。

これで、クラスター構成にしなくてもWildFly Node間でHTTPセッションの共有かつリモートのInfinispan Serverへ保存するということが
実現できました。

この時の、WildFlyのInfinispan関連のメトリクスはこちら。

$ curl -s localhost:9990/metrics | grep -v '^#' | grep infinispan
wildfly_infinispan_activations_total{cache_container="ejb",cache="http-remoting-connector"} 0.0
wildfly_infinispan_active_connections{remote_cache_container="remote-infinispan"} 0.0
wildfly_infinispan_average_read_time_seconds{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.004
wildfly_infinispan_average_remove_time_seconds{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.0
wildfly_infinispan_average_write_time_seconds{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.004
wildfly_infinispan_connections{remote_cache_container="remote-infinispan"} 3.0
wildfly_infinispan_hits_total{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 3.0
wildfly_infinispan_idle_connections{remote_cache_container="remote-infinispan"} 3.0
wildfly_infinispan_misses_total{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.0
wildfly_infinispan_near_cache_hits_total{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.0
wildfly_infinispan_near_cache_invalidations_total{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.0
wildfly_infinispan_near_cache_misses_total{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.0
wildfly_infinispan_near_cache_size{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.0
wildfly_infinispan_passivations_total{cache_container="ejb",cache="http-remoting-connector"} 0.0
wildfly_infinispan_removes_total{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 0.0
wildfly_infinispan_time_since_reset_seconds{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 16119.0
wildfly_infinispan_writes_total{remote_cache_container="remote-infinispan",remote_cache="ROOT.war"} 2.0
wildfly_undertow_max_request_time_seconds{deployment="ROOT.war",servlet="org.littlewings.infinispan.session.CounterServlet",subdeployment="ROOT.war"} 0.0
wildfly_undertow_min_request_time_seconds{deployment="ROOT.war",servlet="org.littlewings.infinispan.session.CounterServlet",subdeployment="ROOT.war"} 0.0
wildfly_undertow_request_count_total{deployment="ROOT.war",servlet="org.littlewings.infinispan.session.CounterServlet",subdeployment="ROOT.war"} 0.0
wildfly_undertow_total_request_time_total_seconds{deployment="ROOT.war",servlet="org.littlewings.infinispan.session.CounterServlet",subdeployment="ROOT.war"} 0.0

Infinispan Serverのメトリクスはこちら。

$ curl -s --digest -u web-session-user:password 172.18.0.2:11222/metrics | grep -v '^#' | grep ROOT_war
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_activations{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_approximate_entries{node="infinispan-server-47530"} 6.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_approximate_entries_in_memory{node="infinispan-server-47530"} 6.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_approximate_entries_unique{node="infinispan-server-47530"} 3.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_average_read_time{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_average_read_time_nanos{node="infinispan-server-47530"} 403717.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_average_remove_time{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_average_remove_time_nanos{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_average_write_time{node="infinispan-server-47530"} 1.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_average_write_time_nanos{node="infinispan-server-47530"} 1979985.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_cache_loader_loads{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_cache_loader_misses{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_current_number_of_entries_in_memory{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_data_memory_used{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_evictions{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_hit_ratio{node="infinispan-server-47530"} 0.8
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_hits{node="infinispan-server-47530"} 24.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_invalidations{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_misses{node="infinispan-server-47530"} 6.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_number_of_entries{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_number_of_locks_available{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_number_of_locks_held{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_off_heap_memory_used{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_passivations{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_read_write_ratio{node="infinispan-server-47530"} 2.090909090909091
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_remove_hits{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_remove_misses{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_required_minimum_number_of_nodes{node="infinispan-server-47530"} 2.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_store_writes{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_stores{node="infinispan-server-47530"} 22.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_time_since_reset{node="infinispan-server-47530"} 16067.0
vendor_cache_manager_default_cache_ROOT_war_cluster_cache_stats_time_since_start{node="infinispan-server-47530"} 600.0
vendor_cache_manager_default_cache_ROOT_war_configuration_eviction_size{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_lock_manager_number_of_locks_available{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_lock_manager_number_of_locks_held{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_passivation_passivations{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_average_replication_time{node="infinispan-server-47530"} 1.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_average_xsite_replication_time{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_maximum_xsite_replication_time{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_minimum_xsite_replication_time{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_number_xsite_requests{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_number_xsite_requests_received{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_replication_count{node="infinispan-server-47530"} 23.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_replication_failures{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_rpc_manager_success_ratio_floating_point{node="infinispan-server-47530"} 1.0
vendor_cache_manager_default_cache_ROOT_war_statistics_approximate_entries{node="infinispan-server-47530"} 2.0
vendor_cache_manager_default_cache_ROOT_war_statistics_approximate_entries_in_memory{node="infinispan-server-47530"} 2.0
vendor_cache_manager_default_cache_ROOT_war_statistics_approximate_entries_unique{node="infinispan-server-47530"} 1.0
vendor_cache_manager_default_cache_ROOT_war_statistics_average_read_time{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_statistics_average_read_time_nanos{node="infinispan-server-47530"} 172998.0
vendor_cache_manager_default_cache_ROOT_war_statistics_average_remove_time{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_statistics_average_remove_time_nanos{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_statistics_average_write_time{node="infinispan-server-47530"} 2.0
vendor_cache_manager_default_cache_ROOT_war_statistics_average_write_time_nanos{node="infinispan-server-47530"} 2944007.0
vendor_cache_manager_default_cache_ROOT_war_statistics_data_memory_used{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_statistics_elapsed_time{node="infinispan-server-47530"} 600.0
vendor_cache_manager_default_cache_ROOT_war_statistics_evictions{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_statistics_hit_ratio{node="infinispan-server-47530"} 1.0
vendor_cache_manager_default_cache_ROOT_war_statistics_hits{node="infinispan-server-47530"} 8.0
vendor_cache_manager_default_cache_ROOT_war_statistics_misses{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_statistics_number_of_entries{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_statistics_number_of_entries_in_memory{node="infinispan-server-47530"} -1.0
vendor_cache_manager_default_cache_ROOT_war_statistics_off_heap_memory_used{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_statistics_read_write_ratio{node="infinispan-server-47530"} 0.8
vendor_cache_manager_default_cache_ROOT_war_statistics_remove_hits{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_statistics_remove_misses{node="infinispan-server-47530"} 0.0
vendor_cache_manager_default_cache_ROOT_war_statistics_required_minimum_number_of_nodes{node="infinispan-server-47530"} 2.0
vendor_cache_manager_default_cache_ROOT_war_statistics_stores{node="infinispan-server-47530"} 10.0
vendor_cache_manager_default_cache_ROOT_war_statistics_time_since_reset{node="infinispan-server-47530"} 600.0
vendor_cache_manager_default_cache_ROOT_war_statistics_time_since_start{node="infinispan-server-47530"} 600.0

オマケ

Cacheの名前はどこで決まっている?

Infinispan Server側に作成されるCacheの名前は、Deploymentの名前ということでした。実際、ROOT.warをデプロイするとROOT.warという
名前のCacheが作成されました。

これはどこで決まっているかというと、ここみたいですね。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/web/hotrod/src/main/java/org/wildfly/clustering/web/hotrod/session/HotRodSessionManagerFactoryServiceConfigurator.java#L78

そして、Cacheを追加している箇所はこちら。

https://github.com/wildfly/wildfly/blob/26.1.1.Final/clustering/infinispan/client/src/main/java/org/wildfly/clustering/infinispan/client/service/RemoteCacheServiceConfigurator.java#L84

Marshallerで警告されている

今回の設定だと、起動時にMarshallerに関するこんなWARNログが出力されます。

19:45:03,247 WARN  [org.jboss.as.clustering.infinispan] (ServerService Thread Pool -- 54) WFLYCLINF0033: Attribute 'marshaller' is configured to use a deprecated value: LEGACY; use one of the following values instead: [JBOSS, PROTOSTREAM]

これは、Remote Cache Containerの設定でMarshallerにLEGACYが設定されているからですが。

/subsystem=infinispan/remote-cache-container=remote-infinispan:read-attribute(name=marshaller)
{
    "outcome" => "success",
    "result" => "LEGACY"
}

PROTOSTREAMを指定するのがInfinispan的には良いと思いますが、今回はこのままで。
※あとで調べた結果、HTTPセッションのケースではPROTOSTREAMを指定したことと同じになるようです

追記) Marshallerについての追加

WildFlyのInfinispanサブシステムでProtoStreamが使われているようになっていたという話 - CLOVER🍀

アプリケーション側で使用するHTTPセッション保存先を指定する

これは、以下の記載を見ます。

High Availability Guide / Distributable Web Applications / Overriding default behavior

WEB-INF/distributable-web.xmlまたはMETA-INF/jboss-all.xmlで指定できるようです。

たとえば、今回は<distributable/>なアプリケーションのHTTPセッションのデフォルトの保存先をRemote Cache Containerとしましたが、
これを他の保存先を指定する場合は以下のように記載します。

src/main/webapp/WEB-INF/distributable-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<distributable-web xmlns="urn:jboss:distributable-web:2.0">
    <session-management name="default"/>
</distributable-web>

これは、今回の設定変更を行った状態でのstandalone/configuration/standalone.xmlで、infinispan-session-managementdefault
指しています(つまり、WildFlyのデフォルトの状態)。

        <subsystem xmlns="urn:jboss:domain:distributable-web:2.0" default-session-management="remote-session" default-single-sign-on-management="default">
            <infinispan-session-management name="default" cache-container="web" granularity="SESSION">
                <local-affinity/>
            </infinispan-session-management>
            <hotrod-session-management name="remote-session" remote-cache-container="remote-infinispan" cache-configuration="org.infinispan.DIST_SYNC" granularity="SESSION">
                <no-affinity/>
            </hotrod-session-management>
            <infinispan-single-sign-on-management name="default" cache-container="web" cache="sso"/>
            <local-routing/>
        </subsystem>

単純に定義済みのものを指定するだけではなく、hotrod-session-managementinfinispan-session-managementの指定をもっと細かく
行えるようです。

ハマったこと

今回の内容で、いくつかハマりそうなこと(ハマったこと)を書いておきます。

認証・認可

現在のInfinispan Serverはデフォルトで認証が有効になっているので、WildFlyからの接続時に認証情報を指定しておかないとアクセスできません。

認証情報を設定していない場合は、こんなエラーが出力されます。

20:21:38,487 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 81) MSC000001: Failed to start service jboss.clustering.web."ROOT.war".cache: org.jboss.msc.service.StartException in service jboss.clustering.web."ROOT.war".cache: org.infinispan.client.hotrod.exceptions.HotRodClientException:Request for messageId=6 returned server error (status=0x85): java.lang.SecurityException: ISPN006017: Operation 'EXEC' requires authentication

Hot Rodのプロパティの指定方法がドキュメントに書かれていないのでちょっとわかりづらいですが、こんな感じの指定方法になります。

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=properties,value={infinispan.client.hotrod.auth_username=web-session-user,infinispan.client.hotrod.auth_password=password})

また今回、Infinispan Serverでユーザーを作成する際にadminグループに属するようにしましたが。

$ bin/cli.sh user create -g admin -p password web-session-user

これをapplicationグループに属するユーザーとすると、

$ bin/cli.sh user create -g application -p password web-session-user

以下のようにCacheを作成する権限がなくてエラーになります。

java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [web-session-app-user, RolePrincipal{name='application'}, InetAddressPrincipal [address=172.18.0.1/172.18.0.1]]' lacks 'CREATE' permission
20:23:53,318 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 79) MSC000001: Failed to start service jboss.clustering.web."ROOT.war".cache: org.jboss.msc.service.StartException in service jboss.clustering.web."ROOT.war".cache: org.infinispan.client.hotrod.exceptions.HotRodClientException:Request for messageId=18 returned server error (status=0x85): org.infinispan.commons.CacheException: java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [web-session-app-user, RolePrincipal{name='application'}, InetAddressPrincipal [address=172.18.0.1/172.18.0.1]]' lacks 'CREATE' permission
java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [web-session-app-user, RolePrincipal{name='application'}, InetAddressPrincipal [address=172.18.0.1/172.18.0.1]]' lacks 'CREATE' permission
org.infinispan.client.hotrod.exceptions.HotRodClientException:Request for messageId=18 returned server error (status=0x85): org.infinispan.commons.CacheException: java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [web-session-app-user, RolePrincipal{name='application'}, InetAddressPrincipal [address=172.18.0.1/172.18.0.1]]' lacks 'CREATE' permission

今回はadminグループに属する強い権限を与えましたが、Cacheさえ作れればよいのでdeployerグループあたりでよいかもですね。
このグループは、Infinispan Serverでデフォルトで使えるClusterRoleMapperのものを使用しています。

Guide to Infinispan Server / Getting started with Infinispan Server / Creating and modifying Infinispan users / User roles and permissions

もっと細かく権限管理したい場合は、Infinispan Serverのドキュメントに従って認証・認可設定を行ってください。

org.wildfly.clustering.web.hotrodモジュールを指定する

org.wildfly.clustering.web.hotrodモジュールをRemote Cache Containerに指定していましたが、これを忘れるとSessionCreationMetaDataKey
シリアライズできずに失敗します。

20:31:38,437 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /counter: org.infinispan.client.hotrod.exceptions.HotRodClientException:: Unable to marshall object of type [org.wildfly.clustering.web.hotrod.session.SessionCreationMetaDataKey]

以下のコマンドは、org.wildfly.clustering.web.hotrodモジュールをRemote Cache Containerに設定しているものです。

/subsystem=infinispan/remote-cache-container=remote-infinispan:write-attribute(name=modules,value=[org.wildfly.clustering.web.hotrod])

この説明は、ドキュメントには一切出てきませんが…。

追記) org.wildfly.clustering.web.hotrodモジュールを足さないとシリアライズに失敗するのは、MarshallerをLEGACYにしているからだと
   いうことに後で気づきました

WildFlyのInfinispanサブシステムでProtoStreamが使われているようになっていたという話 - CLOVER🍀

実はaffinityの指定が必須

途中で以下のように突然affinityの指定を行いましたが。

/subsystem=distributable-web/hotrod-session-management=remote-session/affinity=none:add()

実は、これを入れないとデプロイ時に失敗します。

20:45:46,511 INFO  [org.jboss.as.controller] (DeploymentScanner-threads - 1) WFLYCTL0183: Service status report
WFLYCTL0184:    New missing/unsatisfied dependencies:
      service org.wildfly.clustering.web.session-management-provider.remote-session.affinity (missing) dependents: [service org.wildfly.clustering.web.session-management-provider.remote-session]

hotrod-session-managementの必須項目ではないのですが、まあ要りますということで…。

参考

How to externalize HTTP sessions on Infinispan - Mastertheboss

まとめ

WildFlyで動作するアプリケーションのHTTPセッションの保存先を、Infinispan Serverに設定してみました。

設定内容のイメージはある程度ありましたが、WildFly上でどう表現するのかがよくわからなかったり、モジュール指定が必要だというところで
まあまあハマりました。

とりあえず、実現できたので良しとしましょう。

今回作成したソースコードは、こちらに置いています。

https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-wildfly-hotrod-session