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

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

Avant d'optimiser vos requêtes, consultez les concepts associés.

Optimiser les requêtes avec l'explication des requêtes

Pour déterminer si votre requête et vos index sont optimaux, vous pouvez utiliser Expliquer la requête afin d'obtenir le résumé du plan de requête et les statistiques d'exécution de la requête:

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 correct réduit le nombre d'entrées d'index que Cloud Firestore analyse.

Requêtes simples

Avec l'exemple précédent d'une collection 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 renvoyer cinq documents. Comme le prédicat de la requête n'est pas satisfait, un grand nombre d'entrées d'index sont lues, mais sont 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"
        }
    }
}

Vous pouvez déduire de l'expertise du 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 €. Compte tenu de cette information, vous pouvez constater que la contrainte salary est plus sélective que la contrainte experience. Pour influencer l'index utilisé par Cloud 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, Cloud Firestore utilise l'index (salary ASC, experience ASC) pour exécuter la requête. Étant donné que la sélectivité du premier filtre de plage est plus élevée dans cette requête que dans la requête 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