Daten mit Firestore-Pipelinevorgängen abrufen

Hintergrund

Pipelinevorgänge bieten eine neue Abfrageschnittstelle für Cloud Firestore, die erweiterte Abfragefunktionen und komplexe Ausdrücke unterstützt. Es werden viele neue Funktionen eingeführt, darunter min(...), max(...), substring(...), regex_match(...) und array_contains_all(...), sowie Phasen, um komplexe Transformationen durchführen zu können.

Einstieg

Anleitungen zum Installieren und Initialisieren von Client-SDKs finden Sie in den folgenden Leitfäden:

Syntax

In den folgenden Abschnitten finden Sie eine Übersicht über die Syntax für Pipelinevorgänge.

Konzepte

Ein wesentlicher Unterschied bei Pipelinevorgängen ist die Einführung einer expliziten „Phasen“-Reihenfolge. So können komplexere Abfragen formuliert werden. Es ist jedoch eine bemerkenswerte Abweichung von der vorhandenen Abfrageschnittstelle mit Core-Vorgängen, bei denen die Reihenfolge der Phasen impliziert wurde. Betrachten Sie das folgende Beispiel für Pipelinevorgänge:

Web

const pipeline = db.pipeline()
  // Step 1: Start a query with collection scope
  .collection("cities")
  // Step 2: Filter the collection
  .where(field("population").greaterThan(100000))
  // Step 3: Sort the remaining documents
  .sort(field("name").ascending())
  // Step 4: Return the top 10. Note applying the limit earlier in the
  // pipeline would have unintentional results.
  .limit(10);
Swift
let pipeline = db.pipeline()
  // Step 1: Start a query with collection scope
  .collection("cities")
  // Step 2: Filter the collection
  .where(Field("population").greaterThan(100000))
  // Step 3: Sort the remaining documents
  .sort([Field("name").ascending()])
  // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have
  // unintentional results.
  .limit(10)

Kotlin

val pipeline = db.pipeline()
    // Step 1: Start a query with collection scope
    .collection("cities")
    // Step 2: Filter the collection
    .where(field("population").greaterThan(100000))
    // Step 3: Sort the remaining documents
    .sort(field("name").ascending())
    // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have
    // unintentional results.
    .limit(10)

Java

Pipeline pipeline = db.pipeline()
    // Step 1: Start a query with collection scope
    .collection("cities")
    // Step 2: Filter the collection
    .where(field("population").greaterThan(100000))
    // Step 3: Sort the remaining documents
    .sort(field("name").ascending())
    // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have
    // unintentional results.
    .limit(10);
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

pipeline = (
    client.pipeline()
    .collection("cities")
    .where(Field.of("population").greater_than(100_000))
    .sort(Field.of("name").ascending())
    .limit(10)
)
Go
pipeline := client.Pipeline().
	Collection("cities").
	Where(firestore.FieldOf("population").GreaterThan(100000)).
	Sort(firestore.Orders(firestore.Ascending(firestore.FieldOf("name")))).
	Limit(10)

Initialisierung

Pipelinevorgänge haben eine sehr vertraute Syntax, die von vorhandenen Cloud Firestore-Abfragen stammt. Zuerst initialisieren Sie eine Abfrage, indem Sie Folgendes schreiben:

Web

const { getFirestore } = require("firebase/firestore");
const { execute } = require("firebase/firestore/pipelines");
const database = getFirestore(app, "enterprise");
const pipeline = database.pipeline();
Swift
let firestore = Firestore.firestore(database: "enterprise")
let pipeline = firestore.pipeline()

Kotlin

val firestore = Firebase.firestore("enterprise")
val pipeline = firestore.pipeline()

Java

FirebaseFirestore firestore = FirebaseFirestore.getInstance("enterprise");
PipelineSource pipeline = firestore.pipeline();
Python
firestore_client = firestore.client(default_app, "your-new-enterprise-database")
pipeline = firestore_client.pipeline()
Go
client, err := firestore.NewClientWithDatabase(ctx, projectID, databaseID)
if err != nil {
	fmt.Fprintf(w, "firestore.NewClientWithDatabase failed: %v", err)
	return err
}
defer client.Close()
pipeline := client.Pipeline().Collection("books")

Struktur

