Optimizar consultas con filtros de intervalo y de desigualdad en varios campos

En esta página se proporcionan ejemplos de estrategias de indexación que puedes usar en consultas con filtros de intervalo y de desigualdad en varios campos para crear una experiencia de consulta eficiente.

Antes de optimizar tus consultas, consulta los conceptos relacionados.

Optimizar consultas con Query Explain

Para determinar si tu consulta e índices son óptimos, puedes usar Query Explain para obtener el resumen del plan de consulta y las estadísticas de ejecución de la consulta:

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);

En el siguiente ejemplo se muestra cómo el uso del orden correcto de los índices reduce el número de entradas de índice que analiza Firestore.

Consultas sencillas

En el ejemplo anterior de una colección de empleados, la consulta simple que se ejecuta con el índice (experience ASC, salary ASC) es la siguiente:

Java

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

La consulta analiza 95.000 entradas de índice para devolver cinco documentos. Como no se cumple el predicado de la consulta, se leen muchas entradas de índice, pero se filtran.

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

Puedes deducir de la experiencia en el dominio que la mayoría de los empleados tendrán al menos algo de experiencia, pero pocos tendrán un salario superior a 100.000. Con esta información, puedes ver que la restricción salary es más selectiva que la restricción experience. Para influir en el índice que usa Firestore para ejecutar la consulta, especifica una cláusula orderBy que ordene la restricción salary antes de la restricción experience.

Java

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

Cuando usas explícitamente la cláusula orderBy() para añadir los predicados, Firestore usa el índice (salary ASC, experience ASC) para ejecutar la consulta. Como la selectividad del primer filtro de intervalo es mayor en esta consulta que en la anterior, la consulta se ejecuta más rápido y es más 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"
        }
    }
}

Siguientes pasos