קבלת נתונים באמצעות פעולות של צינורות ב-Firestore

רקע

פעולות בצינור עיבוד הנתונים מספקות ממשק שאילתות חדש ל-Cloud Firestore, שתומך בפונקציונליות מתקדמת של שאילתות ובביטויים מורכבים. הוא כולל הרבה פונקציות חדשות, כמו min(...),‏ max(...),‏ substring(...),‏ regex_match(...) ו-array_contains_all(...), ושלבים לביצוע טרנספורמציות מורכבות.

תחילת העבודה

כדי להתקין ולהפעיל ערכות SDK של לקוחות, עיינו בהוראות במדריכים הבאים:

תחביר

בקטעים הבאים מופיעה סקירה כללית של התחביר לפעולות בצינור עיבוד הנתונים.

מושגים

הבדל בולט אחד בפעולות של Pipeline הוא הוספת סדר "שלבים" מפורש. כך אפשר להגדיר שאילתות מורכבות יותר. עם זאת, זהו שינוי משמעותי מממשק השאילתות הקיים שמשתמש בפעולות ליבה, שבהן סדר השלבים היה משתמע. דוגמה לפעולות ב-Pipeline:

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)
)
המשך
pipeline := client.Pipeline().
	Collection("cities").
	Where(firestore.FieldOf("population").GreaterThan(100000)).
	Sort(firestore.Orders(firestore.Ascending(firestore.FieldOf("name")))).
	Limit(10)

אתחול

לפעולות בצינור עיבוד הנתונים יש תחביר מוכר מאוד משאילתות Cloud Firestore קיימות. כדי להתחיל, מאתחלים שאילתה באמצעות כתיבת הקוד הבא:

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()
המשך
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")

מבנה

יש כמה מונחים שחשוב להבין כשיוצרים פעולות בצינור עיבוד נתונים: שלבים, ביטויים, פונקציות ו-subquery wrappers.

דוגמה שממחישה שלבים וביטויים בשאילתה

שלבים: צינור עיבוד נתונים יכול לכלול שלב אחד או יותר. מבחינה לוגית, הם מייצגים את סדרת השלבים (או השלבים) שבוצעו כדי להריץ את השאילתה.

ביטויים: שלבים בדרך כלל יקבלו ביטוי שמאפשר לכם לבטא שאילתות מורכבות יותר. הביטוי יכול להיות פשוט ולהכיל פונקציה אחת, כמו eq("a", 1). אפשר גם להשתמש בביטויים מורכבים יותר על ידי הטמעה של ביטויים כמו and(eq("a", 1), eq("b", 2)).

פונקציות wrapper של שאילתות משנה: פונקציות כמו array() ו-scalar() מאפשרות להטמיע צינור עיבוד נתונים בתור ביטוי בתוך שלב.

שדות / קבועים / משתנים

פעולות בצינור עיבוד הנתונים תומכות בביטויים מורכבים. לכן חשוב להבחין אם הערך מייצג שדה, קבוע או משתנה.

שדות מתייחסים לנתונים בתוך מסמכים, וקבועים מאפשרים לציין כל ערך כארגומנט לביטוי. לעומת זאת, משתנים מאפשרים להגדיר ערכים זמניים ולהשתמש בהם, והם מוגבלים להיקף של הרצת השאילתה ולא למסמכים שעוברים עיבוד. בהמשך מופיעה סקירה כללית של המושגים האלה. בשלב let(...) מוסבר איך לקרוא ולכתוב משתנים במהלך הרצת השאילתה.

שדות קבועים משתנים
מטרה לגשת לשדות במסמכים או לאחסן אותם לציין ערך קבוע שימוש בערכים זמניים במהלך הפעלת צינור עיבוד הנתונים
שימוש ב-SDK field("name") constant("val") variable("name")
היקף מקומי למסמך הנוכחי גלובלית גלובלי לצינור עיבוד הנתונים ולצינורות המשנה של עיבוד הנתונים
הפניה לא מוגדרת הערך שמתקבל הוא absent לא רלוונטי יוצרת שגיאת זמן ריצה

לדוגמה:

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")))
)
המשך
pipeline := client.Pipeline().Collection("cities").
	Where(firestore.FieldOf("name").Equal(firestore.ConstantOf("Toronto")))

במות

שלבי קלט

שלב הקלט מייצג את השלב הראשון של השאילתה. הוא מגדיר את קבוצת המסמכים הראשונית שאתם שולחים לגביה שאילתה. בפעולות Pipeline, זה דומה במידה רבה לשאילתות קיימות, שבהן רוב השאילתות מתחילות בשלב collection(...) או collection_group(...). שני שלבי קלט חדשים הם database() ו-documents(...), כאשר database() מאפשר להחזיר את כל המסמכים במסד הנתונים, ו-documents(...) פועל באופן זהה לקריאה באצווה.

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()
)
המשך
// 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
}

