2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MarkLogicAdvent Calendar 2017

Day 7

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

Last updated at Posted at 2017-12-06

はじめに

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{また、本文書に記載されている事項は予告なしに変更または削除されることがありますので、予めご了承ください。}$

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?