Mulai menggunakan Kueri Pipeline

Latar belakang

Operasi pipeline adalah antarmuka kueri baru untuk Firestore. Antarmuka ini menyediakan fungsi kueri tingkat lanjut yang mencakup ekspresi kompleks. Fitur ini juga menambahkan dukungan untuk berbagai fungsi baru seperti min, max, substring, regex_match dan array_contains_all.

Dengan operasi Pipeline, pembuatan indeks juga sepenuhnya bersifat opsional, sehingga menyederhanakan proses pengembangan kueri baru. Operasi pipeline juga menghilangkan berbagai batasan pada bentuk kueri, sehingga Anda dapat menentukan kueri in atau or yang besar.

Memulai

Untuk menginstal dan menginisialisasi SDK klien, lihat petunjuk di panduan berikut:

Sintaksis

Bagian berikut memberikan ringkasan sintaksis untuk operasi Pipeline.

Konsep

Salah satu perbedaan penting di operasi Pipeline adalah penerapan pengurutan "tahap" secara eksplisit. Dengan ini, Anda dapat mengekspresikan kueri yang lebih kompleks. Namun, ini merupakan penyimpangan yang signifikan dari antarmuka kueri yang sudah ada (Operasi inti) yang urutan tahapnya bersifat implisit. Perhatikan contoh operasi Pipeline berikut:

Node.js
  db.pipeline()
    .collection() // Step 1 (start a query with 'collection' scope)
    .where()      // Step 2 (filter collection)
    .sort()       // Step 3 (order results)
    .limit()      // Step 4 (limit results)

  // Note: Applying a limit before a sort can yield unintended
  // results (as the limit would be applied before sorting).
    

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

Inisialisasi

Operasi Pipeline memiliki sintaksis yang sangat familier yang berasal dari kueri Firestore yang ada. Untuk memulai, Anda harus menginisialisasi kueri dengan menulis perintah berikut:

