最佳化索引

本頁面說明為應用程式選取 Firestore (Datastore 模式) 索引時應考量的概念。

Firestore (Datastore 模式) 會對所有查詢使用索引,因此查詢效能極高。大多數查詢的效能取決於結果集的大小,而非資料庫的總大小。

Firestore (Datastore 模式) 會為實體中的每個屬性定義內建索引。這些單一屬性索引支援許多簡單查詢。Datastore 模式的 Firestore 支援索引合併功能,可讓資料庫合併內建索引,以支援其他查詢。如要進行較複雜的查詢,您必須預先定義複合式索引。

本頁面著重於索引合併功能,因為這項功能會影響兩項重要的索引最佳化商機:

  • 加快查詢速度
  • 減少複合式索引數量

以下範例說明索引合併功能。

篩選 Photo 實體

假設您有一個 Datastore 模式資料庫,其中包含 Photo 種類的實體:

相片
屬性 值類型 說明
owner_id 字串 使用者 ID
tag 字串陣列 權杖化關鍵字
size 整數 列舉:
  • 1 icon
  • 2 medium
  • 3 large
coloration 整數 列舉:
  • 1 black & white
  • 2 color

假設您需要應用程式功能,讓使用者根據下列邏輯 AND 查詢 Photo 實體:

  • 最多可根據下列屬性套用三項篩選條件:

    • owner_id
    • size
    • coloration
  • tag搜尋字串。應用程式會將搜尋字串權杖化為標記,並為每個標記新增篩選器。

    舉例來說,應用程式會將搜尋字串 outside, family 轉換為查詢篩選器 tag=outsidetag=family

使用內建索引和 Firestore in Datastore 模式的索引合併功能,即可滿足這項 Photo 篩選器功能的索引需求,不必新增其他複合式索引。

Photo 實體的內建索引支援單一篩選器查詢,例如:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_owner_id = client.query(kind="Photo", filters=[("owner_id", "=", "user1234")])

query_size = client.query(kind="Photo", filters=[("size", "=", 2)])

query_coloration = client.query(kind="Photo", filters=[("coloration", "=", 2)])

Photo 篩選器功能也需要查詢,才能將多個等式篩選器與邏輯 AND 結合:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_all_properties = client.query(
    kind="Photo",
    filters=[
        ("owner_id", "=", "user1234"),
        ("size", "=", 2),
        ("coloration", "=", 2),
        ("tag", "=", "family"),
    ],
)

Firestore (Datastore 模式) 可合併內建索引,支援這些查詢。

索引合併

當查詢和索引符合下列所有限制時,Datastore 模式的 Firestore 即可使用索引合併:

  • 查詢僅使用等式 (=) 篩選器
  • 沒有完全符合查詢篩選條件和排序方式的複合索引
  • 每個等式篩選器至少要與一個現有索引相符,且排序方式與查詢相同

在這種情況下,Firestore (Datastore 模式) 可以使用現有索引來支援查詢,不必設定額外的複合式索引。

如果兩個以上的索引採用相同條件排序,Firestore (Datastore 模式) 可以合併多個索引掃描的結果,找出所有這類索引的共同結果。Datastore 模式的 Firestore 可以合併內建索引,因為這些索引都會依實體鍵排序值。

合併內建索引後,Datastore 模式的 Firestore 即可支援對多個屬性套用等值篩選條件的查詢:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_all_properties = client.query(
    kind="Photo",
    filters=[
        ("owner_id", "=", "user1234"),
        ("size", "=", 2),
        ("coloration", "=", 2),
        ("tag", "=", "family"),
    ],
)

Datastore 模式的 Firestore 也可以合併同一索引多個區段的索引結果。Firestore (Datastore 模式) 會合併屬性的內建索引不同部分,藉此支援在邏輯 AND 中合併多個 tag 篩選條件的查詢:tag

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_tag = client.query(
    kind="Photo",
    filters=[
        ("tag", "=", "family"),
        ("tag", "=", "outside"),
        ("tag", "=", "camping"),
    ],
)

query_owner_size_color_tags = client.query(
    kind="Photo",
    filters=[
        ("owner_id", "=", "user1234"),
        ("size", "=", 2),
        ("coloration", "=", 2),
        ("tag", "=", "family"),
        ("tag", "=", "outside"),
        ("tag", "=", "camping"),
    ],
)

合併內建索引支援的查詢,可補足 Photo 篩選功能所需的查詢集。請注意,支援Photo 篩選功能不需要任何額外的複合索引。

為應用程式選取最佳索引時,請務必瞭解索引合併功能。索引合併功能可讓 Datastore 模式的 Firestore 查詢更具彈性,但可能會影響效能。下一節將說明索引合併的效能,以及如何透過新增複合索引提升效能。

尋找合適的索引