כמו בכל השלבים האחרים, סדר התוצאות משלבי הקלט האלה לא יציב. אם נדרש סדר ספציפי, תמיד צריך להוסיף אופרטור sort(...).

איפה

השלב where(...) פועל כפעולת סינון רגילה במסמכים שנוצרו בשלב הקודם, ומשקף בעיקר את התחביר הקיים של "where" לשאילתות קיימות. כל מסמך שבו ביטוי נתון מקבל ערך שאינו true מסונן מהמסמכים המוחזרים.

אפשר לשרשר כמה הצהרות where(...) יחד, והן פועלות כמו ביטוי and(...). לדוגמה, שתי השאילתות הבאות שוות ערך מבחינה לוגית ואפשר להשתמש בהן לסירוגין.

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()
)
המשך
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
}

בחירה / הוספה והסרה של שדות

השלבים select(...),‏ add_fields(...) ו-remove_fields(...) מאפשרים לשנות את השדות שמוחזרים משלב קודם. שלושת השלבים האלה נקראים בדרך כלל שלבים בסגנון הקרנה.

הפונקציות select(...) ו-add_fields(...) מאפשרות לציין את התוצאה של ביטוי לשם שדה שסופק על ידי המשתמש. השלב select(...) יחזיר רק את המסמכים עם שמות השדות שצוינו, ואילו השלב add_fields(...) מרחיב את הסכימה של השלב הקודם (יכול להיות שהוא יחליף ערכים עם שמות שדות זהים).

השלב remove_fields(...) מאפשר לציין קבוצת שדות שרוצים להסיר מהשלב הקודם. אם תציינו שמות של שדות שלא קיימים, לא תתבצע שום פעולה.

עיינו בקטע הגבלת השדות להחזרה בהמשך, אבל באופן כללי, שימוש בשלב כזה כדי להגביל את התוצאה רק לשדות שנדרשים בלקוח עוזר להפחית את העלות ואת זמן האחזור של רוב השאילתות.

צבירה / ייחודי

השלב aggregate(...) מאפשר לבצע סדרה של צבירות במסמכי הקלט. כברירת מחדל, כל המסמכים נצברים יחד, אבל אפשר לספק ארגומנט grouping אופציונלי, שמאפשר לצבור את מסמכי הקלט לקטגוריות שונות.

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()
)
המשך
snapshot := client.Pipeline().
	Collection("books").
	Aggregate(
		firestore.Accumulators(firestore.Average("rating").As("avg_rating")),
		firestore.WithAggregateGroups("genre"),
	).
	Execute(ctx)

אם לא מציינים את groupings, השלב הזה ייצור רק מסמך אחד. אחרת, ייווצר מסמך לכל שילוב ייחודי של ערכי groupings.

השלב distinct(...) הוא אופרטור צבירה פשוט שמאפשר ליצור רק את הערך הייחודי של groupings בלי צוברים. הוא פועל באופן זהה ל-aggregate(...) בכל שאר ההיבטים. בדוגמה הבאה מוצגים:

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()
)
המשך
snapshot := client.Pipeline().
	Collection("books").
	Distinct(firestore.Fields(
		firestore.ToUpper(firestore.FieldOf("author")).As("author"),
		firestore.FieldOf("genre"),
	)).
	Execute(ctx)

פונקציות

פונקציות הן אבני הבניין ליצירת ביטויים ושאילתות מורכבות. רשימה מלאה של פונקציות עם דוגמאות מופיעה בחומר העזר בנושא פונקציות. תזכורת קצרה: זה המבנה של שאילתה אופיינית:

דוגמה שממחישה שלבים ופונקציות בשאילתה

בשלבים רבים אפשר להשתמש בביטויים שמכילים פונקציה אחת או יותר. השימוש הנפוץ ביותר בפונקציות יהיה בשלבים where(...) ו-select(...). יש שני סוגים עיקריים של פונקציות שכדאי להכיר:

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()
)
המשך
// 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
}

מגבלות

ברוב המקרים, במהדורת Enterprise אין מגבלות על צורת השאילתה. במילים אחרות, אתם לא מוגבלים למספר קטן של ערכים בשאילתת IN או OR. במקום זאת, יש שתי מגבלות עיקריות שצריך להכיר:

  • מועד אחרון: 60 שניות (זהה למהדורת Standard).
  • שימוש בזיכרון: מגבלה של 128MiB על כמות הנתונים המהוונים במהלך הרצת השאילתה.

שגיאות

יכולות להיות כמה סיבות לכך ששאילתות נכשלות. בקישור הבא מפורטות שגיאות נפוצות והפעולות שאפשר לבצע כדי לפתור אותן:

