CLOVER🍀

That was when it all began.

InfinispanのL1 Cachingを試してみる

Infinispanのクラスタリング時に使える機能である、L1 Cachingをちょっと試してみました。

L1 Caching
https://docs.jboss.org/author/display/ISPN/Clustering+modes#Clusteringmodes-L1Caching

L1 Cachingとは?

クラスタリングのモードが、Distributionの時に意味を成す機能です。

Distributionモードの時には、自分のローカルにデータがない時は他のNodeにデータを取りに行く(RPCが発生する)わけですが、L1 Cachingを有効にしているとこの時に取得したデータをキャッシュしてRPCを発生しないようにすることができます。

何度もリモートNodeにデータを取得しに行ってしまうようなケースで、有効にするとよいと書かれています。

L1 Cachingを有効にすると、文字通りデータをキャッシュするわけですが、このL1キャッシュの有効な期間は設定することができます。

L1 Cachingを有効にするための最低限の設定は

<l1 enabled="true" />

なのですが、他にもこんな設定を行うことができます。

属性名 デフォルト値 意味
enabled false trueにすると、L1 Cacheを有効にする。デフォルトでは無効
invalidationThreshold 0 L1 Cacheが無効化される時に、マルチキャストかユニキャストを使用するのかを決定する。デフォルトでは、マルチキャストが使用される。もしこのしきい値を-1にした場合は、ユニキャストが常に使用される。デフォルト値である0を指定した場合は、マルチキャストが使用される
lifespan 60000 L1 Cacheのエントリの最大の有効期限。デフォルトは10分
cleanupTaskFrequency 60000 どのくらいの頻度で、L1 Cacheをクリーンアップするタスクを実行させるかを設定する。デフォルトは10分
onRehash false(?) 有効にすると、リハッシュ発生時に削除されたエントリは、削除されるのではなくL1 Cacheに移動する。デフォルトは、無効になっている模様…

この説明でInvalidationがどうのこうのあるように、L1 Cachingは利用にはトレードオフがあることがドキュメントには書かれています。

まず、キーが更新された時には、L1 Cacheに乗っているエントリを無効化するために、Invalidation Messageが必要になります。L1 Cacheは、リクエストを行うNodeがエントリをローカルにキャッシュし、変更を監視するようにします。L1 Cacheのエントリは、メモリの使用を制御するために、内部的な有効期限を持ちます。

L1 Cacheを有効にすることは、ローカルにないキーを繰り返して読み出す際のパフォーマンスを改善しますが、メモリ使用量はある程度増加させてしまいます。

これは、Gridのデータが無効化されるものの「ほとんど読み取り」であることのパフォーマンスと、分散された時のスケーラビリティのよいトレードオフを表しています。

L1 Cacheを正しく理解しましたか?正しいアプローチは、あなたのアプリケーションでL1 Cacheを有効にした時と無効にした時でベンチマークテストを行い、何が最適なアクセスパターンなのかを確認することです。

使ってみる

ドキュメントを読んだところで、実際にL1 Cachingを使ってみましょう。

まずはbuild.sbt。

name := "infinispan-l1-caching"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.10.2"

organization := "littlewings"

fork in run := true

resolvers += "JBoss Public Maven Repository Group" at "http://repository.jboss.org/nexus/content/groups/public-jboss/"

libraryDependencies ++= Seq(
  "org.infinispan" % "infinispan-core" % "5.3.0.Final",
  "net.jcip" % "jcip-annotations" % "1.0"
)

JGroupsの設定は端折ります。Infinispanの設定。
src/main/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:5.3 http://www.infinispan.org/schemas/infinispan-config-5.3.xsd"
    xmlns="urn:infinispan:config:5.3">

  <global>
    <transport clusterName="l1-caching-cluster">
      <properties>
        <property name="configurationFile" value="jgroups.xml" />
      </properties>
    </transport>
    <globalJmxStatistics
        enabled="true"
        jmxDomain="org.infinispan"
        cacheManagerName="DefaultCacheManager"
        />
  </global>

  <namedCache name="l1CacheEnabled">
    <jmxStatistics enabled="true" />
    <clustering mode="distribution">
      <hash numOwners="1" />
      <l1 enabled="true" />
    </clustering>
  </namedCache>
</infinispan>

L1 Cachingは有効にしています。Distributionモード時のnumOwnersは、今回は1としています。

L1 Cachingは他のNodeのデータをキャッシュするという話なので、Nodeがひとつだと意味がありません。というわけで、浮いてもらうサーバを用意。
起動時のサーバ名でキーの名前と範囲が変わり、5秒に1度自分が担当している範囲のキーに対応する値を更新します。
src/main/scala/EmbeddedCacheServer.scala

