これは、なにをしたくて書いたもの?
最近、久しぶりにInfinispanのCacheStoreまわりのAPIをちょっと眺めてみたら、特にAdvancedCacheLoader/AdvancedCacheWriterまわりが
だいぶ変わっているのに驚きまして。
いい機会なので、もう1度CacheStoreまわりのAPIを見ていこうかなと。
いきなりAdvanced〜の方にいくとハードルが高いので、今回はまずCacheLoader/CacheWriterから使っていきます。
Persistence
こちらは、Infinispan 6.0の頃に書いたエントリです。
InfinispanのCacheLoader/CacheWriterを使ってみる - CLOVER🍀
今回はInfinispan 10.1で見直してみます。
Setting Up Persistent Storage
InfinispanからいくつかCacheStoreの実装は提供されていますが、今回は自分でCacheStoreを実装する方を見ていきます。
これが、Infinispan Persistence SPIで、外部ストレージへの読み書きを行うためのAPIとなります。
Infinispan Persistence SPIs
Infinispan Persistence SPIは、以下の特徴を持つと書かれています。
- 標準に準拠 … JSR-107仕様に準拠したCacheWriter、CacheLoaderインターフェース(※1)
- トランザクションへの統合 … Infinispan側でロックを扱うため、CacheStoreの実装は永続ストアへの同時アクセスを考慮しなくてよい(※2)
- Parallel Iteration … マルチスレッドを使った並列処理で永続ストアにあるエントリを扱うことができる
- シリアライズの削減 … Infinispanは保存されたエントリを、リモートに送信可能なシリアライズされた形式で公開しているため、永続ストアからの取得時のデシリアライズ、書き込み時の再度のシリアライズは不要
※1 … そう?
※2 … 永続ストア自体は、複数のスレッドからアクセスされることを許容する必要がある
Infinispan Persistence SPIは、以下のAPIから構成されます。
- ByteBuffer … シリアライズされたオブジェクトを抽象化したもの
- MarshallableEntry … Cacheに登録されたキー/値を永続ストア内に保持するために抽象化したもので、シリアライズ/デシリアライズされた形式の両方が扱える。なお、シリアライズは遅延処理される
- CacheWriter/CacheLoader … 永続ストアへの読み書きに対する基本的な機能を実現
- AdvancedCacheLoader/AdvancedCacheWriter … 並列でのイテレーション、有効期限切れエントリのパージ、クリア、サイズ取得といった、永続ストレージに対するバルク操作を実現
- SegmentedAdvancedLoadWriteStore … セグメントを扱う操作を実現
今回はCacheLoaderおよびCacheWriterを扱うのですが、実際に使うにはAdvancedCacheLoader/AdvancedCacheWriterがないと
機能的には足りないことがわかります。AdvancedCacheLoaderを実装していない場合はイテレーションができず、Expireにも対応
できません。AdvancedCacheWriterを実装していない場合は、有効期限切れのエントリをパージしたりクリアもできません。
なのですが、Advanced〜に関するAPIは割と難しいので、また機会を改めて。
あと、SegmentedAdvancedLoadWriteStoreが追加されていたのは知らなかったですね。Infinispan 9.4からのようです。
では、説明はこれくらいにして、今回はCacheLoader/CacheWriterを使っていってみます。
環境
今回の環境は、こちら。
$ 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.18.0-25-generic", arch: "amd64", family: "unix"
準備
Maven依存関係は、こちら。
<dependencies>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
<version>10.1.5.Final</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.15.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Infinispanは10.1.5.Finalを使用します。また、JUnit 5とAssertJはテストコード用です。
お題
今回は、CacheStoreが使う永続ストレージとしてMap(永続じゃないですが)を使うことにします。
極めて簡易なものですが、とりあえず基本的な使い方ということで。
作成するものは、設定まわりでStoreConfiguration、StoreConfigurationBuilderの実装、そして永続化を扱うためにCacheLoader/CacheWriterの
実装です。
設定まわりのクラスを作る
では、最初に設定まわりのクラスを作成します。
StoreConfiguration (Infinispan JavaDoc All 10.1.5.Final API)
StoreConfigurationBuilder (Infinispan JavaDoc All 10.1.5.Final API)
StoreConfigurationの実装。
src/main/java/org/littlewings/infinispan/persistence/SimpleMapCacheStoreConfiguration.java
package org.littlewings.infinispan.persistence;
import org.infinispan.commons.configuration.BuiltBy;
import org.infinispan.commons.configuration.ConfigurationFor;
import org.infinispan.commons.configuration.attributes.AttributeSet;
import org.infinispan.configuration.cache.AbstractStoreConfiguration;
import org.infinispan.configuration.cache.AsyncStoreConfiguration;
@BuiltBy(SimpleMapCacheStoreConfigurationBuilder.class)
@ConfigurationFor(SimpleMapCacheStore.class)
public class SimpleMapCacheStoreConfiguration extends AbstractStoreConfiguration {
public SimpleMapCacheStoreConfiguration(AttributeSet attributes, AsyncStoreConfiguration async) {
super(attributes, async);
}
}
@BuildByアノテーションでStoreConfigurationBuilderの実装を、@ConfigurationForアノテーションでCacheStoreの実装を指定します。
StoreConfigurationBuilderは、CacheStoreの設定を行うためのBuilderですね。Infinispanの設定をAPIで行おうとすると、
ConfigurationBuilderからbuildしてConfigurationを作るという流れが登場するのですが、自作のCacheStore用に作成している
感じです。
StoreConfigurationBuilder側。
src/main/java/org/littlewings/infinispan/persistence/SimpleMapCacheStoreConfigurationBuilder.java
package org.littlewings.infinispan.persistence;
import org.infinispan.commons.configuration.ConfigurationBuilderInfo;
import org.infinispan.configuration.cache.AbstractStoreConfiguration;
import org.infinispan.configuration.cache.AbstractStoreConfigurationBuilder;
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
public class SimpleMapCacheStoreConfigurationBuilder extends AbstractStoreConfigurationBuilder<SimpleMapCacheStoreConfiguration, SimpleMapCacheStoreConfigurationBuilder> implements ConfigurationBuilderInfo {
public SimpleMapCacheStoreConfigurationBuilder(PersistenceConfigurationBuilder builder) {
super(builder, AbstractStoreConfiguration.attributeDefinitionSet());
}
@Override
public SimpleMapCacheStoreConfiguration create() {
return new SimpleMapCacheStoreConfiguration(attributes.protect(), async.create());
}
@Override
public SimpleMapCacheStoreConfigurationBuilder self() {
return this;
}
}
クラス宣言時のジェネリクスがややこしいですが、まあ、こんな感じです。
なお、StoreConfigurationもStoreConfigurationBuilderも、最低限の実装です。この内容なら、実は作成しなくてもよいくらいですが、
それは後で少し紹介します。
設定とかをちゃんと定義したい場合は、既存のCacheStoreを参考にしましょう。
https://github.com/infinispan/infinispan/tree/10.1.5.Final/core/src/main/java/org/infinispan/configuration/cache
https://github.com/infinispan/infinispan/tree/10.1.5.Final/core/src/main/java/org/infinispan/persistence/file
https://github.com/infinispan/infinispan/tree/10.1.5.Final/core/src/main/java/org/infinispan/persistence/cluster
https://github.com/infinispan/infinispan/tree/10.1.5.Final/persistence/soft-index
https://github.com/infinispan/infinispan/tree/10.1.5.Final/persistence/rocksdb
https://github.com/infinispan/infinispan/tree/10.1.5.Final/persistence/jdbc
https://github.com/infinispan/infinispan/tree/10.1.5.Final/persistence/jpa
https://github.com/infinispan/infinispan/tree/10.1.5.Final/persistence/remote
https://github.com/infinispan/infinispan/tree/10.1.5.Final/persistence/rest
CacheLoader/CacheWriterの実装を作る
続いて、先ほどのStoreConfiguration、StoreConfigurationBuilderにより作成される、CacheLoader/CacheWriterの実装クラスを
作成します。
CacheLoader (Infinispan JavaDoc All 10.1.5.Final API)
CacheWriter (Infinispan JavaDoc All 10.1.5.Final API)
この2つのインターフェースを実装したクラスを作成するのですが、今回はこの2つのインターフェースを継承したExternalStore
インターフェースを実装することにします。
ExternalStore (Infinispan JavaDoc All 10.1.5.Final API)
で、作ったのはこんな感じ。
src/main/java/org/littlewings/infinispan/persistence/SimpleMapCacheStore.java
package org.littlewings.infinispan.persistence;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.infinispan.commons.configuration.ConfiguredBy;
import org.infinispan.commons.io.ByteBuffer;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.commons.persistence.Store;
import org.infinispan.configuration.cache.CustomStoreConfiguration;
import org.infinispan.persistence.spi.ExternalStore;
import org.infinispan.persistence.spi.InitializationContext;
import org.infinispan.persistence.spi.MarshallableEntry;
import org.infinispan.persistence.spi.MarshallableEntryFactory;
import org.infinispan.persistence.spi.PersistenceException;
import org.jboss.logging.Logger;
@Store
@ConfiguredBy(SimpleMapCacheStoreConfiguration.class)
public class SimpleMapCacheStore<K, V> implements ExternalStore<K, V> {
Logger logger = Logger.getLogger(getClass());
SimpleMapCacheStoreConfiguration storeConfiguration;
String storeName;
Map<ByteBuffer, EntryData> store;
InitializationContext ctx;
@Override
public void init(InitializationContext ctx) {
this.ctx = ctx;
storeConfiguration = ctx.getConfiguration();
if (storeConfiguration.properties().getProperty("storeName") != null) {
storeName = storeConfiguration.properties().getProperty("storeName");
} else {
storeName = "defaultStoreName";
}
logging("initialized");
}
@Override
public void write(MarshallableEntry<? extends K, ? extends V> entry) {
ByteBuffer keyAsBinary = entry.getKeyBytes();
ByteBuffer valueAsBinary = entry.getValueBytes();
long created = entry.created();
long expiryTime = entry.expiryTime();
long lastUsed = entry.lastUsed();
ByteBuffer metadataAsBinary = entry.getMetadataBytes();
EntryData entryData = EntryData.create(valueAsBinary, created, expiryTime, lastUsed, metadataAsBinary);
store.put(keyAsBinary, entryData);
logging(
"write entry: %s / %s, created = %d, expiryTime = %d, lastUsed = %d, metadata = %s",
entry.getKey(),
entry.getValue(),
entry.created(),
entry.expiryTime(),
entry.lastUsed(),
entry.getMetadata()
);
}
@Override
public MarshallableEntry<K, V> loadEntry(Object key) {
MarshallableEntryFactory<K, V> marshallableEntryFactory = ctx.getMarshallableEntryFactory();
Marshaller marshaller = ctx.getPersistenceMarshaller().getUserMarshaller();
try {
ByteBuffer keyAsBinary = marshaller.objectToBuffer(key);
EntryData entryData = store.get(keyAsBinary);
if (entryData != null) {
MarshallableEntry<K, V> marshallableEntry =
marshallableEntryFactory.create(keyAsBinary, entryData.valueAsBinry, entryData.metadataAsBinary, entryData.getCreated(), entryData.getLastUsed());
logging(
"load entry: %s / %s, created = %d, expiryTime = %d, lastUsed = %d, metadata = %s",
marshallableEntry.getKey(),
marshallableEntry.getValue(),
marshallableEntry.created(),
marshallableEntry.expiryTime(),
marshallableEntry.lastUsed(),
marshallableEntry.getMetadata()
);
return marshallableEntry;
} else {
logging("missing entry: %s", key);
return null;
}
} catch (IOException e) {
throw new PersistenceException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
@Override
public boolean delete(Object key) {
try {
Marshaller marshaller = ctx.getPersistenceMarshaller();
ByteBuffer keyAsBinary = marshaller.objectToBuffer(key);
EntryData deleted = store.remove(keyAsBinary);
logging("deleted? %b, key = %s", deleted != null, key);
return deleted != null;
} catch (IOException e) {
throw new PersistenceException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
@Override
public boolean contains(Object key) {
try {
Marshaller marshaller = ctx.getPersistenceMarshaller();
ByteBuffer keyAsBinary = marshaller.objectToBuffer(key);
boolean contains = store.containsKey(keyAsBinary);
logging("contains? %b, key = %s", contains, key);
return contains;
} catch (IOException e) {
throw new PersistenceException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
@Override
public void start() {
store = new ConcurrentHashMap<>();
logging("cache store, startup");
}
@Override
public void stop() {
store.clear();
logging("cache store, stopped");
}
void logging(String messageTemplate, Object... params) {
List<Object> mergeParams = new ArrayList<>();
mergeParams.add(ctx.getCache().getAdvancedCache().getDistributionManager().getCacheTopology().getLocalAddress());
mergeParams.add(getClass().getSimpleName());
mergeParams.add(storeName);
mergeParams.addAll(Arrays.asList(params));
logger.infof("[%s - %s - %s] " + messageTemplate, mergeParams.toArray());
}
}
データの持ち先は、Mapにします。
Map<ByteBuffer, EntryData> store;
簡単にConcurrentHashMapということで。ライフサイクルメソッドで、初期化とクリアを仕込み。
@Override
public void start() {
store = new ConcurrentHashMap<>();
logging("cache store, startup");
}
@Override
public void stop() {
store.clear();
logging("cache store, stopped");
}
initは、CacheLoader/CacheWriterの初期化メソッドです。この引数で受け取るInitializationContextから、設定やMarshallerなどを
取得することができます。
@Override
public void init(InitializationContext ctx) {
this.ctx = ctx;
storeConfiguration = ctx.getConfiguration();
if (storeConfiguration.properties().getProperty("storeName") != null) {
storeName = storeConfiguration.properties().getProperty("storeName");
} else {
storeName = "defaultStoreName";
}
logging("initialized");
}
また、今回はストアの名前をプロパティとして受け取ることにしました。
CacheStore操作時のログは、Node名、クラス名、プロパティで指定されたストア名を出力するように実装。
void logging(String messageTemplate, Object... params) {
List<Object> mergeParams = new ArrayList<>();
mergeParams.add(ctx.getCache().getAdvancedCache().getDistributionManager().getCacheTopology().getLocalAddress());
mergeParams.add(getClass().getSimpleName());
mergeParams.add(storeName);
mergeParams.addAll(Arrays.asList(params));
logger.infof("[%s - %s - %s] " + messageTemplate, mergeParams.toArray());
}
エントリの書き込み、読み込み、削除。
@Override
public void write(MarshallableEntry<? extends K, ? extends V> entry) {
ByteBuffer keyAsBinary = entry.getKeyBytes();
ByteBuffer valueAsBinary = entry.getValueBytes();
long created = entry.created();
long expiryTime = entry.expiryTime();
long lastUsed = entry.lastUsed();
ByteBuffer metadataAsBinary = entry.getMetadataBytes();
EntryData entryData = EntryData.create(valueAsBinary, created, expiryTime, lastUsed, metadataAsBinary);
store.put(keyAsBinary, entryData);
logging(
"write entry: %s / %s, created = %d, expiryTime = %d, lastUsed = %d, metadata = %s",
entry.getKey(),
entry.getValue(),
entry.created(),
entry.expiryTime(),
entry.lastUsed(),
entry.getMetadata()
);
}
@Override
public MarshallableEntry<K, V> loadEntry(Object key) {
MarshallableEntryFactory<K, V> marshallableEntryFactory = ctx.getMarshallableEntryFactory();
Marshaller marshaller = ctx.getPersistenceMarshaller().getUserMarshaller();
try {
ByteBuffer keyAsBinary = marshaller.objectToBuffer(key);
EntryData entryData = store.get(keyAsBinary);
if (entryData != null) {
MarshallableEntry<K, V> marshallableEntry =
marshallableEntryFactory.create(keyAsBinary, entryData.valueAsBinry, entryData.metadataAsBinary, entryData.getCreated(), entryData.getLastUsed());
logging(
"load entry: %s / %s, created = %d, expiryTime = %d, lastUsed = %d, metadata = %s",
marshallableEntry.getKey(),
marshallableEntry.getValue(),
marshallableEntry.created(),
marshallableEntry.expiryTime(),
marshallableEntry.lastUsed(),
marshallableEntry.getMetadata()
);
return marshallableEntry;
} else {
logging("missing entry: %s", key);
return null;
}
} catch (IOException e) {
throw new PersistenceException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
@Override
public boolean delete(Object key) {
try {
Marshaller marshaller = ctx.getPersistenceMarshaller();
ByteBuffer keyAsBinary = marshaller.objectToBuffer(key);
EntryData deleted = store.remove(keyAsBinary);
logging("deleted? %b, key = %s", deleted != null, key);
return deleted != null;
} catch (IOException e) {
throw new PersistenceException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
MarshallableEntryというクラスが、マーシャリングした形式のデータを持っているので、こちらを永続ストア(今回はMapですが)に
保存します。
@Override
public void write(MarshallableEntry<? extends K, ? extends V> entry) {
ByteBuffer keyAsBinary = entry.getKeyBytes();
ByteBuffer valueAsBinary = entry.getValueBytes();
long created = entry.created();
long expiryTime = entry.expiryTime();
long lastUsed = entry.lastUsed();
ByteBuffer metadataAsBinary = entry.getMetadataBytes();
EntryData entryData = EntryData.create(valueAsBinary, created, expiryTime, lastUsed, metadataAsBinary);
なお、EntryDataというのは値と各種メタデータを保存するために、今回作成したクラスです。
src/main/java/org/littlewings/infinispan/persistence/EntryData.java
package org.littlewings.infinispan.persistence;
import org.infinispan.commons.io.ByteBuffer;
import org.infinispan.metadata.Metadata;
public class EntryData {
ByteBuffer valueAsBinry;
long created;
long expiryTime;
long lastUsed;
ByteBuffer metadataAsBinary;
public static EntryData create(ByteBuffer valueAsBinary, long created, long expiryTime, long lastUsed, ByteBuffer metadataAsBinary) {
EntryData data = new EntryData();
data.valueAsBinry = valueAsBinary;
data.created = created;
data.expiryTime = expiryTime;
data.lastUsed = lastUsed;
data.metadataAsBinary = metadataAsBinary;
return data;
}
}
読み込みの場合は、キーが渡ってくるのでMarshallerおよびMarshallableEntryFactoryを使ってMarshallableEntryにデータを変換してから
返却します。
@Override
public MarshallableEntry<K, V> loadEntry(Object key) {
MarshallableEntryFactory<K, V> marshallableEntryFactory = ctx.getMarshallableEntryFactory();
Marshaller marshaller = ctx.getPersistenceMarshaller().getUserMarshaller();
try {
ByteBuffer keyAsBinary = marshaller.objectToBuffer(key);
EntryData entryData = store.get(keyAsBinary);
if (entryData != null) {
MarshallableEntry<K, V> marshallableEntry =
marshallableEntryFactory.create(keyAsBinary, entryData.valueAsBinry, entryData.metadataAsBinary, entryData.getCreated(), entryData.getLastUsed());
logging(
"load entry: %s / %s, created = %d, expiryTime = %d, lastUsed = %d, metadata = %s",
marshallableEntry.getKey(),
marshallableEntry.getValue(),
marshallableEntry.created(),
marshallableEntry.expiryTime(),
marshallableEntry.lastUsed(),
marshallableEntry.getMetadata()
);
return marshallableEntry;
ここで返したMarshallableEntryは、DataContainer側で保持されることになります。
削除については、省略。containsは実装はしていますが、呼ばれる気がしませんが…?
確認
では、テストコードで確認してみましょう。といっても、CacheStoreの動作自体はログで見るのですが。
テストコードの雛形。
src/test/java/org/littlewings/infinispan/persistence/SimpleMapStoreTest.java
package org.littlewings.infinispan.persistence;
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.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class SimpleMapStoreTest {
<K, V> void withCache(String cacheName, int numInstances, Consumer<Cache<K, V>> func) {
List<EmbeddedCacheManager> managers =
IntStream
.rangeClosed(1, numInstances)
.mapToObj(i -> {
try {
return new DefaultCacheManager("infinispan.xml");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
)
.collect(Collectors.toList());
try {
managers.forEach(m -> m.getCache(cacheName));
Cache<K, V> cache = managers.get(0).getCache(cacheName);
func.accept(cache);
} finally {
managers.forEach(EmbeddedCacheManager::stop);
}
}
}
簡単にEmbeddedなクラスタを作成できるメソッド付きです。
Infinispanの設定。
src/test/resources/infinispan.xml
xml version="1.0" encoding="UTF-8"
<infinispan
xmlnsxsi="http://www.w3.org/2001/XMLSchema-instance"
xsischemaLocation="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"/>
<distributed-cache name="declarativeSimpleStoreCache">
<persistence>
<store class="org.littlewings.infinispan.persistence.SimpleMapCacheStore">
<property name="storeName">declarativeStore</property>
</store>
</persistence>
</distributed-cache>
</cache-container>
</infinispan>
persistence/storeで、自分で作成したCacheStoreの実装クラスを指定。プロパティも設定しておきます。
<persistence>
<store class="org.littlewings.infinispan.persistence.SimpleMapCacheStore">
<property name="storeName">declarativeStore</property>
</store>
</persistence>
まずは、設定ファイルで定義したCacheを使ってみます。
@Test
public void withDeclarativeSimpleCacheStore() {
this.<String, String>withCache("declarativeSimpleStoreCache", 3, cache -> {
System.out.println("============================== start ==============================");
System.out.println("[Data Put] start");
IntStream.rangeClosed(1, 10).forEach(i -> cache.put("key" + i, "value" + i));
System.out.println("[Data Put] end");
System.out.println("[Data Get] start");
assertThat(cache.get("key1")).isEqualTo("value1");
System.out.println("[Data Get] end");
assertThat(cache.containsKey("key2")).isTrue();
System.out.println("[Data Remove] start");
cache.remove("key3");
System.out.println("[Data Remove] end");
System.out.println("[Data Clear] start");
cache.clear();
System.out.println("[Data Clear] end");
assertThat(cache.containsKey("key2")).isTrue();
System.out.println("[Data Get] start");
IntStream.rangeClosed(1, 10).forEach(i -> cache.get("key" + i));
System.out.println("[Data Get] end");
System.out.println("============================== end ==============================");
});
}
ログを追ってみてみます。
まずはデータのput。
============================== start ==============================
[Data Put] start
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] missing entry: key1
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key1 / value1, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key1 / value1, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] missing entry: key2
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key2 / value2, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key2 / value2, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] missing entry: key3
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key3 / value3, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] write entry: key3 / value3, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] missing entry: key4
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key4 / value4, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] write entry: key4 / value4, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] missing entry: key5
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key5 / value5, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] write entry: key5 / value5, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] missing entry: key6
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key6 / value6, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] write entry: key6 / value6, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] missing entry: key7
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key7 / value7, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key7 / value7, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] missing entry: key8
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key8 / value8, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key8 / value8, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] missing entry: key9
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key9 / value9, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key9 / value9, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] missing entry: key10
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key10 / value10, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key10 / value10, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
[Data Put] end
CacheStoreに書き込みする前に、1度データをロードしようとしていますね。
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] missing entry: key1
書き込みは、2回発生。バックアップ分があるからですね。
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key1 / value1, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] write entry: key1 / value1, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
この状態でデータを取得すると、CacheStore側までアクセスが来ません。
[Data Get] start
[Data Get] end
InfinispanのDataContainer側(InfinispanのCacheの内部)にデータがある場合は、CacheStoreまで来ないからです。
削除。こちらはCacheStoreまでアクセスが来ます。
[Data Remove] start
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] deleted? true, key = key3
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] deleted? true, key = key3
[Data Remove] end
ここで、1度データをクリアします。この時、CacheStore側にはなにも起こりません(起こしたい場合はAdvanced〜が必要)。
[Data Clear] start
[Data Clear] end
この状態だと、DataContainerにはデータはないけれど、CacheStoreにはデータが残ったまま(明示的に削除したものを除く)
という状態になっているので、これを利用してデータをCacheStoreからロードしてみます。
1件取得。データをCacheStoreからロードしに来たことがわかります。
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] load entry: key2 / value2, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
ループで取得。
[Data Get] start
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] load entry: key1 / value1, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] missing entry: key3
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] load entry: key4 / value4, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] load entry: key5 / value5, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-17537 - SimpleMapCacheStore - declarativeStore] load entry: key6 / value6, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] load entry: key7 / value7, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] load entry: key8 / value8, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] load entry: key9 / value9, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore] load entry: key10 / value10, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
[Data Get] end
============================== end ==============================
データの取得の場合は、バックアップ分は動かないんですね。1回しかCacheStoreが動いていません。
あとは、ログにプロパティで指定したCacheStoreの名前が反映されていることを確認。
INFO: [xxxxx-59059 - SimpleMapCacheStore - declarativeStore]
続いて、CacheをAPIで定義する時にCacheStoreを設定する方法を見てみましょう。
@Test
public void withProgrammaticallyDeclarativeSimpleCacheStore() throws IOException {
try (EmbeddedCacheManager manager = new DefaultCacheManager("infinispan.xml")) {
PersistenceConfigurationBuilder persistenceConfigurationBuilder =
new ConfigurationBuilder()
.clustering()
.cacheMode(CacheMode.DIST_SYNC)
.persistence();
Configuration configuration =
persistenceConfigurationBuilder
.addStore(new SimpleMapCacheStoreConfigurationBuilder(persistenceConfigurationBuilder))
.addProperty("storeName", "programmaticallyStore")
.build();
manager.defineConfiguration("programmaticallySimpleStoreCache", configuration);
Cache<String, String> cache = manager.getCache("programmaticallySimpleStoreCache");
System.out.println("============================== start ==============================");
System.out.println("[Data Put] start");
IntStream.rangeClosed(1, 10).forEach(i -> cache.put("key" + i, "value" + i));
System.out.println("[Data Put] end");
System.out.println("[Data Get] start");
assertThat(cache.get("key1")).isEqualTo("value1");
System.out.println("[Data Get] end");
assertThat(cache.containsKey("key2")).isTrue();
System.out.println("[Data Remove] start");
cache.remove("key3");
System.out.println("[Data Remove] end");
System.out.println("[Data Clear] start");
cache.clear();
System.out.println("[Data Clear] end");
assertThat(cache.containsKey("key2")).isTrue();
System.out.println("[Data Get] start");
IntStream.rangeClosed(1, 10).forEach(i -> cache.get("key" + i));
System.out.println("[Data Get] end");
System.out.println("============================== end ==============================");
}
}
テストコード側は先ほどと同じなので省略しますが、CacheStoreの設定方法はこんな感じですね。
PersistenceConfigurationBuilder persistenceConfigurationBuilder =
new ConfigurationBuilder()
.clustering()
.cacheMode(CacheMode.DIST_SYNC)
.persistence();
Configuration configuration =
persistenceConfigurationBuilder
.addStore(new SimpleMapCacheStoreConfigurationBuilder(persistenceConfigurationBuilder))
.addProperty("storeName", "programmaticallyStore")
.build();
manager.defineConfiguration("programmaticallySimpleStoreCache", configuration);
ログにプロパティの内容が反映されていることを確認。
INFO: [xxxxx-30918 - SimpleMapCacheStore - declarativeStore]
OKそうですね。これで、確認できました、と。
あとは、少しオマケを書いていきます。
CustomStoreConfiguration
先に少し書きましたが、今回はCacheStoreの設定としてプロパティくらいしか使わないので、実はStoreConfigurationは作る必要は
なかったりします。
今回のCacheStoreの実装では、@ConfiguredByアノテーションを外し、StoreConfigurationの型をCustomStoreConfigurationに変更するだけで
設定ファイルのCacheを使っている方のテストコードは動作します。
@Store
public class SimpleMapCacheStore<K, V> implements ExternalStore<K, V> {
Logger logger = Logger.getLogger(getClass());
CustomStoreConfiguration storeConfiguration;
String storeName;
Map<ByteBuffer, EntryData> store;
InitializationContext ctx;
@Override
public void init(InitializationContext ctx) {
this.ctx = ctx;
storeConfiguration = ctx.getConfiguration();
こっちは、StoreConfigurationBuilderを直接使っているのでダメですが。
Configuration configuration =
persistenceConfigurationBuilder
.addStore(new SimpleMapCacheStoreConfigurationBuilder(persistenceConfigurationBuilder))
.addProperty("storeName", "programmaticallyStore")
.build();
このような動作になるのは、@ConfiguredByアノテーションが指定されていない場合は、CustomStoreConfigurationBuilderおよび
CustomStoreConfigurationが使われるからです。
https://docs.jboss.org/infinispan/10.1/apidocs/org/infinispan/configuration/cache/CustomStoreConfiguration.html
https://docs.jboss.org/infinispan/10.1/apidocs/org/infinispan/configuration/cache/CustomStoreConfigurationBuilder.html
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/configuration/parsing/Parser.java#L2546-L2563
Interceptor
CacheStoreが呼ばれるようになるのは、Persistenceの設定を行うとInterceptorが追加されるからです。
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/factories/InterceptorChainFactory.java#L247-L280
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/interceptors/impl/CacheLoaderInterceptor.java
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/interceptors/impl/CacheWriterInterceptor.java
DataContainer側になかった場合に、CacheStoreからロードしようとするのは、このあたりの挙動ですね。
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/interceptors/impl/CacheLoaderInterceptor.java#L204-L206
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/interceptors/impl/CacheLoaderInterceptor.java#L390-L450
繰り返しますが、ここでロードしたデータはDataContainer側に保持されるようになっています。
エントリの作成時間やlifespanなどについて
ところで、ログ出力時にcreatedやexpiryTime、lastUsedなどが軒並み-1になっていたと思います。
3月 25, 2020 11:57:46 午後 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-61331 - SimpleMapCacheStore - declarativeStore] write entry: key1 / value1, created = -1, expiryTime = -1, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=-1, maxIdle=-1}
Metadata(lifespan、maxIdle)の部分に関しては、ここで設定されています。いずれも、なにも指定しない場合の初期値は-1です。
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/cache/impl/CacheImpl.java#L195-L196
created、expiryTime、lastUsedなどは、expirationの設定を行うと設定されるようになります。
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/container/impl/AbstractInternalDataContainer.java#L150
https://github.com/infinispan/infinispan/blob/10.1.5.Final/core/src/main/java/org/infinispan/container/impl/InternalEntryFactoryImpl.java#L46-L61
たとえば、Cacheの設定にlifespanを入れると
<distributed-cache name="declarativeSimpleStoreCache">
<expiration lifespan="10000"/>
<persistence>
<store class="org.littlewings.infinispan.persistence.SimpleMapCacheStore">
<property name="storeName">declarativeStore</property>
</store>
</persistence>
</distributed-cache>
値が入るようになります。
3月 26, 2020 12:26:14 午前 org.littlewings.infinispan.persistence.SimpleMapCacheStore logging
INFO: [xxxxx-22348 - SimpleMapCacheStore - declarativeStore] write entry: key1 / value1, created = 1585149974089, expiryTime = 1585149984089, lastUsed = -1, metadata = EmbeddedExpirableMetadata{version=null, lifespan=10000, maxIdle=-1}
まとめ
InfinispanのCacheStoreまわりのAPIが知らないうちにいろいろ変わっていたので、まずはCacheLoader/CacheWriterと設定の基本まわりを
おさらいしてみました。
前に見た時よりも、だいぶ中も追えたのではないでしょうか?
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/embedded-cachestore