Google Cloud Spanner 學習報告
17 Jan 2019
本人介紹,未使用過 Google Cloud Spanner,不是 DBA。
簡介
Google 的 AdWords 曾經(jīng)是靠 MySQL+手工Sharding來支撐,但在擴展和可靠性上不能滿足要求。于是開發(fā)了全局一致性和自動 Sharding 的數(shù)據(jù)庫 Spanner。
Spanner 帶動了一波云原生數(shù)據(jù)庫的熱潮,云原生數(shù)據(jù)庫的特點是,計算和存儲分離,增加 node 就可以增加存儲能力和性能;自動分片;支持分布式事務。
國內(nèi)的云原生數(shù)據(jù)有阿里云的 云數(shù)據(jù)庫POLARDB,騰訊云的
云數(shù)據(jù)庫 CynosDB,這兩個數(shù)據(jù)庫的特點還有完全兼容 MySQL,其中 CynosDB 還可以支持 PostgreSQL 10(參考時間 2019年1月17日),現(xiàn)有應用無需更改 SDK,減少數(shù)據(jù)庫遷移的成本。
TrueTime
Spanner 數(shù)據(jù)庫告訴了人們,當數(shù)據(jù)容量增加,分片數(shù)量增加,依然可以享受高性能和事務全局一致性,依靠的最驚艷的技術就是 TrueTime。
注意,Spanner 的可序列化(serializability)依靠的還是 Lock,但是外部一致性(external consistency)依靠的是 TrueTime
事務依靠全局一致性的順序 id 來保證事務執(zhí)行順序的正確性,Spanner 使用的是時間戳,而就算使用原子時鐘,時間還是會有亂序的存在。TrueTime.now() 返回了 [earliest, latest] 間隔,犧牲了少許等待時間換來更高的一致性。下面 quote 一下 Google 的文章
Thus, if two intervals do not overlap, then we know calls were definitely ordered in real time. If the intervals overlap, we do not know the actual order.
Schema Design
不同的數(shù)據(jù)庫間做數(shù)據(jù)遷移,都要理解兩個數(shù)據(jù)庫各自的優(yōu)點,才能更好的用上新數(shù)據(jù)庫。例如從 MySQL 遷移到 HBase,如果表設計一模一樣的話,簡直就是浪費了 Hbase 的特性,而且因為 Hbase 沒有二級索引(雖然可以簡單設計一下)可能業(yè)務性能會下降。
先簡單說一下 Spanner 的存儲架構
Spanner 的數(shù)據(jù)都是存放在節(jié)點機器,下面簡稱 node,而 node 存放分片(split),數(shù)據(jù)在 split 的訪問順序是有序的,參考過論文,數(shù)據(jù)在 split 中使用 KV 結(jié)構存儲。node 之間的數(shù)據(jù)也是有序的,可以這樣理解:node1{spli1, split2}, node2{split3}, node3{split4, split5, slit 6}。
第一個問題,順序?qū)懭?HotSpot
數(shù)據(jù)主鍵如果是有序的,例如用自增 id 或者 MongoDB 的 ObjectId,新數(shù)據(jù)很大可能會寫入到第一個或者最后一個split,造成單個 node 的負載超高,而其他 node 很空閑,這樣對于寫性能不能做到增加 node 就增加性能。
Spanner 產(chǎn)品文檔給出了幾種解決辦法:
1. 調(diào)換主鍵順序
因為 spanner 是可以定義多個列來組合稱主鍵,登錄日志表如原主鍵是 (time, user_id), 因為 time 字段是自增的,可以用 (user_id, time) 來做主鍵。登錄的 user_id 是無序的所以可以把數(shù)據(jù)寫入到盡量多的 split
2. 增加 shard_id 字段
通過保存原主鍵的 hash 或者只是保存 hash。例如原主鍵是 (shop_id, user_id, order_id),可以增加 shard_id 字段 shard_id = md5(shop_id + user_id + order_id)[0:3],新主鍵為 (shard_id, shop_id, user_id),這里還可以考慮到只需要 shop_id 和 user_id 計算 md5,因為這樣的話每個用戶下的訂單都可能放在同一個 split,加快用戶端的訂單查找速度。而根據(jù) shop_id 查找訂單可能是低頻的,能接受的延時稍高。Spanner 文檔是使用 hash() % N 來存儲數(shù)字型 shard_id,我這里只是給出另一種實現(xiàn),不一定是最好。
3. 使用 UUID v4
新表設計可以使用 UUID,但是如果從舊數(shù)據(jù)庫轉(zhuǎn)移過來,例如 MongoDB,直接把 ObjectId 替換成 UUID 的話,一般都是新增一個主鍵然,保留舊主鍵,并且為舊主鍵增加索引。但是更好的辦法應該是使用前一種方法,增加 shard_id。不過,具體是否更好還是要看業(yè)務,增加 shard_id 的好處是服務使用方還是可以直接舊 id 來訪問單條數(shù)據(jù)。
4. 按位反轉(zhuǎn)
// 隨手打的不要介意
// 不是所有語言都能很好處理 64 位整數(shù)和二進制運算, 下面用 javascript 做例子
function getNewId(oldId) {
let newId = 0;
const MAX_BITS = 51;
for (let i = 0; i < MAX_BITS; i++) {
newId *= 2; // newId <<= 1;
if (oldId & 1) {
newId |= 1;
}
oldId = Math.floor(oldId / 2);
}
return newId;
}
還有一種做法做法其實跟 shard_id 差不多,就是把最高位的第 2 到 N + 1 位用來保存hash,原 id 保存在低 (63 - N) 位
出現(xiàn) HotSpot 的其他因素
1. 索引
Spanner 的索引也是數(shù)據(jù)表,也就是數(shù)索引的設計也會影響寫入性能
Table 主鍵設計好了,但是因為業(yè)務需要需要增加二級索引,而二級索引的字段的數(shù)據(jù)是自增的話,索引表的新數(shù)據(jù)都會寫到同一個 split,從而造成 HotSpot。
2. 父子關系表設計
您可以在一個數(shù)據(jù)庫中定義多個表,而且,如果希望 Cloud Spanner 以物理方式協(xié)同定位表的行,從而實現(xiàn)高效檢索,您還可以選擇定義表之間的父子關系。 https://cloud.google.com/spanner/docs/schema-and-data-model?hl=zh-CN
假設 root table(父表) shops(shop_id, shop_name), 字表 shop_orders(shop_id, order_id) INTERLEAVE IN PARENT shops,這樣同一個 shop_id 下的所有訂單都會放在同一個 split,就算 shop_orders 表加入了 shard_id 都沒沒有用的,當同一個店鋪高并發(fā)寫入訂單的時候,會造成存放該 shop_id 數(shù)據(jù)的 split 出現(xiàn) HotSpot。
父子表適合字表數(shù)據(jù)相對少的情況,例如一個訂單一張發(fā)票,那么發(fā)票表可以是訂單表的子表。
數(shù)據(jù)庫選型考慮
由于 Google Cloud Spanner 是 GCP 托管的服務,存儲費對于新舊數(shù)據(jù)都是一致的,一個電子商務網(wǎng)站,顧客一般不會經(jīng)???1 年前的訂單,這時候的訂單數(shù)據(jù)如何可以存放在成本相對小的服務器,可以大大減少云服務費用。
另外一個考慮就是,不是所有應用都一定要用到 Spanner,Google 給出另一個選擇合適數(shù)據(jù)庫的方法:
https://cloud.google.com/storage-options/

