本頁面說明為應用程式選取 Firestore (Datastore 模式) 索引時應考量的概念。
Firestore (Datastore 模式) 會對所有查詢使用索引,因此查詢效能極高。大多數查詢的效能取決於結果集的大小,而非資料庫的總大小。
Firestore (Datastore 模式) 會為實體中的每個屬性定義內建索引。這些單一屬性索引支援許多簡單查詢。Datastore 模式的 Firestore 支援索引合併功能,可讓資料庫合併內建索引,以支援其他查詢。如要進行較複雜的查詢,您必須預先定義複合式索引。
本頁面著重於索引合併功能,因為這項功能會影響兩項重要的索引最佳化商機:
- 加快查詢速度
- 減少複合式索引數量
以下範例說明索引合併功能。
篩選 Photo
實體
假設您有一個 Datastore 模式資料庫,其中包含 Photo
種類的實體:
相片 | ||
---|---|---|
屬性 | 值類型 | 說明 |
owner_id |
字串 | 使用者 ID |
tag |
字串陣列 | 權杖化關鍵字 |
size |
整數 |
列舉:
|
coloration |
整數 |
列舉:
|
假設您需要應用程式功能,讓使用者根據下列邏輯 AND
查詢 Photo
實體:
最多可根據下列屬性套用三項篩選條件:
owner_id
size
coloration
tag
搜尋字串。應用程式會將搜尋字串權杖化為標記,並為每個標記新增篩選器。舉例來說,應用程式會將搜尋字串
outside, family
轉換為查詢篩選器tag=outside
和tag=family
。
使用內建索引和 Firestore in Datastore 模式的索引合併功能,即可滿足這項 Photo
篩選器功能的索引需求,不必新增其他複合式索引。
Photo
實體的內建索引支援單一篩選器查詢,例如:
Python
Photo
篩選器功能也需要查詢,才能將多個等式篩選器與邏輯 AND
結合:
Python
Firestore (Datastore 模式) 可合併內建索引,支援這些查詢。
索引合併
當查詢和索引符合下列所有限制時,Datastore 模式的 Firestore 即可使用索引合併:
- 查詢僅使用等式 (
=
) 篩選器 - 沒有完全符合查詢篩選條件和排序方式的複合索引
- 每個等式篩選器至少要與一個現有索引相符,且排序方式與查詢相同
在這種情況下,Firestore (Datastore 模式) 可以使用現有索引來支援查詢,不必設定額外的複合式索引。
如果兩個以上的索引採用相同條件排序,Firestore (Datastore 模式) 可以合併多個索引掃描的結果,找出所有這類索引的共同結果。Datastore 模式的 Firestore 可以合併內建索引,因為這些索引都會依實體鍵排序值。
合併內建索引後,Datastore 模式的 Firestore 即可支援對多個屬性套用等值篩選條件的查詢:
Python
Datastore 模式的 Firestore 也可以合併同一索引多個區段的索引結果。Firestore (Datastore 模式) 會合併屬性的內建索引不同部分,藉此支援在邏輯 AND
中合併多個 tag
篩選條件的查詢:tag
Python
合併內建索引支援的查詢,可補足 Photo
篩選功能所需的查詢集。請注意,支援Photo
篩選功能不需要任何額外的複合索引。
為應用程式選取最佳索引時,請務必瞭解索引合併功能。索引合併功能可讓 Datastore 模式的 Firestore 查詢更具彈性,但可能會影響效能。下一節將說明索引合併的效能,以及如何透過新增複合索引提升效能。
尋找合適的索引
索引會先按祖系排序,再按屬性值排序,排序順序則以索引定義為準。查詢的「完美複合式索引」可以讓查詢以最有效率的方式執行,此索引由下列屬性依序定義:
- 等式篩選器使用的屬性
- 排序順序使用的屬性
distinctOn
篩選器使用的屬性- 範圍和不等式篩選器中使用的屬性 (尚未納入排序順序)
- 用於彙整和投影的屬性 (不包含已納入排序順序和範圍與不等式篩選器的屬性)
這可確保每個潛在查詢執行作業的所有結果都會計入。Datastore 模式下的 Firestore 資料庫會依照以下步驟使用完美索引執行查詢:
- 找出查詢種類、篩選屬性、篩選運算子以及排序順序所對應的索引
- 從索引的開頭開始掃描,直至找出符合該查詢所有或部分篩選條件的第一個實體
- 繼續掃描索引,傳回符合所有篩選條件的每個實體,直到:
- 遇到不符合篩選條件的實體,或
- 掃描到索引結尾處,或
- 已收集到查詢所要求的結果數上限
例如,請思考以下查詢:
SELECT * FROM Task
WHERE category = 'Personal'
AND priority < 3
ORDER BY priority DESC
此查詢的完美複合式索引是 Task
種類實體的索引鍵索引,其中包含 category
和 priority
屬性值的欄。索引的排序順序是先按 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
每個篩選器都會對應至下列內建索引中的一個索引掃描:
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
如要提升查詢效能,您可以新增下列複合索引,藉此降低 O(S/(R * I))
中的 S
值 (單一索引掃描中最小的實體集):
Index(Photo, size, coloration)
與使用兩個內建索引相比,這個複合索引會為相同的兩個查詢篩選器產生較少的潛在結果。這種做法會多出一個索引,但可大幅提升效能。
透過索引合併減少複合式索引的數量
雖然完全符合查詢中篩選條件的複合索引效能最佳,但為每個篩選條件組合新增複合索引,不一定是最佳做法,有時也不可行。您必須根據下列因素,調整複合式索引的平衡:
複合式索引限制:
限制 金額 資料庫的複合式索引數量上限 實體的複合式索引項目大小總計上限 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 模式資料庫選取最佳索引:
使用索引合併功能支援其他查詢
- 需要較少的複合式索引
- 降低每個實體的儲存成本
- 改善寫入延遲
- 避免爆炸式索引
- 效能取決於資料的形狀
定義符合查詢中多個篩選條件的複合式索引
- 提高查詢效能
- 查詢效能穩定,不受資料形狀影響
- 不得超過複合式索引的限制
- 每個實體的儲存空間費用增加
- 寫入延遲時間增加
在找出應用程式的最佳索引時,答案可能會隨著資料形狀的變化而改變。查詢效能取樣可讓您瞭解應用程式的常見查詢和緩慢查詢。您可以根據這項資訊新增索引,提升常見查詢和緩慢查詢的效能。