Quizlet Spanner 測試報告

Quizlet Spanner 測試報告

來自專欄 分散式資料庫觀察

(全文摘譯)

阿里雲 DRDS 也是一款支持數據切分、平滑擴容和分散式事務(GTS)的分散式資料庫,相比 Google Spanner, DRDS 擁有更完善的 SQL 語法支持,更豐富的運維管控命令,以及阿里內部 1000 多個應用場景的實踐經驗。採取相同的雲上託管方式,企業使用 DRDS 可以避免自行開發、實施資料庫 sharding 方案和維護資料庫的成本。另外,與其他 Aliware 企業級互聯網組件一樣,DRDS 也支持企業專有雲部署。

產品詳情: aliyun.com/product/drds

作者:in355hz,DRDS/paoding-rose-jade 開發,分散式資料庫研究者

Quizlet 最近發布了一份 Cloud Spanner 的測試報告:

quizlet.com/blog/quizle

與很多初創公司一樣,Quizlet 也面臨資料庫擴容的巨大挑戰。他們之前使用 MySQL, 當然很快遇到了單機瓶頸。因為擔心重寫應用 SQL 的成本,又正好趕上了 Google 發布 Cloud Spanner,所以他們立即開始了嘗試。

從文章內容看,他們對測試結果還是相當滿意的。

經典問題

文章首先描述了一下傳統資料庫擴容的問題。因為依賴資料庫存儲狀態,當應用流量增長的時候,必須優化和提升資料庫性能。他們首先使用了垂直切分和加備庫的辦法,但是為了避免引入複雜性,沒有使用水平切分(horizontal sharding, 把一張表的數據分散保存在多個資料庫上)。

"Quizlet 的 MySQL 架構"

這個架構的根本問題是主庫(master)瓶頸。不做水平切分的話,所有寫和一致性讀都發生在主庫,當數據量/吞吐量變大(測試的數據是 700GB),性能就有達到上限的風險。

然後他們設想了幾種架構升級的方案:

  • 使用 RDS, Aurora (AWS), Cloud SQL (GCP) 這類雲上託管資料庫。但是他們都是基於單機的,解決不了容量問題。
  • 使用 XtraDB / Vitess / CitusDB 這類資料庫集群方案。問題是需要部分改造應用,並且增加了複雜性和運維成本。
  • 開發自己的資料庫擴容技術。問題是工程難度和維護成本。
  • 使用支持水平擴容的 NoSQL 技術,例如 Cassandra, HBase, MongoDB, DynamoDB 和 BigTable。問題是遷移成本:不支持 JOIN, 查詢方式受限,需要修改上百張表和語句。
  • 繼續垂直擴容,使用更大,更貴的企業級設備。例如內存 10TB 的機器。但是不能放在雲上託管,以及單點問題。
  • 跟大多數遇到擴容問題的技術型公司一樣,自己開發 MySQL sharding 方案。問題是需要成隊的工程師來設計、開發、運維和 oncall, 而 Quizlet 缺人:D

最後,他們總結:用戶快速增長和訪問量波動是 Quizlet 急需資料庫擴容的根本原因。因為他們做的是教育行業產品,訪問量主要集中在學年和考試期間。

"Quizlet 2008 年 UV (藍色為 web 端,灰色為移動端)"

Cloud Spanner

經過測試 Quizlet 認為 Cloud Spanner 是解決問題的最佳方案。原因是 Cloud Spanner 的高擴展性,強一致性、以及與 Google 核心存儲和計算服務的高度集成。

文章提到,由於 Spanner 及對 Google 核心技術的信任, Quizlet 打算整體遷移到 Google 雲平台(GCP)。

Quizlet 認為 Spanner 具有嶄新的架構和成熟的產品:基於 Google 雲平台的規模優勢,以 GPS 和原子鐘(Atomic clocks)技術協調分散式節點。Google 在 Spanner 上投入了巨大時間、金錢和人力,先在內部使用,再作為雲產品發布。

Quizlet 還提到:儘管核心概念不變,Gloud Spanner 相比 2012 發表的論文有了顯著改進。不能用論文里的性能和可用性數據來衡量 Cloud Spanner, 但核心原理仍然是相同的。

Cloud Spanner 架構

Spanner 是支持 SQL 語句的分散式關係型資料庫。與其他有狀態的分散式資料庫一樣,Spanner 節點需要頻繁與其他節點協調通訊。Spanner 的關鍵創新是使用精密時鐘,降低在讀寫過程中維護數據一致性所需的通訊量。每個 Spanner 節點都通過 TrueTime API 訪問這些時鐘。在原始 Spanner 論文里是這樣描述的:

TrueTime 提供的時間來自底層的 GPS 和原子鐘。TrueTime 使用兩種方式獲取時間,因為它們的失效模式不同 ... TrueTime 由每個數據中心內的一組主節點以及每個節點上的 timeslave 後台進程組成。大多數主節點有 GPS 接收器和專用天線。這些機器被物理隔離,以減少 GPS 天線故障、無線電干擾和欺騙的影響。其餘主節點(他們稱之為 「Armageddon」 節點)安裝了原子鐘:原子鐘並不昂貴,「Armageddon」 節點的成本和 GPS 節點在同一水平。

擁有精確計時能力對分散式資料庫非常有用。你在讀數據的時候必須了解數據複本(replica)是否最新。如果在讀的同時有多個事務在更新同一數據行,你必須了解事務 B 是否發生在事務 A 之後。當寫入數據之前你需要知道行鎖最後在什麼時間釋放。當你從多個位置發起一致性讀的時候,你需要知道當前事務是否發生在時間點 t 之後。介於大部分分散式資料庫都利用節點間通訊來實現這些特性,如果每個節點都能獲取絕對精確的時間,會讓這些工作變得更為簡單。

Spanner 將數據劃分成 splits(分片?),這個概念在其他分散式資料庫叫 shards 或 tablet(雖然每種資料庫劃分和管理這些分片的方式都各不相同)。主鍵決定了一行數據存儲在哪個分片。

Cloud Spanner 基於數據和查詢壓力自動優化分片配置。「分片配置」 的含義是 split 的個數和數據分布,對用戶不可見。Spanner 在高壓力下會創建更多分片,在低壓力下會減少合併分片。這會產生預熱效應 —— 隨著 Spanner 優化分片配置,查詢性能逐漸提升,這增加了 Quizlet 的測試難度。

Spanner 的虛擬化

Cloud Spanner 通過配置節點(nodes)的個數決定集群的性能上限。增加節點數目就可以提升性能,實現了可擴展性。Google 沒有公布一個節點對應的機器數量,但經驗法則是一個節點大約能處理 2000 次寫入每秒和 10000 次讀每秒。在測試中,Quizlet 發現實際性能與壓力場景有關。

由於 Cloud Spanner 的存儲與計算都已深度虛擬化,所以關鍵是理解一個節點的真實含義。

Cloud Spanner 的一個 「節點」 並不是一台物理機。它是一組機器上的計算能力和磁碟吞吐量配額,由調度機制管理,在這組機器資源池上以任務方式運行。這種架構意味著你可以在 1 秒鐘之內改變一台線上 Spanner 集群的節點個數。這不是調用 API 消耗的時間,而是從修改節點數量到看到改變在外部指標(例如查詢延遲)上生效的時間。

Spanner 節點的數目和存儲容量在架構上是分離的。因此,可以單獨修改一個節點的存儲容量,但 Cloud Spanner 限制了每個節點最多存儲 2TB 數據。

Cloud Spanner 將數據寫入 Google Colossus, 一個支持切片和複製的存儲基礎組件。這意味一個位元組會寫入磁碟幾十次(註:誇張了吧?)。一張表的數據會被分片和複製到上千個磁碟上。因此 Cloud Spanner 不直接訪問磁碟而是調用存儲 API。存儲 API 封裝了提供可靠延遲和吞吐量保障的複雜技術細節,這說明:類似於寫入多個物理數據中心與處理單盤故障這種事已經從 Cloud Spanner 里有效的抽象出來了。

原生雲資料庫

Quizlet 認為 Spanner 相對競爭產品有很大優勢,這種信心來源於 GCP 雲平台的規模,Google 自身的技術優勢,以及雲上託管的成本優勢。

Spanner 的強大來自基礎設施與軟體的深度集成。它依賴於存儲層,計算層,以及精確計時 API;沒有大規模的雲,這種技術是不可能實現的。即使 Spanner 開放源代碼,它也不可能在 Google 數據中心之外的地方運行。相反,如果不能調用 Google 的存儲和計時 API, 即使是專門為 GCP 開發的分散式資料庫也無法獲得匹敵 Spanner 的性能。例如 CockroachDB,它是一個在 Google 平台之外模仿 Spanner 技術的開源資料庫,儘管有著相近的架構,但(性能)還有很大劣勢。

考慮在獨立的環境下複製 Spanner 技術的成本。比如,你需要部署自己的原子鐘。而且,你需要的不僅僅是一個數據中心,還需要工作在上千台機器上的大規模存儲和計算層。一些現存軟體夠實現這些,例如 Hadoop 生態系統,但是它們遠沒有那麼容易大規模部署起來。總之,可擴展的託管基礎設施是擁抱雲平台例如 GCP, AWS 或 Azure 的一個具有說服力的經濟觀點。對小企業來說,購買服務總比自己開發和維護更加合算。