Node.js
  const db = new Firestore({ projectId: '', databaseId: databaseId'})
  db.pipeline()
    
Java
  Firestore db = FirestoreOptions.newBuilder().build().getService();
  db.pipeline()
    

Struktur

Ada beberapa istilah yang penting untuk dipahami saat membuat operasi Pipeline: tahap, ekspresi, dan fungsi.

Contoh yang menunjukkan tahap dan ekspresi dalam kueri

Tahap: Pipeline dapat terdiri dari satu atau beberapa tahap. Secara logis, ini mewakili serangkaian langkah (atau tahap) yang dilakukan untuk menjalankan kueri. Catatan: Dalam praktiknya, tahap dapat dijalankan di luar urutan untuk meningkatkan performa. Namun, hal ini tidak mengubah maksud atau kebenaran kueri.

Ekspresi: Tahap sering kali menerima ekspresi yang memungkinkan Anda mengekspresikan kueri yang lebih kompleks. Ekspresi bisa jadi sederhana dan terdiri dari satu fungsi seperti eq("a", 1). Anda juga dapat membuat ekspresi yang lebih kompleks dengan menyusun ekspresi seperti and(eq("a", 1), eq("b", 2)).

Referensi Kolom vs. Konstan

Operasi pipeline mendukung ekspresi yang kompleks. Oleh karena itu, mungkin perlu dibedakan apakah suatu nilai merepresentasikan kolom atau konstan. Perhatikan contoh berikut:

Node.js
  // Here the two parameters "name" and "toronto" could represent fields or constants.

  db.pipeline()
    .collection("cities")
    .where(eq("name","toronto"))

  // In many cases, it's not necessary to differentiate between the two. In the
  // case of equality, we will implicit treat the first parameter as a field 
  // reference and the second as a constant. However if you need to override you 
  // can always be explicit with the value types
  
  db.pipeline()
    .collection("cities")
    .where(eq(Field.of("name"), Constant.of("toronto")))

  // In some cases, being explicit is always required. However, it should be
  // enough to look at the type signature of the expressions to know what
  //parameters can be used with implicit types, and what should be explicitly specified. 
    

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
Android
val pipeline = db.pipeline()
    .collection("cities")
    .where(field("name").equal(constant("Toronto")))
Java
Android
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")))
)

Tahap

Tahap Input

Tahap input merupakan tahap pertama kueri. Tahap ini menentukan kumpulan dokumen awal yang Anda kueri. Untuk operasi Pipeline, tahap ini sangat mirip dengan kueri yang sudah ada, yang sebagian besar kuerinya dimulai dengan tahap collection(...) atau collection_group(...). Dua tahap input barunya adalah database() dan documents(...). Dengan database(), semua dokumen dapat ditampilkan dalam database. Sementara itu, documents(...) berfungsi mirip dengan operasi baca secara massal.

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

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

  // Return all documents across all collections in the database (huge result!)
  const results = await db.pipeline()
    .database()
    .execute();

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

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

Seperti semua tahap lainnya, urutan hasil dari tahap input ini tidak stabil. Operator sort(...) harus selalu ditambahkan jika urutan tertentu diinginkan.

Di mana

Tahap where(...) berfungsi sebagai operasi filter tradisional pada dokumen yang dihasilkan dari tahap sebelumnya dan sebagian besar mencerminkan sintaksis "where" yang ada untuk kueri yang sudah ada. Dokumen apa pun yang ekspresinya menghasilkan nilai yang bukan true akan difilter dari dokumen yang ditampilkan.

Beberapa pernyataan where(...) dapat dirangkai bersama, dan bertindak sebagai ekspresi and(...). Misalnya, dua kueri berikut secara logis setara dan dapat digunakan secara bergantian.

Node.js
  const results = await db.pipeline()
    .collection("books")
    .where(eq("rating", 5.0))
    .where(lt('published', 1900))
    .execute();
  
  const results = await db.pipeline()
    .collection("books")
    .where(and(
      eq("rating", 5.0), 
      lt('published', 1900)))
    .execute();
    

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
Android
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
Android
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 & Remove Fields

select(...), add_fields(...), & remove_fields(...) memungkinkan Anda mengubah kolom yang ditampilkan dari tahap sebelumnya. Ketiga tahap ini umumnya disebut sebagai tahap gaya proyeksi.

select(...) dan add_fields(...) memungkinkan Anda menentukan hasil ekspresi ke nama kolom yang disediakan pengguna. Ekspresi yang menghasilkan error akan menghasilkan nilai null. select(...) hanya akan menampilkan dokumen dengan nama kolom yang ditentukan, sedangkan add_fields(...) akan memperluas skema tahap sebelumnya (kemungkinan akan menimpa nilai dengan nama kolom yang mirip).

remove_fields(...) memungkinkan penentuan sekumpulan kolom yang akan dihapus dari tahap sebelumnya. Tidak ada operasi yang dilakukan jika nama kolom yang ditentukan tidak ada.

Lihat bagian Membatasi Kolom yang Ditampilkan di bawah. Namun secara umum, penggunaan tahap tersebut untuk membatasi hasil hanya pada kolom yang diperlukan di klien akan membantu mengurangi biaya dan latensi untuk sebagian besar kueri.

Aggregate / Distinct

Tahap aggregate(...) memungkinkan Anda melakukan serangkaian penggabungan pada dokumen input. Secara default, semua dokumen digabungkan, tetapi argumen grouping opsional dapat diberikan, sehingga dokumen input dapat digabungkan ke dalam bucket yang berbeda.

Node.js
  const results = await db.pipeline()
    .collection("books")
    .aggregate({
        accumulators: [avg('rating').as('avg_rating')],
        groups: ['genre'],
      })
    .execute();
    

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
Android
val results = db.pipeline()
    .collection("books")
    .aggregate(
        AggregateStage
            .withAccumulators(AggregateFunction.average("rating").alias("avg_rating"))
            .withGroups(field("genre"))
    )
    .execute()
Java
Android
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()
)

Jika groupings tidak ditentukan, tahap ini hanya akan menghasilkan satu dokumen, atau dokumen akan dibuat untuk setiap kombinasi unik nilai groupings.