Beim Erstellen von Pipelinevorgängen sind einige Begriffe wichtig: Phasen, Ausdrücke und Funktionen sowie Unterabfrage-Wrapper.

Beispiel für Phasen und Ausdrücke in einer Abfrage

Phasen: Eine Pipeline kann aus einer oder mehreren Phasen bestehen. Logisch gesehen stellen diese die Reihe von Schritten (oder Phasen) dar, die zum Ausführen der Abfrage ausgeführt werden.

Ausdrücke: Phasen akzeptieren häufig einen Ausdruck, mit dem Sie komplexere Abfragen formulieren können. Der Ausdruck kann einfach sein und aus einer einzelnen Funktion wie eq("a", 1) bestehen. Sie können auch komplexere Ausdrücke formulieren, indem Sie Ausdrücke wie and(eq("a", 1), eq("b", 2)). verschachteln.

Unterabfrage-Wrapper: Mit Funktionen wie array() und scalar() können Sie eine verschachtelte Pipeline als Ausdruck in eine Phase einbetten.

Felder / Konstanten / Variablen

Pipelinevorgänge unterstützen komplexe Ausdrücke. Daher ist es wichtig, zu unterscheiden, ob ein Wert ein Feld, eine Konstante oder eine Variable darstellt.

Während sich Felder auf Daten in Dokumenten beziehen und Konstanten die Angabe eines beliebigen Werts als Argument für einen Ausdruck ermöglichen, können Sie mit Variablen temporäre Werte definieren und verwenden, die auf die Abfrageausführung und nicht auf die verarbeiteten Dokumente beschränkt sind. Im Folgenden finden Sie eine Übersicht über diese Konzepte. Weitere Informationen zum Lesen und Schreiben von Variablen während der Abfrageausführung finden Sie in der Phase let(...).

Felder Konstanten Variablen
Purpose auf Felder in Dokumenten zugreifen oder sie darin speichern einen festen Wert angeben Temporäre Werte während der Pipelineausführung verwenden
SDK-Nutzung field("name") constant("val") variable("name")
Einsatzbereich lokal für das aktuelle Dokument global Global für Pipeline und Unterpipelines
Undefined Reference wird mit absent ausgewertet – generiert Laufzeitfehler

Beispiele:

Web

const pipeline = db.pipeline()
  .collection("cities")
  .where(field("name").equal(constant("Toronto")));
Swift
let pipeline = db.pipeline()
  .collection("cities")
  .where(Field("name").equal(Constant("Toronto")))

Kotlin

val pipeline = db.pipeline()
    .collection("cities")
    .where(field("name").equal(constant("Toronto")))

Java

Pipeline pipeline = db.pipeline()
    .collection("cities")
    .where(field("name").equal(constant("Toronto")));
Python
from google.cloud.firestore_v1.pipeline_expressions import Field, Constant

pipeline = (
    client.pipeline()
    .collection("cities")
    .where(Field.of("name").equal(Constant.of("Toronto")))
)
Go
pipeline := client.Pipeline().Collection("cities").
	Where(firestore.FieldOf("name").Equal(firestore.ConstantOf("Toronto")))

Phasen

Eingabephasen

Die Eingabephase ist die erste Phase einer Abfrage. Sie definiert die anfängliche Gruppe von Dokumenten, die Sie abfragen. Bei Pipeline-Vorgängen ähnelt dies weitgehend vorhandenen Abfragen, bei denen die meisten Abfragen entweder mit einer collection(...)- oder einer collection_group(...)-Phase beginnen. Zwei neue Eingabephasen sind database() und documents(...), wobei database() die Rückgabe aller Dokumente in der Datenbank ermöglicht, während documents(...) identisch mit einem Batch-Lesevorgang ist.

Web

let results;

// Return all restaurants in San Francisco
results = await execute(db.pipeline().collection("cities/sf/restaurants"));

// Return all restaurants
results = await execute(db.pipeline().collectionGroup("restaurants"));

// Return all documents across all collections in the database (the entire database)
results = await execute(db.pipeline().database());

// Batch read of 3 documents
results = await execute(db.pipeline().documents([
  doc(db, "cities", "SF"),
  doc(db, "cities", "DC"),
  doc(db, "cities", "NY")
]));
Swift
var results: Pipeline.Snapshot

