CLOVER🍀

That was when it all began.

InfinispanのSegmentsを使ってみる

Infinispanのドキュメントにある、Virtual Nodesをちょっと見てみようと思いまして。

Virtual Nodes - Improving the distribution of data
https://docs.jboss.org/author/display/ISPN/Clustering+modes#Clusteringmodes-VirtualNodesImprovingthedistributionofdata

とりあえず、ドキュメントを読む

軽く、書いていることを訳していってみましょう。

Infinspanは、Node間のハッシュ空間を均等には分割しません。均等に分割しないことによって、グリッドに参加するNode、退出するNodeがあっても、隣接する各Nodeが所有権を調整する必要がないことを意味します。これは、ネットワークトラフィックには良い影響を及ぼします。しかし、これはいくつかのNodeが他のNodeより大きなハッシュ空間を引き受けてしまうことを意味する場合があります。キーのハッシュ関数の潜在的な不規則性と合わせて、グリッドを横断するエントリの分配が貧弱になる可能性があるということです。ハッシュキーの不規則性に取り組むために、Infinispanはデフォルトで高度なハッシュ関数(Murmur Hash 3)を使用します。ノード分配の不規則性に取り組むために、InfinispanはVirtual Nodeを使用します。
*bit spreaderってなんでしょう…?

最初に、2、3の分配例について話すことで、Virtual Nodeがどのように概念的に助けてくれるかを考え、その知識を備えたところで、Infinispanがそれらをどのように使用するかを見てください。

1,000個のハッシュ空間(データを入れることができる、1,000個のバケットがある)があると、考えてください。2つのNodeがあった場合、ひとつのNodeがひとつのバケットを使用し、もうひとつのNodeが999個のバケットを使用するようにすることは可能です(これは、最も悲観的な分配です)。もし200のNodeがあった場合は、Nodeの最悪のケースの分配は、結局199のNodeが199の各バケットひとつに対して責任を負い(合計199のバケット)、そして801のバケットに対して責任を追うひとつのNodeとなるでしょう。1,000個のNodeがあった場合、各Nodeはひとつのバケットに対して責任を負うに違いありません。これから、私達はNodeの数がハッシュ空間のサイズに向かうことを推測することができ、Nodeへのバケットの分配を改善することができます。

Infinispanの指針は、エントリを手動で指定のNodeに保存することを許可せず、ハッシュ空間にキーを配置するアルゴリズムを常に使用するということです。任意のNodeに対して、所有権についての情報を配布することなく、キーを所有するNodeを知ることについては許可します。これは、Infinispanのオーバーヘッドを縮小しますが、より重要なことは、Node障害の場合に所有者情報をレプリケーションする必要がないことで冗長性を改善するということです。

これで、エントリの配置先が自動的に決定されることを許可することで、私達はNode間の分配に関する問題についてVirtual Nodeが理想的なソリューションであることがわかります。

Infinispanは、Nodeがグリッドに参加または退出した時に、ハッシュ空間を分割するアルゴリズムを変更することによる、Virtual Nodeを実装しています。Nodeにハッシュ空間のひとつのブロックを割り当てるのではなく、ハッシュ空間中をくまなく分割した多くのブロックを割り当てます。

つまり…

だいぶ訳がめちゃくちゃな気がしますが、用はクラスタが持つハッシュ空間を複数のバケットに分割し、Nodeにはその一定数を割り当てるということを行いますと。この配置ルールはInfinispanがアルゴリズムで算出し、手動で割り付けることは不可です。ただ、あるキーを所有するNodeについて知る方法は提供しますよということでしょうね。

キーを所有するNodeについて知る方法は、DistributionManagerを使おうという話だと思いますが。

使ってみる

あとは、実際使ってみましょうということで。ドキュメントにはVirtual Nodeということで、XMLの設定についてもnumVirtualNodesを書くみたいなことになっているのですが、これはInfinispan 5.2になった時にnumSegmentsに置き換わったみたいです。

Upgrading from 5.1 to 5.2
https://docs.jboss.org/author/display/ISPN/Upgrading+from+5.1+to+5.2

というわけで、今はVirtual NodeではなくSegmentですね。

