MongoDB で hint() を使って存在しないインデックスを指定すると

エラーを吐く。

# hint()

MongoDB でクエリを発行する際に「このインデックスを使ってほしい」という状況では `hint()` を使ってインデックスを指定することができる。

前提として以下のようなインデックスが設定されているとする。

```js
> db.items.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "sandbox.items"
        },
        {
                "v" : 2,
                "key" : {
                        "memo" : 1
                },
                "name" : "memo_1",
                "ns" : "sandbox.items"
        }
]
```

以下のようなクエリを書けば items に対して検索を行う際に使うインデックスを指定することができる。

```js
> db.items.find().hint({memo: 1})
{ "_id" : ObjectId("5dea0c12645c1b6ec8cc356f"), "name" : "item3", "memo" : null }
{ "_id" : ObjectId("5dea0c19645c1b6ec8cc3570"), "name" : "item4" }
{ "_id" : ObjectId("5dea0c0d645c1b6ec8cc356e"), "name" : "item2", "memo" : "" }
{ "_id" : ObjectId("5dea0c07645c1b6ec8cc356d"), "name" : "item1", "memo" : "hoge" }
```

実際に意図するインデックスが使われているかどうかは explain で確認できる。

**インデックスを指定しない場合**

```js
> db.items.find().explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "sandbox.items",
                "indexFilterSet" : false,
                "parsedQuery" : {

                },
                "queryHash" : "8B3D4AB8",
                "planCacheKey" : "8B3D4AB8",
                "winningPlan" : {
                        "stage" : "COLLSCAN",
                        "direction" : "forward"
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "aa8a63a66c06",
                "port" : 27017,
                "version" : "4.2.1",
                "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
        },
        "ok" : 1
}
```

**インデックスを指定した場合**

```js
> db.items.find().hint({memo: 1}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "sandbox.items",
                "indexFilterSet" : false,
                "parsedQuery" : {

                },
                "queryHash" : "8B3D4AB8",
                "planCacheKey" : "8B3D4AB8",
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "memo" : 1
                                },
                                "indexName" : "memo_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "memo" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "memo" : [
                                                "[MinKey, MaxKey]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "aa8a63a66c06",
                "port" : 27017,
                "version" : "4.2.1",
                "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
        },
        "ok" : 1
}
```

`queryPlanner.winningPlan` を見れば `stage` あるいは `inputStage.stage` にてインデックスの利用有無が確認できる。


ハッシュを用いて `{memo: 1}` という形で指定したが name でも指定できる。

```js
> db.items.find().hint('memo_1')
{ "_id" : ObjectId("5dea0c12645c1b6ec8cc356f"), "name" : "item3", "memo" : null }
{ "_id" : ObjectId("5dea0c19645c1b6ec8cc3570"), "name" : "item4" }
{ "_id" : ObjectId("5dea0c0d645c1b6ec8cc356e"), "name" : "item2", "memo" : "" }
{ "_id" : ObjectId("5dea0c07645c1b6ec8cc356d"), "name" : "item1", "memo" : "hoge" }
```

# 存在しないインデックスを指定する

インデックスを貼っていないフィールド `name` のインデックス（をさもあるかのように）指定する。

```js
> db.items.find().hint('name_1')
Error: error: {
        "ok" : 0,
        "errmsg" : "error processing query: ns=sandbox.itemsTree: $and\nSort: {}\nProj: {}\n planner returned error :: caused by :: hint provided does not correspond to an existing index",
        "code" : 2,
        "codeName" : "BadValue"
}
```

name でなくハッシュで `{name: 1}` としても同様である。

`hint()` という名前ではあるが、 **意味的にはヒントというよりも強制している** といえる。

# 貼ってあるインデックスと逆順を指定する

`{memo: 1}` の値の部分をマイナスに変えると降順になる。

```js
> db.items.find().hint({memo: -1})
Error: error: {
        "ok" : 0,
        "errmsg" : "error processing query: ns=sandbox.itemsTree: $and\nSort: {}\nProj: {}\n planner returned error :: caused by :: hint provided does not correspond to an existing index",
        "code" : 2,
        "codeName" : "BadValue"
}
```

これも残念ながらエラーとなる。
なぜなら、フィールド `memo` に対して昇順でしかインデックスを貼っていないためだ。

以下のように降順のインデックスを追加すれば良い。

```js
> db.items.createIndex({memo: -1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}
> db.items.find().hint({memo: -1})
{ "_id" : ObjectId("5dea0c07645c1b6ec8cc356d"), "name" : "item1", "memo" : "hoge" }
{ "_id" : ObjectId("5dea0c0d645c1b6ec8cc356e"), "name" : "item2", "memo" : "" }
{ "_id" : ObjectId("5dea0c12645c1b6ec8cc356f"), "name" : "item3", "memo" : null }
{ "_id" : ObjectId("5dea0c19645c1b6ec8cc3570"), "name" : "item4" }
```

# `hint()` を使うときの注意

大抵の場合、アプリケーションから MongoDB にアクセスするなら ODM やそれに類するものを使うと思う。
たとえば Mongoid にも `hint()` メソッドが用意されている。
Mongoid の `hint()` で存在しないインデックスを指定すると `bad hint` というエラーを吐く。

アプリケーション側でインデックスが存在する前提で `hint()` を使うと、本番 DB でインデックスを貼り忘れたり、インデックスが削除されたりしたタイミングでエラーが大量にでるようになる危険がある。

`hint()` を使わなくても意図通りのインデックスが使用されるのであればわざわざ使わない方が良い。
使う必要があるときは、そのインデックスの重要性を関係者に周知しておくことが望ましい。
特に MongoDB のバージョンを上げたりするとインデックス選択のロジックが変わったりして意図と異なるインデックスが採用されるようになったりするので、そういう場合に `hint()` を使いたくなるはずなのでちゃんと特性を理解して使いこなせるようにしたい。