קוד שגיאה פעולה
DEADLINE_EXCEEDED השאילתה שאתם מריצים חורגת מהמועד האחרון של 60 שניות ודורשת אופטימיזציה נוספת. טיפים בנושא מופיעים בקטע "ביצועים". אם אתם לא מצליחים לזהות את שורש הבעיה, פנו לצוות.
RESOURCE_EXHAUSTED השאילתה שאתם מריצים חורגת ממגבלות הזיכרון וצריך לבצע אופטימיזציה נוספת. טיפים בנושא מופיעים בקטע "ביצועים". אם אתם לא מצליחים לזהות את שורש הבעיה, פנו לצוות.
INTERNAL פנו לצוות התמיכה.

ביצועים

במסדי נתונים במהדורת Enterprise לא נדרש אינדקס. המשמעות היא ששאילתה יכולה להציג זמן אחזור גבוה יותר בהשוואה לשאילתות קיימות, שהיו נכשלות מיד עם שגיאת אינדקס חסר של FAILED_PRECONDITION. כדי לשפר את הביצועים של פעולות ב-Pipeline, אתם יכולים לבצע כמה פעולות.

יצירת אינדקסים

האינדקס שנעשה בו שימוש

הסבר השאילתה מאפשר לכם לזהות אם השאילתה מוגשת על ידי אינדקס או חוזרת לפעולה פחות יעילה, כמו סריקת טבלה. אם השאילתה לא מוצגת במלואה מאינדקס, אפשר ליצור אינדקס לפי ההוראות.

יצירת אינדקסים

כדי ליצור אינדקסים, אפשר לפעול לפי המסמכים הקיימים בנושא ניהול אינדקסים. לפני שיוצרים אינדקס, כדאי להכיר את השיטות המומלצות הכלליות לאינדקסים ב-Cloud Firestore. כדי לוודא שהשאילתה יכולה להשתמש באינדקסים, צריך לפעול לפי השיטות המומלצות ליצירת אינדקסים עם שדות בסדר הבא:

  1. כל השדות שישמשו במסנני שוויון (בכל סדר)
  2. כל השדות שלפיהם יתבצע המיון (באותו סדר)
  3. שדות שישמשו במסנני טווח או אי-שוויון בסדר יורד של סלקטיביות אילוצי השאילתה

לדוגמה, בשאילתה הבאה,

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()
)
המשך
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)

האינדקס המומלץ הוא אינדקס בהיקף של אוסף ב-books עבור (genre [...], published DESC, avg_rating DESC).

צפיפות האינדקס

‫Cloud Firestore תומך באינדקסים דלילים ולא דלילים. מידע נוסף מופיע במאמר צפיפות האינדקס.

שאילתות מכוסות + אינדקסים משניים

‫Cloud Firestore יכול לדלג על אחזור המסמך המלא ולהחזיר רק תוצאות מהאינדקס, אם כל השדות שמוחזרים נמצאים באינדקס משני. בדרך כלל זה מוביל לשיפור משמעותי בזמן האחזור (ובעלות). שימוש בשאילתה לדוגמה שבהמשך:

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()
)
המשך
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)

אם במסד הנתונים כבר יש אינדקס של היקף האוסף ב-books עבור (category [...], title [...], author [...]), הוא יכול להימנע מאחזור של כל דבר מהמסמכים הראשיים עצמם. במקרה כזה, הסדר באינדקס לא משנה, והערך [...] משמש לציון העובדה הזו.

הגבלת השדות שיוחזרו

כברירת מחדל, שאילתת Cloud Firestore מחזירה את כל השדות במסמך, בדומה ל-SELECT * במערכות יחסיות. עם זאת, אם האפליקציה צריכה רק קבוצת משנה של השדות, אפשר להשתמש בשלבים select(...) או restrict(...) כדי להעביר את הסינון הזה לצד השרת. כך אפשר להקטין את גודל התגובה (ולהפחית את עלות תעבורת הנתונים היוצאת ברשת) וגם לשפר את זמן האחזור.

כלים לפתרון בעיות

הסבר על השאילתה

התכונה Query Explain מאפשרת לכם לראות את מדדי הביצוע ופרטים על האינדקסים שבהם נעשה שימוש.

מדדים

פעולות בצינור עיבוד הנתונים אם הן משולבות באופן מלא עם מדדי Cloud Firestore קיימים.

בעיות מוכרות / מגבלות

אינדקסים מיוחדים

פעולות צינור עיבוד נתונים עדיין לא תומכות בסוגי אינדקסים קיימים של array-contains ו-vector. במקום לדחות שאילתות כאלה, Cloud Firestore ינסה להשתמש באינדקסים קיימים אחרים של ascending ו-descending. לכן, ביטויים עם array_contains או find_nearest יהיו איטיים יותר מהביטויים המקבילים הקיימים שלהם.

תמיכה בזמן אמת ובמצב אופליין

לפעולות בצינור עיבוד הנתונים אין יכולות של זמן אמת ואופליין.

המאמרים הבאים