Cloud Datastore 同步一致性與最終一致性的平衡取捨

提供一致的使用者體驗,並運用最終一致性模型調度大型資料集的資源

本文說明採用 Cloud Datastore 的最終一致性模型處理大量資料和使用者之時,如何達成同步一致性讓使用者獲得良好的體驗。

本文的目標讀者是有意採用 Cloud Datastore 建構解決方案的軟體架構設計人員與工程師。為協助對關聯資料庫熟悉程度大於 Cloud Datastore 等非關聯資料庫的讀者,本文會列舉關聯資料庫的類似概念。本文假設您熟悉 Cloud Datastore 基礎概念。要開始使用 Cloud Datastore,最簡單的方式就是使用任何一種支援語言版本的 Google App Engine。如果您未曾用過 App Engine,建議您先參閱任何一個語言版本的入門指南以及儲存資料一節。程式碼片段以 Python 為例,但您不需熟悉 Python,只要按照本文說明完成相關操作即可。

附註: 本文所列的程式碼片段使用 Cloud Datastore 專用的 Python DB 用戶端程式庫,但目前已不建議使用此程式庫。相較之下,NDB 用戶端程式庫有幾項優點,例如能透過 Memcache API 自動快取實體,因此我們極力推薦建構新應用程式的開發人員改用 NDB 用戶端程式庫。如果您目前使用舊版 DB 用戶端程式庫,請參閱 DB 移轉至 NDB 指南

目錄

NoSQL 與最終一致性
Cloud Datastore 的最終一致性
祖系查詢與實體群組
實體群組與祖系查詢的限制
祖系查詢的替代方案
盡可能縮短達到完整一致性的作業時間
結論
其他資源

NoSQL 與最終一致性

非關聯資料庫 (亦稱為 NoSQL 資料庫) 近年來崛起成為關聯資料庫的替代方案。Cloud Datastore 是業界廣泛使用的非關聯資料庫。在 2013 年,Cloud Datastore 每月處理的交易達 4.5 兆件 (Google Cloud Platform 網誌文章)。Google Cloud Platform 能夠讓開發人員輕鬆儲存及存取資料。這種靈活的架構自然而然對應於物件導向語言與指令碼語言。Cloud Datastore 也提供關聯資料庫不適合提供的多項功能,包括針對大範圍發揮高度的效能與可靠性。

對於較習慣使用關聯資料庫的開發人員而言,設計一套能夠使用非關聯資料庫的系統可說是一項挑戰。因為相對來說,他們可能不太熟悉關聯資料庫的部分特性與使用方式。Cloud Datastore 程式設計模型相當簡單,不過,瞭解這些特性還是相當重要的。最終一致性是上述特性之一,而能發揮最終一致性的程式設計則是本文的討論重點。

什麼是最終一致性?

最終一致性是一種理論保證,如果沒有針對實體進行任何新的更新,那麼所有讀取該實體的作業最終都會傳回最近更新的值。網際網路網域名稱系統 (DNS) 是使用最終一致性模型的最佳系統範例。DNS 伺服器不一定能反映最新的值,更具體來說,系統會快取及複製網際網路上各種目錄的所有值。將已修改的值複製到所有 DNS 用戶端與伺服器需要一些時間。即便如此,DNS 系統仍是相當成功的系統,已成為網際網路不可或缺的元素。DNS 系統的可用性極高,並且已獲證實擁有極佳的擴充性,可讓名稱在整個網路網路中查詢超過 1 億個裝置。

圖 1 展示具有最終一致性的複製概念。此圖說明儘管隨時都能讀取副本,但部分副本遲早會發生與來源節點最新寫入值不一致的情況。在本圖中,節點 A 為來源節點,而節點 B 和節點 C 為副本。

圖 1:具有最終一致性的複製概念

相反地,傳統的關聯式資料庫則是根據強式一致性 (亦稱為即時一致性) 設計而成。也就是說,當實體的所有觀察器更新內容為相同時,系統會立即檢視資料。這種特性是許多使用關聯式資料庫的開發人員提出的基本假設。然而,開發人員必須犧牲自己應用程式的擴充性與效能,才能保有強式一致性。簡單來說,在進行更新或複製時,資料必須處於鎖定狀態,以確保沒有其他程序更新同一份資料。

圖 2 所展示的是,具有強式一致性的部署拓撲及複製程序的概念。在該圖中,您可以看到副本如何總能具有與來源節點相同的值,只不過在更新結束之前無法供人存取。

圖 2:同步一致性的複寫概念

同步一致性與最終一致性的平衡取捨

近來非關聯資料庫十分普遍,要求高擴充性與高可用性效能的網路應用程式尤其如此。非關聯式資料庫可讓開發人員在各應用程式的強式一致性與最終一致性之間,取得最佳平衡。這能讓開發人員得以獲得同步一致性與最終一致性兩者的優點。舉例來說,像是「在指定時間內得知好友清單中有哪些人目前在線上」,或是「有多少使用者 +1 了您的訊息」之類的資訊,都是不需要強式一致性的最佳案例。只要善用最終一致性,即可為上述案例提供擴充性與效能。需要強式一致性的使用案例所包含的資訊,可能為「使用者是否已完成付款程序」或是「遊戲玩家在一場戰鬥中所獲得的分數」。

概括來說,若是使用大量實體的用途,通常建議使用最終一致性模型。若某項查詢得到相當大量的結果,納入或排除特定實體可能不會影響使用者體驗。但若是實體數量和內容較少的用途,則建議使用同步一致性模型。由於結構定義會讓使用者察覺到納入或排除了哪些實體,自然會影響使用者體驗。

因此,開發人員一定要瞭解 Cloud Datastore 的非關聯特性。以下章節將探討如何結合最終一致性與同步一致性模型,以利建構出兼具高度擴充性、可用性及效能的應用程式。這樣才能充分符合良好使用者體驗在一致性方面的要求。

Cloud Datastore 的最終一致性

必須從同步一致性的角度檢視資料時,一定要選取正確的 API。表 1 列出各種不同的 Cloud Datastore 查詢 API,以及其對應的一致性模型。

Cloud Datastore API

讀取實體值

讀取索引

全域查詢

最終一致性

最終一致性

純金鑰全域查詢

不適用

最終一致性

祖系查詢

同步一致性

同步一致性

依金鑰查詢 (get())

同步一致性

不適用

表 1:Cloud Datastore 查詢/get 呼叫與一致性可能出現的行為

沒有祖系的 Cloud Datastore 查詢稱為全域查詢,其設計適用於最終一致性模型。全域查詢不保證能發揮同步一致性。純金鑰全域查詢只傳回與查詢相符之實體的金鑰,這種全域查詢不傳回這些實體的屬性。祖系查詢會根據祖系實體決定查詢範圍。以下章節詳細說明各種一致性的行為。

讀取實體時的最終一致性

執行查詢時可能不會立即顯示出更新過的實體值,只有祖系查詢例外。如要瞭解讀取實體值對最終一致性的影響程度,請參考實體 (玩家) 擁有屬性 (分數) 的情況。舉例來說,假設初始分數為 100。經過一段時間後,系統將分數的值更新為 200。若執行全域查詢且結果包含相同的實體 (玩家),所傳回實體的屬性 (分數) 值可能仍為 100,與初始值相同。

Cloud Datastore 伺服器之間的複寫作業是造成這種行為的原因。複寫作業由 Cloud Bigtable 和 Cloud Datastore 的基礎技術 Megastore 管理 (關於 Cloud Bigtable 和 Megastore 的詳細資訊,請參閱其他資源)。複寫作業以 Paxos 演算法執行,這種演算法會同步等到大部分的複本確認更新要求為止。經過一段時間後,便會根據要求的資料更新複本。這段時間通常不久,但也無法保證實際作業時間究竟多長。在更新完成之前,執行查詢可能會讀取到過時的資料。

在大多數情況下,更新很快就能送達所有副本。然而,有幾個因素可能會讓達成一致性所需的時間增加。這些因素包括在不同的資料中心之間,透過大量伺服器進行切換時,資料中心發生的任何意外狀況。由於這些因素各不相同,我們無法明確提供建立完整一致性的所需時間。

查詢通常能快速傳回最新值。不過,在覆寫延遲時間變長的罕見情況下,查詢需要較長的時間才能傳回最新值。設計使用 Cloud Datastore 全域查詢的應用程式時應格外謹慎,日後遇到這種情況時才能迎刃而解。