Tahap distinct(...) adalah operator penggabungan yang disederhanakan yang memungkinkan pembuatan groupings unik secara khusus tanpa akumulator. Perilakunya mirip dengan aggregate(...) dalam segala aspek lainnya. Berikut contohnya:

Node.js
  const results = await db.pipeline()
    .collection("books")
    .distinct(toUppercase(Field.of("author")).as("author"), Field.of("genre"))
    .execute();
    

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
Android
val results = db.pipeline()
    .collection("books")
    .distinct(
        field("author").toUpper().alias("author"),
        field("genre")
    )
    .execute()
Java
Android
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()
)

Fungsi

Fungsi adalah elemen penyusun untuk membuat ekspresi dan kueri yang kompleks. Untuk mengetahui daftar lengkap fungsi beserta contohnya, lihat Referensi fungsi. Sebagai pengingat, pertimbangkan struktur kueri umum:

Contoh yang menunjukkan tahap dan fungsi dalam kueri

Banyak tahap menerima ekspresi yang berisi satu atau beberapa fungsi. Penggunaan fungsi yang paling umum akan ditemukan di tahap where(...) dan select(...). Ada dua jenis fungsi utama yang harus Anda ketahui:

Node.js
  // Type 1: Scalar (for use in non-aggregation stages)
  // Example: Return the min store price for each book.

  const results = await db.pipeline()
    .collection("books")
    .select(logicalMin(Field.of("current"),Field.of("updated")).as("price_min"))
    .execute();

  // Type 2: Aggregation (for use in aggregate stages)
  // Example: Return the min price of all books.

  const results = await db.pipeline()
    .collection("books")
    .aggregate(min(Field.of("price"))
    .execute();
    

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

Batas

Pada umumnya, Edisi Enterprise tidak membatasi bentuk kueri. Dengan kata lain, Anda tidak dibatasi untuk hanya menggunakan sejumlah kecil nilai dalam kueri IN atau OR. Namun, ada dua batasan utama yang harus Anda ketahui:

  • Batas waktu: 60 detik (sama dengan Edisi Standard).
  • Penggunaan Memori: Batas 128 MiB untuk jumlah data yang diwujudkan selama eksekusi kueri.

Error

Anda bisa saja mengalami kegagalan kueri karena sejumlah alasan. Berikut link ke error umum dan tindakan terkait yang dapat Anda lakukan:

Kode Error Tindakan
DEADLINE_EXCEEDED Kueri yang Anda jalankan melebihi batas waktu 60 detik dan memerlukan pengoptimalan tambahan. Lihat bagian performa untuk mendapatkan tips. Jika Anda tidak dapat menemukan akar masalahnya, silakan hubungi tim.
RESOURCE_EXHAUSTED Kueri yang Anda jalankan melebihi batas memori dan memerlukan pengoptimalan tambahan. Lihat bagian performa untuk mendapatkan tips. Jika Anda tidak dapat menemukan akar masalahnya, silakan hubungi tim.
INTERNAL Hubungi tim untuk mendapatkan dukungan.

Performa

Tidak seperti kueri yang sudah ada, operasi Pipeline tidak selalu memerlukan indeks. Artinya, kueri dapat menunjukkan latensi yang lebih tinggi dibandingkan dengan kueri yang sudah ada, yang akan langsung gagal dengan error indeks yang hilang FAILED_PRECONDITION. Untuk meningkatkan performa operasi Pipeline, ada beberapa langkah yang dapat Anda lakukan.

Membuat Indeks

Indeks yang Digunakan

Dengan Query Explain, Anda dapat mengetahui apakah kueri Anda ditayangkan oleh indeks atau dikembalikan ke operasi yang kurang efisien seperti pemindaian tabel. Jika kueri Anda tidak sepenuhnya ditayangkan dari indeks, Anda dapat membuat indeks dengan mengikuti petunjuk yang ada.

Membuat Indeks

Anda dapat mengikuti dokumentasi pengelolaan indeks yang ada untuk membuat indeks. Sebelum membuat indeks, pahami praktik terbaik umum terkait indeks di Firestore. Untuk memastikan kueri Anda dapat memanfaatkan indeks, ikuti praktik terbaik untuk membuat indeks dengan kolom dalam urutan berikut:

  1. Semua kolom yang akan digunakan dalam filter kesetaraan (dalam urutan apa pun)
  2. Semua kolom yang akan diurutkan (dalam urutan yang sama)
  3. Kolom yang akan digunakan dalam filter rentang atau ketidaksetaraan dalam urutan menurun dari selektivitas batasan kueri

Misalnya, untuk kueri berikut,

Node.js
const results = await db.pipeline()
  .collection('books')
  .where(lt('published', 1900))
  .where(eq('genre', 'Science Fiction'))
  .where(gt('avg_rating', 4.3))
  .sort(Field.of('published').descending())
  .execute();
    

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

Indeks yang direkomendasikan adalah indeks cakupan koleksi pada books untuk (genre [...], published DESC, avg_rating DESC).

Kepadatan indeks

Firestore mendukung indeks sparse dan non-sparse. Untuk mengetahui informasi selengkapnya, lihat Kepadatan indeks.

Kueri yang Tercakup + Indeks Sekunder

Firestore dapat melewati pengambilan dokumen lengkap dan hanya menampilkan hasil dari indeks jika semua kolom yang ditampilkan ada dalam indeks sekunder. Hal ini biasanya akan menghasilkan penurunan latensi (dan biaya) yang signifikan. Dengan contoh kueri di bawah:

Node.js
const results = await db.pipeline()
  .collection("books")
  .where(like(Field.of("category"), "%fantasy%"))
  .where(exists("title"))
  .where(exists("author"))
  .select("title", "author")
  .execute();
    

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

Jika database sudah memiliki indeks cakupan koleksi pada books untuk (category [...], title [...], author [...]), database dapat menghindari pengambilan apa pun dari dokumen utama itu sendiri. Dalam kasus ini, urutan dalam indeks tidaklah penting, hal ini ditandai dengan [...].

Membatasi Kolom yang akan Ditampilkan

Secara default, kueri Firestore menampilkan semua kolom dalam dokumen, yang serupa dengan SELECT * dalam sistem tradisional. Namun, jika aplikasi Anda hanya memerlukan sebagian kecil kolom, tahap select(...) atau restrict(...) dapat digunakan untuk mengirim pemfilteran ini ke sisi server. Hal ini akan mengurangi ukuran respons (mengurangi biaya egress jaringan) sekaligus mengurangi latensi.

Alat Pemecahanan Masalah

Query Explain

Dengan Query Explain, Anda dapat memiliki visibilitas terkait metrik eksekusi dan detail tentang indeks yang digunakan.

Metrik

Operasi Pipeline terintegrasi sepenuhnya dengan metrik Firestore yang ada.

Masalah Umum / Batasan

Indeks Khusus

Operasi pipeline belum mendukung jenis indeks array-contains & vector yang ada. Agar tidak terus-menerus menolak kueri tersebut, Firestore akan mencoba menggunakan indeks ascending & descending lainnya yang ada. Selama pratinjau pribadi, operasi Pipeline dengan ekspresi array_contains atau find_nearest tersebut diperkirakan akan lebih lambat daripada operasi yang setara karena hal ini.

Penomoran halaman

Dukungan untuk penomoran halaman yang mudah di set hasil tidak didukung selama pratinjau pribadi. Hal ini dapat diatasi dengan menggabungkan tahap where(...) & sort(...) yang setara seperti yang ditunjukkan di bawah.

Node.js
  // Existing pagination via 'startAt(...)'.
  db.collection("cities")
    .orderBy("population")
    .startAt(1000000);

  // Near-term work around via Pipeline operations.
  db.pipeline()
    .collection("cities")
    .where(gte("population", 1000000)
    .sort(Field.of("population").descending());
    

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
Android
// 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
Android
// 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())
)

Dukungan Emulator

Emulator belum mendukung operasi Pipeline.

Dukungan Real-time dan Offline

Operasi pipeline belum memiliki kemampuan real-time dan offline.

Langkah berikutnya