【MarkLogic Server】XQueryチューニングメモ~Element Range Index概説

More than 1 year has passed since last update.


はじめに

MarkLogicには、データをインポートした時点でUniversal Indexという索引が自動的に設定されます。これとは別に通常のデータベースと同じく追加的な索引を設定することができます。その最たるものがRange Indexです。Range Indexはパス・要素・属性などに対して設定できますが、今回は要素を対象にしたElement Range Indexを中心に説明していきます。


Range Indexの利点

Range Indexは利用される際にメモリに配置されます。このためRange Indexへの参照は通常のディスクアクセスと比べて極めて高速なメモリアクセスになります。


Element Range Indexの設定方法

ブラウザを開いてlocalhostの管理者画面(ポート8001番)にアクセスします。ここで左側のタブからConfigure→Databases→[追加したいデータベース]→Element Range Indexesを選択していきます。addタブを選択すると追加画面が現れました。試しに数値型で入れてみましょう。

パラメータ
説明
今回の設定値

scalar type
データ型の指定
decimal

localname
対象要素名
saleSum

range value positions
range value position indexの設定
false

invalid values
データ型に合わない値の挿入・更新の拒否
reject

saleSum要素に対してdecimal型のRange Indexが設定されました。

同じようにshopNoという要素にstring型のRange Indexを設定します。string型にするとCollationという項目が出てきますが、今回は私のクエリコンソールの設定に合わせてhttp://marklogic.com/collation/codepointとします。クエリコンソールの設定変更は管理者画面でGroups→Default→App Servers→App-Servicesより可能です。


Range Indexの効果

簡単な例で効果を見てみましょう。MarkLogicに以下のような売上額XMLファイルを10万個挿入しました。shopNoは1~200、jobNoは1~5、yearMonthは2017-01~2017-10、saleNoは1~10がいわゆる主キーです。

<root>

<shopNo>100</shopNo>
<jobNo>2</jobNo>
<yearMonth>2017-02</yearMonth>
<saleNo>10</saleNo>
<saleSum>7761</saleSum>
</root>

shopNoごとにsaleSumを合計するXQueryを書いてみましょう。ここでは検索・計算部分の性能が特に重要なので戻り値はmap変数からshopNo=50を返すのみとしました。

☆XQuery①

1ファイルずつ開いて集計していくという素朴なものです。

let $map := map:map()

let $_ :=
for $uri in cts:uris((), (), cts:directory-query("/root/", "infinity"))
let $doc := fn:doc($uri)/root
let $shopNo := $doc/shopNo/text()
let $saleSum := xs:decimal($doc/saleSum/text())
let $cur := map:get($map, $shopNo)
return
if($cur) then map:put($map, $shopNo, ($cur + $saleSum))
else map:put($map, $shopNo, $saleSum)
return map:get($map, "50")

☆XQuery②

こちらも1ファイルずつ見ていくものですが、今度はRange Index利用関数を使って、ループの手前で<URI, shopNo>と<URI, saleSum>の2項関係を取得しておきましょう。

let $map := map:map()

let $dirQuery := cts:diectory-query("/root/", "infinity")
let $uri_shopNo-map := cts:element-value-co-occurrences(
xs:QName("xdmp:document"), xs:QName("shopNo"), ("map"), $dirQuery
)
let $uri_saleSum-map := cts:element-value-co-occurrences(
xs:QName("xdmp:document"), xs:QName("saleSum"), ("map"), $dirQuery
)
let $_ :=
for $uri in cts:uris((), (), $dirQuery)
let $shopNo := map:get($uri_shopNo-map, $uri)
let $saleSum := map:get($uri_saleSum-map, $uri)
let $cur := map:get($map, $shopNo)
return
if($cur) then map:put($map, $shopNo, ($cur + $saleSum))
else map:put($map, $shopNo, $saleSum)
return map:get($map, "50")

☆XQuery③

shopNoを軸にループするように変更しましょう。これによりループ回数は10万回から200回になります。shopNoの一覧の取得はcts:element-valuesを使い、合計の計算はcts:sum-aggregateを使います。

let $map := map:map()

let $dirQuery := cts:diectory-query("/root/", "infinity")
let $shopNo-seq := cts:element-values(xs:QName("shopNo"), (), (), $dirQuery)
let $_ :=
for $shopNo in $shopNo-seq
let $total :=
cts:sum-aggregate(
cts:element-reference(xs:QName("saleSum")), (),
cts:and-query((
$dirQuery,
cts:element-value-query(xs:QName("shopNo"), $shopNo)
))
)
return
map:put($map, $shopNo, $total)
return map:get($map, "50")

クエリコンソールで実行してみた処理時間結果は以下のような感じになりました。

クエリ
処理時間[秒]

XQuery①
23

XQuery②
0.766

XQuery③
0.156

XQuery①とXQuery②はRange Indexの大きな効果を示します。またXQuery②とXQuery③はそこからさらに工夫できる余地があることを示しています。

今回は3種類の関数を紹介しましたが、他にもいくつかありますのでこちらもご参照ください。


他のRange Index

今回は要素のRange Indexを挙げましたが他にもありますので一覧で紹介します。

名称
対象

Element Range Index
要素

Path Range Index
パス

Attribute Range Index
属性

Field Range Index
フィールド


Range Indexの設定のしどころ

概ね以下のような要素が設定を検討する対象となりそうです。

・結合条件となるもの

・ソート条件となるもの

・大小比較対象となるもの

・GroupBy条件となるもの

・集計などの計算対象となるもの


Range Index設定の注意点

一方でRange Indexのデメリットも説明しておきましょう。

・メモリ消費量が大きくなる。

・ロード時間やデータサイズに影響を与える。

・途中から追加すると再索引(リインデックス)が発生して、その間性能が低下する。

・繰り返し要素のように1つのXMLに複数の同名要素が存在する場合使いづらい。

MarkLogic社のブログには多すぎるRange Indexの弊害が取り上げられていますが、100個程度を限度として提示しています。もちろんデータ内容にもよります。私の経験ではDBサイズ30GB程度のデータに65個設定したところ、32GBのメモリの空きがほぼなくなってしまうということもありました。

再索引も性能が低下するため要注意です。こればかりはディスク性能の高いSSDで開発を進めるぐらいしか解決策が見当たりません。


おわりに

これまでいくつか示してきたチューニング方法に比べてRange Indexの活用は劇的な効果を示します。ただし、設定するコストがあることも理解しておく必要があるようです。性能問題が起きたら、Range Index以外の方法も同時に考慮することをお勧めします。

\def\textsmall#1{%

{\rm\scriptsize #1}
}


免責事項

​​​​​$\textsmall{当ユーザ会は本文書及びその内容に関して、いかなる保証もするものではありません。}$

​​​​​$\textsmall{万一、本文書の内容に誤りがあった場合でも当ユーザ会は一切責任を負いかねます。}$

​​​​​$\textsmall{また、本文書に記載されている事項は予告なしに変更または削除されることがありますので、予めご了承ください。}$