只要利用純金鑰查詢、祖系查詢,或是按金鑰查詢 (即 get() 方法),就能避免讀取實體值達到最終一致性。接下來,我們將詳細說明各種類型的查詢。

讀取索引的最終一致性

執行全域查詢時無法更新索引。也就是說,即使可以讀取實體的最新屬性值,系統可能仍會根據舊索引值篩選查詢結果所包含的「實體清單」。

要瞭解最終一致性對讀取索引的影響程度,請設想將新實體 (玩家) 插入 Cloud Datastore 的情況。此實體具有一個初始值為 300 的屬性 (分數)。在插入 Google Cloud Datastore 後,如果您立即執行純金鑰查詢來擷取所有分數值大於 0 的實體,您將能看到剛插入的實體 (玩家) 出現在查詢結果中或者,查詢結果中並未顯示實體 (玩家)。執行查詢時,如果屬性 (分數) 的索引表未更新新插入的值,就有可能發生這種情況。

請記住,Cloud Datastore 一律根據索引表進行所有查詢,但索引表更新程序並非同步。實際上,實體更新程序包含兩個階段。在第一階段 (認可階段) 中,系統會寫入交易紀錄;在第二階段中,系統則會寫入資料並更新索引。只要認可階段可順利完成,寫入階段一定也能順利進行 (但系統可能無法在完成階段後,立即完成寫入階段)。如果您在更新索引之前查詢實體,最終得到的資料可能會不一致。

由於這種程序包含兩個階段,在全域查詢中看見實體的最新更新之前,會發生延遲的情況。與實體值最終一致性相同,雖然延遲時間通常很短,但也可能會加長 (在某些例外情況下,延遲時間可能會長達數分鐘以上)。

除此之外,更新完成後也可能發生這種情況。舉例來說,假設您使用新的屬性 (分數) 值 0 更新現有的實體 (玩家),然後立即執行相同的查詢。您就會發現該實體不會出現在查詢結果中,這是因為新的屬性 (分數) 值 0 會將它排除在外。不過,基於相同的非同步索引更新行為,系統仍有可能將實體納入查詢結果中。

只有使用祖系查詢或按金鑰查詢方法,才能避免讀取索引的最終一致性。純金鑰查詢無法避免這種行為。

讀取實體值與索引的同步一致性

Cloud Datastore 中只有兩種 API 能針對讀取實體值和索引提供同步一致性檢視:(1) 按金鑰查詢方法,以及 (2) 祖系查詢。若應用程式邏輯要求同步一致性,開發人員就該使用以上其中一種方法讀取 Cloud Datastore 中的實體。

Cloud Datastore 特別設計為針對這些 API 提供同步一致性。呼叫其中任何一項 API 時,Cloud Datastore 會清除其中一個複本和索引表中所有未完成的更新,接著執行查詢或祖系查詢。因此,系統一律會以最近的更新為準,根據更新過的索引表傳回最新的實體值。

相較於查詢,按金鑰呼叫查詢只能傳回一個/一組金鑰所指定的一個或一組實體。換句話說,在 Cloud Datastore 中唯有祖系查詢能夠同時符合同步一致性要求與篩選要求。不過,若未指定實體群組,祖系查詢就無法發揮作用。

祖系查詢與實體群組

本文開宗明義指出 Cloud Datastore 具有一項優點,就是能讓開發人員在同步一致性與最終一致性找到最完美的平衡點。Cloud Datastore 中的實體群組是具同步一致性、交易功能以及位置特性的單位。開發人員可以運用實體群組定義應用程式實體之間的同步一致性範圍。如此一來,應用程式就能讓實體群組保持一致性,同時也能發揮完整系統所具備的高度擴充性、可用性及效能。

實體群組是由根實體及其子項或後置項形成的階層架構。[1] 建立實體群組時,開發人員需指定祖系路徑,實際上就是一連串以子項金鑰開頭的父項金鑰。圖 3 說明實體群組的概念。在此範例中,包含「ateam」金鑰的根實體具有兩個帶有「ateam/098745」與「ateam/098746」金鑰的下層實體。

圖 3:實體群組概念的說明圖片

