Azure Cosmos DB でのインデックス作成ポリシー

適用対象: NoSQL

Azure Cosmos DB では、すべてのコンテナーに、コンテナーの項目のインデックスを作成する方法を指示するインデックス作成ポリシーがあります。 新しく作成したコンテナーの既定のインデックス作成ポリシーでは、あらゆる項目のあらゆるプロパティのインデックスが作成され、任意の文字列または数値に範囲インデックスが適用されます。 これにより、インデックス作成とインデックス管理を事前に考慮することなく、優れたクエリ パフォーマンスを得ることができます。

状況によっては、この自動動作を、自分の要件にさらに適合するようにオーバーライドできます。 "インデックス作成モード" を設定し、"プロパティ パス" を含めるか除外することで、コンテナーのインデックス作成ポリシーをカスタマイズできます。

Note

この記事で説明するインデックス作成ポリシーの更新方法は、Azure Cosmos DB の NoSQL 用 API にのみ適用されます。 インデックス作成については、MongoDB 用 Azure Cosmos DB API に関するページを参照してください

インデックス作成モード

Azure Cosmos DB では 2 つのインデックス作成モードがサポートされます。

  • 同期: 項目を作成、更新、削除すると、それに同期してインデックスが更新されます。 つまり、読み取りクエリの一貫性は、アカウント用に構成された整合性になります。
  • None:コンテナーでインデックス作成が無効になっています。 このモードは、コンテナーがセカンダリ インデックスを必要としない純粋なキー値ストアとして使用される場合に一般的に使用されます。 一括操作のパフォーマンスを改善する目的で使用することもできます。 一括操作が完了したら、インデックス モードを Consistent に設定し、完了まで IndexTransformationProgress を利用して監視できます。

Note

Azure Cosmos DB では、Lazy インデックス作成モードもサポートされます。 Lazy 方式のインデックスではインデックス更新の優先順位が低く、エンジンが他に何も作業をしていないときに実行されます。 結果的に、クエリの結果に一貫性がなくなったり、不完全になったりします。 Azure Cosmos DB コンテナーに対してクエリを実行する場合は、Lazy インデックス作成を選択しないでください。 新しいコンテナーでは、Lazy インデックス作成を選択できません。 cosmosdbindexing@microsoft.com に連絡して、除外を要求することができます (Lazy インデックス作成をサポートしないサーバーレス モードで Azure Cosmos DB アカウントを使用している場合を除きます)。

既定では、インデックス作成ポリシーは automatic に設定されます。 これはインデックス作成ポリシーの automatic プロパティを true に設定することで行います。 このプロパティを true に設定すると、Azure Cosmos DB で、項目が書き込まれる時点で自動的に項目にインデックスが作成されます。

インデックス サイズ

Azure Cosmos DB の合計使用ストレージは、データ サイズとインデックス サイズ両方の組み合わせです。 インデックス サイズのいくつかの機能を次に示します。

  • インデックス サイズは、インデックス作成ポリシーによって異なります。 すべてのプロパティのインデックスが作成されている場合、インデックス サイズはデータ サイズよりも大きくなることがあります。
  • データが削除されると、インデックスはほぼ連続して圧縮されます。 ただし、少量のデータを削除する場合は、インデックス サイズの減少をすぐに確認できないことがあります。
  • 物理パーティションが分割されると、インデックス サイズが一時的に増大する可能性があります。 インデックス領域は、パーティション分割が完了した後に解放されます。

プロパティ パスを含めるか除外する

