クエリのパフォーマンスの最適化

低速なクエリのトラブルシューティングを行うには、Query Explain を使用して、クエリ実行プランとランタイム実行プロファイルを取得します。次のセクションでは、実行プロファイルに応じてクエリ パフォーマンスを最適化する手順について説明します。

結果の個数を制限する

実行ツリーの返されたレコードのフィールドを使用して、クエリが多くのドキュメントを返しているかどうかを確認します。limit(...) ステージを使用して、返されるドキュメントの数を制限することを検討してください。これにより、ネットワーク経由でクライアントに返される結果のシリアル化されたバイトサイズが小さくなります。Limit ノードの前に MajorSort ノードがある場合、クエリエンジンでは Limit ノードと MajorSort ノードを統合し、完全なインメモリ実体化と並べ替えを TopN 並べ替えに置き換えて、クエリのメモリ要件を削減できます。

結果のドキュメントのサイズを制限する

select(...) を使用して必要なフィールドのみを返すか、remove_fields(...) を使用して大きすぎるフィールドを破棄することで、返されるドキュメントのサイズを制限することを検討してください。こうすることで、中間結果の処理にかかるコンピューティング コストとメモリコスト、およびネットワーク経由でクライアントに返される結果のシリアル化されたバイトサイズを削減できます。クエリで参照されるすべてのフィールドが通常のインデックスでカバーされている場合、クエリはインデックス スキャンで完全にカバーされるため、プライマリ ストレージからドキュメントを取得する必要がなくなります。

インデックスを使用する

次の手順でインデックスを設定して最適化します。

クエリでインデックスが使用されているかどうかを確認する

クエリがインデックスを使用しているかどうかは、実行ツリーのリーフノードを確認することで特定できます。実行ツリーのリーフノードが TableScan ノードの場合、クエリはインデックスを使用しておらず、プライマリ ストレージからドキュメントをスキャンしています。インデックスが使用されている場合、実行ツリーのリーフノードには、インデックスのインデックス ID とインデックス フィールドが表示されます。

より適切なインデックスを特定する

インデックスは、クエリエンジンがプライマリ ストレージから取得する必要があるドキュメントの数を減らすことができる場合、またはそのフィールドの順序でクエリの並べ替え要件を満たすことができる場合に、クエリに役立ちます。

クエリにインデックスが使用されているにもかかわらず、クエリエンジンが多くのドキュメントを取得して破棄している場合(多くのレコードを返すスキャンノードの後に、少数のレコードを返すフィルタノードが続く場合)、インデックスを使用して満たされたクエリ述部が選択的でないことを示しています。より適切なインデックスを作成するには、インデックスを作成するをご覧ください。

クエリにインデックスが使用されているにもかかわらず、クエリ実行ツリーの MajorSort ノードで特定されるように、クエリエンジンが結果セットのメモリ内並べ替えを実行している場合、使用されているインデックスではクエリの並べ替え要件を満たせないことを示しています。より適切なインデックスを作成するには、次のセクションをご覧ください。

インデックスを作成する

インデックス管理のドキュメントに沿って、インデックスを作成します。クエリでインデックスを使用できるようにするには、次の順序でフィールドを含む通常の(マルチキーではない)インデックスを作成します。

  1. 等価演算子で使用されるすべてのフィールド。クエリ間で再利用される可能性を最大化するには、クエリ間の等価演算子でフィールドが出現する回数の降順でフィールドを並べ替えます。
  2. 並べ替えに使用されるすべてのフィールド(同じ順序)。
  3. 不等式演算子の範囲で使用されるフィールド(クエリ制約の選択性の降順)。
  4. インデックスのクエリの一部として返されるフィールド: このようなフィールドをインデックスに含めると、インデックスでクエリをカバーできるため、プライマリ ストレージからドキュメントを取得する必要がなくなります。

インデックス スキャンまたはテーブル スキャンを強制する

ネイティブ モードで Firestore に対してクエリを実行すると、クエリを効率化する可能性のあるインデックスが自動的に使用されます。そのため、クエリにインデックスを指定する必要はありません。ただし、ワークロードに重要なクエリについては、より安定したパフォーマンスのため、forceIndex オプションを使用することをおすすめします。

まれに、ネイティブ モードの Firestore がクエリのレイテンシを増加させるインデックスを選択することがあります。パフォーマンス低下のトラブルシューティングの手順に従い、クエリに別のインデックスを試すことが理にかなっていると確認した場合、forceIndex オプションを使用してインデックスを指定できます。

パイプライン オペレーションの入力ステージで forceIndex オプションを使用すると、ネイティブ モードの Firestore のデフォルトのクエリプランをオーバーライドして、使用するインデックスを指定したり、テーブル スキャンを強制したりできます。

特定のインデックスを強制する

クエリで特定のインデックスを強制的に使用するには、インデックス ID を文字列として forceIndex オプションに指定します。インデックス ID は、コンソールまたはエラー メッセージで確認できます。

次の例では、プランナーに ID が CICAgOi36pgK のインデックスを使用するように強制します。

// Force Planner to use Index ID CICAgOi36pgK
db.pipeline()
  .collectionGroup({ collectionId: "customers", forceIndex: "CICAgOi36pgK" })
  .limit(100)

特定のインデックスを強制的に使用するユースケースを次に示します。

  • さまざまなインデックスのパフォーマンスをテストする。
  • 特定の既知の最適なインデックスがクエリに使用されるようにします。
  • 特定のクエリに対してデフォルトの選択が最適でない場合に、オプティマイザーをオーバーライドする。

指定したインデックスが見つからない場合、クエリは失敗します。

テーブル スキャンを強制する

テーブル スキャンでは、セカンダリ インデックスを使用せずに、コレクションまたはコレクション グループ内のドキュメントを読み取ります。テーブル スキャンを強制するには、forceIndexprimary に設定します。

次の例では、テーブル スキャンを強制します。

// Force Planner to only do a Full-Table Scan
db.pipeline()
  .collectionGroup({ collectionId: "customers", forceIndex: "primary" })
  .limit(100)

テーブル スキャンは、次のような場合に使用できます。

  • インデックスのオーバーヘッドが正当化されない非常に小さなコレクションの場合。
  • コレクション内のほとんどのドキュメントにアクセスするクエリの場合。
  • デバッグとパフォーマンスの比較に使用します。

Query Explain で forceIndex を使用する

Query Explain(特に analyze オプション)を使用すると、forceIndex の効果を確認できます。

  • 実行ツリーのリーフノードでインデックス ID を確認して、ネイティブ モードの Firestore が forceIndex で指定されたインデックスを使用したことを確認します。
  • forceIndex: "primary" を使用すると、プランに TableScan ノードが表示されることを確認します。
  • forceIndex の有無でレイテンシ、スキャンされたドキュメント、スキャンされたインデックス エントリなどのパフォーマンス指標を比較して、クエリのパフォーマンスを微調整します。

forceIndex のベスト プラクティス

forceIndex を使用するとクエリ実行をより詳細に制御できますが、Firestore ネイティブ モードのクエリ オプティマイザーは、ほとんどのユースケースで効率的です。forceIndex を使用する際は、次のベスト プラクティスを考慮してください。

  • forceIndex は慎重に使用してください。デフォルトのクエリプランでパフォーマンスが最適でない場合は、クエリ Explain を使用して問題を診断してから、インデックスを強制適用します。
  • forceIndex を使用する場合は、クエリのパフォーマンスと費用の特性を把握するために、現実的なデータ量でクエリをテストしてください。
  • 本番環境の大きなコレクションでは forceIndex: "primary" を使用しないでください。