// Return all restaurants in San Francisco
results = try await db.pipeline().collection("cities/sf/restaurants").execute()

// Return all restaurants
results = try await db.pipeline().collectionGroup("restaurants").execute()

// Return all documents across all collections in the database (the entire database)
results = try await db.pipeline().database().execute()

// Batch read of 3 documents
results = try await db.pipeline().documents([
  db.collection("cities").document("SF"),
  db.collection("cities").document("DC"),
  db.collection("cities").document("NY")
]).execute()

Kotlin

var results: Task<Pipeline.Snapshot>

// Return all restaurants in San Francisco
results = db.pipeline().collection("cities/sf/restaurants").execute()

// Return all restaurants
results = db.pipeline().collectionGroup("restaurants").execute()

// Return all documents across all collections in the database (the entire database)
results = db.pipeline().database().execute()

// Batch read of 3 documents
results = db.pipeline().documents(
    db.collection("cities").document("SF"),
    db.collection("cities").document("DC"),
    db.collection("cities").document("NY")
).execute()

Java

Task<Pipeline.Snapshot> results;

// Return all restaurants in San Francisco
results = db.pipeline().collection("cities/sf/restaurants").execute();

// Return all restaurants
results = db.pipeline().collectionGroup("restaurants").execute();

// Return all documents across all collections in the database (the entire database)
results = db.pipeline().database().execute();

// Batch read of 3 documents
results = db.pipeline().documents(
    db.collection("cities").document("SF"),
    db.collection("cities").document("DC"),
    db.collection("cities").document("NY")
).execute();
Python
# Return all restaurants in San Francisco
results = client.pipeline().collection("cities/sf/restaurants").execute()

# Return all restaurants
results = client.pipeline().collection_group("restaurants").execute()

# Return all documents across all collections in the database (the entire database)
results = client.pipeline().database().execute()

# Batch read of 3 documents
results = (
    client.pipeline()
    .documents(
        client.collection("cities").document("SF"),
        client.collection("cities").document("DC"),
        client.collection("cities").document("NY"),
    )
    .execute()
)
Go
// Return all restaurants in San Francisco
results1, err := client.Pipeline().Collection("cities/sf/restaurants").Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

// Return all restaurants
results2, err := client.Pipeline().CollectionGroup("restaurants").Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

// Return all documents across all collections in the database (the entire database)
results3, err := client.Pipeline().Database().Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

// Batch read of 3 documents
results4, err := client.Pipeline().
	Documents([]*firestore.DocumentRef{
		client.Collection("cities").Doc("SF"),
		client.Collection("cities").Doc("DC"),
		client.Collection("cities").Doc("NY"),
	}).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

Wie bei allen anderen Phasen ist die Reihenfolge der Ergebnisse aus diesen Eingabephasen nicht stabil. Wenn eine bestimmte Reihenfolge erforderlich ist, sollte immer ein sort(...)-Operator hinzugefügt werden.

WHERE

Die Phase where(...) fungiert als Standardfiltervorgang für Dokumente, die in der vorherigen Phase generiert wurden, und spiegelt größtenteils die vorhandene „where“-Syntax für vorhandene Abfragen wider. Alle Dokumente, bei denen ein bestimmter Ausdruck zu einem Wert ungleich true ausgewertet wird, werden aus den zurückgegebenen Dokumenten herausgefiltert.

Mehrere where(...)-Anweisungen können miteinander verkettet werden und als and(...)-Ausdruck fungieren. Die folgenden beiden Abfragen sind beispielsweise logisch äquivalent und können synonym verwendet werden.

Web

let results;

results = await execute(db.pipeline().collection("books")
  .where(field("rating").equal(5))
  .where(field("published").lessThan(1900))
);

results = await execute(db.pipeline().collection("books")
  .where(and(field("rating").equal(5), field("published").lessThan(1900)))
);
Swift
var results: Pipeline.Snapshot

results = try await db.pipeline().collection("books")
  .where(Field("rating").equal(5))
  .where(Field("published").lessThan(1900))
  .execute()

results = try await db.pipeline().collection("books")
  .where(Field("rating").equal(5) && Field("published").lessThan(1900))
  .execute()

Kotlin

var results: Task<Pipeline.Snapshot>

