MapR-DBアクセス時の性能を向上させるOJAI Distributed Query Serviceについて

MapR 6.0から[OJAI Distributed Query Service](https://mapr.com/docs/home/MapR-DB/ArchitectureOJAIQueryService.html)(以下ODQS)という機能が使えるようになりました。
本記事ではODQSの概要について説明し、実際にどうやって使うのかについて簡単に紹介します。
ちなみにMapR 6.0.0ではOJAI Query Serviceという名前でしたが、6.0.1からはOJAI Distributed Query Serviceに変更されました。
分散実行できることを強調しているようですね。

# 概要

ODQSは[Java OJAI Client API](https://mapr.com/docs/apidocs/60/OJAI/)を使ってMapR-DB JSON(JSONテーブル)にアクセスする際に、Drillの機能を使って処理の一部をDrillを使って実行し、性能を向上させる仕組みとなります。

詳細に入る前に下図を使ってJSONテーブルへのアクセス方法の概観をおさらいしましょう。

![JSON_DBComponentFlow61.png](https://qiita-image-store.s3.amazonaws.com/0/172136/200e5b49-3eef-7ebf-efbb-37229ae9c96f.png)

図の一番右の紫のパスがOJAI APIを使ったアプリケーションを通じてJSONテーブルに直接アクセスするパスとなります。
もしODQSがインストールされていない環境でOJAI APIを使う場合は、常にこちらのパスが利用されます。
このパスでは単一のインデックスのみ利用可能であり、並列実行は出来ません。
この場合、結果のサイズが非常に大きく、設定した上限値を超えるような場合、実行は失敗に終わる場合があります。

図の真ん中のオレンジのパスがOJAI APIを使ったアプリケーションが一部の処理をDrillの機能を使って処理する、ODQSのパスとなります。
ODQSを利用することで大きいサイズのデータのソートや並列実行が可能となります。
ODQSがインストールされている環境においては、MapR ClientがODQSを利用することで性能向上が見込めるかどうかを自動的に判断します。
ODQSはOJAIを利用する場合に典型的なクエリに適した、Drillクエリエンジンの軽量なサブセットとなります。
ODQSを利用することで単一のクエリの中での複数のインデックスによる、より効率的なインデックスの選択や、コストベースのオプティマイザによる最適なパフォーマンスを提供するインデックスの選択が可能となります。


図の一番左の黄色のパスがBI/SQLツールからDrillからJSONテーブルにアクセスするパスとなります。
このパスでは、大規模なデータセットに対する複雑なクエリを、Drillのフル機能を使って最適化して処理することが出来るため、インタラクティブなレスポンス時間で処理が可能となります。
Drillが利用するオプティマイザはODQSが利用するオプティマイザの上位セットになります。
ODQS同様コストベースのオプティマイザになりますが、より複雑な分析クエリに必要となる包括的な最適化を提供します。


それぞれのパスの特徴についてまとめるとこうなります。

| パス       |       特徴 |
|:-----------------|:------------------|
| Drillクエリ(黄色)             |       * 複数のセカンダリインデックスを利用可能 <br/> * ソートサイズに上限なし <br/> * 並列にクエリ実行が可能 <br/> * クエリ最適化機能  |
| OJAIクエリ(紫色)           |    * 単一のセカンダリインデックスのみ利用可能 <br/> * ソートのサイズは設定によって上限が決まる <br/> * 逐次実行のみ可能 |
| ODQSを利用するOJAIクエリ(オレンジ色)  |  * 複数のセカンダリインデックスを利用可能 <br/> * ソートサイズに上限なし <br/> * 並列実行可能 <br/> * クエリ最適化    |

要はOJAI APIを使ってJSONテーブルにアクセスするアプリケーションを運用する際には、ODQSをインストールしておくと性能的にメリットがありそうだ、という話ですね。

# OJAI Query Distributed Serviceを使ってみる

ODQSを本番で利用する際には、ODQS用にDrillのクラスタとトポロジーを作るといったチューニングが推奨されます。
設定の詳細については[こちらのページ](https://mapr.com/docs/home/Drill/config-multiple-drill-clusters.html)に記載がありますので、本格的に利用する際には参照してください。
以下、本記事ではDrillがインストールされた手元のクラスタでとりあえず使ってみるレベルにとどめます。

## クラスタ側の準備

クラスタ側では以下の手順でセットアップが必要となります

### gatewayの準備

概要で記したようにODQSではSecondary Indexによる最適化が行われますのでSecondary Indexを利用できる環境であることが必要となります。
[こちらのページ](https://qiita.com/bigt23/items/731b0eb0634dc0e6451b#2-gateway%E3%81%AE%E6%BA%96%E5%82%99)を参考にgatewayが利用可能な環境を構築してください。
設定が終わったらgatewayの確認をしておきましょう

```bash
# maprcli cluster gateway list
cluster  gatewayConfig
sample-cluster   node0 node1
```

### ODQSのインストール

ODQSのインストールの際には、以下の2点を実施してください。

まずは各ノードで以下のコマンドを実行してください。

```bash
# /opt/mapr/server/configure.sh -R -QS
```

次に任意のノードで以下のコマンドを実行してください。
ただし`-clusterid`を設定している場合はそちらを指定してください(`<クラスタ名>-drillbits`はデフォルト値となります)

```bash
# maprcli cluster queryservice setconfig -enabled true -clusterid <クラスタ名>-drillbits -storageplugin dfs -znode /drill
```

## アプリケーションの実行

ODQSの挙動の確認に際して、ここでは[MapRのエンジニアが作成したデモアプリケーション](https://github.com/mapr-demos/ojai-2-examples)を利用してみます。

```bash
$ maprcli table create -path /demo_table -tabletype json
$ maprcli table cf edit -path /demo_table -cfname default -readperm p -writeperm p -traverseperm  p
$ git clone git://github.com/mapr-demos/ojai-2-examples
$ cd ojai-2-examples
$ mvn clean packages
$ cd targets
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_001_GetConnectionCreateDocument 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_002_GetStoreAndInsertDocuments 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_003_FindById 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_004_FindAll
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_005_FindAllQuery 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_006_FindQueryWithSelect 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_007_FindQueryWithCondition
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_008_FindQueryWithConditionJson 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_009_FindQueryWithSelectAndCondition 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_010_FindQueryWithOrderBy
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_011_FindQueryWithOrderByLimitOffset 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_012_UpdateDocument 
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_013_ReadYourOwnWrite
```

READMEに書いてあるように実行した後、drillbit.logを確認してみましたがdrillbitが動いている気配がありません。

```bash
# clush -a find /opt/mapr/drill/drill-1.13.0/logs -mmin -3 -name drillbit.log
#
```

次にSecondary Indexを作成してみましょう。
以下のように`maprcli`コマンドからfirstName, lastNameフィールドを対象としてインデックスし、address, age, phoneNumbersを含む形で作成します。
確認は`mapr dbshell`コマンドの`indexscan`コマンドで行います。

```bash
$ maprcli table index add -path /demo_table -index demo_table_idx -indexedfields firstName,lastName -includedfields address,age,phoneNumbers

$ mapr dbshell
====================================================
*                  MapR-DB Shell                   *
* NOTE: This is a shell for JSON table operations. *
====================================================
Version: 6.0.1-mapr

MapR-DB Shell
maprdb mapr:> indexscan --indexname demo_table_idx --t /demo_table
{"_id":"user0001","address":{"city":"San Jose","state":"CA","street":"320 Blossom Hill Road","zipCode":{"$numberLong":95196}},"age":{"$numberLong":26},"phoneNumbers":[{"areaCode":{"$numberLong":555},"number":{"$numberLong":5553827}},{"areaCode":"555","number":"555-6289"}],"firstName":"Jane","lastName":"Dupont"}
{"_id":"user0004","address":{"zipCode":{"$numberLong":95110}},"age":56,"firstName":"Joel","lastName":"Smith"}
{"_id":"user0000","address":{"city":"San Jose","state":"CA","street":"350 Hoger Way","zipCode":{"$numberLong":95110}},"age":{"$numberLong":35},"phoneNumbers":[{"areaCode":{"$numberLong":555},"number":{"$numberLong":5555555}},{"areaCode":"555","number":"555-5556"}],"firstName":"John","lastName":"Doe"}
{"_id":"user0002","address":{"city":"San Jose","state":"CA","street":"38 De Mattei Court","zipCode":{"$numberLong":95196}},"age":{"$numberLong":45},"phoneNumbers":[{"areaCode":{"$numberLong":555},"number":{"$numberLong":5425639}},{"areaCode":"555","number":"542-5656"}],"firstName":"Simon","lastName":"Davis"}
4 document(s) found.
```

再度実行してみます

```bash
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_001_GetConnectionCreateDocument 
...
略
...
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_013_ReadYourOwnWrite
```

再度確認

```bash
# clush -a find /opt/mapr/drill/drill-1.13.0/logs -mmin -3 -name drillbit.log
#
```

動いていないようですね。
では梱包されている`Dataset.java`を使ってデータを増やしてみます。

```bash
maprdb mapr:> find /demo_table
{"_id":"user0000","address":{"city":"San Jose","state":"CA","street":"350 Hoger Way","zipCode":{"$numberLong":95134}},"age":{"$numberLong":35},"firstName":"John","lastName":"Doe","phoneNumbers":[{"areaCode":{"$numberLong":555},"number":{"$numberLong":5555555}},{"areaCode":"555","number":"555-5556"}]}
...
略
...
{"_id":"user2000","address":{"city":"San Jose","state":"CA","street":"38 De Mattei Court 2000","zipCode":{"$numberLong":92000}},"age":{"$numberLong":45},"firstName":"S2000","lastName":"D2000","phoneNumbers":[{"areaCode":{"$numberLong":555},"number":{"$numberLong":5422000}},{"areaCode":"555","number":"542-2000"}]}
2000 document(s) found
```

データを3件から2000件に増やしてみました。
再度実行してみます

```bash
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_001_GetConnectionCreateDocument 
...
略
...
$ java -cp ojai-2-samples-1.0-SNAPSHOT.jar:`mapr clientclasspath` com.mapr.ojai.examples.OJAI_013_ReadYourOwnWrite
```

再度確認

```bash
# clush -a find /opt/mapr/drill/drill-1.13.0/logs -mmin -3 -name drillbit.log
node1: /opt/mapr/drill/drill-1.13.0/logs/drillbit.log
```

更新がありました。
具体的にはこのような表示がありました

```
2018-09-25 00:15:11,742 [24570080-5ea2-ae11-9368-54f7e2fb1680:foreman] INFO  o.a.drill.exec.work.foreman.Foreman - Query text for query id 24570080-5ea2-ae11-9368-54f7e2fb1680: select t.`$$ENC00MZUXE43UJZQW2ZIANRQXG5COMFWWKAC7NFSAAYLHMU`,t.`$$document` from dfs.`/demo_table` t order by t.`lastName` ASC
2018-09-25 00:15:11,815 [24570080-5ea2-ae11-9368-54f7e2fb1680:foreman] INFO  o.a.d.exec.store.dfs.FileSelection - FileSelection.getStatuses() took 0 ms, numFiles: 1
2018-09-25 00:15:11,815 [24570080-5ea2-ae11-9368-54f7e2fb1680:foreman] INFO  o.a.d.exec.store.dfs.FileSelection - FileSelection.getStatuses() took 0 ms, numFiles: 1
2018-09-25 00:15:11,815 [24570080-5ea2-ae11-9368-54f7e2fb1680:foreman] INFO  o.a.d.exec.store.dfs.FileSelection - FileSelection.getStatuses() took 0 ms, numFiles: 1
2018-09-25 00:15:11,816 [24570080-5ea2-ae11-9368-54f7e2fb1680:foreman] INFO  o.a.d.exec.store.dfs.FileSelection - FileSelection.getStatuses() took 0 ms, numFiles: 1
2018-09-25 00:15:11,816 [24570080-5ea2-ae11-9368-54f7e2fb1680:foreman] INFO  o.a.d.exec.store.dfs.FileSelection - FileSelection.getStatuses() took 0 ms, numFiles: 1
2018-09-25 00:15:11,816 [24570080-5ea2-ae11-9368-54f7e2fb1680:foreman] INFO  o.a.d.exec.store.dfs.FileSelection - FileSelection.getStatuses() took 0 ms, numFiles: 1
2018-09-25 00:15:11,816 [24570080-5ea2-ae11-9368-54f7e2fb1680:foreman] INFO  o.a.d.exec.store.dfs.FileSelection - FileSelection.getStatuses() took 0 ms, numFiles: 1
2018-09-25 00:15:12,888 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] INFO  o.a.d.e.w.fragment.FragmentExecutor - 24570080-5ea2-ae11-9368-54f7e2fb1680:0:0: State change requested AWAITING_ALLOCATION --> RUNNING
2018-09-25 00:15:12,889 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] INFO  o.a.d.e.w.f.FragmentStatusReporter - 24570080-5ea2-ae11-9368-54f7e2fb1680:0:0: State to report: RUNNING
2018-09-25 00:15:13,229 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] WARN  o.a.d.e.e.ExpressionTreeMaterializer - Unable to find value vector of path `$$ENC00MZUXE43UJZQW2ZIANRQXG5COMFWWKAC7NFSAAYLHMU`, returning null instance.
2018-09-25 00:15:13,293 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] WARN  o.a.d.e.e.ExpressionTreeMaterializer - Unable to find value vector of path `$$ENC00MZUXE43UJZQW2ZIANRQXG5COMFWWKAC7NFSAAYLHMU`, returning null instance.
2018-09-25 00:15:13,326 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] INFO  o.a.d.e.w.fragment.FragmentExecutor - 24570080-5ea2-ae11-9368-54f7e2fb1680:0:0: State change requested RUNNING --> FINISHED
2018-09-25 00:15:13,326 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] INFO  o.a.d.e.w.f.FragmentStatusReporter - 24570080-5ea2-ae11-9368-54f7e2fb1680:0:0: State to report: FINISHED
2018-09-25 00:15:13,348 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] INFO  o.apache.drill.exec.work.WorkManager - Waiting for 0 queries to complete before shutting down
2018-09-25 00:15:13,348 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] INFO  o.apache.drill.exec.work.WorkManager - Waiting for 1 running fragments to complete before shutting down
2018-09-25 00:15:13,348 [24570080-5ea2-ae11-9368-54f7e2fb1680:frag:0:0] INFO  o.apache.drill.exec.work.WorkManager - New Fragments or queries are added while drillbit is Shutting down
2018-09-25 00:15:13,348 [drill-executor-8] INFO  o.apache.drill.exec.work.WorkManager - Waiting for 0 queries to complete before shutting down
2018-09-25 00:15:13,348 [drill-executor-8] INFO  o.apache.drill.exec.work.WorkManager - Waiting for 0 running fragments to complete before shutting down
```

単一のDrillbitが使われていました。
もっとデータが大きかったりセカンダリインデックスの数が多くなるとより多くのDrillbitを使うのかもしれません。
ちなみにDrillbitを使ったのは`OJAI_010_FindQueryWithOrderBy`でした。


# おわりに

本記事ではODQSによってとりあえずDrillbitが利用される挙動について確認しました。
記事中でも触れましたが、本番で利用する際にはODQS用にクラスタを分けてリソースを確保するなどすることでパフォーマンスを出すことが推奨されます。
こちらについては時間があれば別の記事で触れたいと思います。