例えば、こんな感じで設定します。

    <clustering mode="distribution">
      <hash numOwners="2" numSegments="60" />
    </clustering>

numSegmentsは、デフォルトで60に設定されています。

APIで設定する時のHashConfigurationBuilderにも、ちょっと説明があります。

HashConfigurationBuilder#numSegments(int)
http://docs.jboss.org/infinispan/5.3/apidocs/org/infinispan/configuration/cache/HashConfigurationBuilder.html#numSegments%28int%29

クラスタ単位のハッシュ空間のセグメントの全ての数をコントロールします。

ハッシュ空間のセグメントは、クラスタ内でのキー分配用の粒度です。Nodeは、セグメントの断片ではなく、ひとつまたは十分なセグメントを所有(またはプライマリとして)することができます。そのため、より大きなnumSegmentsの値は、Node間のキーのより均等な分配ができることを意味するでしょう。

言い換えると、新しいコンシステントハッシュのメモリ/帯域幅の利用は、numSegmentsで線形に増大します。それゆえ、私達はnumSegmentsをクラスタのサイズ×10以下にすることを推奨します。

http://docs.jboss.org/infinispan/5.3/apidocs/org/infinispan/configuration/cache/HashConfigurationBuilder.html#numSegments%28int%29

ですって。ここで言う「クラスタのサイズ」というのは、「Nodeの数」と読み替えてOKなのかなぁ?先のVirtual Nodeの説明と合わせて考えると、最適値が考えにくいのは自分だけでしょうか。

まあ、いいや…。

build.sbt

name := "infinispan-segments"

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"
)

設定ファイル。

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="segments-cluster">
      <properties>
        <property name="configurationFile" value="jgroups.xml" />
      </properties>
    </transport>
    <globalJmxStatistics
        enabled="true"
        jmxDomain="org.infinispan"
        cacheManagerName="DefaultCacheManager"
        />
  </global>

  <namedCache name="cacheWithSegments">
    <jmxStatistics enabled="true" />
    <clustering mode="distribution">
      <hash numOwners="2" numSegments="60" />
    </clustering>
  </namedCache>
</infinispan>

JGroupsは、端折ります。

ソースは2本。メインのプログラム。
src/main/scala/InfinispanSegments.scala

import org.infinispan.manager.DefaultCacheManager

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

    try {
      val range = 1 to 60

      range.foreach(i => cache.put(s"key$i", s"value$i"))

      val dm = cache.getAdvancedCache.getDistributionManager
      range.foreach(i =>
        println(s"locate key[key$i] => ${dm.locate(s"key$i")}"))
    } finally {
      manager.stop()
    }
  }
}

Node数を稼ぐために、浮いててもらうサーバ。
src/main/scala/EmbeddedCacheServer.scala

import scala.collection.JavaConverters._

import org.infinispan.manager.DefaultCacheManager
import org.infinispan.notifications.Listener
import org.infinispan.notifications.cachelistener.annotation._
import org.infinispan.notifications.cachelistener.event._

object EmbeddedCacheServer {
  def main(args: Array[String]): Unit = {
    val manager = new DefaultCacheManager("infinispan.xml")
    val cache = manager.getCache[String, String]("cacheWithSegments")
    cache.addListener(new CacheListener)
  }
}

@Listener
class CacheListener {
  @TopologyChanged
  def topologyChanged(event: TopologyChangedEvent[_, _]): Unit =
    if (!event.isPre) {
      val cache = event.getCache
      val keys = cache.keySet.asScala
      val dm = cache.getAdvancedCache.getDistributionManager

      keys.foreach(k => println(s"key[$k] locate => ${dm.locate(k)}"))
    }
}

とりあえず、Nodeを2本ほど立てまして。

# ひとつ
$ sbt "runMain EmbeddedCacheServer"

# ふたつ
$ sbt "runMain EmbeddedCacheServer"

そして、メインのプログラムを実行。

> runMain InfinispanSegments

〜省略〜

