JavaScript
XML
JSON
XQuery
MarkLogic
MarkLogicDay 12

【MarkLogic Server】JSONデータとJavaScript問い合わせ

はじめに

一般にXMLDBとされるMarkLogicですが、JSONの格納にも対応しています。またver7より従来までのXQueryに加えてJavaScriptによる問い合わせに対応するようになりました。今回は試しにJSONとJavaScriptを使って以前に行ったCSV出力をやってみたいと思います。

背景

XMLは仕様変更に伴うデータ設計の変更に柔軟に対応しますが、とにかくタグが付いて回りデータファイルとして冗長な部分があります。これに対してJavaScriptのオブジェクト形式をベースにするJSONは、名前空間などを排除しているものの終わりを示すタグが閉じ括弧だけであるなどXMLに比べてシンプルな構造を持つことから伝送などの点で有利です。
JavaScriptはXQueryよりも扱った経験のある人が多く、記述方法もsort()を使った平易なソート文やfor文内でのbreak文が使える点など優れた点があります。

検証クエリ

検索クエリは新たに以下のものを作りました。XQueryはPROCESSクエリだけ書いておきます。

☆XQuery① 以下$elementName-seqへの代入値は省略

xquery version "1.0-ml";
declare variable $URI xs:string external;
let $elementName-seq := ("key01", "key02", ..., "key50")
let $doc := cts:search(/*, cts:document-query($URI))
let $text-seq := 
  for $i in $elementName-seq
    return $doc/*[fn:name() = $i]/text()
return fn:string-join($text-seq, ",")

☆XQuery②(JSON検索用)
ポイントはcts:searchの引数が変わったところと、/text()が無くなったところです。

xquery version "1.0-ml";
declare variable $URI xs:string external;
let $elementName-seq := ("key01", "key02", ..., "key50")
let $doc := cts:search(/, cts:document-query($URI))
let $text-seq := 
  for $i in $elementName-seq
    return $doc/*[fn:name() = $i]
return fn:string-join($text-seq, ",")

☆JavaScriptにおけるURIクエリ

var uris = cts.uris("", null, cts.directoryQuery(["/dataSize/"]));
fn.insertBefore(uris, 0, fn.count(uris))

☆JavaScript① 以下elementへの代入値は省略
XQueryのほぼ真似です。URIクエリからの外部変数はURIで渡されるようです。「.xpath」というのはここから取ってきました。どうもcts.searchの結果にパスを引く例文が見当たらずcts.docの方がそのままパスを引いたりできそうなので、この関数を使ってみました。

element = new Array("key01", "key02", ..., "key50");
doc = cts.doc(URI);
text = new Array(element.length);
for(i=0; i<element.length; i++){
  text[i] = doc.xpath("/" + element[i]);
};
fn.stringJoin(text, ",");

☆JavaScript②
「.xpath」を使わない場合はこんな感じでしょうか。JavaScriptのオブジェクトを使っている感があります。

element = new Array("key01", "key02", ..., "key50");
doc = cts.doc(URI)["root"];
text = new Array(element.length);
for(i=0; i<element.length; i++){
  text[i] = doc[element[i]];
};
fn.stringJoin(text, ",");

☆JavaScript③
文字列連結をjoin()で行ってみました。これでMarkLogicの関数は検索を行うcts.docだけになります。

element = new Array("key01", "key02", ..., "key50");
doc = cts.doc(URI)["root"];
text = new Array(element.length);
for(i=0; i<element.length; i++){
  text[i] = doc[element[i]];
};
text.join(",");

☆JavaScript④
["root"]を後に書いてみました。[○○]での参照回数が減ります。

element = new Array("key01", "key02", ..., "key50");
doc = cts.doc(URI);
text = new Array(element.length);
for(i=0; i<element.length; i++){
  text[i] = doc["root"][element[i]];
};
fn.stringJoin(text, ",");

実機検証環境・検証条件

実際にサンプルデータと、URI/PROCESSクエリを入れてCoRBを実行してみましょう。

まず、実施環境は以下の通りです。記事執筆の段階でver7はEOL(End of life)となっていましたがver8以降でも引き続きJSONに対応しています。

対象MarkLogicバージョン OS CPU メモリ
8.0-3.3 Windows7
Professional
Intel(R)Core(TM)i7-3740QM CPU2.70GHz 2.70GHz
(4core8スレッド)
16GB

サンプルデータは、1XML/JSONに必ず50要素です。key01~key50まで順番に並んでいます。各データは、それぞれ異なる20文字の要素値を持ちます。つまり空要素や要素の抜け落ちはありません。これを100万ファイル分作成しました。XMLは以前の検証と同じですが、新たに作ったJSONはこんなイメージです。

{
    "key01": "Tjr5v8Ye_000000000001",
    "key02": "0ZCIjYAg_000000000001",
    "key03": "EdnTwU9F_000000000001",
    ・・・ (47要素) ・・・
}

100万ファイルは、MarkLogic内の/dataSize/ディレクトリに全て格納します。これをCSV出力するCoRB実行ファイル、URIクエリとPROCESSクエリは以下のような感じです。

java -cp .;"MarkXCC.Java-8.0-3\lib\marklogic-xcc-8.0.3.jar";"MarkXCC.Java-8.0-3\lib\marklogic-corb-2.1.3.jar" ^ 
-DMODULE-DATABASE="corbTestModule" ^ 
-DMODULE-ROOT="/" ^ 
-DXCC-CONNECTION-URI="xcc://admin:admin@localhost:8062/" ^ 
-DXQUERY-MODULE="/js/Default_PROCESS.sjs" ^ 
-DURIS-MODULE="/js/Default_URIS.sjs" ^ 
-DTHREAD-COUNT=16 ^ 
-DINSTALL="false" ^ 
-DPROCESS-TASK="com.marklogic.developer.corb.ExportBatchtoFileTask" ^ 
-DEXPORT-FILE-NAME="C:\work\test.csv" ^ 
com.marklogic.developer.corb.Manager

検証結果

検索クエリごとに結果を出してみます。XQuery①は以前の検証のうちこれと最も近い条件で行ったものです。データ形式のパターンはXMLとJSONの2パターンです。

☆データサイズの比較

データ形式 データサイズ[GB]
XML 12.83
JSON 12.68

☆CSV出力時間の比較

検索クエリ データ形式 CSV出力時間[秒]
XQuery① XML 726
XQuery② JSON 645
JavaScript① JSON 2,798
JavaScript② JSON 2,196
JavaScript③ JSON 2,247
JavaScript④ JSON 1,598

考察

データサイズについてファイル形式による差はあまりないようです。これはXMLにしろJSONにしろハッシュ化されて同じような構造の圧縮索引にされているためと考えられます。

XQuery間はデータ形式による差が少しあります。これはJSONがルート要素を持たない、/text()パスが不必要などの点で処理が簡素化されたためと思われます。

JavaScriptは少し物足りない結果になりました。関数の使い方や、必要な要素だけパスを引く方法に問題があったかもしれません。あるいはJavaScriptが内部的にはXQueryに変換されてから実行されている可能性もあります。

おわりに

一般的にJSONはXMLと比較して軽量であることを売りにしていますが、この結果を見る限りMarkLogicにおいてはデータサイズの観点だけでJSONにするのではなく、受け取るデータファイルの形式や、検索結果の伝送・パース方法などを総合的にみた方が良さそうです。

JavaScriptの処理時間はXQueryよりは悪かったものの、最初に述べたようにJavaScriptはプログラミングのしやすさという点で優位ですし、今回の実行例がJSON-JavaScriptらしさを生かせないものだったとも考えられるので、その活用をためらうことも無いと思います。

\def\textsmall#1{%
  {\rm\scriptsize #1}
}

免責事項

​​​​​$\textsmall{当ユーザ会は本文書及びその内容に関して、いかなる保証もするものではありません。}$
​​​​​$\textsmall{万一、本文書の内容に誤りがあった場合でも当ユーザ会は一切責任を負いかねます。}$
​​​​​$\textsmall{また、本文書に記載されている事項は予告なしに変更または削除されることがありますので、予めご了承ください。}$