results = db.pipeline().collection("books")
    .where(field("rating").equal(5))
    .where(field("published").lessThan(1900))
    .execute()

results = db.pipeline().collection("books")
    .where(Expression.and(field("rating").equal(5),
      field("published").lessThan(1900)))
    .execute()

Java

Task<Pipeline.Snapshot> results;

results = db.pipeline().collection("books")
    .where(field("rating").equal(5))
    .where(field("published").lessThan(1900))
    .execute();

results = db.pipeline().collection("books")
    .where(Expression.and(
        field("rating").equal(5),
        field("published").lessThan(1900)
    ))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import And, Field

results = (
    client.pipeline()
    .collection("books")
    .where(Field.of("rating").equal(5))
    .where(Field.of("published").less_than(1900))
    .execute()
)

results = (
    client.pipeline()
    .collection("books")
    .where(And(Field.of("rating").equal(5), Field.of("published").less_than(1900)))
    .execute()
)
Go
results1, err := client.Pipeline().
	Collection("books").
	Where(firestore.FieldOf("rating").Equal(5)).
	Where(firestore.FieldOf("published").LessThan(1900)).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

results2, err := client.Pipeline().
	Collection("books").
	Where(firestore.And(
		firestore.FieldOf("rating").Equal(5),
		firestore.FieldOf("published").LessThan(1900),
	)).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

Felder auswählen / hinzufügen und entfernen

Mit select(...), add_fields(...) und remove_fields(...) können Sie die Felder ändern, die von einer vorherigen Phase zurückgegeben werden. Diese drei werden im Allgemeinen als Projektionsphasen bezeichnet.

Mit select(...) und add_fields(...) können Sie das Ergebnis eines Ausdrucks einem vom Nutzer angegebenen Feldnamen zuweisen. select(...) gibt nur die Dokumente mit den angegebenen Feldnamen zurück, während add_fields(...) das Schema der vorherigen Phase erweitert (und möglicherweise Werte mit identischen Feldnamen überschreibt).

Mit remove_fields(...) können Sie eine Reihe von Feldern angeben, die aus der vorherigen Phase entfernt werden sollen. Die Angabe nicht vorhandener Feldnamen ist ein No-Op.

Weitere Informationen finden Sie im Abschnitt Zurückzugebende Felder einschränken unten. Im Allgemeinen ist es jedoch hilfreich, eine solche Phase zu verwenden, um das Ergebnis auf die im Client benötigten Felder zu beschränken. So lassen sich die Kosten und die Latenz für die meisten Abfragen reduzieren.

Aggregiert / eindeutig

In der Phase aggregate(...) können Sie eine Reihe von Aggregationen für die Eingabedokumente ausführen. Standardmäßig werden alle Dokumente zusammengefasst. Sie können jedoch ein optionales grouping-Argument angeben, um die Eingabedokumente in verschiedenen Buckets zusammenzufassen.

Web

const results = await execute(db.pipeline()
  .collection("books")
  .aggregate(
    field("rating").average().as("avg_rating")
  )
  .distinct(field("genre"))
);
Swift
let results = try await db.pipeline()
  .collection("books")
  .aggregate([
    Field("rating").average().as("avg_rating")
  ], groups: [
    Field("genre")
  ])
  .execute()

Kotlin

val results = db.pipeline()
    .collection("books")
    .aggregate(
        AggregateStage
            .withAccumulators(AggregateFunction.average("rating").alias("avg_rating"))
            .withGroups(field("genre"))
    )
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .collection("books")
    .aggregate(AggregateStage
        .withAccumulators(
            AggregateFunction.average("rating").alias("avg_rating"))
        .withGroups(field("genre")))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

results = (
    client.pipeline()
    .collection("books")
    .aggregate(
        Field.of("rating").average().as_("avg_rating"), groups=[Field.of("genre")]
    )
    .execute()
)
Go
snapshot := client.Pipeline().
	Collection("books").
	Aggregate(
		firestore.Accumulators(firestore.Average("rating").As("avg_rating")),
		firestore.WithAggregateGroups("genre"),
	).
	Execute(ctx)

Wenn groupings nicht angegeben ist, wird in dieser Phase nur ein einzelnes Dokument erstellt. Andernfalls wird für jede eindeutige Kombination von groupings-Werten ein Dokument generiert.