[error] INFO: ISPN000128: Infinispan version: Infinispan 'Tactical Nuclear Penguin' 5.3.0.Final
[error] 9 01, 2013 8:13:41 午後 org.infinispan.jmx.CacheJmxRegistration start
[error] INFO: ISPN000031: MBeans were successfully registered to the platform MBean server.
[info] locate key[key1] => [ubuntu-48202, ubuntu-56187]
[info] locate key[key2] => [ubuntu-48202, ubuntu-59607]
[info] locate key[key3] => [ubuntu-48202, ubuntu-59607]
[info] locate key[key4] => [ubuntu-56187, ubuntu-48202]
[info] locate key[key5] => [ubuntu-59607, ubuntu-56187]
[info] locate key[key6] => [ubuntu-48202, ubuntu-56187]
[info] locate key[key7] => [ubuntu-56187, ubuntu-48202]
[info] locate key[key8] => [ubuntu-48202, ubuntu-56187]
[info] locate key[key9] => [ubuntu-59607, ubuntu-56187]
[info] locate key[key10] => [ubuntu-59607, ubuntu-48202]
〜省略〜
[info] locate key[key50] => [ubuntu-59607, ubuntu-48202]
[info] locate key[key51] => [ubuntu-48202, ubuntu-59607]
[info] locate key[key52] => [ubuntu-59607, ubuntu-48202]
[info] locate key[key53] => [ubuntu-59607, ubuntu-48202]
[info] locate key[key54] => [ubuntu-59607, ubuntu-48202]
[info] locate key[key55] => [ubuntu-59607, ubuntu-56187]
[info] locate key[key56] => [ubuntu-59607, ubuntu-48202]
[info] locate key[key57] => [ubuntu-59607, ubuntu-56187]
[info] locate key[key58] => [ubuntu-48202, ubuntu-56187]
[info] locate key[key59] => [ubuntu-48202, ubuntu-56187]
[info] locate key[key60] => [ubuntu-48202, ubuntu-56187]
[error] 9 01, 2013 8:13:42 午後 org.infinispan.remoting.transport.jgroups.JGroupsTransport stop
[error] INFO: ISPN000080: Disconnecting and closing JGroups Channel
[error] 9 01, 2013 8:13:43 午後 org.infinispan.remoting.transport.jgroups.JGroupsTransport stop
[error] INFO: ISPN000082: Stopping the RpcDispatcher
[success] Total time: 4 s, completed 2013/09/01 20:13:43

一応、3つのNodeに分割されていることは確認できます。

では、ちょっと極端な値として設定をこう変えまして。

      <hash numOwners="2" numSegments="1" />

各Nodeを再起動。

# ひとつ
$ sbt "runMain EmbeddedCacheServer"

# ふたつ
$ sbt "runMain EmbeddedCacheServer"

実行。

> runMain InfinispanSegments

  〜省略〜

[error] 9 01, 2013 8:25:11 午後 org.infinispan.factories.GlobalComponentRegistry start
[error] INFO: ISPN000128: Infinispan version: Infinispan 'Tactical Nuclear Penguin' 5.3.0.Final
[error] 9 01, 2013 8:25:11 午後 org.infinispan.jmx.CacheJmxRegistration start
[error] INFO: ISPN000031: MBeans were successfully registered to the platform MBean server.
[info] locate key[key1] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key2] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key3] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key4] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key5] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key6] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key7] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key8] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key9] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key10] => [ubuntu-27761, ubuntu-8473]

  〜省略〜

[info] locate key[key50] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key51] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key52] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key53] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key54] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key55] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key56] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key57] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key58] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key59] => [ubuntu-27761, ubuntu-8473]
[info] locate key[key60] => [ubuntu-27761, ubuntu-8473]
[error] 9 01, 2013 8:25:12 午後 org.infinispan.remoting.transport.jgroups.JGroupsTransport stop
[error] INFO: ISPN000080: Disconnecting and closing JGroups Channel
[error] 9 01, 2013 8:25:12 午後 org.infinispan.remoting.transport.jgroups.JGroupsTransport stop
[error] INFO: ISPN000082: Stopping the RpcDispatcher
[success] Total time: 4 s, completed 2013/09/01 20:25:12

見事にひとつのNodeに偏りました…。

というわけで、やっぱりSegmentというのは

  • クラスタ内の全Nodeを含めたハッシュ空間を、いくつのバケットに分割するのかを指定する

という設定という理解でいいみたいですね。