CLOVER🍀

That was when it all began.

Spring Boot×Hibernate Searchで、インデックスを複数Nodeで共有する

こちらのエントリの続きです。

Spring BootとHibernate Searchで遊ぶ
http://d.hatena.ne.jp/Kazuhira/20141223/1419330401

ここで作成したアプリケーションで持つLuceneのインデックスを、複数Nodeとして起動したアプリケーションで共有してみます。

Luceneのインデックスの保存先は、Infinispanとします。

3.3.1. Infinispan Directory configuration
http://docs.jboss.org/hibernate/search/5.0/reference/en-US/html_single/#infinispan-directories

ここで定義するCacheを、Dist/Replとして各Nodeで共有しようということで。まあ、ほとんどHibernate SearchのInfinispan Integrationが持つデフォルト設定ですが。設定ファイルとしては、明示します。

基本的にはpomやapplication.ymlおよびInfinispan/JGroupsの設定追加・変更のみで、ソースコード自体は変更していません。ソースコード自体は前のエントリを参照してください。

依存関係の定義

pomでのHibernate SearchおよびLuceneの依存関係定義は、今回は以下とします。

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search-orm</artifactId>
      <version>5.0.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search-infinispan</artifactId>
      <version>5.0.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-analyzers-kuromoji</artifactId>
      <version>4.10.2</version>
    </dependency>

hibernate-search-infinispan」が増えました。

Infinispan/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:7.0 http://www.infinispan.org/schemas/infinispan-config-7.0.xsd"
    xmlns="urn:infinispan:config:7.0">
  <jgroups>
    <stack-file name="udp" path="jgroups.xml" />
  </jgroups>

  <cache-container name="indexingCacheManager" shutdown-hook="DONT_REGISTER">
    <transport cluster="cluster" stack="udp" />

    <distributed-cache name="LuceneIndexesData" mode="SYNC" remote-timeout="25000">
      <transaction mode="NONE"/>
      <state-transfer enabled="true" timeout="480000" await-initial-transfer="true" />
      <indexing index="NONE" />
      <locking striping="false" acquire-timeout="10000" concurrency-level="500" write-skew="false" />
      <eviction max-entries="-1" strategy="NONE"/>
      <expiration max-idle="-1"/>
    </distributed-cache>

    <replicated-cache name="LuceneIndexesMetadata" mode="SYNC" remote-timeout="25000">
      <transaction mode="NONE"/>
      <state-transfer enabled="true" timeout="480000" await-initial-transfer="true" />
      <indexing index="NONE" />
      <locking striping="false" acquire-timeout="10000" concurrency-level="500" write-skew="false" />
      <eviction max-entries="-1" strategy="NONE"/>
      <expiration max-idle="-1"/>
    </replicated-cache>

    <replicated-cache name="LuceneIndexesLocking" mode="SYNC" remote-timeout="25000">
      <transaction mode="NONE"/>
      <state-transfer enabled="true" timeout="480000" await-initial-transfer="true" />
      <indexing index="NONE" />
      <locking striping="false" acquire-timeout="10000" concurrency-level="500" write-skew="false" />
      <eviction max-entries="-1" strategy="NONE"/>
      <expiration max-idle="-1"/>
    </replicated-cache>
  </cache-container>
</infinispan>

それぞれインデックスのデータ用、メタデータ用、ロック用のCacheの定義です。まあ、Hibernate Searchのデフォルトのままです。

Hibernate Searchに含まれる、デフォルトのInfinispanの設定
https://github.com/hibernate/hibernate-search/blob/5.0.0.Final/infinispan/src/main/resources/default-hibernatesearch-infinispan.xml

JGroupsの設定は、Infinispan 7.0.2.Finalのデフォルトの設定をベースに、こんな感じにしました。うちはOSのネットワーク関連の設定は、ほぼいじっていないので絞っています。
src/main/resources/jgroups.xml

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="urn:org:jgroups"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/JGroups-3.4.xsd">
  <UDP
      mcast_addr="${jgroups.udp.mcast_addr:228.6.7.8}"
      mcast_port="${jgroups.udp.mcast_port:46655}"
      tos="8"
      ucast_recv_buf_size="150k"
      ucast_send_buf_size="130k"
      mcast_recv_buf_size="150k"
      mcast_send_buf_size="130k"
      max_bundle_size="31k"
      ip_ttl="${jgroups.udp.ip_ttl:0}"
      enable_diagnostics="false"
      bundler_type="sender-sends-with-timer"

      thread_naming_pattern="pl"
      thread_pool.enabled="true"
      thread_pool.min_threads="2"
      thread_pool.max_threads="30"
      thread_pool.keep_alive_time="60000"
      thread_pool.queue_enabled="true"
      thread_pool.queue_max_size="100"
      thread_pool.rejection_policy="Discard"

      oob_thread_pool.enabled="true"
      oob_thread_pool.min_threads="2"
      oob_thread_pool.max_threads="30"
      oob_thread_pool.keep_alive_time="60000"
      oob_thread_pool.queue_enabled="false"
      oob_thread_pool.queue_max_size="100"
      oob_thread_pool.rejection_policy="Discard"

      internal_thread_pool.enabled="true"
      internal_thread_pool.min_threads="2"
      internal_thread_pool.max_threads="4"
      internal_thread_pool.keep_alive_time="60000"
      internal_thread_pool.queue_enabled="true"
      internal_thread_pool.queue_max_size="100"
      internal_thread_pool.rejection_policy="Discard"
      />

  <PING timeout="3000" num_initial_members="3"/>
  <MERGE3/>

  <FD_SOCK/>
  <FD_ALL timeout="15000" interval="3000"/>
  <VERIFY_SUSPECT timeout="1500"/>

  <pbcast.NAKACK2
      xmit_interval="1000"
      xmit_table_num_rows="100"
      xmit_table_msgs_per_row="10000"
      xmit_table_max_compaction_time="10000"
      max_msg_batch_size="100"/>

  <UNICAST3
      xmit_interval="500"
      xmit_table_num_rows="20"
      xmit_table_msgs_per_row="10000"
      xmit_table_max_compaction_time="10000"
      max_msg_batch_size="100"
      conn_expiry_timeout="0"/>

  <pbcast.STABLE stability_delay="500" desired_avg_gossip="5000" max_bytes="1m"/>
  <pbcast.GMS print_local_addr="true" join_timeout="3000" view_bundling="true"/>

  <tom.TOA/> <!-- the TOA is only needed for total order transactions-->

  <UFC max_credits="2m" min_threshold="0.40"/>
  <MFC max_credits="2m" min_threshold="0.40"/>
  <FRAG2 frag_size="30k" />
  <RSVP timeout="60000" resend_interval="500" ack_on_delivery="false" />