實體群組一定具有下列特性:

  • 同步一致性
    • 針對實體群組執行祖系查詢會傳回符合同步一致性的結果。這樣就能反映出按最新索引狀態篩選出的最新實體值。
  • 交易性
    • 只要以程式設計方式區分交易,實體群組即可在交易中提供 ACID 特性 (完整性、一致性、獨立性、耐用性)。
  • 位置特性
    • 由於系統會按金鑰的字典編列順序排序及儲存實體,因此實體群組中的實體都會儲存在 Cloud Datastore 伺服器中的相近位置。正因如此,祖系查詢能快速掃描實體群組,大幅降低 I/O。

祖系查詢是一種特殊的查詢方式,僅能針對指定的實體群組執行。這種查詢能以同步一致性模式執行。Cloud Datastore 會在背景確認執行查詢前已經套用了所有未完成的複寫及索引更新內容。

祖系查詢範例

本節舉例說明實體群組與祖系查詢的使用方式。在以下範例中,我們將管理資料紀錄的問題當作是人數問題。假設我們的程式碼可在您針對某個類型執行查詢後,立即加入該類別的實體。我們將以下方 Python 程式碼範例說明這種概念。

# Define the Person entity
class Person(db.Model):
    given_name = db.StringProperty()
    surname = db.StringProperty()
    organization = db.StringProperty()
# Add a person and retrieve the list of all people
class MainPage(webapp2.RequestHandler):
    def post(self):
        person = Person(given_name='GI', surname='Joe', organization='ATeam')
        person.put()
        q = db.GqlQuery("SELECT * FROM Person")
        people = []
        for p in q.run():
            people.append({'given_name': p.given_name,
                        'surname': p.surname,
                        'organization': p.organization})

這個程式碼的問題是,在大多數情況下,查詢不會傳回以上陳述式所新增的實體。這是因為查詢在插入之後會立即貼齊之後的資料行,因此執行查詢時索引將不會更新。不過,這個使用案例也存在有效性相關問題:是否真的需要在沒有內容的頁面中傳回所有人的清單?萬一人數有 100 萬人,該怎麼辦?人數過多會造成頁面過長,因而無法傳回。

根據這個使用案例,建議您提供部分內容來縮小查詢範圍。在這個範例中,我們所使用的內容為組織。這麼做能將組織當成實體群組使用,只要再執行祖系查詢,就能解決一致性的問題。以下使用 Python 程式碼進行示範。

class Organization(db.Model):
    name = db.StringProperty()
class Person(db.Model):
    given_name = db.StringProperty()
    surname = db.StringProperty()
class MainPage(webapp2.RequestHandler):
    def post(self):
        org = Organization.get_or_insert('ateam', name='ATeam')
        person = Person(parent=org)
        person.given_name='GI'
        person.surname='Joe'
        person.put()
        q = db.GqlQuery("SELECT * FROM Person WHERE ANCESTOR IS :1 ", org)
        people = []
        for p in q.run():
            people.append({'given_name': p.given_name,
                        'surname': p.surname})

這次,由於在 GqlQuery 中指定了祖系組織,查詢便可傳回剛剛插入的實體。您可以擴展本範例,查詢帶有祖系的個人名稱,以便取得某位使用者的詳細資料。另一個方式則是儲存實體金鑰,再使用按金鑰查詢深入瞭解相關資訊。

保持 Memcache 與 Cloud Datastore 之間的一致性

實體群組也可以成組運用,在 Memcache 項目和 Cloud Datastore 實體之間保持一致性。舉例來說,假設您要計算各團隊的成員人數,然後將結果儲存在 Memcache 中。您可以使用實體群組中繼資料,讓快取資料與 Cloud Datastore 中最新的值保持一致。中繼資料會傳回所指定實體群組的最新版本編號。您可以將版本編號與儲存在 Memcache 中的數值進行比較。您可以透過這種方式讀取其中一組中繼資料,以利偵測整個實體群組中的實體變化,不需掃描該群組中的每一個實體。

實體群組與祖系查詢的限制

使用實體群組與祖系查詢的方式,並非沒有缺陷。一般而言,使用實體群組與祖系查詢時必須克服兩個技術面的難題,如下所示。

  1. 每個實體群組均有每秒寫入一次更新的限制。
  2. 建立實體後便無法變更實體群組關聯性。

寫入限制

系統必須是專為控制各實體群組的更新 (或交易) 數目所設計,要做到這點有一定的難度。支援的限制是每個實體群組每秒更新一次。[2] 若更新需求大於限制,實體群組就有可能出現效能瓶頸。