import org.infinispan.manager.DefaultCacheManager

trait EmbeddedCacheServerSupport {
  val nodeNameTo: Map[String, Int] = Map("node1" -> 10, "node2" -> 20)
  val increase: Int = 5
}

object EmbeddedCacheServer extends EmbeddedCacheServerSupport {
  def main(args: Array[String]): Unit = {
    val start = nodeNameTo
      .get(args.toList.headOption.getOrElse(""))
      .getOrElse(throw new IllegalArgumentException(s"Required NodeName[${nodeNameTo.keys.mkString(" or ")}]"))

    val cache = new DefaultCacheManager("infinispan.xml").getCache[Any, Any]("l1CacheEnabled")
    cache.addListener(new CacheListener)

    val fun = (now: Long) => {
      (start to start + increase).foreach { i => cache.put(s"key$i", s"value${i}-${now}") }
      Thread.sleep(5 * 1000L)
    }

    Stream
      .continually(System.currentTimeMillis)
      .foreach(fun)
  }
}

一緒にListenerもくっつけています。
src/main/scala/CacheListener.scala

import org.infinispan.notifications.Listener
import org.infinispan.notifications.cachelistener.annotation.{CacheEntryInvalidated, CacheEntryVisited}
import org.infinispan.notifications.cachelistener.event.{CacheEntryInvalidatedEvent, CacheEntryVisitedEvent}

@Listener
class CacheListener {
  @CacheEntryVisited
  def cacheEntryVisited(event: CacheEntryVisitedEvent[_, _]): Unit =
    println(s"Entry Visited Event: $event")

  @CacheEntryInvalidated
  def cacheEntryInvalidated(event: CacheEntryInvalidatedEvent[_, _]): Unit =
    println(s"Invalidated Event: $event")
}

Invalidationイベントが発生しそうな気がしたので、そちらを監視するのと、Remote Nodeに読み込みが行くかどうかをみるためにVisitedイベントを監視しています。

最後、Remote Nodeからデータを読み出すだけのプログラムです。こちらがL1 Cacheを活用することを想定しています。10秒おきに3回データを読み出し、これを5回繰り返します。
src/main/scala/InfinispanL1Caching.scala

import org.infinispan.manager.DefaultCacheManager

object InfinispanL1Caching extends EmbeddedCacheServerSupport {
  def main(args: Array[String]): Unit = {
    val manager = new DefaultCacheManager("infinispan.xml")
    val cache = manager.getCache[String, String]("l1CacheEnabled")

    cache.addListener(new CacheListener)

    println(cache.asInstanceOf[org.infinispan.CacheImpl[_, _]].getConfigurationAsXmlString)

    try {
      val readValues = () => {
        nodeNameTo.values.foreach { v =>
          (v to v + increase).foreach { i =>
            cache.get(s"key$i")
          }
        }
      }

      val printDataLocality = () => {
        val dm = cache.getAdvancedCache.getDistributionManager

        val rpcManager = cache.getAdvancedCache.getRpcManager
        println("All Cluster Members => " + rpcManager.getMembers)

        nodeNameTo.values.foreach { v =>
          (v to v + increase).foreach { i =>
            val key = s"key$i"
            println(s"Data Locality: Key[$key] => PrimaryLocation[${dm.getPrimaryLocation(key)}], "
                    + s"Locate:${dm.locate(key)}")
          }
        }
      }

      (1 to 5).foreach { i =>
        readValues()
        printDataLocality()
        readValues()
        readValues()
        val waitTime = 10 * 1000L
        println(s"Sleeping... ${waitTime}sec")
        Thread.sleep(waitTime)
      }
    } finally {
      cache.stop()
      manager.stop()
    }
  }
}

では、まずはサーバを起動。

# Node1
$ sbt "run-main EmbeddedCacheServer node1"

# Node2
$ sbt "run-main EmbeddedCacheServer node2"

Node1→Node2の順に起動すると、いきなりInvalidationイベントが発生します。発生するのはNode1側(先に起動した方)です。

[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key14, value=value14-1373107486943, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key13, value=value13-1373107486943, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key10, value=value10-1373107486943, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}

まあ、
Node1起動→Node2起動→リハッシュ→Node1からNodeに移ったデータを無効化
というところでしょうね。

では、メインとなるプログラムを実行します。

> run-main InfinispanL1Caching
[info] Running InfinispanL1Caching 
[error] 7 06, 2013 7:46:58 午後 org.infinispan.remoting.transport.jgroups.JGroupsTransport start
[error] INFO: ISPN000078: Starting JGroups Channel
[info] 
[info] -------------------------------------------------------------------
[info] GMS: address=ubuntu-33510, cluster=l1-caching-cluster, physical address=fe80:0:0:0:20c:29ff:fe5c:cfec%2:42683
[info] -------------------------------------------------------------------