</config>

Infinispanに含まれる、デフォルトのJGroups(UDP)の設定
https://github.com/infinispan/infinispan/blob/7.0.2.Final/core/src/main/resources/default-configs/default-jgroups-udp.xml

application.ymlの変更

InfinispanをLuceneのDirectoryとして使うに伴い、Hibernate Searchの設定も変更します。
src/main/resources/application.yml

spring:
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/practice?useUnicode=true&characterEncoding=utf-8&characterSetResults=utf-8&useServerPrepStmts=true&useLocalSessionState=true&elideSetAutoCommits=true&alwaysSendSetIsolation=false
    username: kazuhira
    password: password
  jpa:
    hibernate.ddl-auto: none
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        search:
          default:
            directory_provider: infinispan
          infinispan.configuration_resourcename: infinispan.xml
          analyzer: org.apache.lucene.analysis.ja.JapaneseAnalyzer
          lucene_version: LUCENE_4_10_2

まず、複数Nodeが上がるのでHibernateddl-autoはオフにしました。先にDDLは実行済みの前提とします。

あとはInfinispanの設定。先ほど作成した設定ファイルを参照するように「hibernate.search.infinispan.configuration_resourcename」を設定します。

これでお終いです。

実行

それでは、まずはパッケージング。

$ mvn package

2つNodeを起動します。

## Node1
$ java -jar target/spring-boot-hibernate-search-integration-0.0.1-SNAPSHOT.jar --server.port=8080

## Node2
$ java -jar target/spring-boot-hibernate-search-integration-0.0.1-SNAPSHOT.jar --server.port=8180

うちだと、2Nodeが限界ギリギリでした…。

起動すると、こんな感じで2つのNodeがクラスタを構成してくれます。

2014-12-23 23:46:41.214  INFO 53740 --- [IndexesMetadata] o.i.r.t.jgroups.JGroupsTransport         : ISPN000094: Received new cluster view for channel cluster: [xxxxx-16900|1] (2) [xxxxx-16900, xxxxx-48328]

では、データを登録します。こちらは、Node1に対して実行。

$ curl -X POST -H "Content-Type: application/json;" -d@books.json http://localhost:8080/book/

続いて、Node2に対して全文検索を実行してみます。
「Spring Java

$ curl -X GET -H 'Content-Type: application/json;' -d '{ "query": "Spring Java"}' http://localhost:8180/book/search | perl -wp -e 's!{!\n{!g
{"fullTextQuery":"+(title:spring summary:spring) +(title:java summary:java)","hits":1,"books":[
{"isbn":"978-4777518654","title":"はじめてのSpring Boot","price":2700,"summary":"「Spring Framework」で簡単Javaアプリ開発"}]}

全文検索 Solr」

$ curl -X GET -H 'Content-Type: application/json;' -d '{ "query": "全文検索 Solr"}' http://localhost:8180/book/search | perl -wp -e 's!{!\n{!g'
{"fullTextQuery":"+((title:全文 title:検索) (summary:全文 summary:検索)) +(title:solr summary:solr)","hits":2,"books":[
{"isbn":"978-4048662024","title":"高速スケーラブル検索エンジン ElasticSearch Server","price":3024,"summary":"Apache Solrを超える全文検索エンジンとして注目を集めるElasticSearch Serverの日本初の解説書です。多くのサンプルを用いた実践的入門書になっています。"},
{"isbn":"978-4774161631","title":"[改訂新版] Apache Solr入門 オープンソース全文検索エンジン","price":3780,"summary":"最新版Apaceh Solr Ver.4.5.1に対応するため大幅な書き直しと原稿の追加を行い、現在の開発環境に合わせて完全にアップデートしました。Apache Solrは多様なプログラミング言語に対応した全文検索エンジンです。"}]}

もちろん、Node1からも検索可能です。

OKそうですね。