Optimiza las consultas con filtros de rango y desigualdad en varios campos

En esta página, se proporcionan ejemplos de la estrategia de indexación que se debe usar en las búsquedas con filtros de rango y desigualdad en varios campos para crear una experiencia de consulta eficiente.

Lee sobre los conceptos relacionados antes de optimizar las consultas.

Optimiza consultas con una explicación de consultas

Para determinar si la consulta y los índices usados son óptimos, puedes usar Explicación de consulta para obtener el resumen del plan de consultas 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 índices reduce la cantidad de entradas de índice que Firestore analiza.

Consultas simples

Con 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 solo para mostrar 5 documentos. Dado que el predicado de la consulta no se cumple, una gran cantidad de entradas de índice se leen, 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"
        }
    }
}

Podemos inferir a partir de la experiencia en el área que la mayoría de los empleados tendrán al menos algo de experiencia, pero pocos tendrán un salario superior a 100,000. A partir de esta estadística, podemos concluir que la restricción salary es más selectiva que la restricción experience. Si quieres influir en el índice que usa Firestore para ejecutar la consulta, especifica una cláusula orderBy que ordene la restricción salary antes que la restricción experience.

Java

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

Cuando usas la cláusula orderBy() de forma explícita para agregar los predicados, Firestore usa el índice (salary ASC, experience ASC) para ejecutar la consulta. Por lo tanto, debido a que la selectividad del primer filtro de rango es más alta en esta consulta en comparación con la consulta 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"
        }
    }
}

Pasos siguientes