在上述範例中,各組織可能必須更新組織內所有成員的記錄。假設「ateam」中有 1,000 人,且每人每秒都可對所有屬性進行一次更新。因此,實體群組中每秒更新可能高達 1,000 次,由於更新上限的緣故,系統將無法產生結果。由此可知,選擇實體群組設計時,必須將所需效能納入考量。而這就是在最終一致性與同步一致性之間取得最佳平衡的難處之一。

實體群組關聯性的不變性

第二項挑戰是實體群組關聯性的不變性。實體群組關聯性是由金鑰命名所組成的靜態關聯性,且無法在建立實體後變更。變更關聯性的唯一可行方式,就是刪除實體群組的實體,然後重新建立實體。這項挑戰導致我們無法使用實體群組即時定義一致性或交易性的自訂範圍。相反地,一致性與交易性範圍則與在設計階段所定義靜態實體群組具有相當緊密的關係。

舉例來說,假設您要在兩個不同的銀行帳戶之間進行電匯,因而需要強式一致性與交易性。然而,這兩個帳戶最終無法歸到同一實體群組,或是無法以全域父系為主要架構。該實體群組會對整個系統造成負面影響,使得系統無法執行其他電匯要求。因此,請勿以這種方式使用實體群組。

可以改用另一種方式,進行兼具高度擴充性與可用性的電匯作業。您可以為每個帳戶分別建立一個實體群組,而不是將所有帳戶納入同一個實體群組。這種方式就能透過交易保證兩個銀行帳戶均進行 ACID 更新。交易是 Cloud Datastore 的一項特色,可以讓您建立一組具有 ACID 特性的作業,最多可以納入二十五個實體群組。請注意,您必須交易中使用同步一致性查詢,例如按金鑰查詢及祖系查詢。有關交易限制的詳細資訊,請參閱交易和實體群組

祖系查詢的替代方案

如現有的應用程式已有大量實體儲存在 Cloud Datastore 中,之後重構時可能難以併入實體群組。要這麼做就必須刪除所有實體,再為這些實體加入實體群組關聯性。因此,在 Cloud Datastore 的資料模型中,於應用程式初期設計階段決定所需實體群組設計相當重要。否則,要重構只能選擇其他替代方案,才能發揮一定程度的一致性,例如在使用按金鑰查詢後執行純金鑰查詢,或是使用 Memcache。

按金鑰查詢後執行純金鑰全域查詢

純金鑰全域查詢是一種特殊的全域查詢方法,可在缺少實體屬性值的情況下,只傳回金鑰。由於傳回的值為純金鑰,因此查詢不會包含可能會不一致的實體值。將純金鑰與具有查詢方式的全域查詢結合,即可讀取最新的實體值。不過要注意的是,純金鑰全域查詢無法排除索引在查詢時出現不一致的可能性,而這可能會導致系統無法擷取實體。查詢結果可能是根據篩選出的舊索引值所產生。總而言之,只有當應用程式條件允許索引值在查詢時不一致,開發人員才能在使用按金鑰查詢後執行純金鑰全域查詢。

使用 Memcache

Memcache 服務雖不太穩定,但能保持同步一致性。因此,只要結合 Memcache 查詢與 Cloud Datastore 查詢,就能建構出一套可將一致性問題發生機率降到最低的系統。

舉例來說,假設某遊戲應用程式保存了一份實體 (玩家) 清單,各實體的分數皆大於 0。

  • 如要發出插入或更新要求,請在 Memcache 及 Cloud Datastore 中將這些實體套用於實體 (玩家) 清單。
  • 如要發出查詢要求,請讀取 Memcache 的實體 (玩家) 清單。如果 Memcache 未顯示該清單,就在 Cloud Datastore 執行純金鑰查詢

只要快取的清單顯示在 Memcache 中,系統傳回的清單就具有一致性。若已收回實體,或者暫時無法使用 Memcache 服務,系統可能需要讀取 Cloud Datastore 查詢的值,有可能會因此傳回不一致的結果。這種方法適用於所有能容許少量不一致的應用程式。

