Couchbase Serverにおけるインデックス機能解説（RDBとの比較を交えながら）



Couchbase Serverにおける、インデックス機能の基本をRDBとの比較を交えながら説明します。

#### 用語法に関する注釈
Couchbase Serverにおけるインデックスは、グローバルセカンダリーインデックス（GSI）と呼ばれています。別に「プライマリーインデックス」、「セカンダリーインデックス」という表現も用いられますが、これらはいずれも、グローバルセカンダリーインデックス（GSI）の中のカテゴリーとなります。（以下は、「プライマリーインデックス」のメタデータの例です。` "is_primary": true` かつ`"using": "gsi"`　であることが分かります）

```JSON
{
      "datastore_id": "http://127.0.0.1:8091",
      "id": "1c23ecbf1f7cd99e",
      "index_key": [],
      "is_primary": true,
      "keyspace_id": "travel-sample",
      "name": "#primary",
      "namespace_id": "default",
      "state": "online",
      "using": "gsi"
    }
```

#### 来歴
グローバルセカンダリーインデックス（GSI）が登場する前には、データの検索のために、MapReduceビュー（View）という機能が使われていました（現在も互換性のために維持されています）。このViewがデータノードに対して「ローカル」であったことに対して、データノードのローカリティに依存しない、クラスター単位で管理されるインデックスという意味で、「グローバル」という表現が用いられていると解釈することができます。「セカンダリー」という形容には、RDBにおけるセカンダリーインデックスのように、任意の数のインデックスを定義できる、というニュアンスを表現していると受け止められます。

Couchbase Serverの「インデックス」といった場合には、（基本的には）技術的な固有名としてのSGIを指していると考えることができます。

情報：　Couchbase Serverには、ローカルセカンダリーインデックス(Local Secondary　Index)という概念は存在しません。

情報：　Couchbase Serverの過去のバージョンではGSIとしてインデックスを作成する際に明示的に`USING GSI` という句が用いられていました。6.5以降のバージョンでは、`USING GSI`の利用はオプションであり、省略可能です。

#### RDBのインデックスとの違い

Couchbase ServerのSGIと、RDBとのインデックスの間には、ユーザーが意識しておくべき、下記のような違いがあります。

- Couchbase Serverにおけるクエリでは、そのクエリを実行するために必要なインデックスが存在しない場合、処理が失敗します。
- インデックスは、非同期で維持されます。
- 同じ内容のインデックスを別名で複数定義できます。

最後の点について、完全に同じ内容のインデックスを別名で、つまり複数、作成することを許していることにどのような意味があるでしょうか？　Couchbase Serverのインデックスは、RDB/SQLの構文を可能な限り踏襲しながら、RDBにはない、ユーザーによる様々な制御の余地を提供しています。例えば、これは分散アーキテクチャー独自の部分になりますが、インデックス定義時に、ローカリティを指定することが可能です。同じ内容のインデックスを別名で、複数定義できるという特徴と組み合わせて、複数のノードに同じインデックスを格納することによって、クエリの性能を向上するという使い方ができます。

その他の特徴についても、それぞれ関連する章で、背景やその特徴を踏まえた利用方法を説明しています。

#### ストレージモデル

Couchbase　Serverのグローバルセカンダリインデックス (GSI) には2つのストレージモデルがあります。

- スタンダードGSI
- メモリ最適化(Memory-optimized)GSI

注意： メモリ最適化GSIは、コミュニティエディションでは利用することができません。

これらは共に、MultiVersion Concurrency Control (MVCC, マルチバージョン コンカレンシー コントロール)を実装しており、一貫したインデックススキャン結果と高スループットを提供します。

スタンダードグローバルセカンダリインデックスは、ForestDBストレージエンジンを使用して、Bツリーインデックスを格納しています。また、最適なワーキングセットのデータをバッファに保持します。

メモリ最適化インデックスは、すべてのインデックスデータをメモリに持ち、ロックフリースキップリストを使用してインデックスを保持しています。メモリ最適化インデックスは、データの変更に際して、に高速に処理が行われます。

重要: スタンダードGSI、メモリ最適化GSIのいずれを使うかは、クラスター単位の設定となり、クラスターの初期構成時に、そのクラスターで使用するインデックスのタイプを選択することになります。


#### プライマリインデックス

プライマリインデックスは、バケット単位で指定するインデックスです。

注意：　Couchbase Serverでは、ここで説明するプライマリインデックスとは関係なく、ドキュメントキーには、一意制約があります。

プライマリインデックスが使用されるケースとしては、以下があります。
- クエリにフィルタ（述語・WHERE句）がない場合
- クエリにフィルタ（述語・WHERE句）があるが、該当するセカンダリーグローバルインデックスがない場合

Couchbase Serverでは、クエリを実行するために必要なインデックスが存在しない場合、処理が失敗するという性質があります。プライマリインデックスを用いることにより、そのような制約を離れ、どのようなクエリでも実行することが可能になります。


```SQL
CREATE PRIMARY INDEX ON MyFirstBucket;
```

作成されたインデックスの定義（`Definition`　）を確認すると分かりますが、上記のDDLは、下記のDDLを実行する際のコーディングシュガーにあたるものです。(Couchbase Serverのインデックスには名前を定義するのが通常であり、上記の省略形は、プライマリーインデックスに特有のものです)

