Quitada ブログ HAX

Hatena Blog でも Quitada ブログ

GemFire に JSON データを put してクエリーしてみた

VMware 社のインメモリキャッシュ製品である vFabric GemFire ですが、バージョン 7.0 から JSON ネイティブサポートしているので、JSON データを put してクエリーしてみて、ついでにインデックスはってってクエリー処理の高速化を試してみた。

まずはリージョンを作成しましょう。JSON データのために特別な設定とかは不要です。とりあえずこのブログエントリーの例では、以下のようにキャッシュサーバーベースで、MyDataRegion とかいう名前でパーティションリージョンでも作ってみます。

<cache>
	<cache-server max-threads="100" notify-by-subscription="false"
            socket-buffer-size="96000">
	</cache-server>

	<region name="MyDataRegion" refid="PARTITION">
		<region-attributes statistics-enabled="false">
			<partition-attributes total-num-buckets="50"
                            redundant-copies="1" />
		</region-attributes>
	</region>
</cache>

さて、次に JSON データを Java アプリから put してみましょう。JSON データは実際は文字列ですが、GemFire では PDX をベースに JSON 対応しているので、最終的には com.gemstone.gemfire.pdx.PdxInstance 型のオブジェクトとして put します。従って、リージョンオブジェクトを作成する際の型変数として PdxInstance を指定してやると良いでしょう。

Region<Integer, PdxInstance> testRegion = ccache.getRegion("MyDataRegion");     

put する JSON データ自体は String 型のオブジェクトとで作成、put する際に以下のように com.gemstone.gemfire.pdx.JSONFormatter の API を使って PdxInstance 型に変換してやります。

testRegion.put(num, JSONFormatter.fromJSON(jsonDoc));

参考のため、指定した数だけダミーの JSON データを大量に put するサンプルコード(GemFire クライアントベース)を添付します(MyJsonDataPutter.java 直)。念のため、当該クライアントアプリ用の cache.xml も添付します(PoolClient.xml 直)。

#なお、このサンプルアプリは、この後のクエリーで一部が条件にヒットするように、微妙に異なる 2 種類のダミーデータを put するようになっています。

次に、クエリーしてみましょう。さきほど添付したサンプルアプリでは、以下のような感じのダミー JSON データを put します。

{
  "division": "Sales0",
  "person": [
    { "name": "Tanaka", "age": 25 },
    { "name": "Quitada", "age": 39 },
    { "name": "Tamamoto", "age": 34 }
  ]
}

部署名(division)と、所属している人(person)をデータ項目としてもっていて、person はさらにネストして配列データをもっていて所属している人の名前(name)と年齢(age)をもっているという構造です。

これを元に、「年齢が 39 歳の人が所属している部署」を条件として、それに合致するデータエントリーすべてを JSON データとしてクエリーしてみます。ポイントは、JSON データでネストして格納されている部分を条件とするところです。

GemFire に付属している quickstart サンプルコード(JSONClient.java)では、リージョンオブジェクトの query メソッドを使って以下のようなコードでクエリーしてます。

SelectResults<PdxInstance> sr = exampleRegion.query("age = 25");

内部的には GemFire の OQL のエンジン使ってクエリーされますが、「select * from [region] where ...」というタイプの単純なクエリーの場合は、この一行ですむんですね。これにならって、上述の条件でクエリーを実現するため、以下のように記述してみました。

SelectResults<PdxInstance> sr = exampleRegion.query("person.age = 39");

結論からすると NG でした。上述のサンプルデータのうち、divison、すなわちネストしてない一番ルートのデータに対しての条件指定は OK でした。いろいろ試行錯誤した結果、以下のようにクエリーを記述することで成功しました。

select r from /MyDataRegion r, r.person p where p.age = 39

where 句の条件指定にはネストした表現が使えないようなので、from 句で person を MyDataRegion リージョンのインスタンス p として指定して、where のところ、p.age みたいに指定。で、結局ほしいのは from 句に指定したもののうちリージョンデータだけなので、select 句には r と記述。

実際にクエリーに合致した JSON データを取得する場合は、イテレーターを使って以下のような感じで取得します(JSONFormatter を使ってて、put した場合と逆にで、toJSON メソッドで変換してやります)。

for (Iterator iter = result.iterator(); iter.hasNext();) {
	System.out.println(JSONFormatter.toJSON((PdxInstance)iter.next()));
}

参考のため、JSON データをクエリーするサンプルコードを添付します(MyJsonQueryClient.java 直)。cache.xml は、上述の PoolClient.xml がそのまま使えます。

最後にインデックスをはった場合について。クエリーのトリッキーさにあわせて、インデックスのはりかたもトリッキーになります。cache.xml の設定ベースだと以下のような感じです。

<index name="personIndex" from-clause="/MyDataRegion r, r.person p" expression="p.age" type="range" />

#ざっくり言うと、from-clause にクエリーの from の部分、expression にクエリーの where の条件部分を指定してやれば良いってことですね。あと、type は今回の場合は明らかにキーインデックスじゃないので、hash と range で試してみたところ、range でないと効果が見られなかったので…。

参考のため、インデックスの設定も含めたキャッシュサーバーの cache.xml 設定ファイルを添付します(Partitioned.xml 直)。

なお、インデックスをはることによる、当方の環境・上述のデータパターンにおいては、クエリー速度を 40% くらい高速化することができました。

  *  *  *

今後は、REST API とか提供して、JavaScript からも GemFire に JSON データを出し入れできるようにしてほしいなぁ。そしたら、JavaScript をもっと真剣に勉強するぜー、プロになるため。

プロになるためのJavaScript入門 ~node.js、Backbone.js、HTML5、jQuery-Mobile (Software Design plus)

プロになるためのJavaScript入門 ~node.js、Backbone.js、HTML5、jQuery-Mobile (Software Design plus)