MongoDBにおけるインデックスの役割

## はじめに
業務でMongoDBを使用しており、今一度インデックスについて理解を深めるため記事にしようと思います。

## インデックスとは
インデックスとはクエリの高速化、つまり**DBから欲しいデータをより早く取得するための技術**です。

わかりやすい例として図書館がよく挙げられます。
本が無造作に散らばっている図書館で本を探そうとするところを想像してみてください。時間がかかりますよね。そこで活躍するのがインデックスで、図書館の目録のような役割をして、必要な本を直接案内してくれるみたいなイメージです。

## インデックスの種類
### 単体インデックス
単体インデックスは、コレクションの1つのフィールドに対して作成されるインデックスです。このインデックスを使用すると、そのフィールドの値に基づいてドキュメントを迅速に検索できます。
```java
db.collection.createIndex({ user_id: 1 });
```
このコマンドは user_id フィールドに昇順の単体インデックスを作成します。1は昇順、-1は降順を表します。
**単体のフィールドに基づく検索クエリのパフォーマンスが向上します。**

### 複合インデックス
複合インデックスは、複数のフィールドを組み合わせて作成されるインデックスです。
```java
db.collection.createIndex({ last_name: 1, first_name: 1 });
```
このコマンドは last_name フィールドと first_name フィールドの両方に昇順の複合インデックスを作成します。
**複数のフィールドに基づく検索クエリのパフォーマンスが向上します。**

## インデックスを新規作成する際の注意点
インデックスは便利ですが、新規作成時は注意する必要があります。

### 管理コストを考慮する
インデックスは追加のデータ構造を作成するため、**ストレージ容量を消費します**。特に、複数のフィールドに対してインデックスを作成すると、ストレージの使用量が増加します。

### ESRの法則を意識する
ESRの法則とは、**複合インデックスの作成時にEqual(一致)、Sort(並び替え)、Range(絞り込み) の順で貼るのが適切である**というものです。

以下参考

https://qiita.com/ryota_seto/items/c318fc03f20d7850732c

## 作成したインデックスが効いているか確かめる方法
クエリの**実行計画**を確認することでインデックスが効いているか確かめることが可能です。

例えば
```java
db.getCollection("user").find({"age": 20})
```
というクエリの実行計画を見るには
```java
db.getCollection("user").explain("executionStats").find({"age": 20})
```
のようにクエリにexplain("executionStats")を追加することで**dbのクエリをどう処理するか、つまり実行計画**を確認できます。

例えばageフィールドにインデックスを追加後に以下のクエリを実行してみます。
```javascript
db.users.find({ age: { $gt: 30 } }).explain("executionStats");
```

実行計画が表示されます。
```java
{
  "queryPlanner": {
    "namespace": "myDatabase.users",
    "indexFilterSet": false,
    "parsedQuery": {
      "age": {
        "$gt": 30
      }
    },
    "winningPlan": {
      "stage": "IXSCAN",
      "keyPattern": {
        "age": 1
      },
      "indexName": "age_1",
      "isMultiKey": false,
      "multiKeyPaths": {
        "age": []
      },
      "direction": "forward",
      "indexBounds": {
        "age": [
          "(30, inf)"
        ]
      }
    },
    "rejectedPlans": []
  },
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 3,
    "executionTimeMillis": 1,
    "totalKeysExamined": 3,
    "totalDocsExamined": 3,
    "executionStages": {
      "stage": "FETCH",
      "nReturned": 3,
      "executionTimeMillisEstimate": 0,
      "works": 4,
      "advanced": 3,
      "needTime": 0,
      "needFetch": 1,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "docsExamined": 3,
      "alreadyHasObj": false,
      "inputStage": {
        "stage": "IXSCAN",
        "nReturned": 3,
        "executionTimeMillisEstimate": 0,
        "works": 4,
        "advanced": 3,
        "needTime": 0,
        "needFetch": 0,
        "saveState": 0,
        "restoreState": 0,
        "isEOF": 1,
        "keyPattern": {
          "age": 1
        },
        "indexName": "age_1",
        "isMultiKey": false,
        "multiKeyPaths": {
          "age": []
        },
        "direction": "forward",
        "indexBounds": {
          "age": [
            "(30, inf)"
          ]
        },
        "keysExamined": 3,
        "seeks": 1,
        "dupsTested": 0,
        "dupsDropped": 0
      }
    }
  },
  "serverInfo": {
    "host": "localhost",
    "port": 27017,
    "version": "4.2.0",
    "gitVersion": "0000000000000000000000000000000000000000"
  },
  "ok": 1
}
```
実行計画では、winningPlan に IXSCAN（インデックススキャン）が使用されており、ageフィールドに追加したインデックスが効いていることを確認できます。
```
"winningPlan": {
 "stage": "IXSCAN",
 "keyPattern": {
 "age": 1
}
```