圖片來自 Google Cloud
參考資料
- Spanner 101, https://www.youtube.com/watch?v=IfsTINNCooY
- Spanner 201, https://www.youtube.com/watch?v=Tzhe7sUNDbg
- What DBAs need to know about Cloud Spanner, part 1: Keys and indexes, https://cloud.google.com/blog/products/gcp/what-dbas-need-to-know-about-cloud-spanner-part-1-keys-and-indexes
- Sharding of timestamp-ordered data in Cloud Spanner, https://chinagdg.org/2018/05/sharding-of-timestamp-ordered-data-in-cloud-spanner/
- Optimizing Applications, Schemas, and Query Design on Cloud Spanner (Cloud Next '18), https://www.youtube.com/watch?v=DxrdatA_ULk
- Cloud Spanner Documentation, https://cloud.google.com/spanner/docs/
- Spanner 論文, https://www.usenix.org/system/files/conference/osdi12/osdi12-final-16.pdf
- Spanner 論文視頻, https://www.usenix.org/conference/osdi12/technical-sessions/presentation/corbett
- Spanner, TrueTime & The CAP Theorem, https://storage.googleapis.com/pub-tools-public-publication-data/pdf/45855.pdf
- Spanner 白皮書集合, https://cloud.google.com/spanner/docs/whitepapers