began.2015-06-20Hazelcastのインデックス利用時の挙動を調べてみるHazelcastちょっと興味がありまして、Hazelcastのインデックスの利用の様子と、クエリ実行時の挙動を調べてみました。Mapに格納するオブジェクトのプロパティに対して、インデックスを張ることができます。Queryを使うことにします。Queryhttp://docs.hazelcast.org/docs/3.5/manual/html-single/hazelcast-documentation.html#distributed-sql-queryでは、見ていってみましょう。3.5がリリースされていたので、今回利用してみました。テストコード用にJUnitとAssertJも利用します。Mapの値として格納するクラスは、以下とします。}}また、Hazelcastの設定はこのようにしました。Mapに対する設定ですが、titleとpriceにインデックスを張っています。priceについては順序を意識する形でインデックス定義しています。</map>テストコードで利用する、簡単なクラスタ構築用のクラス。}}指定数分だけ、JavaVM内でHazelcastのNodeを起動することができます。HazelcastInstanceには、わかりやすいようにポート番号をインスタンス名に含めるようにしました。インデックスは、どこで、誰が保持するのか?まずは、インデックスがどこで保持されるのかというところを確認したいと思います。実装からいくと、以下のクラスで持つことになるようです。順序付けを意識しない場合。https://github.com/hazelcast/hazelcast/blob/v3.5/hazelcast/src/main/java/com/hazelcast/query/impl/UnsortedIndexStore.java順序を意識する場合。https://github.com/hazelcast/hazelcast/blob/v3.5/hazelcast/src/main/java/com/hazelcast/query/impl/SortedIndexStore.java順序不要の場合はConcurrentHashMap、順序付けが必要な場合はConcurrentSkipListMap+ConcurrentHashMapで持つようになっています。クラスタ化されているわけでもなく、ローカルのメモリのみに持っているという感じですね。あとは、これのインデックスを誰が持っているのかということですね。この確認のために、こんなテストクラスを用意しました。}}テストはしていませんけど...。Mapにデータ登録後、少しスリープしてから、実際にキーに対応するエントリがどのNodeに配置されているかを表示しています。いえ、これだけだとわからないのでBytemanのスクリプトを書いて、インデックスへの登録時にコンソール出力するようにしてみました。"]")ENDRULEこのBytemanスクリプトを含めて、テストコードを動かしてみます。Nodeになるようにしているので、クラスタが3つのNodeで構成されます。this}そして、インデックス登録時のログが出力されます。Thread[hz.MyHazelcastInstance-5703.partition-operation.thread-6]最後にスレッド名を出力していて、スレッド名にHazelcastのインスタンス名が入るため、どのNodeにインデックスが入ったかが少しは見やすいと思います。※実行ごとに、結果は変わりますまた、キーの配置状況は、このようになっています。[192.168.254.129]:5703ということは、データのオーナーとなったNodeにインデックスが作成されたということですね。それにしても、SortedIndexStoreにはやたらインデックス登録のリクエストが発行されるんですねぇ...。this}それに伴い再度インデックス作成のログが出力されます。Thread[hz.MyHazelcastInstance-5703.partition-operation.thread-1]これは、Nodeが減った時にデータの再配置が発生するのですが、減ってしまったNodeがそのエントリのオーナーだった場合には別のNodeがオーナーになった際にインデックスが再作成されるということですね。この例だと、5702ポートを使用していたNodeがダウンしたわけですが、「Javaエンジニア養成読本」のエントリのオーナーとなっていたのはこのNodeでした。クエリを実行してみるそれでは続いて、クエリを投げてみましょう。今度は、このようなテストコードを用意しました。}}今度は、テストも入っています(笑)。先ほどと同様、エントリのオーナーの状況も表示した後、クエリを3回実行します。この時の様子をトレースしたいと思います。Bytemanスクリプトも、クエリ実行時の様子が追えるように追加しました。"]")ENDRULE追加したクラスについては、また後で。-DargLine=-javaagent:$BYTEMAN_HOME/lib/byteman.jar=script:indexing-trace.btmクラスタが構成された後、インデックス登録とエントリの配置状況がこのように表示されます。[192.168.254.129]:5702今度は、5702に寄りました...。'Javaエンジニア養成読本'」です。2138));この時のログは、このようになります。titleは順序付けについてのインデックス定義はしていません。Thread[hz.MyHazelcastInstance-5702.generic-operation.thread-3]ここで、mainスレッドは実行した本人のようです(5701ポートのNodeと同義)。クエリは、各Nodeで分散実行されるみたいですね。見ると、3つのNodeで検索が実行され、うち5702のポートを使用するNodeで結果が見つかったようです(オーナーですし)。Thread[hz.MyHazelcastInstance-5702.generic-operation.thread-3]続いて、LIKEの場合。2138));こちらは、かなり膨大なログが出力されます。Thread[hz.MyHazelcastInstance-5702.generic-operation.thread-0]よ〜く見ると、ここでエントリを見つけています。Thread[hz.MyHazelcastInstance-5702.generic-operation.thread-0]最初にインデックスから探して、その後にフルスキャンしているみたいですね。※持っているデータ数の割には、フルスキャン時のログがやたら多いのが気になりますが...。実際、このような実装になっているようです。最初にインデックスから探し、そこで結果が得られなければフルスキャンに移行します。https://github.com/hazelcast/hazelcast/blob/v3.5/hazelcast/src/main/java/com/hazelcast/map/impl/operation/QueryOperation.java#L92この時、インデックスを使った検索時には以下のクラスが使用され、https://github.com/hazelcast/hazelcast/blob/v3.5/hazelcast/src/main/java/com/hazelcast/query/impl/IndexService.javaフルスキャンの時には以下のクラスが使用されるようです。https://github.com/hazelcast/hazelcast/blob/v3.5/hazelcast/src/main/java/com/hazelcast/map/impl/BasicMapContextQuerySupport.javaなるほど。フルスキャンも各Nodeで実行されます、と。追記)デフォルトはNode単位のスキャンはシングルスレッドで動作し、「hazelcast.query.predicate.parallel.evaluation」をtrueにすることで並列実行になるらしいです。