Die Phase distinct(...) ist ein vereinfachter Aggregationsoperator, mit dem nur die eindeutigen groupings ohne Akkumulatoren generiert werden können. In allen anderen Belangen verhält es sich identisch mit aggregate(...). Das folgende Beispiel zeigt:

Web

const results = await execute(db.pipeline()
  .collection("books")
  .distinct(
    field("author").toUpper().as("author"),
    field("genre")
  )
);
Swift
let results = try await db.pipeline()
  .collection("books")
  .distinct([
    Field("author").toUpper().as("author"),
    Field("genre")
  ])
  .execute()

Kotlin

val results = db.pipeline()
    .collection("books")
    .distinct(
        field("author").toUpper().alias("author"),
        field("genre")
    )
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .collection("books")
    .distinct(
        field("author").toUpper().alias("author"),
        field("genre")
    )
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

results = (
    client.pipeline()
    .collection("books")
    .distinct(Field.of("author").to_upper().as_("author"), "genre")
    .execute()
)
Go
snapshot := client.Pipeline().
	Collection("books").
	Distinct(firestore.Fields(
		firestore.ToUpper(firestore.FieldOf("author")).As("author"),
		firestore.FieldOf("genre"),
	)).
	Execute(ctx)

Funktionen

Funktionen sind ein Baustein zum Erstellen von Ausdrücken und komplexen Abfragen. Eine vollständige Liste der Funktionen mit Beispielen finden Sie in der Funktionsreferenz. Zur Erinnerung: Hier ist die Struktur einer typischen Abfrage:

Beispiel für Phasen und Funktionen in einer Abfrage

Viele Phasen akzeptieren Ausdrücke, die eine oder mehrere Funktionen enthalten. Die häufigste Verwendung von Funktionen findet sich in den Phasen where(...) und select(...). Es gibt zwei Haupttypen von Funktionen, mit denen Sie vertraut sein sollten:

Web

let results;

// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results = await execute(db.pipeline().collection("books")
  .select(field("current").logicalMinimum(field("updated")).as("price_min"))
);

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results = await execute(db.pipeline().collection("books")
  .aggregate(field("price").minimum().as("min_price"))
);
Swift
var results: Pipeline.Snapshot

// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results = try await db.pipeline().collection("books")
  .select([
    Field("current").logicalMinimum(["updated"]).as("price_min")
  ])
  .execute()

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results = try await db.pipeline().collection("books")
  .aggregate([Field("price").minimum().as("min_price")])
  .execute()

Kotlin

var results: Task<Pipeline.Snapshot>

// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results = db.pipeline().collection("books")
    .select(
        field("current").logicalMinimum("updated").alias("price_min")
    )
    .execute()

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results = db.pipeline().collection("books")
    .aggregate(AggregateFunction.minimum("price").alias("min_price"))
    .execute()

Java

Task<Pipeline.Snapshot> results;

// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results = db.pipeline().collection("books")
    .select(
        field("current").logicalMinimum("updated").alias("price_min")
    )
    .execute();

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results = db.pipeline().collection("books")
    .aggregate(AggregateFunction.minimum("price").alias("min_price"))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

# Type 1: Scalar (for use in non-aggregation stages)
# Example: Return the min store price for each book.
results = (
    client.pipeline()
    .collection("books")
    .select(
        Field.of("current").logical_minimum(Field.of("updated")).as_("price_min")
    )
    .execute()
)

# Type 2: Aggregation (for use in aggregate stages)
# Example: Return the min price of all books.
results = (
    client.pipeline()
    .collection("books")
    .aggregate(Field.of("price").minimum().as_("min_price"))
    .execute()
)
Go
// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results1, err := client.Pipeline().
	Collection("books").
	Select(firestore.Fields(
		firestore.LogicalMinimum(firestore.FieldOf("current"), firestore.FieldOf("updated")).As("price_min"),
	)).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results2, err := client.Pipeline().
	Collection("books").
	Aggregate(firestore.Accumulators(
		firestore.Minimum("price").As("min_price"),
	)).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

Limits

