これまで、Infinispanの外部領域へのデータ保存に関する機能はあまり使ってこなかったことと、Storeに設定する各種パラメータの意味(fetchPersistentStateとか)をある程度把握したくて、自分でCacheLoader/CacheWriterを書いてみることにしました。
そういえば、HazelcastとかEhcacheでは書いたことありますしね。
4. Persistence
http://infinispan.org/docs/6.0.x/user_guide/user_guide.html#_persistence
まあ、パラメータとか設定ファイルの書き方は、Infinispan 7.0でまた変わりそうですが、ひとまずいいでしょう。
そもそも、Persistenceとは?
CacheStoreと書かれていたり、Persistenceと書かれていたりどれが正しい呼び方なのかよくわかりませんが、Infinispanで保持するデータを外部記憶領域に保存するための機能です。これにより、
- メモリ以外にデータを保持することにより、データの寿命を伸ばすことができる。保存先によっては、プロセスダウンしてもデータが失われない
- Wright-Through。アプリケーションと外部記憶領域と間のキャッシュとして、Infinispanを使う
- 大量のデータを保持した時の追い出し先として使用し、頻繁に使用されるデータをメモリに持ち、他はディスクなどに退避する
などといったことができます。
これらを構成するAPIとして、
- CacheLoader/CacheWriter
- AdvancedCacheLoader/AdvancedCacheWriter
があり、以下のような特徴があります。
- JSR-107(JCache)とよく似たAPIを提供し、移行を容易に
- Infinispanによりロックの管理が行われるため、通常Storeの実装は並行アクセスを気にしなくてもよい(ロックモードにも依存するが、Storeへの複数のスレッドからの操作は想定しておいた方がよい)
- 並列での反復処理(Parallel Iteration)が可能。Map Reduce Frameworkは、この恩恵を受けている
- シリアライズの削減。永続化ストレージからの読み書きの際に、シリアライズ/デシリアライズを意識しなくて済むようなAPIの公開
実際APIを使ってみると、ふーんというところと、理解半分みたいなところがありますが…。
とりあえず、今回は単純なMapをCacheStoreとして用意して、それをシリアライズして保存するような単純なものを作成してみましょう。
準備
まずは、依存関係の定義。
build.sbt
name := "infinispan-persistence" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.2" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") incOptions := incOptions.value.withNameHashing(true) fork in Test := true parallelExecution in Test := true libraryDependencies ++= Seq( "org.infinispan" % "infinispan-core" % "6.0.2.Final" excludeAll( ExclusionRule(organization = "org.jgroups", name = "jgroups"), ExclusionRule(organization = "org.jboss.marshalling", name = "jboss-marshalling-river"), ExclusionRule(organization = "org.jboss.marshalling", name = "jboss-marshalling"), ExclusionRule(organization = "org.jboss.logging", name = "jboss-logging"), ExclusionRule(organization = "org.jboss.spec.javax.transaction", name = "jboss-transaction-api_1.1_spec") ), "org.jgroups" % "jgroups" % "3.4.1.Final", "org.jboss.spec.javax.transaction" % "jboss-transaction-api_1.1_spec" % "1.0.1.Final", "org.jboss.marshalling" % "jboss-marshalling-river" % "1.4.4.Final", "org.jboss.marshalling" % "jboss-marshalling" % "1.4.4.Final", "org.jboss.logging" % "jboss-logging" % "3.1.2.GA", "net.jcip" % "jcip-annotations" % "1.0", "org.scalatest" %% "scalatest" % "2.2.1" % "test" )
毎度お馴染みです。
Infinispanの設定については、後で。
CacheLoader/CacheWriterを書く
今回は、CacheLoader/CacheWriterインターフェースを実装したクラスを作成します。これらをさらに拡張したインターフェースとして、AdvancedCacheLoader/AdvancedCacheWriterがありますが、次回にでも。
で、実装してみたのはこんなクラス。
src/main/scala/org/littlewings/infinispan/persistence/SimpleMapCacheStore.scala
package org.littlewings.infinispan.persistence import scala.collection._ import java.io._ import java.nio.file.{Files, Paths} import java.util.concurrent.Executor import org.infinispan.commons.io.ByteBuffer import org.infinispan.marshall.core.MarshalledEntry import org.infinispan.metadata.InternalMetadata import org.infinispan.persistence.spi.{CacheLoader, CacheWriter, ExternalStore, InitializationContext} //import org.infinispan.persistence.spi.{AdvancedCacheLoader, AdvancedCacheWriter, AdvancedLoadWriteStore, InitializationContext} object SimpleMapCacheStore { private var numberOfCluster: Int = 1 private var current: Int = 1 private val instances: mutable.Map[String, SimpleMapCacheStore[_, _]] = mutable.Map.empty def instances(n: Int): Unit = numberOfCluster = n def aquireStoreName(): String = { require(numberOfCluster + 1 > current) val name = s"store-${current}.dmp" current += 1 name } def register(store: SimpleMapCacheStore[_, _]): Unit = synchronized { val storeName = aquireStoreName() store.storeName = storeName instances += (storeName -> store) } } class SimpleMapCacheStore[K, V] extends CacheLoader[K, V] with CacheWriter[K, V] { // class SimpleMapCacheStore[K, V] extends ExternalStore[K, V] { // こちらでも可 private var storeName: String = _ private var ctx: InitializationContext = _ // from Lifecyele override def start(): Unit = { SimpleMapCacheStore.register(this) println(s"Store Name = $storeName") val path = Paths.get(storeName) if (Files.exists(path)) { val is = new ObjectInputStream(new BufferedInputStream(Files.newInputStream(path))) try { store = is.readObject.asInstanceOf[mutable.Map[K, (V, Array[Byte])]] } finally { is.close() } println(s"Loaded From Store[$storeName], keys = ${store.keys}") } else { println(s"Store[$storeName], not exists.") } } // from Lifecyele override def stop(): Unit = { if (!store.isEmpty) { val path = Paths.get(storeName) val os = new ObjectOutputStream(new BufferedOutputStream(Files.newOutputStream(path))) try { os.writeObject(store) } finally { os.close() } println(s"Store[$storeName] saved, keys = ${store.keys}") } else { println(s"Store[$storeName] is empty.") } } // from CacheLoader, CacheWriter override def init(ctx: InitializationContext): Unit = this.ctx = ctx // from CacheLoader override def contains(key: K): Boolean = { if (store.contains(key)) { println(s"Store[$storeName] contains key[$key]") true } else { println(s"Store[$storeName] not contains key[$key]") false } } // from CacheLoader override def load(key: K): MarshalledEntry[K, V] = load(key, true, true) def load(key: K, fetchValue: Boolean, fetchMetadata: Boolean): MarshalledEntry[K, V] = { println(s"Store[$storeName], try load key[$key]") val value = store.get(key) val marshaller = ctx.getMarshaller val binaryKey = marshaller.objectToByteBuffer(key) val factory = ctx.getByteBufferFactory val keyBuffer = factory.newByteBuffer(binaryKey, 0, binaryKey.size) value match { case Some((v, m)) => val valueBuffer = if (fetchValue) { val binaryValue = marshaller.objectToByteBuffer(v) factory.newByteBuffer(binaryValue, 0, binaryValue.size) } else { null } val metaBuffer = if (fetchMetadata && m != null) factory.newByteBuffer(m, 0, m.size) else null ctx .getMarshalledEntryFactory .newMarshalledEntry(keyBuffer, valueBuffer, metaBuffer) .asInstanceOf[MarshalledEntry[K, V]] case None => println(s"Store[$storeName], missing key[$key]") null } } // from CacheWriter override def write(entry: MarshalledEntry[K, V]): Unit = { println(s"Store[$storeName], write (key, value) = (${entry.getKey}, ${entry.getValue})") store += (entry.getKey -> (entry.getValue -> entry.getMetadataBytes.getBuf)) } // from CacheWriter override def delete(key: K): Boolean = { println(s"Store[$storeName], remove[$key]") if (store.contains(key)) { store -= key true } else { false } } }
今回は、CacheLoaderとCacheWriterインターフェースを実装しましたが、最低限はCacheLoaderのようです。また、両方合わせたインターフェースとして、ExternalStoreというものもあります。
class SimpleMapCacheStore[K, V] extends CacheLoader[K, V] with CacheWriter[K, V] { // class SimpleMapCacheStore[K, V] extends ExternalStore[K, V] { // こちらでも可
これらのインターフェースはLifecycleインターフェースを拡張しているため、起動停止のstart/stopメソッドを実装する必要があります。今回は、起動時にシリアライズして保存したファイルを読み、停止時にファイルに吐き出すようにしました。それまでは、メモリ上でのみの管理です。
loadとwrite、containsはエントリのロード、書き込み、確認ですが、初期化メソッドとしてinitがありInitializationContextを引数として受け取ります。ここから、Cacheや設定情報を取得することが可能です。
load/write時には、MarshalledEntryというインターフェースがつきまといます。キー、値、そしてメタデータを保持しているのですが、これらとバイナリ形式(Infinispanで定義しているByteBuffer)の変換を行うために、Marshaller、MarshalledEntryFactoryを使用します。ともにInitializationContextから取得可能ですので、こちらを使用するようにしてください。
その他、テストコードで動かす際に、同一JavaVM上でクラスタを構成するつもりのため、ファイル名をScalaのオブジェクトで管理して作るようにしました…。本来は、Node単位の設定とか別サーバにすべきですね。
また、今回は簡単のため同期化などは考慮していません。このあたりは、SingleFileStoreを参考にするとよいと思います…。
動かしてみる
で、ドキュメントに習ってこんな感じのCache定義を行って動作させてみると(動かしたコードは省略)
<namedCache name="storeCache"> <clustering mode="dist" /> <persistence passivation="false"> <store class="org.littlewings.infinispan.persistence.SimpleMapCacheStore" fetchPersistentState="false" preload="false" shared="false" purgeOnStartup="false" ignoreModifications="false"> </store> </persistence> </namedCache>
なんと、CacheStoreが華麗にスルーされます。
これはどうしたことか?と思い、実装を確認。
https://github.com/infinispan/infinispan/blob/6.0.2.Final/core/src/main/java/org/infinispan/configuration/parsing/Parser60.java#L668
まあ、SingletonFileStoreとClusterLoader以外、無視するようになっていますね。とはいえ、以前RemoteCacheStoreやJdbcCacheStoreを動かしたことがあるわけですよ。なので、何かトリックがあるんだろうと見てみた結果、XMLのパーサーやConfigurationBuilderなどを作らなくてはいけないんだろうなぁという結論に達しました。
さすがに、XMLパーサーにいきなりトライするのは時間が足りないので、ここはCacheの設定をXMLで行うのはやめて、ConfigurationBuilderで行うことにしました。
先ほど作成した、SimpleMapCacheStoreに対応するConfigurationです。
src/main/scala/org/littlewings/infinispan/persistence/SimpleMapCacheStoreConfiguration.scala
package org.littlewings.infinispan.persistence import java.util.Properties import org.infinispan.commons.configuration.BuiltBy import org.infinispan.commons.configuration.ConfigurationFor import org.infinispan.configuration.cache.{AbstractStoreConfiguration, AsyncStoreConfiguration, SingletonStoreConfiguration} @BuiltBy(classOf[SimpleMapCacheStoreConfigurationBuilder[_, _]]) @ConfigurationFor(classOf[SimpleMapCacheStore[_, _]]) class SimpleMapCacheStoreConfiguration(purgeOnStartup: Boolean, fetchPersistenceState: Boolean, ignoreModifications: Boolean, async: AsyncStoreConfiguration, singletonStore: SingletonStoreConfiguration, preload: Boolean, shared: Boolean, properties: Properties) extends AbstractStoreConfiguration(purgeOnStartup, fetchPersistenceState, ignoreModifications, async, singletonStore, preload, shared, properties)
骨格実装であるAbstractStoreConfigurationを単に拡張したものですが、以下のアノテーションを付与しているところがポイントです。
@BuiltBy(classOf[SimpleMapCacheStoreConfigurationBuilder[_, _]]) @ConfigurationFor(classOf[SimpleMapCacheStore[_, _]])
で、こちらを作成するConfigurationBuilder。こちらも、骨格実装であるAbstractStoreConfigurationBuilderを継承して作成しました。
src/main/scala/org/littlewings/infinispan/persistence/SimpleMapCacheStoreConfigurationBuilder.scala
package org.littlewings.infinispan.persistence import org.infinispan.commons.configuration.Builder import org.infinispan.configuration.cache.{AbstractStoreConfigurationBuilder, PersistenceConfigurationBuilder, StoreConfiguration} class SimpleMapCacheStoreConfigurationBuilder[K, V](builder: PersistenceConfigurationBuilder) extends AbstractStoreConfigurationBuilder[SimpleMapCacheStoreConfiguration, SimpleMapCacheStoreConfigurationBuilder[_, _]](builder) { override def self: SimpleMapCacheStoreConfigurationBuilder[K, V] = this override def create: SimpleMapCacheStoreConfiguration = new SimpleMapCacheStoreConfiguration(purgeOnStartup, fetchPersistentState, ignoreModifications, async.create, singletonStore.create, preload, shared, properties) override def read(template: SimpleMapCacheStoreConfiguration): Builder[_] = { fetchPersistentState = template.fetchPersistentState ignoreModifications = template.ignoreModifications properties = template.properties purgeOnStartup = template.purgeOnStartup async.read(template.async) singletonStore.read(template.singletonStore) preload = template.preload shared = template.shared this } }
両方とも、特に追加パラメータなど入れていないので、骨格実装とほとんど変わりません。
なお、これらのコードを書くために参考にしたのは、SingleFileStoreやJdbcStoreの実装です。
https://github.com/infinispan/infinispan/blob/6.0.2.Final/core/src/main/java/org/infinispan/persistence/file/SingleFileStore.java
https://github.com/infinispan/infinispan/blob/6.0.2.Final/persistence/jdbc/src/main/java/org/infinispan/persistence/jdbc/stringbased/JdbcStringBasedStore.java
テストコードを書く
それでは、このCacheStoreを使ったテストコードを書いてみましょう。
src/test/scala/org/littlewings/infinispan/persistence/SimpleMapCacheStoreSpec.scala
package org.littlewings.infinispan.persistence import org.infinispan.Cache import org.infinispan.configuration.cache.{CacheMode, ConfigurationBuilder} import org.infinispan.manager.DefaultCacheManager import org.scalatest.FunSpec import org.scalatest.Matchers._ class SimpleMapCacheStoreSpec extends FunSpec { describe("simple map cache store spec") { it("persistence") { val numInstances = 2 SimpleMapCacheStore.instances(numInstances) withStoreCache[String, String]("storeCache", numInstances) { cache => println(s"Cache size => ${cache.size}") val range = 1 to 10 range.foreach(i => println(s"key[${s"key$i"}] => ${cache.get(s"key$i")}")) (1 to 10) foreach (i => cache.put(s"key$i", s"value$i")) } } } def withStoreCache[K, V](cacheName: String, numInstances: Int = 1)(fun: Cache[K, V] => Unit): Unit = { val managers = (1 to numInstances).map { _ => val manager = new DefaultCacheManager("infinispan.xml") val persistenceBuilder = new ConfigurationBuilder() .clustering .cacheMode(CacheMode.DIST_SYNC) .persistence manager.defineConfiguration(cacheName, persistenceBuilder .addStore(new SimpleMapCacheStoreConfigurationBuilder(persistenceBuilder)) .fetchPersistentState(false) .preload(false) .shared(false) .purgeOnStartup(false) .ignoreModifications(false) .build) manager } try { managers.foreach(_.getCache[K, V](cacheName)) fun(managers.head.getCache[K, V](cacheName)) } finally { managers.foreach(_.stop()) } } }
なんか設定ファイルを読んでいますが、内容はこの程度です。
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:6.0 http://www.infinispan.org/schemas/infinispan-config-6.0.xsd" xmlns="urn:infinispan:config:6.0"> <global> <transport clusterName="myPersistenceCluster"> <properties> <property name="configurationFile" value="jgroups.xml" /> </properties> </transport> <globalJmxStatistics enabled="true" jmxDomain="org.infinispan" cacheManagerName="DefaultCacheManager" allowDuplicateDomains="true" /> <shutdown hookBehavior="REGISTER"/> </global> </infinispan>
Cacheの定義は、先ほど作成したクラスを使って定義しています。
val persistenceBuilder = new ConfigurationBuilder() .clustering .cacheMode(CacheMode.DIST_SYNC) .persistence manager.defineConfiguration(cacheName, persistenceBuilder .addStore(new SimpleMapCacheStoreConfigurationBuilder(persistenceBuilder)) .fetchPersistentState(false) .preload(false) .shared(false) .purgeOnStartup(false) .ignoreModifications(false) .build)
動かしてみます。
> test 〜省略〜 Store Name = store-1.dmp Store[store-1.dmp], not exists. Store Name = store-2.dmp Store[store-2.dmp], not exists. Cache size => 0 Store[store-1.dmp], try load key[key1] Store[store-1.dmp], missing key[key1] key[key1] => null Store[store-1.dmp], try load key[key2] Store[store-1.dmp], missing key[key2] key[key2] => null Store[store-1.dmp], try load key[key3] Store[store-1.dmp], missing key[key3] key[key3] => null Store[store-1.dmp], try load key[key4] Store[store-1.dmp], missing key[key4] key[key4] => null Store[store-1.dmp], try load key[key5] Store[store-1.dmp], missing key[key5] key[key5] => null Store[store-1.dmp], try load key[key6] Store[store-1.dmp], missing key[key6] key[key6] => null Store[store-1.dmp], try load key[key7] Store[store-1.dmp], missing key[key7] key[key7] => null Store[store-1.dmp], try load key[key8] Store[store-1.dmp], missing key[key8] key[key8] => null Store[store-1.dmp], try load key[key9] Store[store-1.dmp], missing key[key9] key[key9] => null Store[store-1.dmp], try load key[key10] Store[store-1.dmp], missing key[key10] key[key10] => null Store[store-1.dmp], try load key[key1] Store[store-1.dmp], missing key[key1] Store[store-2.dmp], write (key, value) = (key1, value1) Store[store-1.dmp], write (key, value) = (key1, value1) Store[store-1.dmp], try load key[key2] Store[store-1.dmp], missing key[key2] Store[store-2.dmp], write (key, value) = (key2, value2) Store[store-1.dmp], write (key, value) = (key2, value2) Store[store-1.dmp], try load key[key3] Store[store-1.dmp], missing key[key3] Store[store-2.dmp], write (key, value) = (key3, value3) Store[store-1.dmp], write (key, value) = (key3, value3) Store[store-1.dmp], try load key[key4] Store[store-1.dmp], missing key[key4] Store[store-2.dmp], write (key, value) = (key4, value4) Store[store-1.dmp], write (key, value) = (key4, value4) Store[store-1.dmp], try load key[key5] Store[store-1.dmp], missing key[key5] Store[store-1.dmp], write (key, value) = (key5, value5) Store[store-2.dmp], write (key, value) = (key5, value5) Store[store-1.dmp], try load key[key6] Store[store-1.dmp], missing key[key6] Store[store-2.dmp], write (key, value) = (key6, value6) Store[store-1.dmp], write (key, value) = (key6, value6) Store[store-1.dmp], try load key[key7] Store[store-1.dmp], missing key[key7] Store[store-2.dmp], write (key, value) = (key7, value7) Store[store-1.dmp], write (key, value) = (key7, value7) Store[store-1.dmp], try load key[key8] Store[store-1.dmp], missing key[key8] Store[store-2.dmp], write (key, value) = (key8, value8) Store[store-1.dmp], write (key, value) = (key8, value8) Store[store-1.dmp], try load key[key9] Store[store-1.dmp], missing key[key9] Store[store-1.dmp], write (key, value) = (key9, value9) Store[store-2.dmp], write (key, value) = (key9, value9) Store[store-1.dmp], try load key[key10] Store[store-1.dmp], missing key[key10] Store[store-1.dmp], write (key, value) = (key10, value10) Store[store-2.dmp], write (key, value) = (key10, value10) Store[store-1.dmp] saved, keys = Set(key6, key9, key8, key2, key5, key4, key7, key1, key10, key3) Store[store-2.dmp] saved, keys = Set(key6, key9, key8, key2, key5, key4, key7, key1, key10, key3)
最初はデータを持っていませんが、それぞれのput時にStoreに書き込んでいるのがわかります。で、シャットダウン時にはファイル保存を行っています。
保存されたファイルは、このように。
-rw-rw-r-- 1 xxxxx xxxxx 713 8月 3 01:15 store-1.dmp -rw-rw-r-- 1 xxxxx xxxxx 713 8月 3 01:15 store-2.dmp
2回目の起動を行うと、Cacheのサイズは内容に見えるものの、Cache#getした際にはデータをロードしてきて値が見えるようになっていることを確認できます。
Cache size => 0 Store[store-1.dmp], try load key[key1] key[key1] => value1 Store[store-1.dmp], try load key[key2] key[key2] => value2 Store[store-1.dmp], try load key[key3] key[key3] => value3 Store[store-1.dmp], try load key[key4] key[key4] => value4 Store[store-1.dmp], try load key[key5] key[key5] => value5 Store[store-1.dmp], try load key[key6] key[key6] => value6 Store[store-1.dmp], try load key[key7] key[key7] => value7 Store[store-1.dmp], try load key[key8] key[key8] => value8 Store[store-1.dmp], try load key[key9] key[key9] => value9 Store[store-1.dmp], try load key[key10] key[key10] => value10
一応、動いているようです。
ちなみに、この例ではget/putしか書いていませんが、removeするとCacheWriterのdeleteメソッドが呼び出されました。clearについては、何も起こりませんでした。
なお、設定時に指定しているパラメータについて少し。
manager.defineConfiguration(cacheName, persistenceBuilder .addStore(new SimpleMapCacheStoreConfigurationBuilder(persistenceBuilder)) .fetchPersistentState(false) .preload(false) .shared(false) .purgeOnStartup(false) .ignoreModifications(false) .build)
先ほど作成した永続化ファイルは、いったん削除します。
sharedをtrueにして実行すると、書き込み対象のストアが減ります。
Store[store-1.dmp], try load key[key1] Store[store-1.dmp], missing key[key1] Store[store-1.dmp], write (key, value) = (key1, value1) Store[store-1.dmp], try load key[key2] Store[store-1.dmp], missing key[key2] Store[store-1.dmp], write (key, value) = (key2, value2) Store[store-1.dmp], try load key[key3] Store[store-1.dmp], missing key[key3] Store[store-1.dmp], write (key, value) = (key3, value3) Store[store-1.dmp], try load key[key4] Store[store-1.dmp], missing key[key4] Store[store-1.dmp], write (key, value) = (key4, value4) Store[store-1.dmp], try load key[key5] Store[store-1.dmp], missing key[key5] Store[store-2.dmp], write (key, value) = (key5, value5) Store[store-1.dmp], try load key[key6] Store[store-1.dmp], missing key[key6] Store[store-1.dmp], write (key, value) = (key6, value6) Store[store-1.dmp], try load key[key7] Store[store-1.dmp], missing key[key7] Store[store-1.dmp], write (key, value) = (key7, value7) Store[store-1.dmp], try load key[key8] Store[store-1.dmp], missing key[key8] Store[store-1.dmp], write (key, value) = (key8, value8) Store[store-1.dmp], try load key[key9] Store[store-1.dmp], missing key[key9] Store[store-2.dmp], write (key, value) = (key9, value9) Store[store-1.dmp], try load key[key10] Store[store-1.dmp], missing key[key10] Store[store-2.dmp], write (key, value) = (key10, value10)
最終的に、偏った保存のされ方をしました。
Store[store-1.dmp] saved, keys = Set(key6, key8, key2, key4, key7, key1, key3) Store[store-2.dmp] saved, keys = Set(key9, key5, key10)
これは、NodeをまたがってCacheLoaderが永続化先を共有する場合に使用するようです。例としてはクラスタが同じJDBC接続先を使用するような場合で、複数のCacheインスタンスが不必要に同じデータを書き込むことを避けることができます。
続いてignoreModificationsですが、こちらをtrueにすると書き込みを無視するようになります。再び永続化したファイルを削除して実行すると
Store Name = store-1.dmp Store[store-1.dmp], not exists. Store Name = store-2.dmp Store[store-2.dmp], not exists. Cache size => 0 Store[store-1.dmp], try load key[key1] Store[store-1.dmp], missing key[key1] key[key1] => null Store[store-1.dmp], try load key[key2] Store[store-1.dmp], missing key[key2] key[key2] => null Store[store-1.dmp], try load key[key3] Store[store-1.dmp], missing key[key3] key[key3] => null Store[store-1.dmp], try load key[key4] Store[store-1.dmp], missing key[key4] key[key4] => null Store[store-1.dmp], try load key[key5] Store[store-1.dmp], missing key[key5] key[key5] => null Store[store-1.dmp], try load key[key6] Store[store-1.dmp], missing key[key6] key[key6] => null Store[store-1.dmp], try load key[key7] Store[store-1.dmp], missing key[key7] key[key7] => null Store[store-1.dmp], try load key[key8] Store[store-1.dmp], missing key[key8] key[key8] => null Store[store-1.dmp], try load key[key9] Store[store-1.dmp], missing key[key9] key[key9] => null Store[store-1.dmp], try load key[key10] Store[store-1.dmp], missing key[key10] key[key10] => null Store[store-1.dmp], try load key[key1] Store[store-1.dmp], missing key[key1] Store[store-1.dmp], try load key[key2] Store[store-1.dmp], missing key[key2] Store[store-1.dmp], try load key[key3] Store[store-1.dmp], missing key[key3] Store[store-1.dmp], try load key[key4] Store[store-1.dmp], missing key[key4] Store[store-1.dmp], try load key[key5] Store[store-1.dmp], missing key[key5] Store[store-1.dmp], try load key[key6] Store[store-1.dmp], missing key[key6] Store[store-1.dmp], try load key[key7] Store[store-1.dmp], missing key[key7] Store[store-1.dmp], try load key[key8] Store[store-1.dmp], missing key[key8] Store[store-1.dmp], try load key[key9] Store[store-1.dmp], missing key[key9] Store[store-1.dmp], try load key[key10] Store[store-1.dmp], missing key[key10] Store[store-1.dmp] is empty. Store[store-2.dmp] is empty.
なんと、何も保存されません。
その他のパラメータですが、fetchPersistentState、preloadはAdvancedCacheLoaderインターフェース、purgeOnStartupはAdvancedCacheWriterインターフェースを実装していなければ使用することができません。
AdvancedCacheLoader/AdvancedCacheWriterについては、次回ということで。
とりあえず、ここまでのコードはこちらに置いておきました。
https://github.com/kazuhira-r/infinispan-examples/tree/master/infinispan-persistence