CLOVER🍀

That was when it all began.

InfinispanのCache Configuration Templateを試す

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

Infinispanには、設定ファイル内で、Cacheの設定をテンプレートとして扱う機能があります。

最近の機能、というわけではないのですが、1度ちゃんと見ておいた方が良さそうな気がしてきたので、今回試してみることにしました。

Cache Configuration Template

Release Notesを見ると、Infinispan 8.0で導入されたようです。

Release Notes - Infinispan

ブログエントリは、こちら。

Blog: Infinispan 8.0.0.Beta3 out with Lucene 5, Functional API, Templates...etc - Infinispan

Blog: Infinispan 8.0.0.Final - Infinispan

機能としては、Cacheの設定をテンプレートにしてCacheの設定を共通化するものなのですが、その適用方法としては以下の2つがあります。

  • Cacheの設定時に明示的にテンプレートを適用する
  • 名前のマッチ(Glob)を使ってテンプレートを暗黙的に適用する

ドキュメントの記載箇所は、こちらです。

Cache Configuration Templates

Cache Configuration Wildcards

テンプレートを元に定義するCacheは、Cacheの定義時にカスタマイズ(設定の追加、上書き)が可能です。またテンプレート自体を
別のテンプレートを継承する形で作成することもできます。

それでは、試しながら見ていってみましょう。

環境

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

$ java --version
openjdk 11.0.6 2020-01-14
OpenJDK Runtime Environment (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1)
OpenJDK 64-Bit Server VM (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-88-generic", arch: "amd64", family: "unix"

準備

Maven依存関係やプラグインの設定は、こちら。

    <dependencies>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-core</artifactId>
            <version>10.1.2.Final</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.6.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.6.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

Infinispanは10.1.2.Finalを使用します。それ以外は、テストコード用です。

テストコードの雛形とお題

最初に、テストコードの雛形を作成します。

こんな感じで作成。
src/test/java/org/littlewings/infinispan/configuration/ConfigurationTemplateTest.java

package org.littlewings.infinispan.configuration;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.infinispan.Cache;
import org.infinispan.configuration.cache.ClusteringConfiguration;
import org.infinispan.configuration.cache.ExpirationConfiguration;
import org.infinispan.configuration.cache.MemoryConfiguration;
import org.infinispan.configuration.cache.StorageType;
import org.infinispan.configuration.cache.TransactionConfiguration;
import org.infinispan.eviction.EvictionType;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class ConfigurationTemplateTest {
    <K, V> void withCache(String configurationFileName, int numOfInstances, String cacheName, Consumer<Cache<K, V>> func) {
        List<EmbeddedCacheManager> managers =
                IntStream
                        .rangeClosed(1, numOfInstances)
                        .mapToObj(i -> {
                            try {
                                return new DefaultCacheManager(configurationFileName);
                            } catch (IOException e) {
                                throw new UncheckedIOException(e);
                            }
                        })
                        .collect(Collectors.toList());

        try {
            managers.forEach(manager -> manager.getCache(cacheName));

            Cache<K, V> cache = managers.get(0).getCache(cacheName);
            func.accept(cache);
        } finally {
            managers.forEach(EmbeddedCacheManager::stop);
        }
    }

    // ここに、テストを書く!!
}

簡易的にクラスタを構成できるようなメソッドを付けておきました。

こちらを使いつつ、Cacheの設定のテンプレートを使っていってみましょう。Cacheは、基本的にはDistributed Cacheを使います。

また、Infinispanの設定ファイルは、以下のイメージで作成します。コメントの箇所に、CacheやCache Configuration Templateを埋めていく感じで
書いていきます。

<?xml version="1.0" encoding="UTF-8"?>
<infinispan
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:infinispan:config:10.1 https://infinispan.org/schemas/infinispan-config-10.1.xsd"
        xmlns="urn:infinispan:config:10.1">

    <cache-container>
        <transport stack="udp"/>

        <!-- CacheやCache Configuration Templateを定義する -->
    </cache-container>
</infinispan>

まずはシンプルに

最初は、Cache Configuration Templateを使わずにいきましょう。

こんな感じで、max-idleとlifespanを設定したDistributed Cacheを定義します。

        <distributed-cache name="simpleDistributedCache">
            <expiration max-idle="5" lifespan="10"/>
        </distributed-cache>

確認。

    @Test
    public void simpleDistributedCache() {
        this.<String, String>withCache("infinispan-simple.xml", 3, "simpleDistributedCache", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isDistributed()).isTrue();

            ExpirationConfiguration expirationConfiguration = cache.getCacheConfiguration().expiration();
            assertThat(expirationConfiguration.maxIdle()).isEqualTo(5L);
            assertThat(expirationConfiguration.lifespan()).isEqualTo(10L);
        });
    }

この設定内容を、Cache Configuration Templateにしてみましょう。

はじめてのCache Configuration Template

Cache Configuration Templateは、「xxxxx-cache-configuration」で定義します(xxxxxの部分は、Cacheの種類が入ります)。

今回は、Distributed CacheのCache Configuration Templateとして作成。

        <distributed-cache-configuration name="expirationCacheTemplate">
            <expiration max-idle="5" lifespan="10"/>
        </distributed-cache-configuration>

Cache Configuration Templateを利用するCacheは、configuration属性に定義したCache Configuration Templateの名前を指定します。

        <distributed-cache name="expirationCache" configuration="expirationCacheTemplate"/>

今回は、その他の設定は一切入れていません。

確認。

        this.<String, String>withCache("infinispan-use-template1.xml", 3, "expirationCache", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isDistributed()).isTrue();

            ExpirationConfiguration expirationConfiguration = cache.getCacheConfiguration().expiration();
            assertThat(expirationConfiguration.maxIdle()).isEqualTo(5L);
            assertThat(expirationConfiguration.lifespan()).isEqualTo(10L);
        });

Cache自体にはなにも設定を入れていないのに、expirationに関する設定が入っている(Cache Configuration Templateが適用されている)ことが
確認できました。

また、単純にテンプレートを適用するだけではなく、カスタマイズすることもできます。

たとえば、以下はCache Configuration Templateは使用するものの、expirationのlifespan属性を変更し、トランザクションに対応させたCacheです。

        <distributed-cache name="expirationExtendedCache" configuration="expirationCacheTemplate">
            <expiration lifespan="15"/>
            <transaction mode="NON_XA"/>
        </distributed-cache>

確認してみると、expirationのlifespanは再定義した値になっていますが、max-idleについてはCache Configuration Templateの内容が引き継がれている
ことが確認できます。また、トランザクションにも対応するようになりました。

        this.<String, String>withCache("infinispan-use-template1.xml", 3, "expirationExtendedCache", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isDistributed()).isTrue();

            ExpirationConfiguration expirationConfiguration = cache.getCacheConfiguration().expiration();
            assertThat(expirationConfiguration.maxIdle()).isEqualTo(5L);
            assertThat(expirationConfiguration.lifespan()).isEqualTo(15L);

            TransactionConfiguration transactionConfiguration = cache.getCacheConfiguration().transaction();
            assertThat(transactionConfiguration.transactionMode().isTransactional()).isTrue();
        });
    }

基本的な使い方は、これでOKみたいです。

ちなみに、Cache Configuration Templateは、それを使うCacheの前に定義しておく必要があります。たとえば、以下のように先にCacheを
定義してしまうと

        <distributed-cache name="expirationCache" configuration="expirationCacheTemplate"/>

        <distributed-cache-configuration name="expirationCacheTemplate">
            <expiration max-idle="5" lifespan="10"/>
        </distributed-cache-configuration>

こんなエラーを見ることになります。

org.infinispan.commons.CacheConfigurationException: ISPN000374: No such template 'expirationCacheTemplate' when declaring 'expirationCache'

    at org.infinispan.configuration.parsing.Parser.getConfigurationBuilder(Parser.java:2195)
    at org.infinispan.configuration.parsing.Parser.parseDistributedCache(Parser.java:2073)
    at org.infinispan.configuration.parsing.Parser.parseContainer(Parser.java:685)
    at org.infinispan.configuration.parsing.Parser.readElement(Parser.java:123)
    at org.infinispan.configuration.parsing.ParserRegistry.parseElement(ParserRegistry.java:224)
    at org.infinispan.configuration.parsing.ParserRegistry.parse(ParserRegistry.java:194)
    at org.infinispan.configuration.parsing.ParserRegistry.parse(ParserRegistry.java:180)
    at org.infinispan.configuration.parsing.ParserRegistry.parse(ParserRegistry.java:151)
    at org.infinispan.manager.DefaultCacheManager.<init>(DefaultCacheManager.java:335)

〜省略〜
    @Test
    public void useBadTemplate1() {
        assertThatThrownBy(() -> new DefaultCacheManager("infinispan-bad-template1.xml"))
                .hasMessage("ISPN000374: No such template 'expirationCacheTemplate' when declaring 'expirationCache'");
    }

それ以外にも気になることとして、Cacheの種類の違うCache Configuration Templateを使ってCacheの定義をすると、これ自体はできてしまうのですが

        <distributed-cache-configuration name="expirationCacheTemplate">
            <expiration max-idle="5" lifespan="10"/>
        </distributed-cache-configuration>

        <replicated-cache name="expirationCache" configuration="expirationCacheTemplate"/>

