MapR-DB JSONテーブルのSecondary Indexを使ってみる

MapR-DB JSONテーブルのSecondary Indexについて、@mitagaki さんが[書いてくれました](https://qiita.com/mitagaki/items/b30a0f0aa7d6b2565c6c)。本記事ではSecondary Indexの使い方について簡単に触れていきます。
今回は既にクラスタが構築されている状況を前提とし、作業としては以下のようになります。順番に見ていきましょう。

1. [ライセンスのチェック](https://qiita.com/bigt23/items/731b0eb0634dc0e6451b#1-%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9%E3%81%AE%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF)
2. [gatewayの準備](https://qiita.com/bigt23/items/731b0eb0634dc0e6451b#2-gateway%E3%81%AE%E6%BA%96%E5%82%99)
3. [テーブル作成](https://qiita.com/bigt23/items/731b0eb0634dc0e6451b#3-%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E4%BD%9C%E6%88%90)
4. [index作成](https://qiita.com/bigt23/items/731b0eb0634dc0e6451b#4%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E4%BD%9C%E6%88%90)
5. [クエリ実行](https://qiita.com/bigt23/items/731b0eb0634dc0e6451b#5%E3%82%AF%E3%82%A8%E3%83%AA%E5%AE%9F%E8%A1%8C)

#1. ライセンスのチェック

Secondary Indexはテーブルのレプリケーション機能を利用して対象のカラムをキーとするテーブルを作成します。
MapRではテーブルのレプリケーション機能を利用する上では商用もしくはテスト用のライセンスが必要となりますので、こちらを適用する必要があります。
テスト用のライセンス(30日間有効)は[こちら](https://mapr.com/user/)から作成可能となります。
作成後は[MCSやCLIからライセンスを適用](https://maprdocs.mapr.com/60/ClusterAdministration/admin/cluster/AddLicense.html)してください。


#2. gatewayの準備

gatewayとはMapR-DBやMapR-ESのレプリケーションを利用するための機能となり、これを利用してSecondary Index用のテーブルを作成します。
gatewayについては別のエントリで詳細を紹介しますので、今回は簡単にセットアップします。
クラスタのいずれかのノード(今回は**node1**)にて以下を実施します。

```shell
[node1]$ sudo yum install mapr-gateway
[node1]$ sudo /opt/mapr/server/configure.sh -R
[node1]$ maprcli node services -name gateway -action restart -nodes node1
```

これでgatewayプロセスが稼働しました。
次にテーブルのレプリケーションの設定ですが、**Secondary Index用のテーブルは同一のクラスタに対して作成**しますので、以下のようにgatewayの設定を行います。
ちなみに'map60'がクラスタ名となります。

```shell
[node1]$ maprcli cluster gateway set -dstcluster mapr60 -gateways node1
```

#3. テーブル作成

JSONテーブルを作成してデータを突っ込んでおきます。
データは[こちらのページ](https://matome.naver.jp/odai/2141429881363927801)を参考にいたしました。ありがとうございます！！！

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

MapR-DB Shell
maprdb mapr:> create /user/mapr/db/test
Table /user/mapr/db/test created.

$ hadoop fs -cat /tmp/test.json
{"_id":"2000","USERNAME":"信"    ,"total_score":242, "PROP": {"power": 90, "lead":80, "intelligence":72}  }
{"_id":"2001","USERNAME":"嬴政"  ,"total_score":264, "PROP": {"power": 78, "lead":98, "intelligence":88}  }
{"_id":"2002","USERNAME":"漂"    ,"total_score":233, "PROP": {"power": 78, "lead":75, "intelligence":80}  }
{"_id":"2003","USERNAME":"昌文君","total_score":255, "PROP": {"power": 80, "lead":85, "intelligence":90}  }
{"_id":"2004","USERNAME":"河了貂","total_score":233, "PROP": {"power": 60, "lead":85, "intelligence":88}  }
{"_id":"2005","USERNAME":"壁"    ,"total_score":250, "PROP": {"power": 80, "lead":84, "intelligence":86}  }
{"_id":"2006","USERNAME":"羌瘣"  ,"total_score":262, "PROP": {"power": 95, "lead":80, "intelligence":87}  }
{"_id":"2007","USERNAME":"成蟜"  ,"total_score":199, "PROP": {"power": 55, "lead":68, "intelligence":76}  }
{"_id":"2008","USERNAME":"竭氏"  ,"total_score":195, "PROP": {"power": 50, "lead":65, "intelligence":80}  }
{"_id":"2009","USERNAME":"左慈"  ,"total_score":210, "PROP": {"power": 87, "lead":61, "intelligence":62}  }

$ mapr importJSON -src /tmp/test.json -dst /user/mapr/db/test
# 結果省略

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

MapR-DB Shell
maprdb mapr:> find /user/mapr/db/test
{"_id":"2000","PROP":{"intelligence":72,"lead":80,"power":90},"USERNAME":"信","total_score":242}
{"_id":"2001","PROP":{"intelligence":88,"lead":98,"power":78},"USERNAME":"嬴政","total_score":264}
{"_id":"2002","PROP":{"intelligence":80,"lead":75,"power":78},"USERNAME":"漂","total_score":233}
{"_id":"2003","PROP":{"intelligence":90,"lead":85,"power":80},"USERNAME":"昌文君","total_score":255}
{"_id":"2004","PROP":{"intelligence":88,"lead":85,"power":60},"USERNAME":"河了貂","total_score":233}
{"_id":"2005","PROP":{"intelligence":86,"lead":84,"power":80},"USERNAME":"壁","total_score":250}
{"_id":"2006","PROP":{"intelligence":87,"lead":80,"power":95},"USERNAME":"羌瘣","total_score":262}
{"_id":"2007","PROP":{"intelligence":76,"lead":68,"power":55},"USERNAME":"成蟜","total_score":199}
{"_id":"2008","PROP":{"intelligence":80,"lead":65,"power":50},"USERNAME":"竭氏","total_score":195}
{"_id":"2009","PROP":{"intelligence":62,"lead":61,"power":87},"USERNAME":"左慈","total_score":210}
10 document(s) found.

```

# 4.インデックス作成

今回はtotal_scoreカラムに対してインデックスを貼ります。
インデックス作成は以下のようにmaprcliコマンドを利用して行います。

```shell
$ maprcli table index add -path <テーブルへのパス> 
                          -index <インデックス名>
                          -indexedfields <インデックスするフィールド名>
                          [ -includedfields <インデックステーブルに含めるフィールド名> ]
                          [ -hashed [ハッシュインデックスを有効にするかどうか true | false> ]
                          [ -numhashpartitions <ハッシュパーティションの数>]
```

コマンドの詳細については[こちら](https://maprdocs.mapr.com/60/ReferenceGuide/table-index-add.html)を御覧ください。
ハッシュについては別の記事で紹介しますので、本件ではそれ以外のオプションを使っていきます。

今回は'total_score'フィールドに対してインデックスを貼り、'total_score'順にその他の項目を参照することを考えます。
この場合、indexの貼り方は以下のようになります。

```shell
$ maprcli table index add -path /user/mapr/db/test \
                          -index total_score_idx \
                          -indexedfields total_score \
                          -includedfields PROP,USERNAME
```

比較のためincludedfieldsを全く指定しない場合も作成してみます。
この場合はテーブルの主キー('_id')とインデックス用のフィールドのみのテーブルが作成されます。

```shell
$ maprcli table index add -path /user/mapr/db/test \
                          -index total_score_idx2 \
                          -indexedfields total_score
```

作成後はdbshellのindexscanコマンドを使ってインデックステーブルの中身を参照してみます。

下記のように右端にある'total_score'の順番に各項目が表示されています。
また、インデックス作成時のincludedfields次第で参照できる内容が異なることがわかります。

```shell
$ mapr dbshell

maprdb mapr:> indexscan --indexname total_score_idx --t /user/mapr/db/test
{"_id":"2008","USERNAME":"竭氏","PROP":{"intelligence":80,"lead":65,"power":50},"total_score":195}
{"_id":"2007","USERNAME":"成蟜","PROP":{"intelligence":76,"lead":68,"power":55},"total_score":199}
{"_id":"2009","USERNAME":"左慈","PROP":{"intelligence":62,"lead":61,"power":87},"total_score":210}
{"_id":"2002","USERNAME":"漂","PROP":{"intelligence":80,"lead":75,"power":78},"total_score":233}
{"_id":"2004","USERNAME":"河了貂","PROP":{"intelligence":88,"lead":85,"power":60},"total_score":233}
{"_id":"2000","USERNAME":"信","PROP":{"intelligence":72,"lead":80,"power":90},"total_score":242}
{"_id":"2005","USERNAME":"壁","PROP":{"intelligence":86,"lead":84,"power":80},"total_score":250}
{"_id":"2003","USERNAME":"昌文君","PROP":{"intelligence":90,"lead":85,"power":80},"total_score":255}
{"_id":"2006","USERNAME":"羌瘣","PROP":{"intelligence":87,"lead":80,"power":95},"total_score":262}
{"_id":"2001","USERNAME":"嬴政","PROP":{"intelligence":88,"lead":98,"power":78},"total_score":264}
10 document(s) found.

maprdb mapr:> indexscan --indexname total_score_idx2 --t /user/mapr/db/test
{"_id":"2008","total_score":195}
{"_id":"2007","total_score":199}
{"_id":"2009","total_score":210}
{"_id":"2002","total_score":233}
{"_id":"2004","total_score":233}
{"_id":"2000","total_score":242}
{"_id":"2005","total_score":250}
{"_id":"2003","total_score":255}
{"_id":"2006","total_score":262}
{"_id":"2001","total_score":264}
10 document(s) found.
```


# 5.クエリ実行

今回は性能の比較などは行いませんが、Secondary Indexが利用されているか確認しましょう

```shell
$ sqlline -u jdbc:drill:drillbit=node1 -n mapr -p xxxxxxxx
apache drill 1.13.0-mapr
"json ain't no thang"
0: jdbc:drill:drillbit=node1> select _id,PROP,USERNAME,total_score from dfs.`/user/mapr/db/test` where total_score > 250;
+-------+-------------------------------------------------+-----------+-------------+
|  _id  |                      PROP                       | USERNAME  | total_score  |
+-------+-------------------------------------------------+-----------+-------------+
| 2003  | {"intelligence":90.0,"lead":85.0,"power":80.0}  | 昌文君       | 255.0       |
| 2006  | {"intelligence":87.0,"lead":80.0,"power":95.0}  | 羌瘣        | 262.0       |
| 2001  | {"intelligence":88.0,"lead":98.0,"power":78.0}  | 嬴政        | 264.0       |
+-------+-------------------------------------------------+-----------+-------------+
```

これだけだと実際にSecondary Indexを使っているのか不明なのでEXPLAINを見てみましょう。

```shell
0: jdbc:drill:drillbit=node1> explain plan for select _id,PROP,USERNAME,total_score from dfs.`/user/mapr/db/test` where total_score > 250;
+------+------+
| text | json |
+------+------+
| 00-00    Screen
00-01      Project(_id=[$1], PROP=[$2], USERNAME=[$3], total_score=[$0])
00-02        Scan(table=[[dfs, /user/mapr/db/test]], groupscan=[JsonTableGroupScan [ScanSpec=JsonScanSpec [tableName=maprfs:///user/mapr/db/test, condition=(total_score >
 {"$numberLong":250}), indexName=total_score_idx], columns=[`total_score`, `_id`, `PROP`, `USERNAME`], maxwidth=1]])
以下省略
```

上述のように、Scanの中でtotal_score_idxテーブルを参照していることがわかります。

それではtotal_score_idxを削除して同様のクエリを実行するとどうなるでしょうか？


```shell-session

$ maprcli table index remove -path /user/mapr/db/test -index total_score_idx

0: jdbc:drill:drillbit=node1> explain plan for select _id,PROP,USERNAME,total_score from dfs.`/user/mapr/db/test` where total_score > 250;
+------+------+
| text | json |
+------+------+
| 00-00    Screen
00-01      Project(_id=[$1], PROP=[$2], USERNAME=[$3], total_score=[$0])
00-02        Scan(table=[[dfs, /user/mapr/db/test]], groupscan=[JsonTableGroupScan [ScanSpec=JsonScanSpec [tableName=maprfs:///user/mapr/db/test, condition=(total_score > {"$numberLong":250})], columns=[`total_score`, `_id`, `PROP`, `US
ERNAME`], maxwidth=1]])
 | {
以下省略


0: jdbc:drill:drillbit=node1> explain plan for select _id,total_score from dfs.`/user/mapr/db/test` where total_score > 250;
+------+------+
| text | json |
+------+------+
| 00-00    Screen
00-01      Project(_id=[$1], total_score=[$0])
00-02        Scan(table=[[dfs, /user/mapr/db/test]], groupscan=[JsonTableGroupScan [ScanSpec=JsonScanSpec [tableName=maprfs:///user/mapr/db/test, condition=(total_score > {"$numberLong":250}), indexName=total_score_idx2], columns=[`total_score`, `_i
d`], maxwidth=1]])
以下省略

```

上記のように、'PROP', 'USERNAME'フィールドをインクルードするtotal_score_idxを削除した結果、PROP, USERNAMEを参照するクエリではIndexへの参照がなくなりました。
一方で、これらのフィールドを参照しない場合は、total_score_idx2を参照しています。

このようにクエリとインデックステーブルの構成が、Secondary Indexが実際に利用されるかどうかに影響しますので、Secondary Indexを利用する場合はこれらを考慮する必要があります。

ということで今回はSecondary Indexを使ってみる最初の一歩についてご紹介しました。

Secondary Indexを利用するにあたっては[SSDが推奨環境](https://maprdocs.mapr.com/60/AdvancedInstallation/PlanningtheCluster-hardware.html#PlanningtheCluster-Cluste_26982442-d3e1181)となりますので注意しましょう。
またgatewayも今回は1台で間に合わせましたが、本番環境では[2つ以上実行してロードバランスを冗長化をもたせることが推奨](https://maprdocs.mapr.com/home/Gateways/ConfiguringMapRGatewaysForTRAndI.html)されます。