この時、自分自身は「ubuntu-33510」という名前で起動しています。

途中で出力されるデータの配置場所は、こんな感じになっています。

[info] All Cluster Members => [ubuntu-48076, ubuntu-34567, ubuntu-33510]
[info] Data Locality: Key[key10] => PrimaryLocation[ubuntu-34567], Locate:[ubuntu-34567]
[info] Data Locality: Key[key11] => PrimaryLocation[ubuntu-48076], Locate:[ubuntu-48076]
[info] Data Locality: Key[key12] => PrimaryLocation[ubuntu-48076], Locate:[ubuntu-48076]
[info] Data Locality: Key[key13] => PrimaryLocation[ubuntu-34567], Locate:[ubuntu-34567]
[info] Data Locality: Key[key14] => PrimaryLocation[ubuntu-34567], Locate:[ubuntu-34567]
[info] Data Locality: Key[key15] => PrimaryLocation[ubuntu-48076], Locate:[ubuntu-48076]
[info] Data Locality: Key[key20] => PrimaryLocation[ubuntu-33510], Locate:[ubuntu-33510]
[info] Data Locality: Key[key21] => PrimaryLocation[ubuntu-48076], Locate:[ubuntu-48076]
[info] Data Locality: Key[key22] => PrimaryLocation[ubuntu-34567], Locate:[ubuntu-34567]
[info] Data Locality: Key[key23] => PrimaryLocation[ubuntu-34567], Locate:[ubuntu-34567]
[info] Data Locality: Key[key24] => PrimaryLocation[ubuntu-48076], Locate:[ubuntu-48076]
[info] Data Locality: Key[key25] => PrimaryLocation[ubuntu-33510], Locate:[ubuntu-33510]

L1 Cacheは効いていますが、ここで分かるのはキャッシュされてもデータの配置先としては管理されないということですね。ま、そりゃそうですよね。

こちらでは、連続して3回全データを読み込むのですが、Remoteの各Nodeで出力されるログは、各キーに対して2回(pre/post)ずつになります。
Node1。

[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key11, value=value11-1373107647714, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key11, value=value11-1373107647714, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key12, value=value12-1373107647714, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key12, value=value12-1373107647714, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key15, value=value15-1373107647714, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key15, value=value15-1373107647714, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key21, value=value21-1373107647706, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key21, value=value21-1373107647706, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key24, value=value24-1373107647706, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-48076, key=key24, value=value24-1373107647706, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}

Node2。

[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key10, value=value10-1373107637614, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key10, value=value10-1373107637614, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key13, value=value13-1373107637614, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key13, value=value13-1373107637614, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key14, value=value14-1373107637614, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key14, value=value14-1373107637614, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key22, value=value22-1373107637606, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key22, value=value22-1373107637606, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key23, value=value23-1373107637606, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}
[info] Entry Visited Event: EventImpl{type=CACHE_ENTRY_VISITED, pre=false, cache=Cache 'l1CacheEnabled'@ubuntu-34567, key=key23, value=value23-1373107637606, oldValue=null, transaction=null, originLocal=true, transactionSuccessful=false, entries=null, created=false}

つまり、2回目以降の読み出しではRPCは発生していないということになります。

また、Node1、Node2では、5秒おきにデータの再設定が発生するので、メインとなるプログラムが10秒スリープしている間に、Invalidationイベントが発生します。

[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key10, value=value10-1373107617424, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key21, value=value21-1373107617436, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key11, value=value11-1373107617424, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key22, value=value22-1373107617436, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key23, value=value23-1373107617436, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key12, value=value12-1373107617424, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key13, value=value13-1373107617424, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key24, value=value24-1373107617436, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key14, value=value14-1373107617424, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}
[info] Invalidated Event: EventImpl{type=CACHE_ENTRY_INVALIDATED, pre=true, cache=Cache 'l1CacheEnabled'@ubuntu-33510, key=key15, value=value15-1373107617424, oldValue=null, transaction=null, originLocal=false, transactionSuccessful=false, entries=null, created=false}

Node1、Node2が共に全キーを更新するので、全部無効化されています。

なお、このイベントはNode1およびNode2では発生しません。データの読み出しを行っていないので、L1 Cacheに乗らないことが理由だと思いますが。

とりあえず、L1 Cachingを有効にした時にRPCが発生していないこと、そしてデータのオーナーが更新を行った時に、L1 Cacheのエントリが無効化されることは確認できましたね。