本文件旨在說明將 Spanner 當做主要後端資料庫用來儲存遊戲狀態的最佳做法。您可以使用 Spanner 取代用來儲存玩家驗證資料和庫存資料的常用資料庫。本文件適用對象為負責管理長期狀態儲存作業的遊戲後端工程師,以及支援這些系統且有意在Google Cloud中託管後端資料庫的遊戲基礎架構作業人員和管理員。
多人對戰遊戲和線上遊戲已不斷進化,需要越來越複雜的資料庫結構來追蹤玩家授權、狀態和庫存資料。不斷增加的玩家人數和越來越複雜的遊戲會導致資料庫解決方案難以擴充及管理,因而經常需要使用資料分割或叢集功能。追蹤寶貴的遊戲內物品或至關重要的玩家進度通常會涉及交易行為,因此在許多類型的分散式資料庫中處理交易資訊頗具挑戰性。
Spanner 是第一個專為雲端環境打造、遍及全球且具同步一致性的可擴充企業級資料庫服務,結合了關聯資料庫結構和非關聯水平資源調度的優點。許多遊戲公司發現這項服務非常適合取代實際運作規模系統中的遊戲狀態和驗證資料庫。您可以使用Google Cloud 主控台新增節點,藉此擴充額外的效能或儲存空間。Spanner 具備同步一致性,能夠以公開透明的方式處理全域複製作業,讓您無須管理區域備用資源。
這份最佳做法文件將說明下列事項:
- Spanner 的重要概念,以及與其他常用遊戲資料庫的差異。
- Spanner 適合做為遊戲資料庫的條件。
- 使用 Spanner 做為遊戲資料庫時應避免的資料模式。
- 使用 Spanner 做為遊戲資料庫來設計資料庫的作業流程。
- 建立資料模型和結構定義,以便透過 Spanner 獲得最佳效能。
術語
- 授權
- 屬於玩家的遊戲、擴充內容或應用程式內購項目。
- 個人識別資訊 (PII)
- 在遊戲中,玩家個人資訊通常包含電子郵件地址和付款帳戶資訊,例如信用卡號碼和帳單地址。在部分市場中,這項資訊可能還包含國民身分證號碼。
- 遊戲資料庫
- 用來儲存玩家的遊戲進度和庫存的資料庫。
- 驗證資料庫
- 包含玩家授權以及玩家購物時所用 PII 的資料庫。驗證資料庫也稱為帳戶資料庫或玩家資料庫。這類資料庫有時會與遊戲資料庫合併,但擁有多個遊戲的工作室或發布商經常會分開建立這兩種資料庫。
- 交易
- 資料庫交易:具有「全部生效或全部無效」特性的一連串寫入作業。一種情況是交易成功且所有更新都生效,另一種情況則是資料庫回復到不含任何交易更新的狀態。在遊戲中,資料庫交易是處理付款以及指派有價值的遊戲內庫存物品所有權或貨幣所有權的關鍵。
- 關聯資料庫管理系統 (RDBMS)
- 以資料表和資料列為基礎相互參照的資料庫系統。SQL Server、MySQL 和 Oracle® (較少見) 都是遊戲中所用的關聯資料庫範例。這些資料庫可提供熟悉的操作方法以及明確保證交易成功,因此相當常用。
- NoSQL 資料庫
- 沒有關聯結構化的資料庫。這類資料庫在資料模型變更時可提供的靈活性極佳,因此在遊戲中漸受歡迎。NoSQL 資料庫包括 MongoDB 和 Cassandra。
- 主鍵
- 通常是包含庫存物品、玩家帳戶和購買交易專屬 ID 的資料欄。
- 執行個體
- 單一資料庫。舉例來說,叢集會執行多個資料庫軟體副本,但會在遊戲後端顯示為單一執行個體。
- 節點
- 就本文件而言,指的是執行資料庫軟體副本的單一機器。
- 備用資源
- 資料庫的第二個副本。備用資源經常用來還原資料,可提供高可用性或提升讀取總處理量。
- 叢集
- 在多台機器上執行的多個軟體副本,會以單一執行個體的形式在遊戲後端顯示。叢集可用來提高擴充性和可用性。
- 資料分割
- 資料庫的例項。許多遊戲工作室會執行多個性質相同的資料庫執行個體,而每個執行個體都包含一部分的遊戲資料。每個執行個體都稱為「資料分割」。資源分割的目的通常是為了提高效能或擴充性,但同時會增加應用程式的複雜度,因而降低管理效率。在 Spanner 中,資料分割是透過「分割」實作。
- 分割
- Spanner 會將資料拆成名為分割的區塊,而個別分割可獨立移動,並指派給不同伺服器。分割定義為頂層 (亦即非交錯) 資料表中的某個資料列範圍,其中資料列會按主鍵排序。此範圍內的起始及結束索引鍵稱為「分割界線」。Spanner 會自動新增和移除分割界線,進而變更資料庫中的分割數量。Spanner 會依據負載來分割資料,做法是在單一分割中偵測到許多索引鍵都有高讀取和寫入負載時,即自動新增分割界線。
- 無線基地台
- 當分散式資料庫 (例如 Spanner) 中的單一分割含有接收資料庫中大部分的查詢記錄時。這類情形會降低效能,並不是理想的結果。
使用 Spanner 設計遊戲
在大多數情況下,如果您考慮使用 RDBMS 設計遊戲,Spanner 是最適合的選擇,因為它可以有效取代遊戲資料庫或驗證資料庫,或在多數情況下同時取代這兩者。
遊戲資料庫
Spanner 可做為單一全球交易機構,非常適合做為遊戲庫存系統。任何可交易、出售、贈送或以其他方式從某位玩家轉到另一位玩家的遊戲內貨幣或物品,對大規模遊戲後端而言都相當不容易處理。當遊戲的熱門程度逐漸攀升時,傳統資料庫在單一節點資料庫中處理所有內容的能力已經無法負荷這些遊戲的需要。視遊戲類型而定,資料庫可能難以應付處理玩家負載和儲存資料量所需的作業數量,往往導致遊戲開發人員必須分割資料庫,以獲得額外的效能或儲存不斷增長的資料表。這類解決方案會增加作業複雜度和維護負擔。
降低這類複雜度的常見方法之一,就是分別執行完全獨立的遊戲地區,讓資料無法在地區之間移動。在這種情況下,不同遊戲地區的玩家無法交換物品和貨幣,因為每個地區的庫存物品都會分開儲存於不同的資料庫。不過,這類設定會犧牲玩家偏好的遊戲體驗,但有助於開發人員簡化開發作業。
另一方面,您可以在地理分割資料庫中允許跨地區交易,但通常作業複雜度高。這項設定需開放在多個資料庫執行個體之間進行交易,會導致應用程式端邏輯趨於複雜且容易出錯。嘗試在多個資料庫上鎖定交易功能可能會對效能產生重大影響。此外,無法依賴不可分割交易時,可能會衍生玩家鑽漏洞行為 (例如複製遊戲內的貨幣或物品),進而對遊戲的生態系統和社群造成傷害。
Spanner 可簡化庫存物品和貨幣交易的方法。即使將 Spanner 用來保存全球所有的遊戲資料,仍能提供比傳統的不可分割性、一致性、隔離性和永久性 (ACID) 更強大的讀寫交易服務。憑藉 Spanner 的擴充性,當系統需要更多效能或儲存空間時,您不須將資料分割到不同的資料庫執行個體,只要新增更多節點即可。此外,為了確保高可用性和資料彈性,遊戲經常將多個資料庫聚集,而 Spanner 會以公開透明的方式保有這兩個屬性,無需額外的設定或管理程序。
驗證資料庫
Spanner 也適合做為驗證資料庫,特別是當您要在工作室或發布商層級的單一 RDBMS 上進行標準化。雖然遊戲的驗證資料庫通常不要求 Spanner 的規模等級,但其交易保證和高資料可用性的特性可讓遊戲效能更具優勢。Spanner 內建公開透明且即時同步的資料複製功能。Spanner 的設定可提供高達 99.99% (「四個九」) 或 99.999% (「五個九」) 的可用性,其中「五個九」相當於一年內不到五分半鐘的不可用性。由於 Cloud Spanner 具備如此優良的可用性,因此很適合做為在每個玩家工作階段一開始必須提供的重要認證路徑。
最佳做法
本節提供如何在遊戲設計中使用 Spanner 的相關建議。建議您善用 Spanner 提供的獨特功能為遊戲資料建立模型。雖然您可透過關聯資料庫語意存取 Spanner,但也有部分結構定義設計重點可協助您提高效能。您可參閱 Spanner 說明文件中詳細的結構定義設計建議,而以下各節將列舉一些遊戲資料庫的最佳做法。
本文件中的最佳做法是以客戶使用情形和個案研究經驗為根據。
使用 UUID 做為玩家和角色 ID
一般來說,在玩家資料表中,每位玩家及其遊戲內貨幣、進度或其他不易對應到分散庫存物品資料表資料列的這些資料都會儲存在一個資料列中。如果您的遊戲允許玩家為多個角色分別儲存進度 (例如許多大型持久性多人遊戲),那麼在這個資料表中每個角色通常有專屬的資料列。資料模式在其他方面則相同。
建議您使用全域專屬角色或玩家識別碼 (角色 ID) 做為角色資料表的主鍵。此外,也建議您使用通用唯一識別碼 (UUID) v4,因為這個識別碼可將玩家資料分配至不同的資料庫節點,協助您提升 Spanner 效能。
使用交錯庫存資料表
庫存資料表通常包含遊戲內物品,例如角色裝備、卡片或數量。一般來說,單一玩家會有許多的庫存物品,而每件物品在資料表中都會有對應的單一資料列。
與其他關聯資料庫類似,Spanner 中的庫存資料表具有主鍵,而主鍵就是該物品的全域專屬識別碼,如下表所示。
itemID
|
type
|
playerID
|
---|---|---|
7c14887e-8d45 |
1 |
6f1ede3b-25e2 |
8ca83609-bb93 |
40 |
6f1ede3b-25e2 |
33fedada-3400 |
1 |
5fa0aa7d-16da |
e4714487-075e |
23 |
5fa0aa7d-16da |
d4fbfb92-a8bd |
14 |
5fa0aa7d-16da |
31b7067b-42ec |
3 |
26a38c2c-123a |
為了方便閱讀,範例庫存資料表中的 itemID
和 playerID
只顯示部分內容。實際的庫存資料表還包含許多範例中未列出的其他資料欄。
一般來說,在 RDBMS 中追蹤物品擁有權的做法是使用資料欄做為儲存目前擁有者玩家 ID 的外鍵。這個資料欄是個別資料庫資料表的主鍵。在 Spanner 中,您可以使用交錯將庫存資料列儲存在相關聯的玩家資料表資料列附近,以獲得更優異的效能。使用交錯資料表時,請注意下列事項:
- 您無法產生沒有擁有者的物件。您可以在遊戲設計中避免使用沒有擁有者的物件 (如果您已得知有這項限制)。
設計索引以避免資源使用率不均
許多遊戲開發人員會對多數庫存欄位建立索引,以改善特定的查詢效能。在 Spanner 中使用該索引中的資料建立或更新資料列時,會產生額外寫入負載,而負載量與已建立索引的欄數成正比。如要提高 Spanner 效能,您可以消除不常使用的索引,或以不影響資料庫效能的其他方式建立索引。
下列範例為長期玩家最高分記錄的資料表:
CREATE TABLE Ranking (
PlayerID STRING(36) NOT NULL,
GameMode INT64 NOT NULL,
Score INT64 NOT NULL
) PRIMARY KEY (PlayerID, GameMode)
這個資料表包含玩家 ID (UUIDv4)、代表遊戲模式、階段或季別的編號,以及玩家的得分。
為了加快篩選遊戲模式的查詢速度,請考慮使用下列索引:
CREATE INDEX idx_score_ranking ON Ranking (
GameMode,
Score DESC
)
如果所有玩家都選擇名為 1
的相同遊戲模式,這個索引便會產生資源使用率不均情形 (GameMode=1
)。如果您要取得這個遊戲模式的排名資訊,索引只會掃描含有 GameMode=1
的資料列,以快速傳回排名結果。
如果您變更先前索引的排序,就可以解決這個資源使用率不均的問題:
CREATE INDEX idx_score_ranking ON Ranking (
Score DESC,
GameMode
)
玩家在相同遊戲模式中競爭時,只要分數分布在可能範圍內。這個索引就不會產生顯著的資源使用率不均情形。不過,為了判斷 GameMode=1
是否成立,查詢作業會掃描所有模式的所有分數,因此取得分數的速度不如上一個索引。
因此,重新排序的索引確實可解決先前在遊戲模式中產生的資源使用率不均問題,但仍有改善的空間,如以下設計所示。
CREATE TABLE GameMode1Ranking (
PlayerID STRING(36) NOT NULL,
Score INT64 NOT NULL
) PRIMARY KEY (PlayerID)
CREATE INDEX idx_score_ranking ON Ranking (
Score DESC
)
建議您將遊戲模式移出資料表結構定義,並盡量採取每個模式使用個別資料表的設計。採用這個方法後,當您擷取某個模式的分數時,系統只會查詢該模式中的分數並產生資料表。您可以依分數建立這個資料表的索引,以便快速擷取分數範圍,避免發生重大的資源使用率不均問題 (前提是分數均勻分布)。撰寫本文件時,Spanner 中每個資料庫的資料表數量上限為 2560,對大多數遊戲而言相當足夠。
為每個用戶群個別建立資料庫
在其他工作負載中,我們建議使用不同的主鍵值為 Spanner 設計多租戶架構 ;但針對遊戲資料,我們建議為每個用戶群使用個別資料庫的傳統方法。在已上線的遊戲中,結構定義變更隨著新遊戲功能一起發布的情形相當常見,而且在資料庫層級個別隔開用戶群可以簡化結構定義更新作業。這個方法還可以縮短備份或還原用戶群資料所需的時間,因為這些作業會一次在整個資料庫中執行。
避免增量結構定義更新
與部分傳統關聯資料庫不同,Spanner 在結構定義更新期間仍可正常運作。系統會先傳回對舊結構定義的所有查詢結果 (雖然結果傳回速度不如平常);待新結構定義開放後,系統就會傳回對新結構定義的查詢結果。您可以按照需要設計更新程序,以便遊戲在 Spanner 更新結構定義期間保持正常運作 (前提是您必須留意上述限制)。
然而,當系統正在處理結構定義時,如果您提出其他結構定義變更的要求,新的更新會排入佇列,直到先前所有的結構定義更新完成後才會執行。如要避免發生這種情況,您可以規劃更大型的結構定義更新,來取代短時間內發布多個增量結構定義更新的做法。如要進一步瞭解結構定義更新 (包含如何執行需要資料驗證的結構定義更新),請參閱 Spanner 結構定義更新文件
考慮資料庫存取方式和大小
當您要開發採用 Spanner 的遊戲伺服器和平台服務時,請考慮遊戲存取資料庫的方式以及如何調整資料庫大小,以避免不必要的成本。
使用內建的驅動程式和程式庫
當您針對 Spanner 進行開發時,請考慮程式碼與資料庫介接的方式。Spanner 提供多種主流程式語言的內建用戶端程式庫,功能豐富且效能良好。Cloud Spanner 也提供 JDBC 驅動程式,支援資料操縱語言 (DML) 和資料定義語言 (DDL) 陳述式。如要在新開發專案中採用 Spanner,建議您使用 Spanner 的 Cloud 用戶端程式庫。雖然一般遊戲引擎整合功能在語言選擇方面沒有太多彈性,但對於存取 Spanner 的平台服務,有些遊戲客戶會使用 Java 或 Go。對於高總處理量應用程式,請選取可用於多個連續要求的相同 Spanner 用戶端的程式庫。
根據測試和實際運作需求調整資料庫大小
在開發期間,單一節點 Spanner 例項可能足以讓您執行大多數活動,包括功能測試。
評估正式環境的 Spanner 需求
當您從開發階段轉向測試階段,接著進入實際工作環境時,必須重新評估 Spanner 需求,以確保您的遊戲能處理即時玩家上線流量。
在進入實際工作環境前,為了驗證後端能夠處理實際運作期間的工作負載,執行工作負載測試非常重要。建議您以實際工作環境中預期工作負載量的兩倍執行工作負載測試,以因應使用量暴增以及遊戲比預期更受歡迎的情形。
使用實際資料執行工作負載測試
使用合成資料執行工作負載測試並不足夠。您還必須使用貼近實際工作環境中預期的資料和存取模式執行工作負載測試。合成資料可能無法偵測出 Spanner 結構定義設計中潛在的資料使用率不均問題。建議您邀請真實玩家執行 Beta 版測試 (公測或封測),以驗證 Spanner 使用實際資料運作的情形。
下圖為某間遊戲工作室提供的玩家資料表結構定義範例,說明了使用 Beta 版測試執行工作負載測試的重要性。
該工作室根據過去幾年經營的遊戲歷史趨勢準備了這份資料,並預期該結構定義能充分代表這款新遊戲中的資料。
每筆玩家記錄都有一些與記錄相關的數字屬性,用於追蹤玩家的遊戲進度 (例如排名和遊戲時間)。對於上表使用的範例屬性,系統會將新玩家的起始值設為 50,隨著遊戲進行,這個值會變成 1 到 100 之間的值。
該工作室想要為這個屬性建立索引,以加速遊戲過程中的重要查詢。
根據這項資料,工作室建立了下列 Spanner 資料表,主要索引鍵使用 PlayerID
,次要索引則針對 Attribute
。
CREATE TABLE Player (
PlayerID STRING(36) NOT NULL,
Attribute INT64 NOT NULL
) PRIMARY KEY (PlayerID)
CREATE INDEX idx_attribute ON Player(Attribute)
然後查詢該索引,需找出最多 10 個 Attribute=23
的玩家,如下所示:
SELECT PlayerID
FROM Player@{force_index=idx_attribute}
WHERE Attribute = 23
LIMIT 10
根據最佳化結構定義設計說明文件,Spanner 儲存索引資料的方式與儲存資料表相同,每一列儲存一個索引項目。在負載測試中,這個模型可將次要索引的讀取和寫入負載分配至多個 Spanner 分割中,如下圖所示。
雖然工作負載測試中所用的合成資料與遊戲的最終穩定狀態 (Attribute
值分布均勻) 相似,但是遊戲設計要求所有玩家都從 Attribute=50
開始。由於每一位新玩家都是以 Attribute=50
開始,當他們加入遊戲時,系統會將他們插入在 idx_attribute
次要索引中相同的部分。這表示更新會轉送至相同的 Spanner 分割,造成遊戲在發布期間產生資源使用率不均的情形,這會導致 Spanner 使用效率不佳。
在下圖中,於發布後將 IndexPartition
資料欄加入結構定義,就能解決資源使用率不均的問題,而且系統會將玩家平均分配至可用的 Spanner 分割中。用於建立資料表和索引的更新版指令如下所示:
CREATE TABLE Player (
PlayerID STRING(36) NOT NULL,
IndexPartition INT64 NOT NULL
Attribute INT64 NOT NULL
) PRIMARY KEY (PlayerID)
CREATE INDEX idx_attribute ON Player(IndexPartition,Attribute)
IndexPartition
值的範圍必須有所限制才能確保查詢效率,但是該範圍也必須至少為分割數量的 2 倍才能達成有效分配。
在這種情況下,遊戲工作室會針對遊戲應用程式中的每一位玩家,手動指派介於 1
至 6
的 IndexPartition
。
此外,其他的替代方法是為每個玩家指派一個隨機數值,或指派針對 PlayerID
值雜湊衍生的值。如需更多應用程式層級的資料分割策略,請參閱「DBA 必須瞭解的 Spanner 知識 (第 1 部分):索引鍵與索引」。
更新先前的查詢以使用這個改良版索引,內容大致如下:
SELECT PlayerID
FROM Player@{force_index=idx_attribute}
WHERE IndexPartition BETWEEN 1 and 6
AND Attribute = 23
LIMIT 10
由於該工作室並未執行 Beta 版測試,所以沒發現他們使用了假設錯誤的資料進行測試。雖然合成工作負載測試適合用來驗證執行個體的每秒查詢次數 (QPS),但您必須邀請真實玩家執行 Beta 版測試,才能驗證您的結構定義並完成遊戲發布準備作業。
調整實際工作環境的大小,預先為尖峰需求做好準備
備受矚目的遊戲在發布時經常遇到流量尖峰。因此,不僅需要為平台服務和專用遊戲伺服器建立可擴充的後端,資料庫也同樣需要。使用 Google Cloud 解決方案 (例如 App Engine) 可打造出能夠快速擴充的前端 API 服務。雖然 Spanner 提供線上新增及移除節點的使用彈性,但本身並不是可自動調度資源的資料庫。您必須提供足夠的節點來處理遊戲發布時的流量高峰。
根據您在工作負載測試期間或任何公開 Beta 版測試作業中收集到的資料,您可以預估所需的節點數量,以便處理遊戲發布時的要求。建議您新增一些節點做為緩衝區,以免玩家人數超過預期。資料庫的大小不應超過 65% 的平均 CPU 使用率。
在遊戲發布前為資料庫暖機
建議您在發布遊戲前暖機資料庫,以便充分運用 Spanner 並行處理功能。詳情請參閱「在應用程式啟動前為資料庫暖機」。
監控及瞭解效能
所有的實際工作環境資料庫都須具備完整的監控功能和效能指標。Spanner 具備的內建指標都位在 Cloud Monitoring 中。由於提供的 gRPC 程式庫內含 OpenCensus 追蹤記錄,因此建議您盡可能將這些程式庫與您的遊戲後端流程整合。OpenCensus 追蹤記錄可讓您在 Cloud Trace 中查看查詢追蹤記錄以及其他支援的開放原始碼追蹤工具。
您可以在 Cloud Monitoring 中查看 Spanner 使用情形的詳細資料,包括資料儲存空間和 CPU 使用率。在大多數情況下,建議您根據這個 CPU 使用率指標或觀察到的延遲情形制定 Spanner 資源調度決策。如要進一步瞭解可達到最佳效能的建議 CPU 使用率,請參閱最佳做法。
Spanner 提供查詢執行計畫。您可以在 Google Cloud 控制台中查看這些計畫;如果需要我們協助瞭解查詢效能,請與支援團隊聯絡。
由於 Spanner 會根據您的資料存取資料模式,在幕後以公開透明的方式分割資料以提升成效,因此在評估效能時請盡量縮短週期測試時間。您必須使用實際的持續查詢工作負載來評估效能。
移除資料時需刪除資料列,而不是重新建立資料表
使用 Spanner 時,新建立的資料表還沒經過可提升效能的分割作業 (依工作負載或資料大小進行)。當您要刪除資料時,如果將資料表刪除然後再重新建立,Spanner 仍需要資料、查詢和時間,才能決定正確的資料表分割。如果您打算將相同類型的資料重新填入資料表 (例如執行連續效能測試時),也可以找出您不再需要的資料,並在對應的資料列上執行 DELETE
查詢。也因如此,執行結構定義更新時,您必須採用提供的 Cloud Spanner API,避免手動操作 (例如建立新資料表並複製其他資料表或備份檔案中的資料)。
選擇資料位置以符合法規遵循要求
許多遊戲在世界各地推出時都必須遵守資料本地性法規 (例如 GDPR)。如需 GDPR 需求相關協助,請參閱 Google Cloud 和 GDPR 白皮書,並選取正確的 Spanner 地區設定。
後續步驟
- 瞭解 Bandai Namco Entertainment 如何運用 Spanner 成功推出 Dragon Ball Legends。
- 觀看 Cloud Next '18 大會,瞭解如何在 Spanner 上最佳化應用程式、結構定義和查詢設計。
- 請參閱從 DynamoDB 遷移至 Spanner 說明指南。