以 Memcache 做為 Cloud Datastore 的快取層時,可以參考一些最佳做法:

  • 擷取 Memcache 的例外狀況和錯誤,讓 Memcache 值和 Cloud Datastore 值保持一致。如在更新 Memcache 的實體時發生例外狀況,請務必撤銷 Memcache 中的舊實體。否則,實體的值 (Memcache 的舊值與 Cloud Datastore 的新值) 可能會不一樣。
  • 設定 Memcache 項目的有效期限。針對 Memcache 例外狀況,建議為每個實體設定較短的有效期限,盡可能降低發生不一致情形的機率。
  • 更新並行控制的項目時,請使用比較與設定功能。這項功能有助於避免相同實體的同步更新程序彼此干擾。

逐步移轉至實體群組

在上一節中所提出的建議,只能降低發生不一致行為的可能性。如果您需要強式一致性,建議您根據實體群組來設計應用程式。不過,無論是全域查詢還是祖系查詢,這種做法可能不適合用來移轉現有的應用程式,其中包含變更現有的資料模型與應用程式邏輯。想達成這個目的,就要透過以下的逐步轉換程序:

  1. 確認並決定應用程式中,需要強式一致性之各功的優先順序。
  2. 除了 (而非取代) 現有邏輯以外,還能利用實體群組為 insert() 或 update() 函數寫入新的邏輯。如此一來,不論是新實體群組還是舊實體的新插入或更新程序,都能交由適當的函數處理。
  3. 如果要求中包含新的實體群組,請針對先執行的讀取或查詢函數祖系查詢修改現有的邏輯。如果實體群組不存在,請執行舊的全域查詢做為後援邏輯。

這種對策適用於將現有的資料模型逐步移轉至以實體群組為主要架構的新資料模型,以便將最終一致性所造成的問題風險降到最低。這個方法會隨指定的使用案例與應用程式的需求而更動,以對應實際的系統。

模式降級的備用方案

目前,要系統以程式方式偵測應用程式一致性下降的情況並不容易。不過,若您透過其他方式確認了應用程式發生一致性下降情形,可以執行能夠關閉或開啟的降級模式,以利停用某些要求同步一致性的應用程式邏輯。舉例來說,與其在帳單報表中顯示不一致的查詢結果,倒不如在特定畫面顯示維護訊息。這種方式不僅能讓應用程式中的其他服務繼續提供服務,還能降低對使用者體驗造成的影響。

盡可能縮短達到完整一致性的作業時間

若是使用者多達數百萬人或 Cloud Datastore 實體數以 TB 計的大型應用程式,Cloud Datastore 使用不當有可能會導致一致性下降。使用不當包括以下情形:

  • 實體金鑰的序號
  • 索引過多

上述做法不會對小型應用程式造成影響。只不過,一旦應用程式變得非常大,運用這種做法將達成一致性所需時間拉長的可能性也會提高。因此,建議您在應用程式設計初期階段避免使用這些做法。

反面模式 #1:實體金鑰的序號

在 App Engine SDK 1.8.1 發佈之前,Cloud Datastore 使用連續的小整數 ID,預設自動產生金鑰名稱通常是連續的。某些文件將這種做法稱為建立實體的「繼承原則」,這類實體所含的金鑰名稱均不指定應用程式。這個繼承原則會使用序號產生實體金鑰名稱,例如 1000、1001、1002。不過,如前所述,Cloud Datastore 會按照金鑰名稱的字典編列順序儲存實體,因此,很可能會將即將建立的實體儲存在同一個 Cloud Datastore 伺服器上。如果應用程式產生的流量相當大,這組序號可能會加重特定伺服器的運算負載,導致一致性延遲變得更長。

在 App Engine SDK 1.8.1 中,Cloud Datastore 採用一套新的 ID 編號法,其預設原則是使用離散 ID (請參閱參考說明文件)。這個預設原則隨機產生的編號,最長可達 16 位數字,並可平均分佈數字的順序。透過這個原則,在降低達成一致性所需時間的同時,系統也能在 Cloud Datastore 伺服器中更妥當地劃分大型應用程式流量。除非您確定應用程式需要與繼承原則相容,否則建議您使用預設原則。

如要設定實體的金鑰名稱,請將命名配置設計成能夠均等存取整個金鑰名稱空間上的所有實體。換句話說,基於金鑰名稱所採用的字典序,請勿加重特定範圍存取的負載。否則,可能會發生與序號相關的問題。

如要進一步瞭解在金鑰空間上的存取分佈不均情形,請參閱系統使用連續的金鑰名稱建立實體的範例,如下列程式碼所示:

