Cosmos DB (Azure と Fabric) は、スキーマに依存しないデータベースであり、スキーマまたはインデックス管理を処理することなく、アプリケーションを反復処理できます。 この機能は、 読み取り時のスキーマとも呼ばれます。つまり、Cosmos DB では、データベースに書き込まれるときにデータにスキーマが適用されません。 スキーマは、読み取りまたは書き込み操作中にデータベースからデータを逆シリアル化するときに、アプリケーション内で作成するクラスで定義されます。
Microsoft Fabric の Cosmos DB 内でのインデックス作成は、データの進化に関係なく、高速で柔軟なクエリ パフォーマンスを実現するように設計されています。 既定では、Cosmos DB では、スキーマを定義したりセカンダリ インデックスを構成したりしなくても、コンテナー内のすべての項目のすべてのプロパティのインデックスが自動的に作成されます。
概念ツリー
項目がコンテナーに格納されるたびに、そのコンテンツは JSON ドキュメントとして投影され、ツリー表現に変換されます。 この変換により、その項目のすべてのプロパティがツリー内のノードとして表示されます。 擬似ルート ノードは、項目のすべての第 1 レベル プロパティに対する親として作成されます。 リーフ ノードには、項目に格納されている実際のスカラー値が含まれます。
たとえば、次の項目を検討してみましょう。
{
"id": "00000000-0000-0000-0000-000000004368",
"name": "Cofaz Jacket",
"tags": [
{ "category": "clothing", "type": "jacket" },
{ "category": "outdoor", "type": "winter" }
],
"inventory": { "warehouse": "Seattle", "quantity": 50 },
"distributors": [
{ "name": "Contoso" },
{ "name": "AdventureWorks" }
]
}
この概念ツリーは、サンプル JSON 項目を表します。
-
id:00000000-0000-0000-0000-000000004368 -
name:Cofaz Jacket tags0-
category:clothing -
type:jacket
-
1-
category:outdoor -
type:winter
-
inventory-
warehouse:Seattle -
quantity:50
-
distributors0-
name:Contoso
-
1-
name:AdventureWorks
-
ツリー内での配列のエンコード方法に注意してください。配列内のすべてのエントリは、配列内のそのエントリのインデックスでラベル付けされた中間ノードを取得します。 たとえば、最初のエントリは 0 、2 番目のエントリは 1。
プロパティ パス
Cosmos DB は、これらのツリー内のパスを使用してプロパティを参照できるため、項目をツリーに変換します。 プロパティのパスを取得するには、ルート ノードからそのプロパティまでツリーを走査し、たどった各ノードのラベルを連結します。
前述の例の項目から各プロパティへのパスは次のとおりです。
| 経路 | 価値 |
|---|---|
/id |
"00000000-0000-0000-0000-000000004368" |
/name |
"Cofaz Jacket" |
/tags/0/category |
"clothing" |
/tags/0/type |
"jacket" |
/tags/1/category |
"outdoor" |
/tags/1/type |
"winter" |
/inventory/warehouse |
"Seattle" |
/inventory/quantity |
50 |
/distributors/0/name |
"Contoso" |
/distributors/1/name |
"AdventureWorks" |
Cosmos DB では、項目が書き込まれると、各プロパティのパスとそれに対応する値のインデックスが効果的に作成されます。
Cosmos DB のインデックスの種類
Cosmos DB では現在、4 種類のインデックスがサポートされています。
インデックス作成ポリシーを定義するときに、これらのインデックスのタイプを構成できます。
範囲インデックス
範囲インデックスは、順序付けされたツリーのような構造に基づいています。 範囲インデックス タイプは、次のために使用されます。
等値クエリ:
SELECT * FROM container c WHERE c.property = 'value'SELECT * FROM container c WHERE c.property IN ("value1", "value2", "value3")配列要素の等値一致
SELECT * FROM container c WHERE ARRAY_CONTAINS(c.tags, "tag1")範囲クエリ:
SELECT * FROM container c WHERE c.property > 0注
>、<、>=、<=、!=に対応しますプロパティが存在するかどうかの確認:
SELECT * FROM container c WHERE IS_DEFINED(c.property)文字列システム関数:
SELECT * FROM container c WHERE CONTAINS(c.property, "value")SELECT * FROM container c WHERE STRINGEQUALS(c.property, "value")ORDER BYクエリ:SELECT * FROM container c ORDER BY c.propertyJOINクエリ:SELECT d FROM container c JOIN d IN c.properties WHERE d = 'value'
範囲インデックスは、スカラー値 (文字列または数値) に使用できます。 新しく作成したコンテナーの既定のインデックス作成ポリシーでは、文字列または数値に範囲インデックスが適用されます。
注
1 つのプロパティで並べ替えるORDER BY 句は常に範囲インデックスが必要であり、参照するパスにインデックスがない場合は失敗します。 同様に、複数のプロパティで並べ替える ORDER BY クエリ には、常に 複合インデックスが必要です。
空間インデックス
空間 インデックスを使用すると、ポイント、線、多角形、マルチポリゴンなどの地理空間オブジェクトに対して効率的なクエリを実行できます。 これらのクエリでは、 ST_DISTANCE、 ST_WITHIN、 ST_INTERSECTS キーワードが使用されます。 空間インデックス タイプを使用するいくつかの例を次に示します。
地理空間距離クエリ:
SELECT * FROM container c WHERE ST_DISTANCE(c.property, { "type": "Point", "coordinates": [0.0, 10.0] }) < 40クエリ内の地理空間:
SELECT * FROM container c WHERE ST_WITHIN(c.property, {"type": "Point", "coordinates": [0.0, 10.0] })地理空間交差クエリ:
SELECT * FROM container c WHERE ST_INTERSECTS(c.property, { 'type':'Polygon', 'coordinates': [[ [31.8, -5], [32, -5], [31.8, -5] ]] })
空間インデックスは、正しく書式設定された GeoJSON オブジェクトで使用できます。 現在、Points、LineStrings、Polygons、MultiPolygons がサポートされています。
複合インデックス
複合インデックスを使用すると、複数のフィールドに対して操作を実行するときの効率が向上します。 複合インデックス タイプは、次のために使用されます。
複数のプロパティに対する
ORDER BYクエリ:SELECT * FROM container c ORDER BY c.property1, c.property2フィルターと
ORDER BYを使用するクエリ。 これらのクエリは、フィルター プロパティがORDER BY句に追加された場合に、複合インデックスを利用できます。SELECT * FROM container c WHERE c.property1 = 'value' ORDER BY c.property1, c.property2少なくとも 1 つのプロパティが等値フィルターである 2 つ以上のプロパティに対するフィルターを含むクエリ:
SELECT * FROM container c WHERE c.property1 = 'value' AND c.property2 > 'value'
1 つのフィルター述語でいずれかのインデックスの種類が使用されている限り、クエリ エンジンは、残りの部分をスキャンする前に最初にその型を評価します。 たとえば、 SELECT * FROM c WHERE c.department = "Information Technology" and CONTAINS(c.team, "Pilot")などの SQL クエリがある場合は、次のようになります。
このクエリは、インデックスを使用して
department = "Information Technology"エントリのフィルターを最初に適用します。 その後、後続のパイプラインを通じてすべてのdepartment = "Information Technology"エントリを渡し、CONTAINSフィルター述語を評価します。CONTAINSなどのフル スキャンを実行する関数を使用する場合は、クエリを高速化し、完全なコンテナー スキャンを回避できます。 インデックスを使用するフィルター述語をさらに追加して、これらのクエリを高速化できます。 フィルター句の順序は重要ではありません。 クエリ エンジンでは、どの述語がより選択的であるかが判断され、それに応じてクエリが実行されます。
ベクター インデックス
ベクトル インデックス作成では、VECTORDISTANCE システム関数を使用して、ベクトル検索の実行効率を向上させます。 ベクター検索では、ベクター インデックスを使用する場合の待機時間が短く、スループットが高く、RU 消費量が少なくなります。 Cosmos DB では、サイズが 4,096 次元未満のベクター埋め込み (テキスト、画像、マルチモーダルなど) がサポートされています。
ORDER BYベクトル検索クエリ:SELECT TOP 10 * FROM container c ORDER BY VECTORDISTANCE(c.vector1, c.vector2)ベクトル検索クエリにおける類似性スコアのプロジェクション:
SELECT TOP 10 c.name, VECTORDISTANCE(c.vector1, c.vector2) AS score FROM container c ORDER BY VECTORDISTANCE(c.vector1, c.vector2)類似性スコアの範囲フィルター。
SELECT TOP 10 * FROM container c WHERE VECTORDISTANCE(c.vector1, c.vector2) > 0.8 ORDER BY VECTORDISTANCE(c.vector1, c.vector2)
Important
ベクター ポリシーとベクター インデックスは、作成後に変更できません。 変更するには、新しいコレクションを作成します。
クエリでインデックスを使用する方法
クエリ エンジンでクエリ フィルターを評価できる 5 つの方法を、最も効率的なものから非効率なものの順に以下に示します。
- インデックス シーク
- 正確なインデックス スキャン
- 拡張インデックス スキャン
- フル インデックス スキャン
- フル スキャン
プロパティ パスのインデックスを作成すると、クエリ エンジンによって可能な限り効率的にインデックスが自動的に使用されます。 新しいプロパティ パスのインデックス作成以外に、クエリでインデックスを使用する方法の最適化に関して構成する必要があるものは何もありません。 クエリの RU 料金は、インデックス使用の RU 料金と、項目読み込みの RU 料金を合わせたものです。
次の表は、Cosmos DB でインデックスを使用するさまざまな方法をまとめたものです。
| Lookup タイプ | Description | 一般的な例 | インデックス使用量からの課金 | トランザクション データ ストアからアイテムを読み込む場合の料金 |
|---|---|---|---|---|
| インデックス シーク | 必要なインデックス値のみの読み取りと、一致する項目のみのトランザクション データ ストアからの読み込み | 等値フィルター、IN | 等値フィルターごとの定数 | クエリ結果内の項目数に基づいて増加します |
| 正確なインデックス スキャン | 必要なインデックス値のバイナリ検索と、一致する項目のみのトランザクション データ ストアからの読み込み | 範囲の比較 (>、<、<=、>=)、StartsWith | インデックス シークと比較して、インデックス付けされたプロパティのカーディナリティの分だけ少し増えます | クエリ結果内の項目数に基づいて増加します |
| 拡張インデックス スキャン | インデックス値の最適化された検索 (ただし、バイナリ検索よりは非効率的) と、一致する項目のみのトランザクション データ ストアからの読み込み | StartsWith (大文字と小文字の区別なし)、StringEquals (大文字と小文字の区別なし) | インデックス付けされたプロパティのカーディナリティの分だけ少し増えます | クエリ結果内の項目数に基づいて増加します |
| フル インデックス スキャン | インデックス値の個別のセットの読み取りと、一致する項目のみのトランザクション データ ストアからの読み込み | Contains、EndsWith、RegexMatch、LIKE | インデックス付けされたプロパティのカーディナリティに基づいて、直線的に増加します | クエリ結果内の項目数に基づいて増加します |
| フル スキャン | トランザクション データ ストアからすべての項目を読み込みます | Upper、Lower | N/A | コンテナー内の項目数に基づいて増加します |
クエリを作成するときは、インデックスを可能な限り効率的に使用するフィルター述語を使用する必要があります。 たとえば、ユース ケースに StartsWith または Contains のいずれかを使用できる場合は、StartsWith を選択する必要があります。そうすれば、フル インデックス スキャンではなく、正確なインデックス スキャンが実行されます。
インデックスの使用の詳細
ヒント
このセクションでは、クエリでインデックスがどのように使用されるかについて詳しく説明します。 このレベルの詳細は、Cosmos DB の使用を開始する方法を学習するために必要ではありませんが、興味のあるユーザー向けに詳細に記載されています。 このドキュメントで前に共有した項目の例を参照します。
次の 2 つの項目の例を考えてみましょう。
[
{
"id": "00000000-0000-0000-0000-000000004368",
"name": "Cofaz Jacket",
"tags": [
{ "category": "clothing", "type": "jacket" },
{ "category": "outdoor", "type": "winter" }
],
"inventory": { "warehouse": "Seattle", "quantity": 50 },
"distributors": [
{ "name": "Contoso" },
{ "name": "AdventureWorks" }
]
},
{
"id": "00000000-0000-0000-0000-000000004002",
"name": "Potana bike",
"tags": [
{ "category": "cycling", "type": "mountain" }
],
"inventory": { "warehouse": "Seattle", "quantity": 30 },
"distributors": [
{ "name": "Contoso" },
{ "name": "Fabrikam" },
{ "name": "Northwind" }
]
}
]
Cosmos DB では、逆インデックスが使用されます。 インデックスは、各 JSON パスを、その値が含まれる項目のセットにマップすることによって機能します。 項目 ID のマッピングは、コンテナーのさまざまなインデックス ページで表されます。 次に示すのは、2 つの項目例が含まれるコンテナーの逆インデックスのサンプルの図です。
| 経路 | 価値 | アイテム識別子の一覧 |
|---|---|---|
/tags/0/category |
clothing |
[00000000-0000-0000-0000-000000004368] |
/tags/0/category |
cycling |
[00000000-0000-0000-0000-000000004002] |
/tags/0/type |
jacket |
[00000000-0000-0000-0000-000000004368] |
/tags/0/type |
mountain |
[00000000-0000-0000-0000-000000004002] |
/tags/1/category |
outdoor |
[00000000-0000-0000-0000-000000004368] |
/tags/1/type |
winter |
[00000000-0000-0000-0000-000000004368] |
/inventory/warehouse |
Seattle |
[00000000-0000-0000-0000-000000004368, 00000000-0000-0000-0000-000000004002] |
/inventory/quantity |
30 |
[00000000-0000-0000-0000-000000004002] |
/inventory/quantity |
50 |
[00000000-0000-0000-0000-0000-000000004001] |
逆インデックスには、2 つの重要な属性があります。
特定のパスでは、値は昇順に並べ替えられます。 そのため、クエリ エンジンでインデックスから簡単に
ORDER BYを提供できます。特定のパスについて、クエリ エンジンで可能な値の個別のセットをスキャンして、結果が存在するインデックス ページを識別できます。
クエリ エンジンでは、4 つの方法で逆インデックスを利用できます。
インデックス シーク
次のクエリがあるとします。
SELECT
tag
FROM
tag IN product.tags
WHERE
tag.category = 'outdoor'
クエリ述語 (タグのカテゴリが "outdoor" である項目に対するフィルター処理) は、ここで呼び出されたパスと一致します。
tags1-
category:outdoor
-
"ID"、"名前"、"タグ"、"インベントリ"、"ディストリビューター" の 5 つの分岐を持つルート ノードを示すツリー図。 "ID" 分岐には、値 "000000000-0000-0000-0000-0000000004368" が表示されます。 "name" ブランチに "Cofaz Jacket" と表示されます。 "Tags" は 2 つの番号付きノード (0 と 1) に分割され、それぞれに "category" サブノードと "type" サブノード ("clothing/jacket" と "outdoor/winter" ) が含まれます。 "Inventory" には"warehouse" ("Seattle") と "quantity" ("50") があります。 "ディストリビューター" は 2 つの番号付きノード (0 と 1) に分割され、それぞれに "name" サブノード ("Contoso" と "AdventureWorks") が付けられます。 "タグ"、"1"、"カテゴリ"、"屋外" のパスが強調表示されています。
このクエリには等値フィルターがあるため、このツリーを走査した後、クエリ結果を含むインデックス ページをすばやく特定できます。 この場合、クエリ エンジンは Item 00000000-0000-0000-0000-000000004368を含むインデックス ページを読み取ることになります。 インデックス シークは、インデックスを使用する最も効率的な方法です。 インデックス シークを使用すると、必要なインデックス ページだけが読み取られて、クエリ結果内の項目だけが読み込まれます。 そのため、インデックス検索の時間と、インデックス検索による RU 料金は、合計データ量に関係なく、非常に少なくなります。
正確なインデックス スキャン
次のクエリがあるとします。
SELECT
*
FROM
product
WHERE
product.inventory.quantity > 30
クエリ述語 (インベントリ内に 30 ユニットを超えるアイテムに対するフィルター処理) は、 inventory/quantity パスの正確なインデックス スキャンを使用して評価できます。 正確なインデックス スキャンを実行すると、クエリ エンジンによって最初に可能な値の個別のセットのバイナリ検索が実行され、30 パスに対する値 inventory/quantity の場所が検索されます。 各パスの値は昇順に並べ替えられているため、クエリ エンジンによるバイナリ検索は簡単に実行できます。 クエリ エンジンで値 30 が検出された後、残りのすべてのインデックス ページの読み取りが開始されます (昇順の方向)。
バイナリ検索を行うことにより、クエリ エンジンで不要なインデックス ページをスキャンしなくて済むため、正確なインデックス スキャンの待機時間と RU 料金は、インデックス シーク操作と同程度になる傾向があります。
拡張インデックス スキャン
次のクエリがあるとします。
SELECT
*
FROM
product
WHERE
STARTSWITH(product.inventory.warehouse, "Sea", true)
クエリ述語 (大文字と小文字を区別しない "Sea" で始まる倉庫内の在庫があるアイテムに対するフィルター処理) は、 inventory/warehouse パスの拡張インデックス スキャンを使用して評価できます。 拡張インデックス スキャンを実行する操作が備える最適化によって、すべてのインデックス ページをスキャンする必要はありませんが、正確なインデックス スキャンのバイナリ検索よりはコストが多少高くなります。
たとえば、大文字と小文字の区別なしで StartsWith を評価する場合、クエリ エンジンにより、大文字と小文字の値の考えられるさまざまな組み合わせで、インデックスがチェックされます。 この最適化により、クエリ エンジンでインデックス ページの大部分を読み取らずに済むようになります。 すべてのインデックス ページの読み取りを避けるために使用できる最適化は、システム関数によって異なるため、これらは拡張インデックス スキャンとして広く分類されています。
フル インデックス スキャン
次のクエリがあるとします。
SELECT
*
FROM
product
WHERE
CONTAINS(product.inventory.warehouse, "eat")
クエリ述語 ("eat" を含むウェアハウス内のインベントリを持つアイテムに対するフィルター処理) は、 inventory/warehouse パスのインデックス スキャンを使用して評価できます。 正確なインデックス スキャンとは異なり、フル インデックス スキャンでは、常に、可能な値の個別のセットがスキャンされて、結果が存在するインデックス ページが特定されます。 この場合、CONTAINS がインデックスで実行されます。 インデックス スキャンでのインデックス検索の時間と RU 料金は、パスのカーディナリティが増えるにつれて増加します。 つまり、クエリ エンジンでスキャンする必要がある、個別の可能な値が増えるにつれて、フル インデックス スキャンの実行に伴う待機時間と RU 料金が高くなります。
たとえば、name と warehouse という 2 つのプロパティで考えてみましょう。 名前のカーディナリティは 5,000 で、 warehouse のカーディナリティは 200 です。
CONTAINS プロパティに対して完全なインデックス スキャンを実行するname システム関数を持つクエリの例を次に示します。 名前のカーディナリティが warehouseよりも高いため、最初のクエリでは 2 番目のクエリよりも多くの要求ユニット (RU) が使用されます。
SELECT
*
FROM
container c
WHERE
CONTAINS(c.name, "Pack", false)
SELECT
*
FROM
c
WHERE
CONTAINS(c.inventory.warehouse, "Sea", false)
フル スキャン
場合によっては、クエリ エンジンがインデックスを使用してクエリ フィルターを評価できない場合があります。 この場合は、クエリ エンジンでクエリ フィルターを評価するために、トランザクション ストアからすべての項目を読み込む必要があります。 フル スキャンではインデックスは使用されず、RU 料金は総データ サイズに比例して増加します。 幸運なことに、フル スキャンを必要とする操作はめったにありません。
ベクトル インデックスが定義されていないベクトル検索クエリ
ベクター インデックス ポリシーを定義せず、VECTORDISTANCE句で ORDER BY システム関数を使用する場合、このクエリではフル スキャンが実行され、ベクター インデックス ポリシーを定義した場合よりも RU 料金が高くなります。 類似度は、ブルート フォースブール値が VECTORDISTANCE に設定されたを使用し、ベクター パスに対してflatインデックスが定義されていない場合は、フル スキャンが実行されます。
複雑なフィルター式を使用するクエリ
前の例では、単純なフィルター式を使用するクエリ (たとえば、1 つの等値フィルターまたは範囲フィルターだけを含むクエリ) のみを検討しました。 実際には、ほとんどのクエリでさらに複雑なフィルター式が使用されています。
次のクエリがあるとします。
SELECT
*
FROM
product
WHERE
product.inventory.quantity = 50 AND CONTAINS(product.inventory.warehouse, "Sea")
このクエリを実行するには、クエリ エンジンで、inventory/quantity に対するインデックス シークと、inventory/warehouse に対するフル インデックス スキャンを行う必要があります。 クエリ エンジンには内部ヒューリスティックがあり、クエリ フィルター式を可能な限り効率的に評価するために使用されます。 この場合は、クエリ エンジンによって、インデックス シークを最初に実行することにより、不要なインデックス ページを読み取る必要がなくなります。 たとえば、等値フィルターと一致するのが 50 項目のみの場合、クエリ エンジンで CONTAINS を評価する必要があるのは、それら 50 項目が含まれるインデックス ページだけです。 コンテナー全体のフル インデックス スキャンは必要ありません。
スカラー集計関数でのインデックスの使用
集計関数を使用するクエリでインデックスを使用するには、そのインデックスのみに依存している必要があります。
場合によっては、インデックスで擬陽性が返されることがあります。 たとえば、インデックスの CONTAINS を評価する場合、インデックス内の一致の数がクエリ結果の数を超える可能性があります。 クエリ エンジンにより、すべてのインデックス一致が読み込まれ、読み込まれた項目に対してフィルターが評価されて、正しい結果だけが返されます。
ほとんどのクエリでは、擬陽性のインデックス一致を読み込んでも、インデックスの使用に大きな影響はありません。
たとえば、次のクエリを考えてみましょう。
SELECT
*
FROM
product
WHERE
CONTAINS(product.inventory.warehouse, "Sea")
CONTAINSシステム関数は誤検知の一致を返す可能性があるため、クエリ エンジンは、読み込まれた各項目がフィルター式と一致するかどうかを確認する必要があります。 この例では、クエリ エンジンが読み込む必要がある項目はごくわずかであるため、インデックス使用率と RU 料金への影響は最小限です。
ただし、集計関数を使用するクエリでインデックスを使用するには、そのインデックスのみに依存している必要があります。 たとえば、次のような COUNT 集計を含むクエリを考えてみます。
SELECT
COUNT(1)
FROM
product
WHERE
CONTAINS(product.inventory.warehouse, "Sea")
最初の例と同様に、CONTAINS システム関数は誤ったポジティブな一致を返す場合があります。 ただし、SELECT * クエリとは異なり、COUNT クエリでは、読み込まれた項目のフィルター式を評価して、すべてのインデックスの一致を検証することはできません。
COUNT クエリはインデックスに排他的に依存する必要があるため、フィルター式で擬陽性の一致が返される可能性がある場合は、クエリ エンジンによってフル スキャンが実行されます。
次の集計関数を使用するクエリは、インデックスだけに依存する必要があるため、一部のシステム関数を評価するにはフル スキャンが必要です。