In der Enterprise-Version gibt es im Allgemeinen keine Einschränkungen für die Form der Abfrage. Mit anderen Worten: Sie sind nicht auf eine kleine Anzahl von Werten in einer IN- oder OR-Abfrage beschränkt. Stattdessen gibt es zwei primäre Limits, die Sie beachten sollten:

  • Frist: 60 Sekunden (identisch mit der Standard-Version).
  • Arbeitsspeichernutzung: Limit von 128 MiB für die Menge der materialisierten Daten während der Abfrageausführung.

Fehler

Fehlgeschlagene Abfragen können verschiedene Ursachen haben. Hier finden Sie einen Link zu häufigen Fehlern und den entsprechenden Maßnahmen, die Sie ergreifen können:

Fehlercode Aktion
DEADLINE_EXCEEDED Die von Ihnen ausgeführte Abfrage überschreitet ein Zeitlimit von 60 Sekunden und erfordert eine zusätzliche Optimierung. Tipps finden Sie im Abschnitt zur Leistung. Wenn Sie die Ursache des Problems nicht ermitteln können, wenden Sie sich an das Team.
RESOURCE_EXHAUSTED Die von Ihnen ausgeführte Abfrage überschreitet die Speicherlimits und erfordert eine zusätzliche Optimierung. Tipps finden Sie im Abschnitt zur Leistung. Wenn Sie die Ursache des Problems nicht ermitteln können, wenden Sie sich an das Team.
INTERNAL Wenden Sie sich an das Team, um Unterstützung zu erhalten.

Leistung

Für Datenbanken der Enterprise-Version ist nicht immer ein Index erforderlich. Das bedeutet, dass eine Abfrage eine höhere Latenz aufweisen kann als vorhandene Abfragen, die sofort mit dem Fehler FAILED_PRECONDITION (fehlender Index) fehlgeschlagen wären. Es gibt einige Schritte, die Sie unternehmen können, um die Leistung von Pipeline-Vorgängen zu verbessern.

Indexe erstellen

Verwendeter Index

Mit Query Explain können Sie feststellen, ob Ihre Abfrage von einem Index bedient wird oder auf einen weniger effizienten Vorgang wie einen Tabellenscan zurückgreift. Wenn Ihre Abfrage nicht vollständig über einen Index ausgeführt wird, können Sie einen Index erstellen. Folgen Sie dazu der Anleitung.

Indexe erstellen

Sie können der vorhandenen Dokumentation zur Indexverwaltung folgen, um Indexe zu erstellen. Bevor Sie einen Index erstellen, sollten Sie sich mit den allgemeinen Best Practices für Indexe in Cloud Firestore vertraut machen. Damit Ihre Abfrage Indexe nutzen kann, sollten Sie die Best Practices befolgen, um Indexe mit Feldern in der folgenden Reihenfolge zu erstellen:

  1. Alle Felder, die in Gleichheitsfiltern verwendet werden (in beliebiger Reihenfolge)
  2. Alle Felder, nach denen sortiert wird (in derselben Reihenfolge)
  3. Felder, die in Bereichs- oder Ungleichheitsfiltern verwendet werden, in absteigender Reihenfolge der Selektivität der Abfrageeinschränkung

Beispiel für die folgende Abfrage:

Web

const results = await execute(db.pipeline()
  .collection("books")
  .where(field("published").lessThan(1900))
  .where(field("genre").equal("Science Fiction"))
  .where(field("rating").greaterThan(4.3))
  .sort(field("published").descending())
);
Swift
let results = try await db.pipeline()
  .collection("books")
  .where(Field("published").lessThan(1900))
  .where(Field("genre").equal("Science Fiction"))
  .where(Field("rating").greaterThan(4.3))
  .sort([Field("published").descending()])
  .execute()

Kotlin

val results = db.pipeline()
    .collection("books")
    .where(field("published").lessThan(1900))
    .where(field("genre").equal("Science Fiction"))
    .where(field("rating").greaterThan(4.3))
    .sort(field("published").descending())
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .collection("books")
    .where(field("published").lessThan(1900))
    .where(field("genre").equal("Science Fiction"))
    .where(field("rating").greaterThan(4.3))
    .sort(field("published").descending())
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

