複数のプロパティに対する範囲フィルタと不等式フィルタを使用してクエリを最適化する

このページでは、効率的なクエリ エクスペリエンスを作成するために、複数のフィールドに対して範囲フィルタと不等式フィルタを使用したクエリに対して使用できるインデックス戦略の例を紹介します。

クエリを最適化する前に、複数のプロパティに対する範囲フィルタと不等式フィルタのコンセプトをご覧ください。

クエリ Explain を使用してクエリを最適化する

使用するクエリとインデックスが最適かどうかを判断するには、クエリ Explain を使用してクエリを作成し、実行の概要を確認します。

Java

...
// Build the query
Query<Entity> query =
    Query.newEntityQueryBuilder()
        .setKind("employees")
        .setFilter(
            CompositeFilter.and(
                PropertyFilter.gt("salary", 100000), PropertyFilter.gt("experience", 0)))
        .setOrderBy(OrderBy("experience"), OrderBy("salary"))
        .build();

// Set the explain options to get back *only* the plan summary
ExplainResults<Entity> explainResults = datastore.run(query, ExplainOptions.newBuilder().build());

// Get the explain metrics
Optional<ExplainMetrics> explainMetrics = results.getExplainMetrics();
if (!explainMetrics.isPresent()) {
  throw new Exception("No explain metrics returned");
}

// Get the plan summary
PlanSummary planSummary = explainMetrics.get().getPlanSummary();
List<Map<String, Object>> indexesUsed = planSummary.getIndexesUsed();
System.out.println("----- Indexes Used -----");
indexesUsed.forEach(map -> map.forEach((s, o) -> System.out.println(s + ": " + o)));

// Get the execution stats
if (!explainMetrics.getExecutionStats().isPresent()) {
  throw new Exception("No execution stats returned");
}

ExecutionStats queryStats = explainMetrics.getExecutionStats().get();
Map<String, Object> debugStats = queryStats.getDebugStats();
System.out.println("----- Debug Stats -----");
debugStats.forEach((s, o) -> System.out.println(s + ": " + o));

次の例は、正しいインデックスの順序付けを使用して、Datastore モードの Firestore がスキャンするエンティティの数を節約する方法を示しています。

単純なクエリ

従業員のコレクションに関する前述の例では、(salary, experience) インデックスで実行するシンプルなクエリが次のようになります。

GQL

SELECT *
FROM /employees
WHERE salary > 100000 AND experience > 0
ORDER BY experience, salary;

Java

Query<Entity> query =
   Query.newEntityQueryBuilder()
    .setKind("employees")
    .setFilter(
        CompositeFilter.and(
            PropertyFilter.gt("salary", 100000), PropertyFilter.gt("experience", 0)))
    .setOrderBy(OrderBy("experience"), OrderBy("salary"))
    .build();

このクエリは 95,000 件のインデックス エントリをスキャンしますが、返されるドキュメントは 5 件にすぎません。多数のインデックス エントリが読み取られましたが、クエリ述部を満たしていないため除外されました。

// Output query planning info
{
        "indexesUsed": [
            {
                "query_scope": "Collection Group",
                "properties": "(experience ASC, salary ASC, __name__ ASC)"
            }
        ]
    },
    // Output Query Execution Stats
    {
        "resultsReturned": "5",
        "executionDuration": "2.5s",
        "readOperations": "100",
        "debugStats": {
            "index_entries_scanned": "95000",
            "documents_scanned": "5",
            "billing_details": {
                "documents_billable": "5",
                "index_entries_billable": "95000",
                "small_ops": "0",
                "min_query_cost": "0"
            }
        }
    }

前の例から、salary 制約は experience 制約よりも選択的であると推定できます。

GQL

SELECT *
FROM /employees
WHERE salary > 100000 AND experience > 0
ORDER BY salary, experience;

Java

Query<Entity> query =
   Query.newEntityQueryBuilder()
    .setKind("employees")
    .setFilter(
        CompositeFilter.and(
            PropertyFilter.gt("salary", 100000), PropertyFilter.gt("experience", 0)))
    .setOrderBy(OrderBy("salary"), OrderBy("experience"))
    .build();

orderBy() 句を明示的に使用して、前の順序で述語を追加する場合、Datastore モードの Firestore は、(salary, experience) インデックスを使用してクエリを実行します。最初の範囲フィルタの選択性が前のクエリよりも優れているため、クエリの実行速度が速くなり、費用対効果が向上します。

    // Output query planning info
{
    "indexesUsed": [
        {
            "query_scope": "Collection Group",
            "properties": "(salary ASC, experience ASC, __name__ ASC)"
        }
    ],
    // Output Query Execution Stats
        "resultsReturned": "5",
        "executionDuration": "0.2s",
        "readOperations": "6",
        "debugStats": {
            "index_entries_scanned": "1000",
            "documents_scanned": "5",
            "billing_details": {
                "documents_billable": "5",
                "index_entries_billable": "1000",
                "small_ops": "0",
                "min_query_cost": "0"
            }
        }
    }

次のステップ