SpringでNeo4jを利用する際の注意点

spring-bootからNeo4jを利用する場合、spring-data-neo4j(neo4j-ogm)と言ったライブラリでアクセスする事になります。
その際、バグと思われる現象に遭遇しましたので、忘れないように記しておきます。

概要

ValueObjectの配列またはリストをフィールドにもつEntityを作成します。
Neo4jにValueObjectをNodeとして登録させたくない為、フィールドをコンバータで文字列シリアライズしSaveを行いました。
Saveは正常に行われ、Neo4jへのストアされた内容も想定通りのものでしたが、このエンティティをLoadすると、コンバータの処理で型キャストエラーが発生しLoadに失敗します。

詳細

  • LoadできるEntity
    下記のEntityはSave、Loadが行えます

 

  • LoadできないEntity
    下記のEntityはSaveできるが、Load時にエラーが発生する

ValueObjectのListフィールドを追加する。フィールドをコンバーターにより文字列(Json)に変換する

このエンティティをSave後にLoadを実行すると、下記のような例外が発生し失敗します。スタックトレースから、キャストエラーが確認できます。

ここで発生している例外がListからStringへの変換例外だった為、なぜ発生するのか原因がわかりませんでした。Load時に行う処理は文字列(JSON)からValueObjectへの変換のはずです。なぜListを文字列にCastしようとしているのか、、、

原因

スタックトレースを追っていくと、GraphEntityMapperクラスに問題がありそうです。
当該箇所を確認してみます。

配列、Listの場合の分岐があります。どうやら、デシリアライズ先のフィールドがList(配列)の場合のみここで型の変換を行っています。

Neo4jのNodeは、プロパティに数値、文字列、真偽値またはそれらの配列を持つ事ができます。そのため為のロジックだと思いますが、コンバータの有無を確認せずに型変換を行っています。Writerの中でCallされるコンバータは文字列を期待ていますがListが渡される為、文字列にキャストを行おうとし例外となりました。

対応

色々試行錯誤した結果、当該部分を削除する対応を取りました。
テストして見る限り、動作に変わりは無いようなのですが、

まとめ

