CLOVER🍀

That was when it all began.

InfinispanのCacheLoader/CacheWriterを使ってみる

これまで、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  83 01:15 store-1.dmp
-rw-rw-r--  1 xxxxx xxxxx  713  83 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