索引會先按祖系排序,再按屬性值排序,排序順序則以索引定義為準。查詢的「完美複合式索引」可以讓查詢以最有效率的方式執行,此索引由下列屬性依序定義:

  1. 等式篩選器使用的屬性
  2. 排序順序使用的屬性
  3. distinctOn 篩選器使用的屬性
  4. 範圍和不等式篩選器中使用的屬性 (尚未納入排序順序)
  5. 用於彙整和投影的屬性 (不包含已納入排序順序和範圍與不等式篩選器的屬性)

這可確保每個潛在查詢執行作業的所有結果都會計入。Datastore 模式下的 Firestore 資料庫會依照以下步驟使用完美索引執行查詢:

  1. 找出查詢種類、篩選屬性、篩選運算子以及排序順序所對應的索引
  2. 從索引的開頭開始掃描,直至找出符合該查詢所有或部分篩選條件的第一個實體
  3. 繼續掃描索引,傳回符合所有篩選條件的每個實體,直到:
    • 遇到不符合篩選條件的實體,或
    • 掃描到索引結尾處,或
    • 已收集到查詢所要求的結果數上限

例如,請思考以下查詢:

SELECT * FROM Task
WHERE category = 'Personal'
  AND priority < 3
ORDER BY priority DESC

此查詢的完美複合式索引是 Task 種類實體的索引鍵索引,其中包含 categorypriority 屬性值的欄。索引的排序順序是先按 category 遞增排序,再依 priority 遞減排序:

indexes:
- kind: Task
  properties:
  - name: category
    direction: asc
  - name: priority
    direction: desc

形式相同但包含不同篩選條件值的兩個查詢使用同一個索引。例如,以下查詢使用與前述查詢相同的索引:

SELECT * FROM Task
WHERE category = 'Work'
  AND priority < 5
ORDER BY priority DESC

此索引

indexes:
- kind: Task
  properties:
  - name: category
    direction: asc
  - name: priority
    direction: asc
  - name: created
    direction: asc

先前的索引可滿足下列兩個查詢:

SELECT * FROM Task
WHERE category = 'Personal'
  AND priority = 5
ORDER BY created ASC

SELECT * FROM Task
WHERE category = 'Work'
ORDER BY priority ASC, created ASC

最佳化索引選取範圍

本節說明索引合併的效能特徵,以及與索引合併相關的兩項最佳化機會:

  • 新增複合式索引,加快依賴合併索引的查詢速度
  • 運用合併索引減少複合式索引的數量

索引合併效能

在索引合併作業中,Datastore 模式下的 Firestore 會使用之字形合併聯結演算法,有效率地合併索引。Datastore 模式會使用這項演算法,彙整多個索引掃描的潛在相符項目,產生符合查詢的結果集。索引合併會在讀取時合併篩選器元件,而不是在寫入時合併。與大多數 Firestore (Datastore 模式) 查詢不同,索引合併查詢的效能取決於查詢中的篩選條件,以及資料庫考量的潛在相符項目數量,而一般查詢的效能只取決於結果集的大小。

如果索引中每個可能的相符項目都符合查詢篩選條件,索引合併作業就能達到最佳成效。在本例中,效能為 O(R * I),其中 R 是結果集的大小,I 則是掃描的索引數量。

如果資料庫必須考慮許多可能的相符項目,但只有少數項目符合查詢篩選條件,就會發生最差的效能。在這種情況下,效能為 O(S),其中 S 是單一索引掃描中最小的潛在實體集大小。

實際效能取決於資料的形狀。每個傳回結果的平均實體數量為 O(S/(R * I))。如果每個索引掃描都比對到許多實體,但整體查詢比對到的實體很少,也就是 R 很小,而 S 很大,查詢效能就會變差。

有四種方法可降低這項風險:

  • 查詢規劃工具會等到確定實體符合整個查詢,才會查詢實體。

  • 鋸齒狀演算法不必找出所有結果,即可傳回下一個結果。如果您要求前 10 個結果,則只需支付尋找這 10 個結果的延遲時間。

  • 之字形演算法會略過大量偽陽性結果。只有在掃描之間,偽陽性結果完全交錯 (依排序順序) 時,才會發生最差的效能。

  • 延遲時間取決於每次索引掃描找到的實體數量,而非符合各篩選條件的實體數量。如下一節所示,您可以新增複合索引,提升索引合併效能。

加快索引合併查詢速度

當 Datastore 模式的 Firestore 合併索引時,每個索引掃描通常會對應至查詢中的單一篩選條件。您可以新增複合式索引,比對查詢中的多個篩選條件,藉此提升查詢效能。

請參考以下查詢:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_owner_size_tag = client.query(
    kind="Photo",
    filters=[
        ("owner_id", "=", "username"),
        ("size", "=", 2),
        ("tag", "=", "family"),
    ],
)

每個篩選器都會對應至下列內建索引中的一個索引掃描:

Index(Photo, owner_id)
Index(Photo, size)
Index(Photo, tag)