spring-data-neo4jでList(配列)フィールドにコンバーターを設定する場合は、本現象が発生します。
また、本現象ですが、キャッシュオブジェクトが利用されると再現しませんのでご注意ください。凄くハマりました(–;

Solr Cloudを動かしてみる

シーマークの山本です。

Apache Solr は全文検索エンジンとして様々なところで使われており、もはや特殊なプロダクトでは無く、検索機能を実現するならSolrというほどコモディティーとしての広がりを見せています。Solrバージョン5からはSolr Cloud機能が実装され、検索「エンジン」から検索「システム」へと変貌を遂げてきています。

そこで、Solr Cloudを使って検索システムを構築する方法についてまとめていきたいと思います。ただ、Solr Cloudのチュートリアルにある付属のサンプルを動かす方法ではなく、実際の商用プロジェクトで必要になりそうな「こんな時どうする」的なTipsを中心にまとめていきます。

構築する検索サービスの想定(=要件)

商用で提供する検索サービスであれば最低限気にすることではありますが、今回説明していくためのサービス想定、言い換えれば要件を列挙しました。

  1. AmazonのAWSや、Microsoft Azureなどのクラウド環境で複数台のサーバインスタンスを使ってSolr Cloudを構築する
  2. 検索サービスが止まらないよう高い可用性を実現する
  3. 検索サービスで提供する機能が増え、Solrの設定を変更する場合でも簡単に行える
  4. 利用者が増えても検索速度が落ちないよう簡単に負荷分散できる
  5. 検索対象データが増えても検索速度が落ちないよう簡単に拡張できる

こう書いてみると、なかなか大変そうな要件ではありますが、Solr Cloudを使えば、案外簡単にできちゃったりする(かも)のでご安心を。

 

1. 開発環境の構築

まず、「要件1 複数サーバを使って構築すること」をいきなり実現しようとすると、AWSのアカウントだったり費用がかかったりするので、まずは自分のPC上で開発環境をつくるところから始めましょう。

開発環境ですが、私がMac派なので、Macを前提に書いていきます。ただ、Windowsとまるっきり違うかどうかと言えば、あまり差異はないはずです。明らかに違うところは補足を入れます。

1.1 Solrのダウンロード

まずは、Solrのダウンロードです。執筆時点では 6.2.1 が最新でした。

https://lucene.apache.org/solr/mirrors-solr-latest-redir.html

ダウンロードしたSolrをお好きなところに展開します。

 

1.2 検索サービス用のSolr configセット

検索サービスとしてどのようなデータをインデックスし、どのように検索させるかを定義するため、Solrではいくつかの設定ファイルを用意する必要があります。通常のSolrとは異なり、Solr Cloudで利用するSolr設定ファイルセットは、zookeeperという別のミドルウェアに登録し管理することになります。

Solr設定ファイルの内容については、本題からはずれることもあり、Solrダウンロードパッケージに含まれるサンプルである「techproducts」用の設定ファイルをそのまま利用します。既にあるので、それを適当なところへコピーします。

 

次は、Solrの設定から一旦はなれ、Solr Cloudを構成する名脇役 zookeeperの設定についてです。

 

1.3 zookeeperを設定する

zookeeperって初めて聞いたって人もいるかと思いますので、軽く紹介を。

https://zookeeper.apache.org/

Apache zookeeperは、複数のサーバインスタンスで構成される分散システムにおいて、設定情報の同期、グルーピングや名前付けの管理を提供するミドルウェアです。

Solr Cloudでは、Solr設定ファイルの集中管理と配布、Solrサーバの管理(shards / Replica、solr nodeの生死等)をzookeeperが提供しています。

zookeeperのインストールは、上記のzookeeperのサイトからダウンロードして、Solrと同じく好きなところに展開します。(もし、Mac環境でbrewをお使いであれば、brew install zookeeperでインストールも可能です)

執筆時点での stableなバージョンは 3.4.9 でした。

http://ftp.jaist.ac.jp/pub/apache/zookeeper/stable/zookeeper-3.4.9.tar.gz

zookeeperは、検索システムの設定情報や構成情報を集中的に管理しています。zookeeperが止まれば、Solr Cloudも停止します。そこで、zookeeperの可用性を高めるため、zookeeperを複数台のサーバーで稼働させます。できれば、Solr Cloudとは異なる専用サーバを用意することが望ましいです。また複数台と言ってもzookeeperの特性上、奇数台で運用をすることが要件となっています。つまり、3台、5台構成をとることが必要です。開発環境では1台の中で3プロセスを起動するようにしましょう。

まず、3プロセス用のzookeeper設定ファイルを用意します。

そして、格納されるデータディレクトリ(dataDirで指定されているディレクトリ)を作成し、そこに設定ファイルを追加します。

これで、zookeeperを起動する準備が整いました。さて起動させましょう。

一応止めるコマンドも。

これで、zookeeperの準備は整いました。

 

1.4 Solr configファイルセットのzookeeperへの登録

1.2 で用意したSolr設定ファイルセットをzookeeperに登録しましょう。登録する際、zookeeper上で管理するための名前を付ける必要があります。ここでは「techp」としました。

これで、Solr Cloudで利用される設定ファイルセットがzookeeperで「techp」という名前で管理されました。

 

1.5 Solr cloudの立ち上げ

本体であるSolr Cloudを立ち上げましょう。複数台で構成するのでしたよね。開発環境なので、1台で複数インスタンスを立ち上げましょう。今回は4台構成とします。

まず、そのための準備です。

では、立ち上げます。Solrの待受ポートは通常は8983なんですが、4つのインスタンスを立ち上げるため、8983, 8984, 8985, 8986として起動します。

これで、4インスタンスがSolr Cloudとして立ち上がっています。

念のため、停止させるコマンドも。

 

1.6 collectionの作成

Solr Cloudでは、検索データをとりまとめる単位をCollectionと呼んでいます(通常起動のSolrでcoreと呼ばれていたものと同じ概念です)。Collectionを作成しましょう。Solr Cloudでは、複数台のサーバにうちどれか1台に対して操作するだけで、Solr Cloud全体に反映されます。

パラメータの説明です

パラメータ設定内容
-ctechproductsCollection名
-ntechpzookeeperで登録されている設定ファイルセット名
-shards2シャード数。1つのインデックスを複数に分割する場合。通常SolrでいうShardsと同じ意味。
-replicationFactor2レプリカ数。レプリケーションとしていくつコピーを作成しておくかの指定。通常Solrではreplication用Solrの数と同じ意味。
-p8984操作対象とするSolrインスタンス。今回であれば、8983, 8984, 8985, 8986のどれか

 

作成されたことを、Solr管理画面で確認してみましょう。

localhost8983

 

1.6 データの投入

Solr Cloudへのデータ投入は様々な方法があります。これについては本題からはずれるため、サンプルで用意されているデータファイルをコマンドを使って投入してみましょう。ここでも、複数台のうちどれかにデータを投げれば、Solr Cloud全台に反映されます。8984, 8986 に投げてみる例です。

検索できるかどうか、Solr管理画面で確認してみましょう。

solrsearch

できましたね。

今回は、ここまでといたします。残りの部分は、後日アップいたします。

 

antlr4を使う(1)

シーマークすずきです。

antlr4について少しメモをまとめておく。

  • antlr4とは

ANTLR (ANother Tool for Language Recognition)は強力なコンパイラコンパイラです。コンパイラとは、簡単に言えば、人が理解しやすい表現の記述から、目的に合わせてコンピュータが処理しやすい形式に変換することです。コンパイラは通常次のステップを行います。まず初めに、「元になるソースから構造を発見する」次に「目的に合わせて変換」します。後者の目的部分は、各コンパイラプログラムによって様々ですが、初めのステップは、一般的には更に次の処理に分割されます。

  1. ソースからトークン(token)に分割・分解し、トークンを発掘する(字句解析)
  2. 複数のトークンを構文に従って木構造に構造化(組み上げ)する(構文解析)

コンパイラコンパイラは、上記の字句解析と構文解析するコンパイラプログラム(別名パーサーと呼ばれています)を作成するためのコンパイラです。ANTLRを使い、構文定義(grammarファイル)を記述し、その定義に従って、つまり、grammarファイルをソースにして字句解析し構文解析するためのプログラムソースを生成してくれます。開発者は目的に応じて木構造を捜索したり、変換したりすれば良いのです。ANTLRをパーサージェネレーターと呼ぶ場合もあるようです。

  • 何ができる?

たとえば、JavaやC++のソースをパースするプログラムを作成することも可能です。他にも、日本語で「式」を定義し、それを計算するというプログラムも作成できます。”(上底 + 下底) * 高さ / 2.0”などの式の文字列をパースして、意味のある単位の「変数」や「演算子」や「括弧」のトークンに分割し、定義した優先順位でトークンを組上げて、それを順番に変数を数値変換して計算を行うための計算機プログラムを作成することもできます。

  • 使ってみる

パーサーは文法が定義できればほぼ何でも作成できます。HTML、JSONなどのデータファイルをパースするプログラムも面白いですが、ここでは、簡単なcsvのパーサーを作成してみましょう。まず初めに文法定義をします。以下に簡単なcsvのantlr4の文法定義です。CSV.gとファイル名を付けます。

CSVファイルの構造の定義をしています。CSVファイルは、ヘッダー(hdr)と複数の行(row)でできている、としています。複数個(0個以上)の行である表現は’+’が付いているからです。次に、ヘッダー(hdr)は1行(row)で構成されると定義しています。行(row)の定義です。行はフィールド(fieldがカンマ(,)で区切られて複数あること、行の終端は’\r”\n’或は’\n’である、と定義しています。’*’は0回以上の繰り返しです。定義はまだ、続きます。フィールド(field)はTEXTかSTRINGのいずれかで構成されると宣言しています。TEXTは、カンマ(,)か(’\n’)か(‘\r’)の何れでもない文字の0個以上の繰り返しです、としています。つまりTEXTは数値や英数文字も全てです。STRINGは、ダブルクオートで囲われていて、連続する2つのダブルクオートか1つのダブルクオート以外の全ての文字です。

文法定義ファイルをantlr4コマンドに以下のようにわたします。

> antlr4 CSV.g

> ls

CSV.g CSV.tokens CSVBaseListener.java
CSVLexer.java CSVLexer.tokens CSVListener.java CSVParser.java

生成されたJavaソースをjavacでコンパイルします。早速、antlr4の検証ツールgrunで試してみましょう。

> grun CSV file -tree

user.name, user.age, *omit*user.gender
suzuki,50,male
ange,12,female
seamark,20,male

Control+Dで、以下がコンソールに出力されます。

(file (hdr (row (field user.name) , (field user.age) , (field *omit*user.gender) \n)) (row (field suzuki) , (field 50) , (field male) \n) (row (field ange) , (field 12) , (field female) \n) (row (field seamark) , (field 20) , (field male) \n))

階層をGUI表示させたい場合は、-treeを-guiとします。

> grun CSV file -gui

user.name, user.age, *omit*user.gender
suzuki,50,male
ange,12,female
seamark,20,male

Control+Dで以下の画面表示がされます。

antlr4_parse_tree_1

  • 今後

次回は、grunを使わずに、javaプログラムからantlr4で生成したソースを呼び出して利用してみます。

 

csvからjsonへ変換する(メモ1)

シーマークすずきです。

開発プロジェクトでいつも取り組むことのひとつが、プログラムのテストのために、テストデータを用意することです。ここでは、先日使ってみたcsvtojsonというライブラリがよく出来ていたので、簡単に紹介します。

■テストデータの元ネタはCSVで作っとく

テストデータをプログラマーが書くことは良くありますよね。入力データと結果期待値データ。プログラムに喰わせる訳ですから、プログラムが読み易いフォーマットが良いのですが、お客さんにレビューしてもらう都合上、お客さんには表計算ソフトの書式で出したいことが良くあります。そこでよく利用されるのはcsv書式です。

■csvtojsonが便利

先日も参加している開発チーム内で、テストプログラムを開発している担当者側はJSONで欲しい。テスト仕様書作成担当者は、表計算ソフトで作成中です、っと。つまり、どっかで誰かが、csvからjsonに変換する必要が発生しました。csvは構造を持たないフラット構造ですが、jsonは構造を持つための階層構造。ネットで少し調べていたら、 node.jsのライブラリでcsvtojsonがよく出来ていそうだったので、使ってみました。

参考URL: https://github.com/Keyang/node-csvtojson

■インストール

上記URLからgit cloneして利用できます。または、 npmでインストール可能です。

>npm install -g csvtojson

■動かしてみる

csvファイルのサンプルは以下のsample.csvです。

$ cat sample.csv
user.name, user.age, user.gender
suzuki,50,male
ange,12,female
seamark,20,male

変換してみましょう。

$ bin/csvtojson < sample.csv
[
{“user”:{“name”:”suzuki”,”age”:50,”gender”:”male”}}
,
{“user”:{“name”:”ange”,”age”:12,”gender”:”female”}}
,
{“user”:{“name”:”seamark”,”age”:20,”gender”:”male”}}

]

とっても簡単です。出力したくない列には、*omit* を指定すれば、jsonに変換しません。

$ cat sample.csv
user.name, user.age, *omit*user.gender
suzuki,50,male
ange,12,female
seamark,20,male

*omit*指定したuser.gender列は以下の通り変換されません。

$ bin/csvtojson < sample.csv
[
{“user”:{“name”:”suzuki”,”age”:50}}
,
{“user”:{“name”:”ange”,”age”:12}}
,
{“user”:{“name”:”seamark”,”age”:20}}

]

これのおかげで、テスト仕様書をレビュー後、直ぐにjson変換ができました。