p1 = Person(key_name='0001')
p2 = Person(key_name='0002')
p3 = Person(key_name='0003')
...

該應用程式的存取模式可能會為金鑰名稱的特定範圍建立一個「作用點」,例如讓剛建立實體 (使用者) 存取的負載加重。在這種情況下,所有頻繁存取的金鑰會有較高的編號。之後,負載可能會集中在某一個特定的 Cloud Datastore 伺服器上。

此外,為瞭解金鑰空間上的平均分佈情況,請設想使用較長的隨機字串做為金鑰名稱的情況。如以下範例所示:

p1 = Person(key_name='t9P776g5kAecChuKW4JKCnh44uRvBDhU')
p2 = Person(key_name='hCdVjL2jCzLqRnPdNNcPCAN8Rinug9kq')
p3 = Person(key_name='PaV9fsXCdra7zCMkt7UX3THvFmu6xsUd')
...

最近建立的實體 (使用者) 會分散到金鑰空間與多個伺服器,前提是您有相當大量的實體 (個人)。

反面模式 #2:索引過多

在 Cloud Datastore 中,每更新實體一次,就必須更新為該實體類型定義的所有索引 (詳細資訊請參閱 Datastore 寫入的存留時間)。如果應用程式使用多種自訂索引,當您進行一次更新後,就可能必須更新數十個、數百個,甚至數千個索引表。在大型應用程式中,使用過多的自訂索引可能會加重伺服器的負載,並增加達成一致性的延遲時間。

在大多數情況下,系統會新增自訂索引,以滿足客戶支援、疑難排解,或資料分析工作等需求。BigQuery 是能大規模擴充的查詢引擎,可以在不需預先建立索引的情況下即時查詢大型資料集。這種方式較適合用於客戶支援、疑難排解,或者是所需查詢比 Cloud Datastore 更複雜的資料分析。

其中一種做法是結合 Cloud Datastore 和 BigQuery 來因應不同的業務需求。使用 Cloud Datastore 進行核心應用程式邏輯所需的線上交易處理 (OLTP),BigQuery 則用於後端作業所需的線上分析處理 (OLAP)。可能必須連續將資料從 Cloud Datastore 匯出到 BigQuery,才能移動這些查詢所需的資料。

除了使用自訂索引這個替代執行方案之外,另外也建議明確指定未編入索引的屬性 (請參閱屬性和值類型)。根據預設,Cloud Datastore 會為同一種實體中每一個可編入索引的屬性分別建立一個不同的索引表。如果一個種類包含 100 個屬性,就會有 100 份索引表,而該實體的所有更新也會多 100 個更新。如果這些屬性並非查詢條件所需,那麼建議您視情況將屬性設為未編入索引。

除了能降低一致性作業時間變長的可能性外,若大型應用程式頻繁使用索引,這些索引最佳化作業有助於大量降低 Cloud Datastore 儲存成本

結論

最終一致性是非關聯資料庫的必要元素,能讓開發人員得以在擴充性、效能以及一致性之間保持最佳平衡。一定要瞭解如何在最終一致性與同步一致性之間保持平衡,為自己的應用程式設計出最理想的資料模型,這一點非常重要。在 Cloud Datastore 中,若要保證讓一定範圍內的實體保持同步一致性,使用實體群組和祖系查詢是最好的方式。如果您的應用程式因先前所述的限制而無法併入實體群組,建議您使用其他方法,例如使用純金鑰查詢或 Memcache。若是大型應用程式,請使用離散 ID 及減少索引等方式縮短達成一致性所需的時間。合併運用 Cloud Datastore 和 BigQuery 也很重要,這樣能夠因應對於複雜查詢的業務需求,也能盡量減少使用 Cloud Datastore 索引。

其他資源

下列是與本文主題相關的資源:




[1] 實體群組函數一律根據金鑰之間的關聯性執行,因此,即使是在不儲存根或上層實體的情況下,也能單純透過指定單一根金鑰或父項的方式形成實體群組。

[2] 支援的限制是交易之外每個實體群組每秒更新一次,或者是每個實體群組每秒進行一次交易。如您將多個更新匯總至一筆交易,系統會將您的最大交易容量限制在 10 MB,並且限於 Datastore 伺服器的最大寫入速度。

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

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

這個網頁
Cloud Datastore 說明文件