如果新增複合索引 Index(Photo, owner_id, size),查詢會對應至兩次索引掃描,而非三次:

#  Satisfies both 'owner_id=username' and 'size=2'
Index(Photo, owner_id, size)
Index(Photo, tag)

假設有許多大型圖片和黑白圖片,但大型全景圖片不多。如果查詢合併內建索引,篩選全景和黑白圖片的查詢速度就會變慢:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_size_coloration = client.query(
    kind="Photo", filters=[("size", "=", 2), ("coloration", "=", 1)]
)

如要提升查詢效能,您可以新增下列複合索引,藉此降低 O(S/(R * I)) 中的 S 值 (單一索引掃描中最小的實體集):

Index(Photo, size, coloration)

與使用兩個內建索引相比,這個複合索引會為相同的兩個查詢篩選器產生較少的潛在結果。這種做法會多出一個索引,但可大幅提升效能。

透過索引合併減少複合式索引的數量

雖然完全符合查詢中篩選條件的複合索引效能最佳,但為每個篩選條件組合新增複合索引,不一定是最佳做法,有時也不可行。您必須根據下列因素,調整複合式索引的平衡:

  • 複合式索引限制:

    限制 金額
    資料庫的複合式索引數量上限
    • 如果尚未為 Google Cloud 專案啟用計費功能,則為 200。

      如果需要更多配額,則須為專案啟用計費功能。 Google Cloud

    • 為 Google Cloud 專案啟用計費功能時,系統會提供 1,000 點。

      如要提高這項上限,請與支援團隊聯絡

    實體的複合式索引項目大小總計上限 2 MiB
    實體的下列項目總和上限:
    • 已編入索引的屬性值數量
    • 複合式索引項目的數量
    20,000
  • 每個額外索引的儲存空間費用。
  • 對寫入延遲的影響。

多值欄位 (例如 Photo 實體的 tag 屬性) 經常會發生索引問題。

舉例來說,假設 Photo 篩選功能現在需要支援以四個額外屬性為準的遞減排序子句:

相片
屬性 值類型 說明
date_added 整數 日期/時間
rating 浮點值 使用者綜合評分
comment_count 整數 留言數
download_count 整數 下載次數

如果忽略 tag 欄位,則可以選取符合每種 Photo 篩選器組合的複合索引:

Index(Photo, owner_id, -date_added)
Index(Photo, owner_id, -comments)
Index(Photo, size, -date_added)
Index(Photo, size, -comments)
...
Index(Photo, owner_id, size, -date_added)
Index(Photo, owner_id, size, -comments)
...
Index(Photo, owner_id, size, coloration, -date_added)
Index(Photo, owner_id, size, coloration, -comments)

複合式索引總數為:

2^(number of filters) * (number of different orders) = 2 ^ 3 * 4 = 32 composite indexes

如果嘗試支援最多 3 個 tag 篩選器,複合索引總數為:

2 ^ (3 + 3 tag filters) * 4 = 256 indexes.

包含多值屬性的索引 (例如 tag) 也會導致爆炸式索引問題,進而增加儲存成本和寫入延遲。

如要支援這項功能的 tag 欄位篩選器,您可以依賴合併索引來減少索引總數。如要支援使用排序的 Photo 篩選功能,至少需要下列複合索引組合:

Index(Photo, owner_id, -date_added)
Index(Photo, owner_id, -rating)
Index(Photo, owner_id, -comments)
Index(Photo, owner_id, -downloads)
Index(Photo, size, -date_added)
Index(Photo, size, -rating)
Index(Photo, size, -comments)
Index(Photo, size, -downloads)
...
Index(Photo, tag, -date_added)
Index(Photo, tag, -rating)
Index(Photo, tag, -comments)
Index(Photo, tag, -downloads)

定義的複合式索引數量為:

(number of filters + 1) * (number of orders) = 7 * 4 = 28

合併索引也有下列優點:

  • 允許 Photo 實體支援最多 1000 個標記,且每個查詢的 tag 篩選器數量沒有限制。
  • 減少索引總數,進而降低儲存空間費用和寫入延遲時間。

為應用程式選取索引

您可以透過下列兩種方法,為 Datastore 模式資料庫選取最佳索引:

  • 使用索引合併功能支援其他查詢

    • 需要較少的複合式索引
    • 降低每個實體的儲存成本
    • 改善寫入延遲
    • 避免爆炸式索引
    • 效能取決於資料的形狀
  • 定義符合查詢中多個篩選條件的複合式索引

    • 提高查詢效能
    • 查詢效能穩定,不受資料形狀影響
    • 不得超過複合式索引的限制
    • 每個實體的儲存空間費用增加
    • 寫入延遲時間增加

在找出應用程式的最佳索引時,答案可能會隨著資料形狀的變化而改變。查詢效能取樣可讓您瞭解應用程式的常見查詢和緩慢查詢。您可以根據這項資訊新增索引,提升常見查詢和緩慢查詢的效能。