カスタム インデックス作成ポリシーには、明示的にインデックス作成に含めるかインデックス作成から除外するプロパティ パスを指定できます。 インデックスが作成されるパスの数を最適化することによって、書き込み操作の待ち時間と RU 料金を大幅に削減できます。 これらのパスは、インデックス作成の概要内のセクションで説明されている方法に従って定義され、以下のように追加されます。

  • スカラー値 (文字列または数値) へのパスは /? で終わる
  • 配列の要素は、(/0/1 ではなく) /[] 表記でまとめて処理される
  • /* ワイルドカードを使用してノードの下の任意の要素を一致させることができる

同じ例をもう一度使用します。

    {
        "locations": [
            { "country": "Germany", "city": "Berlin" },
            { "country": "France", "city": "Paris" }
        ],
        "headquarters": { "country": "Belgium", "employees": 250 },
        "exports": [
            { "city": "Moscow" },
            { "city": "Athens" }
        ]
    }
  • headquartersemployees パスは /headquarters/employees/?

  • locationscountry パスは /locations/[]/country/?

  • headquarters の下にあるもののパスは /headquarters/*

たとえば、/headquarters/employees/? パスを含めることができます。 このパスにより employees プロパティのインデックスが確実に作成されますが、このプロパティ内で入れ子になっている追加の JSON のインデックスは作成されません。

包含/除外戦略

すべてのインデックス作成ポリシーには、含まれるパスまたは除外されるパスのいずれかとしてルート パス /* を指定する必要があります。

  • インデックスを作成する必要がないパスを選択的に除外するためにルート パスを指定する。 モデルに追加される可能性がある新しいプロパティのインデックスを Azure Cosmos DB で先を見越して作成できるため、この方法が推奨されます。

  • インデックスを作成する必要があるパスを選択的に含めるためにルート パスを除外する。 パーティション キーのプロパティ パスは、既定では除外戦略によりインデックスが作成されないため、必要に応じて明示的に含める必要があります。

  • 英数字や _ (アンダースコア) を含む通常文字から成るパスの場合は、パス文字列を二重引用符で囲んでエスケープする必要はありません ("/path/?" など)。 他の特殊文字を含むパスの場合は、パス文字列を二重引用符で囲んでエスケープする必要があります ("/"path-abc"/?" など)。 パスの中に特殊文字が予測される場合は、安全のために、すべてのパスをエスケープすることができます。 機能的には、すべてのパスをエスケープしても、特殊文字を含むパスだけをエスケープしても、違いはまったくありません。

  • システム プロパティ _etag は、etag がインデックス作成対象パスに追加されていない限り、既定でインデックス作成から除外されます。

  • インデックス作成モードが [consistent](同期) に設定されている場合、システム プロパティ id_ts には自動的にインデックスが作成されます。

  • 明示的にインデックスされたパスが項目に存在しない場合は、パスが未定義であることを示す値がインデックスに追加されます。

明示的に含まれるすべてのパスには、特定の項目についてパスが未定義の場合でも、コンテナー内の各項目についてインデックスに値が追加されます。

パスを含めたり除外したりするためのインデックス作成ポリシーの例については、こちらのセクションを参照してください。

優先順位を含める、または除外する

含まれるパスと除外されるパスに競合がある場合は、より正確なパスが優先されます。

次に例を示します。

含まれるパス: /food/ingredients/nutrition/*

除外されるパス: /food/ingredients/*

この場合、含まれるパスはより正確であるため、除外されるパスよりも優先されます。 これらのパスに基づいて、food/ingredients パス内または入れ子になっているすべてのデータは、インデックスから除外されます。 例外は、含まれるパス /food/ingredients/nutrition/* 内のデータで、インデックスが作成されます。

Azure Cosmos DB の含まれるパスと除外されるパスの優先順位に関するいくつかの規則を次に示します。

  • より深いパスは、浅いパスよりも正確です。 たとえば、/a/b/?/a/? よりも正確です。

  • /?/* よりも正確です。 たとえば /a/?/a/* よりも正確であるため、/a/? が優先されます。

  • パス /* には、含まれるパスまたは除外されるパスを指定する必要があります。

空間インデックス

インデックス作成ポリシーで空間パスを定義する場合は、そのパスに適用するインデックスの type を定義する必要があります。 空間インデックスには、次のような種類があります。

  • ポイント

  • 多角形

  • MultiPolygon

  • LineString

Azure Cosmos DB では、既定で空間インデックスは作成されません。 空間 SQL 組み込み関数を使用する場合は、必要なプロパティに対して空間インデックスを作成する必要があります。 空間インデックスを追加するためのインデックス作成ポリシーの例については、このセクションを参照してください。

複合インデックス

2 つ以上のプロパティを使用する ORDER BY 句が含まれるクエリには、複合インデックスが必要です。 また、複合インデックスを定義して、多くの等値クエリと範囲クエリのパフォーマンスを向上させることもできます。 既定では、複合インデックスは定義されないため、必要に応じて複合インデックスを追加する必要があります。

含まれるパスまたは除外されるパスとは異なり、/* ワイルドカードを使用してパスを作成することはできません。 すべての複合パスには、指定する必要のないパスの末尾に暗黙的な /? があります。 複合パスは、複合インデックスに含まれる唯一の値であるスカラー値になります。 複合インデックスにあるパスが項目に存在しない場合は、パスが未定義であることを示す値がインデックスに追加されます。

複合インデックスを定義する場合は、次のものを指定します。

  • 2 つ以上のプロパティ パス。 プロパティ パスが定義されるシーケンスが重要です。

  • 順序 (昇順または降順)。

Note

複合インデックスを追加したとき、新しい複合インデックスの追加が完了するまで、クエリは既存の範囲インデックスを利用します。 そのため、複合インデックスを追加しても、すぐにパフォーマンス向上が見られないことがあります。 いずれかの SDK を使用して、インデックス変換の進行状況を追跡できます。

複数のプロパティに対する ORDER BY クエリ:

2 つ以上のプロパティを使用する ORDER BY 句が含まれるクエリに複合インデックスを使用する場合は、次の考慮事項を確認します。

  • 複合インデックスのパスが ORDER BY 句の中のプロパティのシーケンスに一致しない場合、その複合インデックスはクエリをサポートできません。

  • 複合インデックスのパスの順序 (昇順または降順) は、ORDER BY 句の中の order とも一致する必要があります。

  • 複合インデックスはまた、すべてのパスで反対の順序を持つ ORDER BY 句もサポートします。

複合インデックスがプロパティ name、age、および _ts に対して定義されている次の例を考えてみます。

複合インデックス サンプルの ORDER BY クエリ 複合インデックスでサポートされているか
(name ASC, age ASC) SELECT * FROM c ORDER BY c.name ASC, c.age asc Yes
(name ASC, age ASC) SELECT * FROM c ORDER BY c.age ASC, c.name asc No
(name ASC, age ASC) SELECT * FROM c ORDER BY c.name DESC, c.age DESC Yes
(name ASC, age ASC) SELECT * FROM c ORDER BY c.name ASC, c.age DESC No
(name ASC, age ASC, timestamp ASC) SELECT * FROM c ORDER BY c.name ASC, c.age ASC, timestamp ASC Yes
(name ASC, age ASC, timestamp ASC) SELECT * FROM c ORDER BY c.name ASC, c.age ASC No

すべての必要な ORDER BY クエリに対応できるように、インデックス作成ポリシーをカスタマイズする必要があります。

複数のプロパティに対するフィルターを含むクエリ

クエリに 2 つ以上のプロパティに対するフィルターが含まれている場合は、これらのプロパティの複合インデックスを作成すると便利な場合があります。

たとえば、等値および範囲の両方のフィルターが含まれる次のクエリについて考えてみます。

SELECT *
FROM c
WHERE c.name = "John" AND c.age > 18

このクエリは、(name ASC, age ASC) の複合インデックスを利用できる場合、時間が短縮され、RU の消費が少なくなるため、より効率的に実行できます。

範囲フィルターが複数含まれるクエリを複合インデックスを使用して最適化することもできます。 ただし、個々の複合インデックスで最適化できる範囲フィルターはそれぞれ 1 つだけです。 範囲フィルターは、><<=>=!= です。 範囲フィルターは、複合インデックス内で最後に定義する必要があります。

等値フィルターが 1 つと範囲フィルターが 2 つ含まれる次のクエリについて考えてみます。

SELECT *
FROM c
WHERE c.name = "John" AND c.age > 18 AND c._ts > 1612212188

このクエリは、(name ASC, age ASC)(name ASC, _ts ASC) の複合インデックスを使用すると、より効率的に実行できます。 ただし、等値フィルターが含まれるプロパティを複合インデックス内で最初に定義する必要があるため、クエリでは (age ASC, name ASC) の複合インデックスが使用されません。 各複合インデックスで最適化できる範囲フィルターは 1 つだけであるため、(name ASC, age ASC, _ts ASC) の複合インデックスを 1 つだけではなく、個別の複合インデックスを 2 つ必要とします。

複数のプロパティに対するフィルターが含まれるクエリ用に複合インデックスを作成する場合は、次の考慮事項を確認します。

  • フィルター式では、複数の複合インデックスを使用できます。
  • クエリのフィルター内のプロパティは、複合インデックス内のプロパティと一致している必要があります。 複合インデックスにプロパティが含まれていても、クエリにフィルターとして含まれていない場合、このクエリでは複合インデックスは使用されません。
  • 複合インデックスで定義されていない他のプロパティがフィルターに含まれているクエリの場合は、複合インデックスと範囲インデックスの組み合わせを使用してクエリが評価されます。 この場合、範囲インデックスのみ使用する場合よりも、必要な RU が少なくなります。
  • プロパティに範囲フィルター (><<=>=、または !=) が含まれている場合、このプロパティは複合インデックス内で最後に定義する必要があります。 クエリに複数の範囲フィルターが含まれている場合、複数の複合インデックスを利用すると効果的になる可能性があります。
  • 複数のフィルターが含まれるクエリを最適化するために複合インデックスを作成する場合、複合インデックスの ORDER は結果に影響しません。 このプロパティは省略可能です。

複合インデックスがプロパティ name、age、および timestamp に対して定義されている次の例を考えてみます。

複合インデックス サンプル クエリ 複合インデックスでサポートされているか
(name ASC, age ASC) SELECT * FROM c WHERE c.name = "John" AND c.age = 18 Yes
(name ASC, age ASC) SELECT * FROM c WHERE c.name = "John" AND c.age > 18 Yes
(name ASC, age ASC) SELECT COUNT(1) FROM c WHERE c.name = "John" AND c.age > 18 Yes
(name DESC, age ASC) SELECT * FROM c WHERE c.name = "John" AND c.age > 18 Yes
(name ASC, age ASC) SELECT * FROM c WHERE c.name != "John" AND c.age > 18 No
(name ASC, age ASC, timestamp ASC) SELECT * FROM c WHERE c.name = "John" AND c.age = 18 AND c.timestamp > 123049923 Yes
(name ASC, age ASC, timestamp ASC) SELECT * FROM c WHERE c.name = "John" AND c.age < 18 AND c.timestamp = 123049923 No
(name ASC, age ASC) and (name ASC, timestamp ASC) SELECT * FROM c WHERE c.name = "John" AND c.age < 18 AND c.timestamp > 123049923 Yes

フィルターと ORDER BY を使用したクエリ

クエリで 1 つ以上のプロパティがフィルター処理され、ORDER BY 句に異なるプロパティが含まれている場合は、フィルター内のプロパティを ORDER BY 句に追加すると便利な場合があります。

たとえば、フィルター内のプロパティを ORDER BY 句に追加すると、複合インデックスを利用するために次のクエリを書き直すことができます。

範囲インデックスを使用するクエリ:

SELECT *
FROM c 
WHERE c.name = "John" 
ORDER BY c.timestamp

複合インデックスを使用するクエリ:

SELECT * 
FROM c 
WHERE c.name = "John"
ORDER BY c.name, c.timestamp

フィルターを使用した任意の ORDER BY クエリに対しても同じクエリ最適化を一般化できます。このとき、個々の複合インデックスでサポートできる範囲フィルターは最大で 1 つだけであることに注意してください。

範囲インデックスを使用するクエリ:

SELECT * 
FROM c 
WHERE c.name = "John" AND c.age = 18 AND c.timestamp > 1611947901 
ORDER BY c.timestamp

複合インデックスを使用するクエリ:

SELECT * 
FROM c 
WHERE c.name = "John" AND c.age = 18 AND c.timestamp > 1611947901 
ORDER BY c.name, c.age, c.timestamp

さらに、複合インデックスを使用して、システム関数と ORDER BY を含むクエリを最適化することができます。

範囲インデックスを使用するクエリ:

SELECT * 
FROM c 
WHERE c.firstName = "John" AND Contains(c.lastName, "Smith", true) 
ORDER BY c.lastName

複合インデックスを使用するクエリ:

SELECT * 
FROM c 
WHERE c.firstName = "John" AND Contains(c.lastName, "Smith", true) 
ORDER BY c.firstName, c.lastName

次の考慮事項は、フィルターと ORDER BY 句が含まれるクエリを最適化するために複合インデックスを作成する場合に適用されます。

  • 1 つのプロパティに対するフィルターと、異なるプロパティを使用する別の ORDER BY 句が含まれるクエリに対して複合インデックスを定義しなかった場合でも、クエリは成功します。 しかし、複合インデックスを使用すると、特に ORDER BY 句内のプロパティのカーディナリティが高い場合、クエリの RU コストを削減できます。
  • クエリでプロパティをフィルター処理する場合は、最初にこれらのプロパティを ORDER BY 句に含める必要があります。
  • クエリで複数のプロパティをフィルター処理する場合は、等値フィルターを ORDER BY 句内で最初のプロパティとする必要があります。
  • クエリで複数のプロパティをフィルター処理する場合、複合インデックスごとに使用できる範囲フィルターまたはシステム関数の数は最大で 1 つとなります。 範囲フィルターまたはシステム関数で使用するプロパティは、複合インデックス内で最後に定義する必要があります。
  • 複合インデックスを、複数のプロパティが含まれる ORDER BY クエリ、および複数のプロパティに対するフィルターが含まれるクエリに対して作成する際の考慮事項もすべて、適用されます。
複合インデックス サンプルの ORDER BY クエリ 複合インデックスでサポートされているか
(name ASC, timestamp ASC) SELECT * FROM c WHERE c.name = "John" ORDER BY c.name ASC, c.timestamp ASC Yes
(name ASC, timestamp ASC) SELECT * FROM c WHERE c.name = "John" AND c.timestamp > 1589840355 ORDER BY c.name ASC, c.timestamp ASC Yes
(timestamp ASC, name ASC) SELECT * FROM c WHERE c.timestamp > 1589840355 AND c.name = "John" ORDER BY c.timestamp ASC, c.name ASC No
(name ASC, timestamp ASC) SELECT * FROM c WHERE c.name = "John" ORDER BY c.timestamp ASC, c.name ASC No
(name ASC, timestamp ASC) SELECT * FROM c WHERE c.name = "John" ORDER BY c.timestamp ASC No
(age ASC, name ASC, timestamp ASC) SELECT * FROM c WHERE c.age = 18 and c.name = "John" ORDER BY c.age ASC, c.name ASC,c.timestamp ASC Yes
(age ASC, name ASC, timestamp ASC) SELECT * FROM c WHERE c.age = 18 and c.name = "John" ORDER BY c.timestamp ASC No

フィルターと集計を使用したクエリ

クエリが 1 つ以上のプロパティをフィルター処理し、集計システム関数を持つ場合は、フィルターおよび集計システム関数のプロパティの複合インデックスを作成すると便利な場合があります。 この最適化は、SUM および AVG システム関数に適用されます。

次の考慮事項は、フィルターと集計システム関数が含まれるクエリを最適化するために複合インデックスを作成する場合に適用されます。

  • 複合インデックスは、集計を使用したクエリを実行するときは省略可能です。 しかし、多くの場合、クエリの RU コストは複合インデックスによって大きく削減できます。
  • クエリで複数のプロパティをフィルター処理する場合、等値フィルターを複合インデックスの最初のプロパティとする必要があります。
  • 範囲フィルターを複合インデックスごとに最大で 1 つ設定でき、それを集計システム関数のプロパティに指定する必要があります。
  • 集計システム関数のプロパティは、複合インデックス内で最後に定義する必要があります。
  • order (ASC または DESC) は関係ありません。
複合インデックス サンプル クエリ 複合インデックスでサポートされているか
(name ASC, timestamp ASC) SELECT AVG(c.timestamp) FROM c WHERE c.name = "John" Yes
(timestamp ASC, name ASC) SELECT AVG(c.timestamp) FROM c WHERE c.name = "John" No
(name ASC, timestamp ASC) SELECT AVG(c.timestamp) FROM c WHERE c.name > "John" No
(name ASC, age ASC, timestamp ASC) SELECT AVG(c.timestamp) FROM c WHERE c.name = "John" AND c.age = 25 Yes
(age ASC, timestamp ASC) SELECT AVG(c.timestamp) FROM c WHERE c.name = "John" AND c.age > 25 No

インデックス作成ポリシーの変更

コンテナーのインデックス作成ポリシーは、Azure portal またはサポートされている SDK のいずれかを使用していつでも更新できます。 インデックス作成ポリシーを更新すると、古いインデックスから新しいものへの変換がトリガーされ、オンラインでその場で実行されます (そのため、この操作中に記憶域が追加で消費されることはありません)。 古いインデックス作成ポリシーは、新しいポリシーに効率的に変換され、コンテナー上での書き込み可用性、読み取り可用性、またはプロビジョニングされたスループットが影響を受けることはありません。 インデックス変換は非同期操作であり、完了までにかかる時間は、プロビジョニングされたスループット、項目の数、およびそれらのサイズによって決まります。 複数のインデックス作成ポリシーの更新を行う必要がある場合は、インデックスの変換ができるだけ早く完了するように、すべての変更を 1 回の操作として実行することをお勧めします。

重要

インデックス変換は要求ユニットを消費する操作です。 サーバーレス コンテナーを使用している場合、現在のところ、インデックス変換によって消費される要求単位には課金されません。 これらの要求単位は、サーバーレスが一般提供されるようになったときに課金されます。

Note

インデックス変換の進行状況は、Azure portal で、またはいずれかの SDK を使用して追跡できます。

インデックス変換中に、書き込み可用性への影響はありません。 インデックス変換にはプロビジョニングされた RU が使用されますが、CRUD 操作やクエリよりも低い優先順位になります。

新しいインデックス付きパスを追加するときに、読み取り可用性への影響がありません。 クエリで新しいインデックス付きパスが使用されるのは、インデックス変換が完了してからです。 つまり新しいインデックス付きパスを追加しているとき、そのインデックス付きパスの恩恵を受けるクエリのパフォーマンスは、インデックス変換の前と途中では同じです。 インデックス変換が完了すると、クエリ エンジンは新しいインデックス付きパスを使用し始めます。

インデックス付きパスを削除しているときは、すべての変更を 1 つのインデックス作成ポリシー変換にまとめる必要があります。 1 回のインデックス作成ポリシーの変更で、複数のインデックスを削除する場合は、クエリ エンジンによって、インデックス変換全体で結果の整合性と完全性が提供されます。 ただし、複数回のインデックス作成ポリシーの変更を通じてインデックスを削除する場合、クエリ エンジンでは、すべてのインデックス変換が完了するまで、結果の整合性または完全性は提供されません。 ほとんどの開発者は、インデックスの削除後、これらのインデックスを使用するクエリをすぐに実行しようとはしません。実際には、このような状況になることはほとんどありません。

インデックス付きパスを破棄すると、クエリ エンジンはすぐに使用するのを止めて、代わりにフル スキャンを実行します。

Note

可能であれば、複数のインデックス削除を 1 回のインデックス作成ポリシーの変更にグループ化しようとする必要があります。

重要

インデックスの削除は直ちに反映されますが、新しいインデックスの追加は、インデックス作成変換が必要であるため、しばらく時間がかかります。 1 つのインデックスを別のインデックスに置き換える場合 (たとえば、単一のプロパティ インデックスを複合インデックスに置き換える場合)、まず新しいインデックスを最初に追加し、インデックスの変換が完了するのを待ってから、以前のインデックスをインデックス作成ポリシーから削除するようにしてください。 そうしないと、前のインデックスに対してクエリを実行する機能に悪影響が及び、前のインデックスを参照するアクティブなワークロードが中断される可能性があります。

インデックス作成ポリシーと TTL

Time-to-Live (TTL) 機能を使用するには、インデックス作成が必要です。 これは、次のことを意味します。

  • インデックス作成モードが none に設定されているコンテナーで、TTL をアクティブにすることはできません。
  • TTL がアクティブになっているコンテナーで、インデックス作成モードを [なし] に設定することはできません。

インデックス作成が必要なプロパティ パスはないものの、TTL が必要なシナリオでは、インデックス作成モードが consistent に設定され、パスが含まれておらず、/* が除外された唯一のパスとなっているインデックス作成ポリシーを使用できます。

次のステップ

以下の記事で、インデックス作成についての詳細を参照してください。