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

רקע

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

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

תחילת העבודה

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

תחביר

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

מושגים

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

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

אתחול

לפעולות של צינורות יש תחביר מוכר מאוד שמגיע משאילתות 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()

מבנה

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

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

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

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

הפניות לשדה לעומת הפניות לקבוע

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

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

במות

שלבי קלט

שלב הקלט מייצג את השלב הראשון של שאילתה. הוא מגדיר את קבוצת המסמכים הראשונית שאתם שולחים לגביה שאילתה. בפעולות של צינורות, זה דומה במידה רבה לשאילתות קיימות, שרוב השאילתות מתחילות בשלב 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()
)

כמו בכל השלבים האחרים, סדר התוצאות משלבי הקלט האלה לא קבוע. אם רוצים להגדיר סדר ספציפי, תמיד צריך להוסיף אופרטור 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()
)

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

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

הפונקציות select(...) ו-add_fields(...) מאפשרות לציין את התוצאה של ביטוי לשם שדה שסופק על ידי המשתמש. ביטוי שמוביל לשגיאה יחזיר את הערך null. הפונקציה 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()
)

אם לא מציינים את 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()
)

פונקציות

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

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

בשלבים רבים אפשר להשתמש בביטויים שמכילים פונקציה אחת או יותר. השימוש הנפוץ ביותר בפונקציות הוא בשלבים 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()
)

מגבלות

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

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

שגיאות

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

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

ביצועים

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

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

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

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

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

כדי ליצור אינדקסים, אפשר לפעול לפי התיעוד הקיים לניהול אינדקסים. לפני שיוצרים אינדקס, כדאי לעיין בשיטות המומלצות הכלליות לשימוש באינדקסים ב-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()
)

האינדקס המומלץ הוא אינדקס בהיקף אוסף ב-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()
)

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

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

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

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

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

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

מדדים

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

בעיות ידועות / מגבלות

מדדים מיוחדים

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

חלוקה לדפים

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

Web

// Existing pagination via `startAt()`
const q =
  query(collection(db, "cities"), orderBy("population"), startAt(1000000));

// Private preview workaround using pipelines
const pageSize = 2;
const pipeline = db.pipeline()
  .collection("cities")
  .select("name", "population", "__name__")
  .sort(field("population").descending(), field("__name__").ascending());

// Page 1 results
let snapshot = await execute(pipeline.limit(pageSize));

// End of page marker
const lastDoc = snapshot.results[snapshot.results.length - 1];

// Page 2 results
snapshot = await execute(
  pipeline
    .where(
      or(
        and(
          field("population").equal(lastDoc.get("population")),
          field("__name__").greaterThan(lastDoc.ref)
        ),
        field("population").lessThan(lastDoc.get("population"))
      )
    )
    .limit(pageSize)
);
Swift
// Existing pagination via `start(at:)`
let query = db.collection("cities").order(by: "population").start(at: [1000000])

// Private preview workaround using pipelines
let pipeline = db.pipeline()
  .collection("cities")
  .where(Field("population").greaterThanOrEqual(1000000))
  .sort([Field("population").descending()])

Kotlin

// Existing pagination via `startAt()`
val query = db.collection("cities").orderBy("population").startAt(1000000)

// Private preview workaround using pipelines
val pipeline = db.pipeline()
    .collection("cities")
    .where(field("population").greaterThanOrEqual(1000000))
    .sort(field("population").descending())

Java

// Existing pagination via `startAt()`
Query query = db.collection("cities").orderBy("population").startAt(1000000);

// Private preview workaround using pipelines
Pipeline pipeline = db.pipeline()
    .collection("cities")
    .where(field("population").greaterThanOrEqual(1000000))
    .sort(field("population").descending());
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

# Existing pagination via `start_at()`
query = (
    client.collection("cities")
    .order_by("population")
    .start_at({"population": 1_000_000})
)

# Private preview workaround using pipelines
pipeline = (
    client.pipeline()
    .collection("cities")
    .where(Field.of("population").greater_than_or_equal(1_000_000))
    .sort(Field.of("population").descending())
)

תמיכה באמולטור

האמולטור עדיין לא תומך בפעולות של צינורות.

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

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

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