まあ、やめた方がいいでしょうね。

    @Test
    public void useBadTemplate2() {
        this.<String, String>withCache("infinispan-bad-template2.xml", 3, "expirationCache", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isReplicated()).isTrue();

            ExpirationConfiguration expirationConfiguration = cache.getCacheConfiguration().expiration();
            assertThat(expirationConfiguration.maxIdle()).isEqualTo(5L);
            assertThat(expirationConfiguration.lifespan()).isEqualTo(10L);
        });
    }

Cache Configuration Templateを拡張したCache Configuration Templateを使う

Cache Configuration Templateは、Cacheに適用するだけではなく、Cache Configuration Templateに適用して拡張することもできます。

たとえば、Off-Heapを使うCacheを、Cache Configuration Templateを使って定義してみます。
※値は超適当です

        <distributed-cache-configuration name="offHeapContainerCacheTemplate">
            <memory>
                <off-heap eviction="MEMORY" size="896000"/>
            </memory>
        </distributed-cache-configuration>

        <distributed-cache name="offHeapCache" configuration="offHeapContainerCacheTemplate"/>

確認。

        this.<String, String>withCache("infinispan-use-template2.xml", 3, "offHeapCache", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isDistributed()).isTrue();

            MemoryConfiguration memoryConfiguration = cache.getCacheConfiguration().memory();
            assertThat(memoryConfiguration.storageType()).isEqualTo(StorageType.OFF_HEAP);
            assertThat(memoryConfiguration.evictionType()).isEqualTo(EvictionType.MEMORY);
            assertThat(memoryConfiguration.size()).isEqualTo(896000L);
        });

ここに、expirationの設定も足したい…と思うとCacheのカスタマイズになりますが、Cache Configuration Templateを組み合わせて
拡張することができます。

たとえば、以下はexpirationを定義したCache Configuration Templateを、Off-Heapを使うCache Configuration Templateで拡張した例です。
さらに、expirationの内容は(だいぶ意図的ですが)、拡張先のCache Configuration Templateでカスタマイズすることができます。

        <distributed-cache-configuration name="expirationCacheTemplate">
            <expiration max-idle="5" lifespan="10"/>
        </distributed-cache-configuration>

        <distributed-cache-configuration name="offHeapContainerWithExpirationCacheTemplate" configuration="expirationCacheTemplate">
            <expiration lifespan="15" interval="120000"/>
            <memory>
                <off-heap eviction="MEMORY" size="896000"/>
            </memory>
        </distributed-cache-configuration>

さらに、このCache Configuration Templateを適用したCacheで、off-heapのsize属性をカスタマイズしたり、トランザクション対応にさせてみたり。

        <distributed-cache name="offHeapWithExpirationCache" configuration="offHeapContainerWithExpirationCacheTemplate">
            <memory>
                <off-heap size="1792000"/>
            </memory>
            <transaction mode="NON_XA"/>
        </distributed-cache>

ちょっとやりすぎ感、ありますが、こういうったこともできますよ、ということで。

確認。

        this.<String, String>withCache("infinispan-use-template2.xml", 3, "offHeapWithExpirationCache", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isDistributed()).isTrue();

            MemoryConfiguration memoryConfiguration = cache.getCacheConfiguration().memory();
            assertThat(memoryConfiguration.storageType()).isEqualTo(StorageType.OFF_HEAP);
            assertThat(memoryConfiguration.evictionType()).isEqualTo(EvictionType.MEMORY);
            assertThat(memoryConfiguration.size()).isEqualTo(1792000L);

            ExpirationConfiguration expirationConfiguration = cache.getCacheConfiguration().expiration();
            assertThat(expirationConfiguration.maxIdle()).isEqualTo(5L);
            assertThat(expirationConfiguration.lifespan()).isEqualTo(15L);
            assertThat(expirationConfiguration.wakeUpInterval()).isEqualTo(120000L);

            TransactionConfiguration transactionConfiguration = cache.getCacheConfiguration().transaction();
            assertThat(transactionConfiguration.transactionMode().isTransactional()).isTrue();
        });

Globを使う

最後に、Globを使ってみましょう。

これは、Cache Configuration TemplateにGlobパターンを使うことにより、マッチしたCacheに対してCache Configuration Templateを適用する機能です。

こんな感じで「*」を含む名前で、Cache Configuration Templateを定義すると

        <distributed-cache-configuration name="expirationCache*">
            <expiration max-idle="5" lifespan="10"/>
        </distributed-cache-configuration>

