OceanBase 4.3 版本上線了列存功能,以滿足實時分析的需求。
本文作為《特性解讀:列存技術》的后續,將詳細闡述列存技術在OceanBase數據庫架構中的應用、發展歷程,以及未來的趨勢。
一、前言
1970 年,關系模型之父 Codd 提出關系模型,正式開啟了數據庫的時代。1979 年,Oracle 發布第一個商業數據庫版本,數據庫技術開始廣泛應用于各行各業。在那個數據量還不是特別大、查詢也相對簡單的年代,單一的數據庫系統能夠滿足用戶的需求。
隨著時間的推移,數據量急劇增加,查詢也變得越來越復雜。單一的數據庫已經無法滿足用戶的事務處理和分析處理需求。因此,Codd 于 1993 年正式提出 OLAP(聯機分析處理)概念,并提出了 12 條準則。從此,OLTP(聯機事務處理)和 OLAP 開始分道揚鑣,在兩個領域出現了許多數據庫產品。大約十年后的 2005 年,StoneBraker 提出了第一個基于列存的數據庫原型 CStore,證明了列存在分析領域的巨大潛力,自此列存儲成為 OLAP 數據庫的標準配置。然而,值得注意的是,盡管數據庫產品在 OLTP 領域主導地位穩固,但在 OLAP 領域,全球技術代表性產品層出不窮,比如 GreenPlum(2006 年)、SnowFlake(2014 年)、DataBrick(2014 年)、ClickHouse(2016 年)等。
盡管 OLTP 和 OLAP 數據庫各自在自己的領域占據主導地位,但用戶對 OLTP 和 OLAP 的需求卻是同時存在的。為了支持業務,用戶通常需要使用兩套數據庫系統,一套用于 OLTP,一套用于 OLAP,并通過數據同步組件進行 OLTP 到 OLAP 的數據同步。然而,這種方式會帶來一系列問題:
首先,不僅數據冗余了一份,系統也冗余了一份。盡管面向 OLAP 的系統可以使用相對廉價的存儲,但 CPU 和內存的冗余消耗仍然存在。此外,多了一套系統,也意味著增加了一套系統運維的成本。
其次,OLAP 和 OLTP 系統之間的數據同步總會存在延遲,并且很難保證延遲時間。一旦 OLAP 系統或數據同步組件出現問題,數據修復可能需要數天時間,這段時間內 OLAP 的業務就一直處于不可服務的狀態。
最后,隨著互聯網技術的發展,對 OLAP 實時性的要求也越來越高。設想這樣一個場景,用戶在網上購物下單,線上交易這是再經典不過的 OLTP 場景。而系統希望能夠根據用戶下單的商品以及其他相關信息,自動推薦出用戶可能繼續加購的商品以增加成交額,這則是典型的 OLAP 場景。用戶在 APP 或者頁面上的瀏覽速度幾乎是電光火石之間,等數據同步到 OLAP 系統再進行響應,可能就來不及了。
因此,盡管相比上個世紀,數據庫承載的數據量增加了成百上千倍,之前一套簡單系統的美好時光還能回來嗎?2016 年,Gartner 正式提出了 HTAP(混合事務/分析處理)的概念,成為這個問題最好的答案,即無需增加實體,如果一套系統能夠滿足大部分的 OLTP 和 OLAP 需求,就不需要搭建兩套復雜系統了。
相對來說,行存儲更適合 OLTP 類負載,而列存儲更適合 OLAP 類負載。一套支持 HTAP 實時分析的數據庫通常需要同時支持行存儲和列存儲。盡管相比分別部署 OLTP 和 OLAP 數據庫,使用一套 HTAP 數據庫可以解決同步延遲和數據實時性的問題,但數據冗余的問題似乎并沒有得到解決。然而,實際上,使用一份數據來實現 HTAP 是可能的,這取決于我們如何看待和使用列存。
二、列存是副本
列存副本方案是一種較為直接的 HTAP 實現方式,它相當于在單一系統內構建了兩套獨立的引擎:一套基于行存儲的引擎面向 OLTP,另一套基于列存儲的引擎面向 OLAP。這種方案對用戶屏蔽了數據同步的細節,并能提供無延遲的 OLAP 數據訪問。Google F1 Lightning 和 PingCAP TiDB 等業內優秀的數據庫都采用了類似的方案。
如圖所示,左側的三個副本(Node 1/2/3)基于行存儲引擎提供 OLTP 能力,而右側的副本 Node 4 則是一個列存儲引擎,提供 OLAP 能力。兩個引擎之間通過 Raft/CDC 進行日志數據同步。該方案的優點在于可以提供較好的隔離性,OLAP 引擎的數據訪問不會影響 OLTP 引擎本身的穩定性。
當然,這種方式也存在一定的弊端,那就是成本高昂,尤其是對大體量的數據場景非常顯著。不僅數據本身會額外冗余一份,用于支持列存引擎的 CPU 和內存也需要冗余,而且運維成本也絲毫沒有減少。此外,作為獨立的列存引擎,一旦出現問題,也需要專門的人員進行處理。
三、列存是索引
通過列存索引的方式來實現 HTAP,比較典型的代表是 SQL Server。盡管早在 2012 年就推出了 Column Index (列存索引)功能,但當時的版本僅支持只讀,無法滿足用戶的更新需求。直到 2016 年,SQL Server 可更新的列存索引正式發布,這項特性開始為用戶提供更加友好的體驗。
如圖所示,SQL Server 內部也單獨開發了一套列存存儲引擎,與原有的行存引擎并行工作。SQL 層會統一對接底層的不同引擎,如果表是行存的,則使用行存引擎存儲數據;如果表上還構建了額外的列存索引,那么就會對這些列存索引使用列存引擎存儲。行存和列存可以同時存在,也可以同時構建多個列存索引。這種方式具有很高的靈活度,可以根據需要只針對特定的列構建列存索引,數據冗余程度也遠低于列存副本方案。此外,SQL Server 在執行 SQL 語句時可以同時利用列存和行存的能力,極大地提升了執行效率。
具體到實現層面,SQL Server 的列存存儲不會按照主鍵順序排序,而是類似于堆表的方式進行組織,將固定數量的行組成一個 Row Group。在每個 Row Group 中,每個列都會單獨存儲到不同的 Segment 中。Row Group 一旦生成便不再修改,刪除操作通過 Delete Bitmap 標記完成,更新操作則通過 Delete + Insert 完成。后續的 Insert 操作會被放入 Delta Store,查詢時需要將列存數據、Delete Bitmap、Delete Buffer 和 Delta Store 中的數據進行合并得到最終結果。
SQL Server 的列存方案很好地解決了延遲、實時性以及成本等問題,但對于索引組織表來說,列存索引仍然在很大程度上依賴于行存,主鍵約束和唯一鍵約束的維護也需要依靠行存來完成。不僅如此,Delta Store 和 Delete Bitmap 的維護也并非沒有代價,列存索引的引入會對行存 OLTP 的性能造成一定影響。
四、列存是緩存
Oracle 的做法是將列存作為緩存實現 HTAP 混合負載。2013 年,Oracle 發布了 12C 版本,并推出了名為 IMC(In-Memory Column Store)的特性。
從嚴格意義上講,IMC 更像是基于行存的列存加速緩存,而非完整意義上的列存。Oracle 允許在列、分區、表、表空間等不同粒度上開啟 IMC,靈活度很高。如果對某張表的某些列開啟了 IMC,Oracle 會將這些列的數據從行存中加載到內存中,并以列存的形式存儲。但需要注意的是,數據仍然存儲在行存中,列存數據不會直接落盤。后續的增刪改等修改操作會通過內部刷新機制更新到列存。在 Oracle 的內存管理中,SGA 中的 Buffer Cache 承擔了主要的增刪改查等事務操作。如果要開啟 IMC,則需要在 Buffer Cache 之外額外分配一塊單獨的內存區域。
這個做法避免了磁盤數據冗余的代價,也可以向用戶提供實時無延遲的 OLAP 能力,并且提供了一定的靈活性,用戶可以根據自身需要對列存進行靈活的配置。然而,其問題也很明顯,內存的代價并未減少,而且相比于磁盤來說,內存總是更加寶貴,用昂貴的內存來支持 OLAP 能力總體成本較高。此外,OLAP 要處理的數據量通常非常龐大,將所有數據都存儲在內存中并不現實。一旦需要訪問磁盤,就需要將數據從行存中讀出并轉換成內存列存。在這種場景下,列存相較于行存可以減少 I/O 代價的優勢也就無法體現了。
五、列存是數據
無論是 SQL Server 還是 Oracle,其底層存儲引擎都基于 B-Tree。如果我們將視角拓寬到 LSM-Tree,就會發現列存與 LSM-Tree 才是天作之合,產生更顯著的化學反應。LSM-Tree 中,數據被劃分為 MemTable 和 SSTable 兩個部分。MemTable 駐留在內存中,支持動態修改,天然適合行存;而 SSTable 存儲在磁盤上不可修改,非常適合用來做列存。在 OceanBase 中,SSTable 又會被細分為轉儲 SSTable 和基線 SSTable。通常,轉儲 SSTable 用于存儲最近修改的數據,而基線 SSTable 則用于存儲較老的數據。
OLTP 類負載以短事務為主,主要包括插入、小范圍更新、刪除和查詢最近的數據。這類負載涉及的數據大多位于 MemTable 和轉儲 SSTable 中。因此,OceanBase 針對 MemTable 和轉儲 SSTable 使用行存存儲,并對基線 SSTable 增加 Bloom Filter 過濾以阻斷大部分空查,同時使用 cache 緩存部分熱點列存數據以加速熱查,從而確保 OLTP 類負載的性能。
OLAP 類負載以大查詢為主,涉及的數據大多位于基線 SSTable 中。對于基線 SSTable 的數據,OceanBase 直接使用列存。但與 SQL Server 不同的是,OceanBase 的列存數據并非無序存儲,而是整體按照主鍵順序排列。這樣一來,即使在列存中處理少量 OLTP 類請求,需要尋找單獨一行數據,OceanBase 也能夠通過二分法快速定位到目標數據行。很多用戶在 POC 階段評價,這是可以支持 OLTP 業務的列存。
通過這種方式,OceanBase 可以通過一份數據同時兼顧 OLTP 和 OLAP。考慮到相對于轉儲 SSTable 來說,基線 SSTable 通常占據了數據量的絕大部分,且列存相較于行存具有更高的數據壓縮率,OceanBase 的架構可以將成本降至最低。然而,該架構也面臨一些挑戰:
首先,如何做好 OLTP 和 OLAP 多工作負載的資源隔離是一項極具挑戰性的任務。在理想的調度機制下,OLAP 負載可以靈活地從 OLTP 負載處獲取資源,并在 OLTP 空載的情況下使用大部分系統資源。這理論上是可以實現的,就像現在大多數數據庫都可以部署在 Docker 容器中一樣,但很少有人會擔心容器對系統資源的隔離能力。然而,這對于特別高等級的隔離需求來說可能還不夠。
其次,也可能存在部分查詢需求,在基線 SSTable 使用行存時會表現得更好。或者將若干列混合存儲在一起,可能會帶來更好的查詢性能。
六、列存是所有
基于 LSM-Tree 架構,OceanBase 可以將數據做列存存儲,以提供最極限的成本節省。但這并不意味著列存存儲是 OceanBase 的唯一選擇,它還可以作為緩存、索引和副本,為用戶提供更高的靈活性和無限的可能性。
○ ?首先,OceanBase 可以將列存視作緩存,在緩存中存儲部分區域的列存數據,以加速熱點范圍的查詢。
○ ?其次,OceanBase 可以將列存看做索引,在基線 SSTable 中同時存儲行存與列存數據,或者做部分列的聚合冗余存儲。根據查詢需要,查詢列存或者行存,或者更合適的列組。
○ ?再次,OceanBase 可以將列存視為副本,在主副本中使用行存,在只讀副本中使用列存,以提供更高等級的資源隔離。
○ ?最后,可能在不遠的未來,除了提供以上的靈活度以外,OceanBase 或許還可以讓用戶擺脫行存和列存這些底層存儲方式的限制,忘掉 OLTP 和 OLAP 等形態,讓數據庫回歸到最初的本質。用戶把數據和查詢給到數據庫,數據庫把結果給用戶,無論列存還是行存,數據庫總是按照最適合負載的形式組織數據,以最快的速度返回結果。當用戶覺得查詢有些慢又不想做調優時,只需加資源即可。
七、寫在最后
以上是我們對列存能力的理解和整體規劃,OceanBase 的列存功能已經在 4.3 版本中正式推出,更多強大功能正在研發中。我們希望這些新特性能盡快與大家見面,讓更多用戶體驗到列存在實時分析領域帶來的優勢和便捷。
本文作者: