最佳做法

您可以使用這裡列出的最佳做法,快速瞭解建構使用 Cloud Firestore (Datastore 模式) 的應用程式時,應注意的事項。如果您才剛開始使用 Datastore 模式,本頁面不會說明如何使用 Datastore 模式的基本操作,因此可能不是開始熟悉 Datastore 模式的最佳選擇。如果您是新使用者,建議您從開始使用 Cloud Firestore (Datastore 模式) 一文開始。

一般注意事項

  • 命名空間名稱、種類名稱、屬性名稱及自訂索引鍵名稱請一律採用 UTF-8 字元。這類名稱如使用非 UTF-8 字元,可能會干擾 Datastore 模式的功能。例如,屬性名稱若含非 UTF-8 字元,可能會無法建立使用該屬性的索引。
  • 種類名稱或自訂索引鍵名稱不可使用正斜線 (/)。這類名稱如使用正斜線,可能會干擾未來的功能。
  • 避免將機密資訊儲存在 Cloud 專案 ID 中。專案結束後,專案 ID 仍有可能保留下來。

API 呼叫

  • 讀取、寫入及刪除應採用批次作業而非單一作業。批次作業的效率較高,原因在於這類作業能夠執行多項和單一作業負擔相同的作業。
  • 交易失敗,請務必復原交易。這是因為,當交易中有其他要求爭用相同資源時,復原作業能把因為重試而導致的延遲時間減至最短。請注意,復原本身也有可能失敗,因此復原嘗試也只能盡力而為,無法保證成功。
  • 盡可能以非同步呼叫取代同步呼叫。非同步呼叫可以盡可能降低延遲造成的衝擊。以需要同步 lookup() 及查詢結果才能顯示回應的應用程式為例。若 lookup() 及查詢不具資料相依關係,就不需要同步等待 lookup() 完成再開始查詢。

實體

  • 寫入實體的頻率請避免超過每秒一次。若寫入頻率高於這個限制,且常態如此,可能導致應用程式發生逾時,並使整體效能變慢。
  • 同一次修訂不要重複包含同一個實體 (以索引鍵為準)。在相同的修訂版本中多次包含同一個實體,可能會影響延遲時間。

索引鍵

  • 建立實體時若未提供索引鍵名稱,會由系統自動產生。索引鍵名稱的分配方式是平均散佈於索引鍵空間。
  • 如使用自訂索引鍵名稱,請一律使用 UTF-8 字元,但勿使用正斜線 (/)。非 UTF-8 字元會干擾多個程序,包括將 Datastore 模式的匯出檔案匯入 BigQuery。而正斜線則可能會干擾未來的功能。
  • 索引鍵若使用數字 ID:
    • ID 不可使用負數。負數 ID 可能會干擾排序功能。
    • ID 不可使用 0 (零) 值,否則您的 ID 會自動由系統分配。
    • 如要自行為您所建立的實體手動指派數字 ID,請設定讓應用程式使用 allocateIds() 方法取得 ID 區塊。這樣做可避免 Datastore 模式將您手動指派的其中一個數字 ID 指派給其他實體。
  • 若自行為您所建立的實體手動指派數字 ID 或自訂名稱,請勿使用單調遞增數值,例如:

    1, 2, 3, …,
    "Customer1", "Customer2", "Customer3", ….
    "Product 1", "Product 2", "Product 3", ….
    

    如果應用程式產生大量流量,此類連續編號方式可能會導致資源使用率不均,進而影響 Datastore 模式的延遲時間。為避免連續數字 ID 造成問題,請使用 allocateIds() 方法取得數字 ID。allocateIds() 方法會產生均勻分佈的連續數字 ID。

  • 之後,只要指定索引鍵或儲存系統產生的名稱,不需發出尋找實體的查詢亦可針對該實體執行 lookup()

索引

  • 若查詢再也不需要某項屬性,可以將屬性排除於索引之外。若建立不需要的屬性索引,可能會拉長延遲時間,也可能會增加索引項目的儲存成本
  • 避免過多的複合式索引。過度使用複合式索引可能會拉長延遲時間,也可能會增加索引項目的儲存成本。如果您需要在沒有先定義索引的情況下,對大型資料集執行臨時查詢,請使用 BigQuery
  • 具備單調遞增數值的屬性 (例如 NOW() 時間戳記),請勿建立索引。若應用程式的讀取率和寫入率較高,維護此類索引可能會造成資源使用率不均,進而影響 Datastore 模式的延遲時間。有關處理單調屬性的建議,請參閱下方的高速讀取/寫入小範圍索引鍵

屬性

  • 類型字串屬性一律使用 UTF-8 字元。類型字串屬性如使用非 UTF-8 字元,可能會干擾查詢。如需使用非 UTF-8 字元儲存資料,請改用位元組字串
  • 屬性名稱不可使用點 (.)。屬性名稱中如有點,則可能會干擾嵌入實體屬性的索引建立。

查詢

  • 若只需存取查詢結果的索引鍵,請使用純索引鍵查詢。純索引鍵查詢傳回結果的延遲機率較低,成本也低於擷取整個實體。
  • 如只需存取實體的特定屬性,請使用投影查詢。投影查詢傳回結果的延遲機率較低,成本也低於擷取整個實體。
  • 同樣地,如果只需存取包含在查詢篩選器中的屬性 (例如 order by 子句中列出的屬性),請使用投影查詢
  • 不可使用位移,需改用游標。使用位移只能避免將略過的實體傳回應用程式中,但是系統仍會在內部擷取這些實體。略過的實體會影響查詢延遲時間,而且應用程式在擷取這類實體時所須進行的讀取作業也要計費。

資源調度設計

實體更新

Datastore 模式中單一實體的更新速度不宜過快。

如使用 Datastore 模式,請將應用程式設計為不需要每秒更新實體超過一次。若更新實體群組的速度過快,Datastore 模式寫入作業發生延遲、逾時以及其他類型錯誤的機率會更高。這就是所謂的爭用現象。

Datastore 模式寫入單一實體的速度有時可能會超過每秒一次的上限,因此,載入測試不見得能夠彰顯這個問題。

高速讀取/寫入小範圍索引鍵

請避免高速讀取或寫入字母順序接近的文件,否則應用程式會發生爭用錯誤。此問題稱為資源使用率不均,如果您的應用程式執行以下任一操作,就可能會發生此問題:

  • 以非常高的速率建立新實體,並自行分配單純增加的 ID。

    Datastore 模式會使用分散式演算法來分配索引鍵。如使用自動實體 ID 分配功能建立新實體,則應該不會在寫入時遇到資源使用率不均的問題。

  • 使用舊版依序配置 ID 政策,以極高的速度建立新實體。

  • 針對含有少數實體的種類,以極高的速度建立新實體。

  • 使用已建立索引且單純增加的屬性值 (如時間戳記),以極高的速度建立新實體。

  • 高速刪除類別中的實體。

  • 以極高的速度寫入資料庫,但不逐漸增加流量。

使用 Datastore 模式時,如果一小範圍索引鍵的寫入速率突然提升,則可能會因資源使用率不均而導致寫入作業緩慢。Cloud Firestore (Datastore 模式) 終究會為了支援高載入而分割索引鍵空間。

讀取上限通常比寫入上限高得多,除非您是以高速讀取單一索引鍵。

資源使用率不均可能會發生在實體索引鍵和索引所用的索引鍵範圍上。

在某些情況下,資源使用率不均現象對應用程式造成的影響可能不僅止於妨礙讀取或寫入一小範圍索引鍵。例如,執行個體啟動時可能會對資源使用率高的索引鍵進行寫入或讀取,導致載入要求失敗。

若您的索引鍵或已建立索引的屬性將單純增加,您可以在前面加上隨機雜湊碼,確保索引鍵分割到多個子表上。

同樣地,如需使用排序或篩選器查詢單純增加 (或減少) 的屬性,可以改成為新的屬性建立索引,再於單純增加的值之前加上一個在整個資料集中均採用高基數的值,惟該值需為要執行之查詢範圍中的所有實體所共同擁有。例如,若您想要依時間戳記查詢項目,但一次只需要傳回一位使用者的結果,就可以在時間戳記前加上使用者 ID,然後改成為這個新屬性建立索引。這樣您仍然能夠進行查詢,也能排序該名使用者的結果,但由於加上了使用者 ID,因此能確保索引本身獲得良好分割。

增加流量

逐漸增加新種類或索引鍵空間部分的流量。

為了讓 Cloud Firestore (Datastore 模式) 有足夠的時間為流量增加做準備,您應該逐漸增加傳送至新種類的流量。建議傳送至新種類的流量不要超過每秒 500 項作業,接著每 5 分鐘增加 50% 的流量。理論上,這種增幅時程能在 90 分鐘後達到每秒處理 740K 個作業的速率。整個索引鍵範圍內的寫入作業一定要相當均勻。我們的 SRE 將這稱為「500/50/5」規則。