Cache Configuration Templateのnameで定義したパターンにマッチするCacheを取得すると、その設定が適用されます。以下では、2つのCacheを
Cache Configuration Templateが適用された状態で取得しました。

        this.<String, String>withCache("infinispan-use-glob.xml", 3, "expirationCache1", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isDistributed()).isTrue();

            ExpirationConfiguration expirationConfiguration = cache.getCacheConfiguration().expiration();
            assertThat(expirationConfiguration.maxIdle()).isEqualTo(5L);
            assertThat(expirationConfiguration.lifespan()).isEqualTo(10L);
        });

        this.<String, String>withCache("infinispan-use-glob.xml", 3, "expirationCache2", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isDistributed()).isTrue();

            ExpirationConfiguration expirationConfiguration = cache.getCacheConfiguration().expiration();
            assertThat(expirationConfiguration.maxIdle()).isEqualTo(5L);
            assertThat(expirationConfiguration.lifespan()).isEqualTo(10L);
        });

もう1パターン。今度は、前方一致。

        <distributed-cache-configuration name="*ExpirationCache">
            <expiration max-idle="5" lifespan="10"/>
        </distributed-cache-configuration>

確認。

        this.<String, String>withCache("infinispan-use-glob.xml", 3, "myExpirationCache", cache -> {
            ClusteringConfiguration clusteringConfiguration = cache.getCacheConfiguration().clustering();
            assertThat(clusteringConfiguration.cacheMode().isDistributed()).isTrue();

            ExpirationConfiguration expirationConfiguration = cache.getCacheConfiguration().expiration();
            assertThat(expirationConfiguration.maxIdle()).isEqualTo(5L);
            assertThat(expirationConfiguration.lifespan()).isEqualTo(10L);
        });

Globとして使える文字は、「*(任意の文字列)」と「?(任意の1文字)」です。

https://github.com/infinispan/infinispan/blob/10.1.2.Final/commons/src/main/java/org/infinispan/commons/util/GlobUtils.java

ちなみに、マッチするパターンがないCacheの場合は、例外がスローされます。

        assertThatThrownBy(() -> {
            EmbeddedCacheManager manager = new DefaultCacheManager("infinispan-use-glob.xml");

            try {
                manager.getCache("simpleCache");
            } finally {
                manager.stop();
            }
        })
                .hasMessage("ISPN000436: Cache 'simpleCache' has been requested, but no cache configuration exists with that name and no default cache has been set for this container");

これは、ドキュメントに書いてあるとおりです。

Infinispan throws exceptions if cache names match more than one wildcard.

Cache Configuration Wildcards

なのですが、ドキュメントを信じてCacheの定義自体を設定ファイルに書いてしまうと

    <cache-container>
        <local-cache-configuration name="basecache*"> 
            <expiration interval="10500" lifespan="11" max-idle="11"/>
        </local-cache-configuration>
        <local-cache name="basecache-1"/> 
        <local-cache name="basecache-2"/> 
    </cache-container>

実はCache Configuration Templateは適用されなかったりします。

これは、Cacheに関する設定の定義がない場合にGlobパターンが使えるかどうか確認する処理が適用されるのですが

https://github.com/infinispan/infinispan/blob/10.1.2.Final/core/src/main/java/org/infinispan/configuration/ConfigurationManager.java#L67-L91

Cacheの定義自体を設定ファイルに書いてしまうと、設定ファイルのパース時にCacheの設定を登録してしまい、Cache Configuration Templateが
無視されてしまうからです。

https://github.com/infinispan/infinispan/blob/10.1.2.Final/core/src/main/java/org/infinispan/configuration/ConfigurationManager.java#L41-L42

なので、Globを使う場合はCache Configuration Templateだけ定義して、Cacheの名前はプログラム中で指定する使い方になります。

使うなら、明示的にCache Configuration Templateを適用するパターンかな?と思ったりしているので、こちらでCache Configuration Templateの
継承といったことは深追いしていません。

もう少し、中身を

Cache Configuration Templateは、Cacheの定義に使うConfigurationを利用して実現しています。もともと、あるConfigurationを元にして
別のConfigurationを作成することができるので、それを拡張した仕組みのように見えます。

パース時にこの仕組みが使われているのは、このあたりですね。

https://github.com/infinispan/infinispan/blob/10.1.2.Final/core/src/main/java/org/infinispan/configuration/parsing/Parser.java#L2201

まとめ

今回は、InfinispanのCache Configuration Templateを試してみました。

今時点では特に新しい機能ではないですし、存在すること自体は知っていたのですが、Infinispan Serverを見る上でちゃんと知っておいた方が
いいなぁと思い始めたので、今回試してみました。

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

https://github.com/kazuhira-r/infinispan-getting-started/tree/master/embedded-configuration-template