Spanner 介面

SQL 語法

Cloud Spanner 採用符合 ANSI SQL:2011 標準的 SQL 語法,為特殊功能增加了一些擴展。這個 SQL 標準相比常見單機資料庫(例如 MySQL)的更為簡單,支持關係模型(例如 JOIN),也支持 DDL 例如 CREATE TABLE。

Spanner 支持 7 種數據類型:bool, int64, float64, string, bytes, date, timestamp。

然而 Cloud Spanner 並不支持 DML 語句,例如 INSERT 和 UPDATE。作為替代,Spanner 介面中包含了按主鍵更新數據行的 RPC 協議 [21]。這個對 Quizlet 有點意外,他們期待的是一個功能完整的 SQL 資料庫。儘管應用可以不用 DML, 但是控制台肯定需要用 DML 來做些一次性操作。

儘管和其他資料庫相比,Cloud Spanner 支持較少的 SQL 語法,但是非常匹配 Quizlet 的場景。他們的基本需求是能替代 MySQL 並且支持二級索引和常見 SQL 聚合計算,例如 GROUP BY。由於 Quizlet 已經去掉了大部分 JOIN 語句, 所以他們並沒有測試 Cloud Spanner 的 JOIN 性能。

數據分布

Cloud Spanner 提供了兩個工具來管理表的數據分布。Quizlet 強調它的原因是 MySQL 和 Postgres 都沒有類似的工具。

第一個工具是交錯表(interleaved tables)。在許多查詢場景里存在多個表之間的緊密關聯。Cloud Spanner 文檔給的例子是一個資料庫里有一張音樂藝術家表和一張專輯表。兩張表有明顯的關聯。為了優化查詢,可以把專輯的數據和專輯的藝術家數據存儲在(磁碟上)相鄰的位置。交錯表允許在表結構中顯式定義這種關係,這給用戶提供了一個簡單而強大的方式管理數據分布。

第二個工具用於管理索引中的數據分布。Cloud Spanner 允許用戶顯式定義在二級索引里存放哪些(額外)欄位信息。這非常有用,因為掃描二級索引時已經訪問了一次磁碟,通過在二級索引的葉子節點存儲額外信息,可以避免再訪問索引引用的完整數據行,節省一次磁碟訪問。

通訊協議

Cloud Spanner 使用 gRPC 作為通訊協議框架,Quizlet 認為這對大部分資料庫通訊是一個巨大的改進。gRPC 有助於改進複雜生產環境中與資料庫交互的關鍵細節。

大部分資料庫在經由網路訪問時,都需要一個協議來接入客戶端連接和傳遞數據。通常的經驗是設計一個自定義的二進位協議,然後在所有支持的編程語言下編寫客戶端實現該協議,MySQL 和 Postgres 就是例子。對於通訊協議,在不同資料庫之間沒有真正的標準,唯一確定的就是這些資料庫的代碼 [25],這意味著給這些資料庫寫客戶端或者代理伺服器是一個困難且容易出錯的過程。

Cloud Spanner 使用 gRPC 作為它的通訊協議框架。gRPC 是一個通用的資料庫通訊標準,但有助於解決這個問題。在這個場景下使用 gRPC 的優勢是:

  • 協議由機器可讀的格式(Protobuf)書寫, 對用戶可見,容易在收發端對消息類型進行檢查。相比 MySQL 和 Postgres 的複雜標準,需要仔細研究才能了解傳遞的是什麼消息。
  • gRPC 可以為 10 種官方支持語言自動生成客戶端。實際上這些客戶端還需要一些封裝以加強可用性,但提供了一個堅實的起點。例如,枚舉類型會被嚴格的定義。
  • gRPC 基於 HTTP2 協議進行通訊,因此可以利用現有的 7 層協議工具,例如 HTTP2 代理。
  • Spanner 的認證(主要)使用 gRPC 的通用認證機制。在其他協議中,身份驗證是與二進位協議綁定的。
  • 可以從 gRPC 定義生成 REST 風格的 HTTP 介面,給用戶提供了另一種選擇。

Quizlet 認為相比自定義二進位協議的舊風格實踐,用 gRPC 作為通訊層框架更加現代和便捷,這才是未來的方向。Quizlet 預測未來的新資料庫都會採用 gRPC 類似的框架。例如,CockroachDB, 從 2014 年開始使用 gRPC 作為它的通訊協議。

壓測方案

Quizlet 的測試目標是確定他們的線上 MySQL 資料庫壓力是否能在 Cloud Spanner 上運行。測試策略是模擬生產 MySQL 狀況,構建綜合壓力場景,並在可控環境下對 MySQL 與 Cloud Spanner 進行對比壓測。

表結構

