Optimiser les requêtes avec des filtres de plage et d'inégalité sur plusieurs champs

Cette page fournit des exemples de stratégie d'indexation à utiliser pour les requêtes comportant des filtres de plage et d'inégalité sur plusieurs champs afin de créer une expérience de requête efficace.

Découvrez les concepts associés avant d'optimiser vos requêtes.

Optimiser les requêtes avec Query Explain

Pour déterminer si la requête et les index utilisés sont optimaux, vous pouvez obtenir le résumé du plan de requête et les statistiques d'exécution de la requête à l'aide de la fonctionnalité Query Explain:

Java

Query q = db.collection("employees").whereGreaterThan("salary",
100000).whereGreaterThan("experience", 0);

ExplainResults<QuerySnapshot> explainResults = q.explain(ExplainOptions.builder().analyze(true).build()).get();
ExplainMetrics metrics = explainResults.getMetrics();

PlanSummary planSummary = metrics.getPlanSummary();
ExecutionStats executionStats = metrics.getExecutionStats();

System.out.println(planSummary.getIndexesUsed());
System.out.println(stats.getResultsReturned());
System.out.println(stats.getExecutionDuration());
System.out.println(stats.getReadOperations());
System.out.println(stats.getDebugStats());

Node.js

let q = db.collection("employees")
      .where("salary", ">", 100000)
      .where("experience", ">",0);

let options = { analyze : 'true' };
let explainResults = await q.explain(options);

let planSummary = explainResults.metrics.planSummary;
let stats = explainResults.metrics.executionStats;

console.log(planSummary);
console.log(stats);

L'exemple suivant montre comment l'utilisation d'un ordre d'index approprié réduit le nombre d'entrées d'index analysées par Firestore.

Requêtes simples

Pour l'exemple précédent d'un ensemble d'employés, la requête simple exécutée avec l'index (experience ASC, salary ASC) est la suivante:

Java

db.collection("employees")
  .whereGreaterThan("salary", 100000)
  .whereGreaterThan("experience", 0)
  .orderBy("experience")
  .orderBy("salary");

La requête analyse 95 000 entrées d'index pour ne renvoyer que 5 documents. Comme le prédicat de requête n'est pas satisfait, un grand nombre d'entrées d'index sont lues, mais filtrées.

// Output query planning info
{
    "indexesUsed": [
        {
            "properties": "(experience ASC, salary ASC, __name__ ASC)",
            "query_scope": "Collection"
        }
    ],

    // 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"
        }
    }
}

Nous pouvons déduire de l'expertise dans le domaine que la plupart des employés auront au moins une certaine expérience, mais que peu d'entre eux auront un salaire supérieur à 100 000. Grâce à cet insight, nous pouvons en conclure que la contrainte salary est plus sélective que la contrainte experience. Pour influencer l'index utilisé par Firestore pour exécuter la requête, spécifiez une clause orderBy qui ordonne la contrainte salary avant la contrainte experience.

Java

db.collection("employees")
  .whereGreaterThan("salary", 100000)
  .whereGreaterThan("experience", 0)
  .orderBy("salary")
  .orderBy("experience");

Lorsque vous utilisez explicitement la clause orderBy() pour ajouter les prédicats, Firestore utilise l'index (salary ASC, experience ASC) pour exécuter la requête. Ainsi, étant donné que la sélectivité du filtre de la première plage est plus élevée dans cette requête que dans la précédente, la requête s'exécute plus rapidement et est plus rentable.

// Output query planning info
{
    "indexesUsed": [
        {
            "properties": "(salary ASC, experience ASC, __name__ ASC)",
            "query_scope": "Collection"
        }
    ],

    // 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"
        }
    }
}

Étape suivante