results = (
    client.pipeline()
    .collection("books")
    .where(Field.of("published").less_than(1900))
    .where(Field.of("genre").equal("Science Fiction"))
    .where(Field.of("rating").greater_than(4.3))
    .sort(Field.of("published").descending())
    .execute()
)
Go
snapshot := client.Pipeline().
	Collection("books").
	Where(firestore.FieldOf("published").LessThan(1900)).
	Where(firestore.FieldOf("genre").Equal("Science Fiction")).
	Where(firestore.FieldOf("rating").GreaterThan(4.3)).
	Sort(firestore.Orders(firestore.Descending(firestore.FieldOf("published")))).
	Execute(ctx)

Der empfohlene Index ist ein Index mit Sammlungsbereich für books für (genre [...], published DESC, avg_rating DESC).

Indexdichte

Cloud Firestore unterstützt dünnbesetzte und nicht dünnbesetzte Indexe. Weitere Informationen finden Sie unter Indexdichte.

Abgedeckte Abfragen und sekundäre Indexe

Cloud Firestore kann das Abrufen des vollständigen Dokuments überspringen und nur Ergebnisse aus dem Index zurückgeben, wenn alle zurückgegebenen Felder in einem sekundären Index vorhanden sind. Dies führt in der Regel zu einer erheblichen Verbesserung der Latenz (und der Kosten). Verwenden Sie die folgende Beispielabfrage:

Web

const results = await execute(db.pipeline()
  .collection("books")
  .where(field("category").like("%fantasy%"))
  .where(field("title").exists())
  .where(field("author").exists())
  .select(field("title"), field("author"))
);
Swift
let results = try await db.pipeline()
  .collection("books")
  .where(Field("category").like("%fantasy%"))
  .where(Field("title").exists())
  .where(Field("author").exists())
  .select([Field("title"), Field("author")])
  .execute()

Kotlin

val results = db.pipeline()
    .collection("books")
    .where(field("category").like("%fantasy%"))
    .where(field("title").exists())
    .where(field("author").exists())
    .select(field("title"), field("author"))
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .collection("books")
    .where(field("category").like("%fantasy%"))
    .where(field("title").exists())
    .where(field("author").exists())
    .select(field("title"), field("author"))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

results = (
    client.pipeline()
    .collection("books")
    .where(Field.of("category").like("%fantasy%"))
    .where(Field.of("title").exists())
    .where(Field.of("author").exists())
    .select("title", "author")
    .execute()
)
Go
snapshot := client.Pipeline().
	Collection("books").
	Where(firestore.FieldOf("category").Like("%fantasy%")).
	Where(firestore.FieldOf("title").FieldExists()).
	Where(firestore.FieldOf("author").FieldExists()).
	Select(firestore.Fields("title", "author")).
	Execute(ctx)

Wenn die Datenbank bereits einen Index für den Sammlungsbereich für books für (category [...], title [...], author [...]) hat, muss nichts aus den Hauptdokumenten selbst abgerufen werden. In diesem Fall spielt die Reihenfolge im Index keine Rolle. Dies wird durch [...] angegeben.

Zurückzugebende Felder einschränken

Standardmäßig gibt eine Cloud Firestore-Abfrage alle Felder in einem Dokument zurück, analog zu einem SELECT * in relationalen Systemen. Wenn Ihre Anwendung jedoch nur einen Teil der Felder benötigt, können Sie die Phasen select(...) oder restrict(...) verwenden, um diese Filterung serverseitig durchzuführen. Dadurch wird sowohl die Antwortgröße (und damit die Kosten für ausgehenden Netzwerktraffic) als auch die Latenz verringert.

Tools zur Fehlerbehebung

Query Explain

Mit Query Explain können Sie Ausführungsmesswerte und Details zu den verwendeten Indexen sichtbar machen.

Messwerte

Pipelinevorgänge sind vollständig in vorhandene Cloud Firestore-Messwerte eingebunden.

Bekannte Probleme / Einschränkungen

Spezialisierte Indexe

Pipelinevorgänge unterstützen noch keine vorhandenen array-contains- und vector-Indextypen. Anstatt solche Abfragen einfach abzulehnen, versucht Cloud Firestore, andere vorhandene ascending- und descending-Indexe zu verwenden. Es ist davon auszugehen, dass Ausdrücke mit array_contains oder find_nearest dadurch langsamer sind als ihre bestehenden Äquivalente.

Echtzeit- und Offline-Unterstützung

Pipelinevorgänge bieten keine Echtzeit- und Offlinefunktionen.

Nächste Schritte