Quizlet 選擇了 Term 表進行壓測,這是他們最關鍵的數據集和查詢場景。Quizlet 大約有 1 億 5 千萬組學習卡片,卡片里的每一組術語和定義對應 Term 表的一條記錄。Term 表有 60 億條記錄,625GB 數據,80GB 索引,在高峰期每秒大約處理 3000 次查詢。

在 MySQL 里,Term 表的結構定義如下:

CREATE TABLE `terms` ( `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, `set_id` bigint(19) unsigned NOT NULL, `term` text NOT NULL, `definition` text NOT NULL, `photo` varchar(255) NOT NULL, `term_crc` int(11) unsigned NOT NULL, `rank` int(11) unsigned NOT NULL, `last_modified` int(11) unsigned NOT NULL DEFAULT 0, `is_deleted` tinyint(3) unsigned NOT NULL DEFAULT 0, `term_custom_audio_id` int(11) unsigned DEFAULT NULL, `definition_custom_audio_id` int(11) unsigned DEFAULT NULL, PRIMARY KEY (`id`,`set_id`), KEY `set_id_is_deleted` (`set_id`,`is_deleted`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED /*!50100 PARTITION BY HASH (set_id) PARTITIONS 25 */

映射到 Cloud Spanner 數據類型以後,Term 表的結構是:

CREATE TABLE terms ( id INT64 NOT NULL, set_id INT64 NOT NULL, term STRING(MAX) NOT NULL, definition STRING(MAX) NOT NULL, photo STRING(1024) NOT NULL, term_crc INT64 NOT NULL, rank INT64 NOT NULL, last_modified TIMESTAMP NOT NULL, is_deleted INT64 NOT NULL, term_custom_audio_id INT64, definition_custom_audio_id INT64) PRIMARY KEY(set_id, id);

見備註 [27]。

壓測方法

Quizlet 抓取了 Term 表在生產 MySQL 上的負載樣本。把這些樣本去掉查詢參數,可以聚合成若干個 SQL 模板,例如 SELECT a FROM b WHERE a = $1. 為便於測試,他們去掉了一些低頻查詢,總共整理出 18 個 SQL 模板。

Quizlet 把每個 SQL 語句改寫成 Spanner 語法,放進一個壓測引擎在 Cloud Spanner 上運行。他們盡量還原生產壓力的特徵,並且模擬了 SQL 參數的取值分布:例如,對 rowid 的查詢一般不是平均分布,最近插入的數據查詢頻率更高。

儘管 SQL 是隨機生成的,但測試數據來自 Ouizlet 的生產庫。

Quizlet 對測試方案(他們稱之為 「綜合壓力測試」)很有信心。因為採用隨機生成 SQL 而不是回放線上流量的方式,提供了一個可控、乾淨、而且可擴展的辦法對 Spanner 進行壓測。但是,這套方案需要仔細保證壓力場景和原始樣本相同;並且,由於邊界條件 SQL 語句對性能有極大的影響,處理 n QPS 的綜合壓力測試還是往往要比處理 n QPS 的實際線上壓力來的輕鬆。

壓測場景總計 73% 讀和 27% 寫。由於 Quizlet 生產環境用 Memcached 緩存了一部分讀請求,因此對比整體應用讀寫比,後端的寫入比率較高。

作為對比的 Percona MySQL 5.7 部署在掛載 PD-SSD 磁碟的大內存 64-core GCE 實例上。該類型實例擁有 416GB 內存,因此 InnoDB 緩存池設置成 340GB。另外所有 GCE 實例的磁碟都是遠程掛載的。

Quizlet 申明這是一個有限的測試,僅代表他們的線上業務場景運行在 Cloud Spanner 上的效果(而並不是全方位、全維度的評估 Spanner 與 MySQL)。不同的測試場景結論可能大不相同,然而,就這個特定場景,Quizlet 嘗試儘可能公平的對比 MySQL 與 Cloud Spanner。

壓測結果

壓測的關鍵結論是:相比在虛擬機上運行的 MySQL, Cloud Spanner 在低吞吐量下延遲較高。然而,Spanner 的高擴展性表現在大規模集群的處理能力輕鬆超越 MySQL 架構。

這是 3000 QPS 下壓出的性能基線。Spanner 的延遲中位數為 8-12ms,而 MySQL 的延遲是 1-2ms。

值得注意的是,需要遍歷和過濾大量數據的複雜查詢(查詢 5)在 Spanner 下表現較好。而 UPDATE 語句(查詢 14-18)在 Spanner 下的延遲高於簡單 SELECT 和批量 INSERT。

當壓力到達 MySQL 性能上限後,平均延遲開始上升。到 9000 QPS 後,Spanner 的延遲基本沒變,而 MySQL 已經大幅升高了。

在這組簡單測試中,Quizlet 特意壓到 64-core MySQL 的性能上限,以表現單機資料庫的局限性。在實踐中可以用 MySQL 水平切分超越這一上限,但是又引入了另一層額外的複雜性。

延遲與吞吐量曲線

可擴展性是 Quizlet 壓測 Spanner 的主要目標,為此他們對比了 9, 15, 30 個節點的查詢延遲。選擇 9 的原因是:9 是能裝下他們壓測數據的最小節點個數。這一組測試幫助 Quizlet 評估不同節點數下的最高吞吐量。

當 MySQL 在接近性能極限時延遲急劇上升;而隨著吞吐量增長 Spanner 的延遲基本不變,只在尾部上升,就像 p99 看到的那樣。

Spanner 的平滑表現令 Quizlet 印象深刻 —— 在低壓力下 MySQL 反而有比較高的的 p99 延遲。

從這些圖可以觀察到 Spanner 幾乎是線性擴展的。由於新加入節點的通訊開銷,分散式資料庫無法做到完全線性擴展。而 Spanner 在 15 個節點下跑到 17000 QPS,30 個節點下跑到 33000 QPS,這意味著節點規模增加一倍時產生的額外開銷很小。

節點數 vs 吞吐量

為更好理解 Spanner, Quizlet 還測試了節點數變化的曲線。在測試中,Quizlet 通過改變節點數觀察 Spanner 在相同客戶端壓力下的最大吞吐量。

最大吞吐量的評估方式是無限制給 Cloud Spanner 加壓,同時觀察 QPS(達到上限時會有變化),達到峰值後稍稍降低 100 QPS 讓 Spanner 在這個壓力下穩定運行。

節點數 vs 延遲

Quizlet 也測試了當吞吐量不變時改變節點個數的效果,他們發現延遲中位數與節點數負相關。這點很有趣但是並不令人驚訝 —— 計算能力的提升更好的分散了查詢壓力,減少了資源爭用,從而降低了延遲。該效果的強度或許取決於查詢壓力。

測試結論

儘管 Cloud Spanner 有完善的文檔,但是經過測試還是有一些發現。

最重要的發現是:相比 MySQL, Spanner 執行簡單查詢的延遲要高一些,但是具備高擴展性。不是所有的應用都能接受至少大約 5ms 的查詢延遲,但是如果接受 [30],用戶獲得的是在極高吞吐量下也能保持這一延遲的能力。

二級索引優化

Spanner 的查詢時間與訪問的分片數成正比。因此一條查詢訪問不同分片下的 10 條數據,要比訪問相同分片下的更慢。這在分散式系統下是可預見的。Quizlet 沒有預見的是:二級索引對查詢性能的影響。

在高壓力下,Quizlet 發現批量寫入會嚴重影響二級索引上的查詢性能。儘管 Quizlet 的寫入區域性很強(區域性 = 位於單個分片),但批量寫入意味著有一批分片上的二級索引都需要更新。由於 Spanner 在批量更新多個分片上的二級索引時使用悲觀鎖,這會導致讀這些二級索引時需要等鎖。

Quizlet 的結論是:儘管 Spanner 的二級索引是一個強大的工具,但是在特定場景下會有較高的成本。應用的優化辦法是盡量在主鍵中包含二級索引欄位,從而消除二級索引,徹底的降低延遲 [31]。這並不是一個 Spanner 的問題或 bug,任何資料庫都有自己的架構特性,需要用戶優化應用去適配。

節點/分片數比例

Quizlet 發現 Spanner 限制了每個節點的最大分片數。這一限制與 Spanner 的分片自動配置功能結合導致了一個意外效果:無法降低 Spanner 的節點數,即使 Spanner 之前就在這個節點數下運行。

因為 Cloud Spanner 並沒有對外暴露資料庫的分片數,所以不經過嘗試,你無法預測可以設置的最小節點數。這會在容量規劃的時候產生問題。例如,在更大的集群規模下自動增加分片後,你可能無法降低 Spanner 集群到原來的規模。

成本

Cloud Spanner 的成本取決於應用壓力。對小規模或者低壓力的資料庫來說用 Cloud Spanner 並不合算,單節點成本 0.90 美元每小時,或者說 648 美元每月(按 30 天計算),再加上存儲成本。在高壓力場景上使用更為明智。

Quizlet 的壓測樣本運行在 3 台配有 SSD 硬碟的大型 GCE 實例上。他們估算,成本與一個 10 節點的 Cloud Spanner 集群大致相當。根據壓測,Cloud Spanner 的成本相當甚至稍微便宜一些。

這對 Quizlet 是一個好消息。儘管他們沒有生產環境部署 Cloud Spanner 的經驗,但是 Quizlet 估算使用 Spanner 的成本低於人工部署和季節性訪問量波動時自動擴容和縮容 MySQL 資料庫的成本。

運維須知

之前的老闆告訴我 「開發一個資料庫需要 10 年」 這個稍有缺陷的描述包含了一個事實,即:編寫真正的線上基礎設施需要長期的迭代 —— 反覆經歷上線、優化、再次上線的反饋循環,暴露出事先無法預測的性能瓶頸和缺陷。

資料庫從成熟到真正經歷線上考驗需要時間,在這之前很難得到信任。想想 MySQL 從第一次發布之後過了 6 年後都還沒有加入默認的 InnoDB 引擎。

Spanner 之所以引入注目,部分原因是在於它在 Google 內部已經做了 10 年左右的時間。它支持了 Google 的廣告和分析業務,這是高吞吐、需要高可靠性的業務場景。Google 自身極為關注 Spanner 的開發,至少在內部,它已經被證明是一個線上可用的系統。很少有新發布的資料庫擁有這樣的投入水平和業務場景鍛煉。

故障恢復

在系統上線前,最重要的是知道在什麼情況下會發生故障。Quizlet 沒有在生產環境部署過 Spanner, 他們的最好辦法就是研究它,儘可能的測試它,以了解它的故障特性。

文檔里是這樣描述 Cloud Spanner 的跨區域複製的:

Cloud Spanner 在每個區域維護 3 個副本,每個副本都位於該區域不同的 Google 雲平台可用區內。由於每個副本都是在線資料庫的完整拷貝,因此都可以處理讀寫和只讀請求。Spanner 在每個區域內訪問不同的副本,因此當一個區域發生故障後,您的資料庫仍然可用。

很明顯,Spanner 的設計是為了防止單可用區故障。這意味著,即使 GCP 某個區域的整個數據中心宕機,那麼 Spanner 還能繼續響應查詢。這避免了大部分類型的故障,但不能完全回答 「什麼情況下會掛」 的問題。這是 Quizlet 看待 Spanner 故障風險的觀點。

Spanner 依賴的主要資源是計算節點、磁碟和網路。Quizlet 定義了 4 種故障模式 —— 每種資源對應 1 個,其他情況是另外 1 個。

Google 建議:模擬計算節點故障的最佳方式是在運行壓測的同時修改節點個數。強調一下,節點數不對應具體機器,它更像是分配給 Spanner 集群的計算資源。

Google 告知,在運行時刪除一些節點的效果和計算節點故障是非常接近的。那麼當節點數量改變時,查詢延遲會如何變化呢?

上圖是在 2000 QPS 壓力下調整節點個數記錄下的訪問延遲。延遲中位數基本不受影響,除非節點數量低於閾值(2 個節點)。p90 和 p99 延遲大幅度變化。正如上面的討論,「節點」 是計算資源的抽象,而不是真正的物理節點。Cloud Spanner 可以瞬間讓修改立即生效,表現為調整節點後訪問延遲迅速發生變化。注意,這裡的壓測場景和前面圖表裡展示的稍微有點不一樣。

由於 Cloud Spanner 已經抽象化了磁碟,因此單盤故障可以小到忽略不計。當開啟了本地和跨區複製,每一個比特都會在磁碟間複製幾十次。因此,有意義的問題是:「當存儲系統出現故障,會發生什麼?」 不經歷過這種故障,就不清楚會發生什麼 —— 這很可能是很有破壞性的。

由於計算節點和磁碟都已虛擬化,所以這些系統之間的網路訪問對 Spanner 正常工作至關重要。Spanner 對單點失效很健壯,因此要問的問題是 「如果發生 **系統級 ** 故障怎麼辦?」

尤其是網路故障,Cloud Spanner 很可能會遇到。Quizlet 以前僅僅在 GCE 上遇到過網路隔離,但通常很快就解決掉了。Cloud Spanner 的架構保證數據在網路隔離後保持一致,但如果 Spanner 的計算節點無法訪問,則可能對延遲造成影響。如果網路分區造成一部分 Spanner 數據分片無法訪問,則邏輯上一些查詢就會失敗。

其他故障模式仍然是個懸而未決的問題。Spanner 在 Google 內部運行多年,Quizlet 對其核心技術的故障模式抱有信心。作為一個公有雲產品,Spanner 還很年輕。

備份

像原始論文描述的一樣,Spanner 的架構格外有助於資料庫備份。

Spanner 擁有兩個分散式資料庫難以實現的特性:提供外部一致性的讀/寫,以及按照時間戳、跨資料庫的全局一致性讀。這些特性使得 Spanner 支持一致的數據備份、一致的 MapReduce 計算、以及原子表結構更新。—— 一切都在全局範圍內,甚至在正在運行的事務過程中。

通過在查詢中設置一個訪問數據的時間戳,客戶端可以定義查詢資料庫的歷史時間。按這個時間戳掃描整個資料庫就獲得了一致的數據備份。

Quizlet 預計未來 Cloud Spanner 會提供一鍵備份功能,只需要簡單的客戶端交互就可以創建和導出備份數據。即使沒有提供這個特性,Spanner 的上述架構也使得在客戶端實現備份成為可能。對於海量數據來說,做一次完整的數據導出需要大量工作。

不可置疑的,Cloud Spanner 是一個設計良好、可擴展的數據存儲。 但是,它作為一個線上資料庫仍然有一些粗糙的地方。Quizlet 希望這些問題會隨著時間推移而得到解決,但是你在使用之前仍然值得了解一下。

索引選擇

當需要判斷使用哪個索引的時候,Cloud Spanner 還不是非常準確。當執行查詢之前,資料庫查詢引擎通常會在主鍵索引和二級索引之間選擇,根據運行時間的估計決定使用哪個索引。在 Quizlet 測試中,Spanner 的估算似乎並不是很準確。

結果是,如果需要使用二級索引,你應該在查詢中顯式指定。例如:

SELECT a FROM my_table@{FORCE_INDEX=my_index} WHERE a = @a_value

類似的,強制使用主鍵索引,需要用:

SELECT a FROM my_table@{FORCE_INDEX=_base_table} WHERE a = @a_value

Quizlet 不認為這是一個重大問題:為高性能資料庫編寫查詢的開發人員應該明確的選擇索引。自動選擇索引只是一個便利特性。

索引掃描

對於某些查詢,快速索引掃描似乎只支持從一端開始。查詢語句:

select max(id) from mytable /* id 是主鍵 */

如果 id 是升序索引,則需要做一次全表掃描。而:

select min(id) from mytable

則會執行得很快,因為這個查詢只需要遍歷主鍵索引的最左路徑。反過來也一樣;如果是降序索引,則 max(id) 執行得更快。

這看起來像是一種特色,但在某些情況下會很煩人。

監控

儘管 Spanner 很複雜,或者正因為這種複雜性,監控 Cloud Spanner 的內部機制當前是一個大問題。

Cloud Spanner 沒有監控實例當前正在運行查詢的介面。從客戶端可能會發起代價很高、需要執行很長時間的查詢,例如全表掃描。因為實例的資源池是有限的,資源密集型查詢可能會影響集群上運行的其他查詢的性能和延遲。從運維的角度,最重要的是可視化當前運行的查詢壓力,以便診斷線上問題並掌控性能。這一點對創建索引也很重要,它可能從任意客戶端開始執行,並且在大表上跑好幾個小時。

Cloud Spanner 提供了一些關於資源使用的指標,但是作為一個雲產品,它對系統性能的監控要比自運維的解決方案少得多。目前 Cloud Spanner 提供的指標例如:CPU 利用率(全集群的平均值和最大值),讀寫操作次數和吞吐量,以及消耗的總存儲量。這些都是有用的,但是並沒有提供足夠的信息來解決特定類型的問題。例如,診斷一個熱點分片問題,並將其對應到一個特定的查詢仍然是困難的。當運維一個複雜的線上系統時,信息總是越多越好。Cloud Spanner 仍然缺少對關鍵指標的監控,比如集群上的分片數量,分片優化器的操作記錄,以及運行時間最長的查詢。

Spanner 的 EXPLAIN 展現了詳細執行計劃,但是沒有把查詢計劃應用到實際數據上。例如,它沒有提供查詢掃描的數據行數的信息。而 MySQL 和 Postgres 的 EXPLAIN 更為有用,更有助於理解和優化查詢。

總之 Spanner 給用戶提供了一些關鍵信息,但在其他地方缺乏透明性。為了達到完全的線上可用,Cloud Spanner 必須提供額外的監控信息,方便用戶診斷問題並規劃節點容量。

展望

Cloud Spanner 被 Quizlet 稱為最引人注目的,能提供高擴展、高吞吐的關係型資料庫訪問(類似 MySQL)的雲服務。作為一個線上系統,它仍然有些粗糙,但是它的可擴展性和低延遲是獨一無二的。

Quizlet 從沒有見到另一個資料庫,關係型資料庫或者其他類型,可以像 Cloud Spanner 一樣平滑和快速的擴容。而且 Spanner 是在雲上託管的,消除了大部分運維成本。

主資料庫是 Quizlet 基礎設施中最關鍵的部分,但也是最難擴容的部分。考慮到對 Cloud Spanner 的評估,以及 Quizlet 的基礎設施已經部署到 GCE 上的事實,Quizlet 計劃繼續測試 Spanner,並有可能在 2017 年春季末的時候遷入第一個線上資料庫。

儘管 Cloud Spanner 的能力還沒有驗證,但是它依然很有潛力。Google 是能夠提供這類服務的少數幾個公司之一。因為它需要資料庫軟體與底層雲基礎設施的深度集成,並且重度依賴現有的存儲和計算資源。Quizlet 對 Spanner 的未來,以及它幫助 Quizlet 平滑擴容核心基礎設施的潛力感到樂觀。

備註:

順便翻譯一些有意思的備註,有助於理解 Spanner 的特性。

21 - 由於 Cloud Spanner 必須使用主鍵更新,你可以開啟一個事務藉助二級索引進行更新:先從二級索引讀出需要更新的主鍵,再利用主鍵更新數據行,最後結束事務。

25 - 關於通訊協議:舊式架構由於每個資料庫都用特定的方式通訊,並且需要在每一種編程語言上實現客戶端,沒有標準化或者共享的工具可用。不能用 nginx 這樣的 HTTP 工具是非常痛苦的(儘管 HTTP 協議有一定的開銷)。假設你要寫一個服務,它使用上游資料庫的二進位協議維持一個連接池 —— 有很多客戶端接入這個服務,用它作為一個代理連接到上游資料庫,看起來是一個簡單的工作。如果走 HTTP 協議,你只需要配置 nginx,但是在資料庫這種情況,你需要實現一個定製協議的服務端來接收連接,並且實現定製協議的客戶端與上游伺服器進行通訊。突然一下子,開發資料庫代理伺服器就變成了一項複雜的工作。

你可能在想,「有一個現成的標準,用 ODBC」 這樣離根本問題更遠了。ODBC 並不是一個通訊標準,而是一個 lib 規範,這意味著它只定義了兼容 ODBC 的函數調用規範。你還是需要為自定義的資料庫通訊協議開發客戶端。由於只是一個 lib,它是特定於某個操作系統的。最新的編程語言對 ODBC 的支持並不完整。例如,沒有官方的 ODBC 客戶端可用。這就像是用 Facebook API 作為一個二進位網路協議,讓操作系統去開發專門的驅動支持它一樣 —— 瘋狂。ODBC 作為一個 25 年前的標準,根本無法很好的支持現代應用程序的開發。

27 - 有關測試表結構:

Spanner 不支持列的默認值。

Quizlet 在 MySQL 表結構里已經把 last_modified 欄位從 Unix 時間戳映射到 TIMESTAMP 類型。

每條 Term 數據都屬於一個 Set。Term 和 Set 之間是 1 對多關係。Spanner 支持管理數據分布:如果兩個表存在父-子關係,在父表數據鄰近存儲子表數據是有用的,但仍然是獨立的表。Quizlet 在測試中沒有使用這個功能,因為他們想直接比較 MySQL 和 Spanner 在相同業務場景下的表現。

通過 set_id 欄位進行分庫,Quizlet 已經在 MySQL 下優化了業務場景。這是一個很好的分庫欄位,因為同一個 Set 對應的 Term 數據都在一個分庫上。然而,分庫帶來的問題是不含 set_id 的查詢都必須在每個分庫上執行。在 Spanner 上,Quizlet 是通過指定 (set_id, id) 作為主鍵達到相同的效果。

30 - 延遲對 Quizlet 有很大的影響。就像前面提到的,Quizlet 使用 memcahed 作為查詢結果緩存。這對一致性有重大影響,因為 memcached 必須與資料庫保持同步。否則,緩存的結果是不對的。如果 Cloud Spanner 的延遲只有 1ms,這樣就可以去掉緩存,儘管這會影響總體吞吐量,並且不一定節省成本。關鍵是:延遲很重要,而且越低越好。

31 - 確切的說,Quizlet 最初的設計模式是在 Cloud Spanner 上使用複合主鍵 (欄位 A, 欄位 B),然後在欄位 B 上添加一個二級索引。這與 MySQL 的壓測場景更為接近 —— 不是按主鍵而是按 B 欄位分庫。 然而 Quizlet 發現在 Spanner 上,調換複合主鍵順序為 (欄位 B, 欄位 C) 更好,因為這樣可以去掉二級索引,帶來巨大的性能提升。這個例子非常依賴於特定場景 —— 很容易想到在另一個場景下還是會需要二級索引。


推薦閱讀:

UCloud雲資料庫團隊誠招分散式資料庫研發
大國崛起:資料庫領域的中國力量
【技術解密】SequoiaDB分散式存儲原理
PhxPaxos架構設計、實現分析
圖資料庫發展

TAG:分散式資料庫 | 資料庫 | 分散式系統 |