若您為了停止使用 A 種類並改用 B 種類而變更程式碼,這種逐漸增加的模式特別重要。要處理這樣的移轉作業,可以透過一種原始方式,那就是將程式碼改為讀取 B 種類,若 B 種類不存在,才讀取 A 種類。不過,這樣會導致索引鍵空間極小部分的新種類流量突然增加。

若移轉實體,以使其使用同一個種類但不同範圍的索引鍵,可能也會發生一樣的問題。

您將實體移轉至新種類或索引鍵時所採用的策略,應取決於您的資料模型。以下所舉的策略範例稱為「平行讀取」。您需要判斷這項策略是否適用於您的資料。平行作業在移轉階段對成本造成的影響會是相當重要的考量因素。

請先讀取舊的實體或索引鍵。若沒有舊的實體或索引鍵,可以讀取新的實體或索引鍵。高速讀取不存在的實體可能會造成資源使用率不均現象,因此請務必逐漸增加負載。先將舊實體複製到新實體再刪除舊實體,會是比較好的策略。逐漸增加平行讀取,確定新的索引鍵空間分割情況良好。

要逐漸增加新種類的讀取或寫入,可行策略之一是使用決定性的使用者 ID 雜湊隨機取得寫入新實體的使用者百分比。一定要避免隨機函式或使用者行為扭曲使用者 ID 雜湊結果。

同時,請執行 Dataflow 工作,將舊實體或索引鍵中的資料全數複製到新實體或索引鍵。批次工作應避免寫入連續索引鍵,以避免資源使用率不均。批次工作完成後,您就只能讀取新的位置。

這個策略的進階版是一次只移轉一小批使用者。在使用者實體中新增一個欄位,用於追蹤該名使用者的移轉狀態。根據使用者 ID 的雜湊碼選取一批使用者。Mapreduce 或 Dataflow 工作會移轉這一批使用者的索引鍵。正在進行移轉的使用者將使用平行讀取。

請注意,除非您在移轉階段同時寫入新舊實體,否則無法輕易復原。這會增加 Datastore 模式所產生的費用。

刪除作業

避免在一小範圍的索引鍵上刪除大量實體。

Cloud Firestore (Datastore 模式) 會定期重新寫入資料表來移除已刪除的項目,並重新整理您的資料,藉此提高讀取和寫入的效率。這個程序稱為壓縮。

如在一小範圍的索引鍵上刪除大量的 Datastore 模式實體,在壓縮完成前,此索引部分上的查詢會較為緩慢。在極罕見的情況下,查詢作業可能會在傳回結果之前逾時。

這種情況與在索引欄位使用時間戳記值代表實體到期時間背道而馳。為擷取過期的實體,您需要查詢這個索引欄位,而這個欄位可能存在於索引鍵空間與最近刪除實體之索引項目重疊的部分。

「資料分割查詢」會在時間戳記前加上固定長度的字串,您可以利用這類查詢提升效能。索引的依據整個字串進行排序,因此可以找到整個索引鍵範圍內屬於相同時間戳記的實體。您需同時執行多項查詢,以擷取每一項資料分割作業的結果。

要解決到期時間戳記問題,更全面的解決方案是使用「產生號碼」,這是一種會定期更新的全域計數器。系統會將產生號碼加在到期時間戳記之前,以利依產生號碼排列查詢順序,接著依序是分割資料和時間戳記。刪除舊實體的作業會在前一次產生時發生。凡未刪除的實體,產生號碼都要遞增。刪除完成後,就繼續進行下一次產生作業。若在完成壓縮前查詢舊的產生作業,效能會較差。您可能需要等待完成數次產生作業之後,才能查詢索引並取得待刪除實體的清單,這是為了降低最終不一致遺漏結果的風險。

資料分割與複製

使用資料分割或複製來處理資源使用率不均的問題。

如果需要以快於 Cloud Firestore (Datastore 模式) 允許之速度讀取索引鍵範圍的一部分,則可使用複製做法。這種策略能夠儲存同一個實體的 N 個複本,讀取速度也能比單一實體所支援的速度高出 N 倍。

如果需要以快於 Cloud Firestore (Datastore 模式) 允許之速度寫入索引鍵範圍的一部分,則可使用資料分割做法。資料分割會將實體分成數個較小的片段。

資料分割時的常見錯誤如下:

  • 以時間為字首進行資料分割。若時間前進到下一個字首,則新的未分割部分就會發生資源使用率不均現象。您應該改為逐漸為一部分寫入增加新的字首。

  • 只針對資源使用率不均現象最嚴重的實體進行資料分割。若分割實體總數的其中一小部分,資源使用率不均實體之間的資料列可能不足,因而無法確認這些實體是否屬於不同的分割資料。

後續步驟

本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Cloud Datastore 說明文件