```SQL
CREATE PRIMARY INDEX `#primary` ON `MyFirstBucket`
```

したがって当然、次の例に示すように、明示的に、名前を付けて定義することもできます。

```SQL
CREATE PRIMARY INDEX <インデックス名> ON <バケット名>;
```

注意：　プライマリインデックスによる検索は、検索条件の内容に関わらず、対象バケットに対する全件スキャンを実行することに留意が必要です。これは、ドキュメントキーによる検索の場合も同様です（ドキュメントキーを用いたセカンダリーインデックスの定義については別に触れます）。実際上、プライマリインデックスは、開発時の利便性のためのものであると捉えることができます。実運用においては、プライマリインデックス定義を用いないことがベストプラクティスとなります。


情報：アドホックなクエリのために、別稿にて触れるアダプティブ（Adaptive）インデックスの利用を検討することができます。アダプティブインデックスの利用により、プライマリーインデックスの特徴である全件スキャンと、それに伴う検索性能劣化、を避けることができますが、あえて不利な点を明記するなら、アダプティブインデックスでは、インデックス作成・更新のためのリソース消費のオーバーヘッドの面では、プライマリーインデックスよりも負担が大きいということが言えます。


ここで、例えば、マスターデータのように、あるテーブル（データ種類）のレコードを全て取得するというユースケースは通常考えられると思われる方もいらっしゃるかもしれません。この場合、以下を思い出すことが有意義と思われます。

- Couchbase Serverにおいては、クエリはアプリケーションがデータを取得のための唯一の方法ではなく、Dataサービスから直接データを取得可能。
- バケットとデータ種類との不一致。Couchbase Serverは、バケット単位でメモリリソースを確保します。そのため、バケットとデータ種類とを一致させることは、一般的ではありません。例えば、データ種別を表すフィールドをドキュメント内に持つことによって、複数の異なるデータ種別を一つのバケットに格納することが頻繁に行われてきました。

#### セカンダリインデックス

JSONドキュメントの特定の要素（フィールドまたはドキュメントのキー）に対して設定するインデックスは、（バケット単位で定義されるインデックスであるプライマリインデックスと区別して）セカンダリインデックスと呼ばれています。

まずは以下、JSONのフィールドの扱いの違いによる定義方法を見ていきます。

##### トップレベルフィールド

```SQL
CREATE INDEX travel_name ON `travel-sample`(name);
```

この例では、`name` は次のような単純なスカラー値です。

```SQL
{ "name": "Air France" }
```

述語条件の左項は、スカラー値のみではなく、JSONの他のデータ形式（配列、オブジェクト）を用いることも可能ですが、この場合、述語条件の右側の値は同様に構造化されたデータ形式（配列、オブジェクト）である必要があります。


##### ネストされたオブジェクト

ドット(`.` )表記（ノーテーション）により、ネストされたオブジェクト（のフィールド）を指定することができます。

```SQL
CREATE INDEX travel_geo on `travel-sample`(geo.alt);
```
```SQL
CREATE INDEX travel_geo on `travel-sample`(geo.lat);
```

`geo` は、次のようなドキュメント内に埋め込まれたオブジェクトです。

```JSON
"geo": {
    "alt": 12,
    "lat": 50.962097,
    "lon": 1.954764
    }
```

#### 複合（コンポジット）インデックス

複数のキーを持つインデックスを定義することが可能です。

```SQL
CREATE INDEX travel_info ON `travel-sample`（name, type, id, icoo, iata）;
```

#### ドキュメントキーのインデックス

セカンダリインデックスのキーとして、ドキュメントキーを用いることができます。ドキュメントキーは、ドキュメント内の情報ではないため、　`meta()`　キーワードを用います。

```SQL
CREATE INDEX travel_geo on `travel-sample`(meta().id);
```


#### 参考情報：インデックスのメタデータ

`system:indexes` に対してクエリすることにより、インデックスのメタデータを確認することができます。

利用例

```SQL
SELECT * FROM system:indexes WHERE name = '#primary';
```

```JSON
[
  {
    "indexes": {
      "datastore_id": "http://127.0.0.1:8091",
      "id": "1c23ecbf1f7cd99e",
      "index_key": [],
      "is_primary": true,
      "keyspace_id": "travel-sample",
      "name": "#primary",
      "namespace_id": "default",
      "state": "online",
      "using": "gsi"
    }
  },
  {
    "indexes": {
      "datastore_id": "http://127.0.0.1:8091",
      "id": "bddd0348350cfd8c",
      "index_key": [],
      "is_primary": true,
      "keyspace_id": "MyFirstBucket",
      "name": "#primary",
      "namespace_id": "default",
      "state": "online",
      "using": "gsi"
    }
  }
```

メタデータは、インデックスが存在するノード（`datastore_id` ）、状態（`state` ）など、インデックスに関する情報を提供します。



参考情報：コミュニティエディションとエンタープライズエディションの違い

Indexサービスにおける、コミュニティエディションには含まれない、エンタープライズエディションの機能として、いかがあります。

- インデックスのパーティション化
- インデックスレプリカ

- メモリ最適化インデックスストレージ
- Plasma（インデックス用の高速ストレージエンジン）

- インデックスアドバイザー
   - コミュニティユーザーは、右のサイトを使って、インデックスアドバイザー機能を利用することができます：https://index-advisor.couchbase.com/indexadvisor