これは、なにをしたくて書いたもの?
最近、久しぶりにInfinispanのCacheStoreまわりのAPIをちょっと眺めてみたら、特にAdvancedCacheLoader/AdvancedCacheWriterまわりが
だいぶ変わっているのに驚きまして。
いい機会なので、もう1度CacheStoreまわりのAPIを見ていこうかなと。
いきなりAdvanced〜の方にいくとハードルが高いので、今回はまずCacheLoader/CacheWriterから使っていきます。
Persistence
こちらは、Infinispan 6.0の頃に書いたエントリです。
InfinispanのCacheLoader/CacheWriterを使ってみる - CLOVER🍀
今回はInfinispan 10.1で見直してみます。
InfinispanからいくつかCacheStoreの実装は提供されていますが、今回は自分でCacheStoreを実装する方を見ていきます。
これが、Infinispan Persistence SPIで、外部ストレージへの読み書きを行うためのAPIとなります。
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/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; //CustomStoreConfiguration 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; } // getter/setterは省略 }
読み込みの場合は、キーが渡ってくるので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 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"/> <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 // @ConfiguredBy(SimpleMapCacheStoreConfiguration.class) // ココ public class SimpleMapCacheStore<K, V> implements ExternalStore<K, V> { Logger logger = Logger.getLogger(getClass()); //SimpleMapCacheStoreConfiguration storeConfiguration; 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が使われるからです。
Interceptor
CacheStoreが呼ばれるようになるのは、Persistenceの設定を行うとInterceptorが追加されるからです。
DataContainer側になかった場合に、CacheStoreからロードしようとするのは、このあたりの挙動ですね。
繰り返しますが、ここでロードしたデータは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です。
created、expiryTime、lastUsedなどは、expirationの設定を行うと設定されるようになります。
たとえば、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