Elasticsearchのmappingフォーマットは見づらい
例えば次のようなJSONドキュメントがある場合
{
"person": {
"name": "BIG BOSS",
"sons": [{
"name": "Solid Snake",
"age": 32
}, {
"name": "Liquid Snake",
"age": 32
}]
}
}
そのmappingはこんな感じになります
{
"person": {
"properties": {
"name": {
"type": "string"
},
"sons": {
"type": "nested",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "integer"
}
}
}
}
}
}
はい、非常にわかりづらいですね。
なぜか。propetiesのせいです。
ElasticsearchはJSONフォーマットでmappingを表現してるので、そのフィールドの属性(typeなど)と子要素(propeties)は両者ともオブジェクトの中に一つネストして格納するしかありません。そのためJSONドキュメントより階層が1つ深くなってしまい、mappingとJSONドキュメントの対応が直感的にわかりにくくなります。mappingの方がpropertiesのために階層が2倍深くなってしまうんですね(ついでに閉じカッコも必要となるので全体の行数も増えます)。
繰り返しになりますが、フィールドの属性と子要素がごっちゃになってしまうJSONの表現能力の限界が問題なのです。
ではJSONフォーマット以外でそのような用途に適したフォーマットはあるか。ありますよね、XMLという最適なフォーマットが。
MappingをXMLで表現してみる
試しに上の例のmappingと等価な情報をXMLで表現したのが以下になります。
<person>
<name type="string" />
<sons type="nested">
<name type="string" />
<age type="integer" />
</sons>
</person>
以下の点が劇的に改善しました。
- ネストの減少
- JSONフォーマットだと最大6回のインデントがされてましたがXML表記ですと最大2回
- 全体行数の減少
- JSONフォーマットだと20行だったものがXMLでは7行
- propetiesという不要な定数文字列の削除
- 親子構造をデータフォーマットそのもので表すことができるようになったため、propetiesという定数文字列がなくせた
残った問題点
現状いくつか問題点がわかってます。
1. ネストされた属性はXMLで表現できない
XMLで属性へと変換されるproperties以外のプロパティの中にもしネストされてるものがあった場合、それはXMLでの属性として表現できないことです。
{
"son": {
"properties": {
"name": {
"type": "string",
"norms": {
"enabled": false
}
}
}
}
}
この例でのnormsのように、属性の持つ情報がネストされてるとそれをそのままXMLで属性化することはできません。
Elasticsearchのmapping仕様上、このような属性は決まっており、自分の確認した限り2014/09/21のバージョン1.3.2時点ではネストする属性は以下になります。
- norms
- fielddata
- copy_to
- multiple fields
解決案
1. 上の4つは例外ケースとして無視する(それらを使う場合はXMLフォーマットで書かない)
一時的に使う分にはそれでいいかもしれませんが、mappingの表現の標準としてXMLを使いたい場合は現実的ではありません。
2. ネストされた属性は属性名のみエスケープして構造はそのまま維持する
ネストされてる場合は属性として表現するのが困難なのでXMLでもタグによる木構造として表現します。
ただし、他のタグと名前が衝突しないように属性名の頭へアンダースコアをつけます。
Elasticsearchの仕様上アンダースコア始まりのフィールド名は予約されており、それらのどれかとネストされた属性の名前が衝突しない限りは安全です。
{
"son": {
"properties": {
"name": {
"type": "string",
"norms": {
"enabled": false
}
}
}
}
}
<son>
<name type="string">
<_norms enabled="false" />
</name>
</son>
2. XMLの属性は文字列型以外持てない
XMLではデータの型というものが存在せず属性もすべて文字列であるため、JSONフォーマットでの文字列・真偽型・数値型の型情報が削ぎ落とされます。
{
"son": {
"properties": {
"name": {
"type": "string",
"store": true
}
}
}
}
ここでのstoreのような値が文字列以外であるような属性はXMLで表現するのは難しいです。
解決案
1. 型情報のprefixをつける
<son>
<name
type="string"
store="boolean-true"
/>
</son>
非常に単純ですが問題も多いです。
- ドキュメントがわかりにくくなる
- prefixのルールがどのようなものかは決めの問題 = 覚えにくく忘れやすい
- たまたまそのprefixのルールと合致するものが文字列として書かれる可能性がある
- それを回避するためにエスケープすることもできるが、XML表記での仕様の複雑性を高める
2. すべて文字列として単純変換し、その属性の型は属性名から推測
Elasticsearchの仕様上、属性の数と種類は限定されます。また属性の型は属性によって一意です。
よってXMLとして表記するときは単純にそのまま文字列へと変換し、XMLからJSONへと変換するときには用意した[属性名-型]の辞書を参照して適切な型へと変換します。
<son>
<name
type="string"
store="true"
/>
</son>
問題点
- 属性の種類を変換スクリプトで保持する必要がある = Elasticsearchが新しい属性を追加するたび、スクリプトへも追加してやる必要がある
まとめ
本当はJSON⇔XMLの変換スクリプトを書こうとしたのですが、上記の複数の問題がわかったので現状では思考実験にとどまっています。
今の感触としては解決案の2と2をそれぞれ使うことで変換スクリプトは書けそうなのですが、まだ落とし穴がありそう。
ただ、仕事上で数百行のmappingを扱うにあたりあの冗長なmappingのフォーマットはなんとかしないといけないと思ってます。
なんらかアクションが取りたいなあ・・・。