阿里資深技術專家:優秀的數據庫存儲引擎應具備哪些能力?

摘要: 作爲數據庫的底盤,一個成熟的存儲引擎如何實現高效數據存取?

導讀

本文作者是阿里巴巴OLTP數據庫團隊資深技術專家——曲山。作爲自研高性能、低成本存儲引擎X-Engine的負責人,曲山眼中的優秀關係型數據庫存儲引擎應該具備哪些能力呢?

正文

數據庫內核按層次來分,就是兩層:SQL & Storage。SQL Layer負責將你輸入的SQL statement通過一系列步驟(parse/resolve/rewrite/optimize…)轉換成物理執行計劃,同時負責計劃的執行,執行計劃通常是一顆樹的形式,其中樹的葉子節點(執行器算子)部分往往負責單表的數據操作,這些操作算子就要在storage layer來執行了。

因此,一個數據庫存儲引擎的主要工作,簡單來講就是存取數據,但是前提是保證數據庫的ACID(atomicity/consistency/isolation/durability)語義。存儲引擎對外提供的接口其實比較簡單,主要就是數據寫入/修改/查詢,事務處理(start transaction/commit/rollback…),修改schema對象/數據字典(可選), 數據統計,還有一些周邊的運維或數據導入導出功能。

僅僅從功能上來說,要實現一個存儲引擎似乎並不困難,如今也有很多Key-Value Store搖身一變就成爲了數據庫存儲引擎,無非是加上一套事務處理機制罷了。但是作爲數據庫的底盤,一個成熟的存儲引擎必須要考慮效率,如何高效(性能/成本最大化)的實現數據存取則成了在設計上做出種種權衡的主要考量。可以從存儲引擎的幾個主要組件來討論:

數據組織

數據在內存和磁盤中的組織方式很大程度上決定了存取的效率,不同的應用場景選擇也不同,典型的如:

數據按行存儲(NSM),對事務處理比較友好,因爲事務數據總是完整行寫進來, 多用於OLTP場景。

按列存儲(DSM),把tuples中相同的列值物理上存儲在一起,這樣只需要讀取需要的列,在大規模數據掃描時減少大量I/O。另外列存做壓縮的效果更好,適合OLAP場景,但是事務處理就不那麼方便,需要做行轉列。所以大部分AP數據庫事務處理效率都不怎麼高,某些甚至只支持批量導入。

混合存儲(FSM),行列混合佈局,有把數據先按行分組(Segment, SubPage),組內使用DSM組織,如PAX, RCFile,也有先按列分組(Column Group),組內指定的列按NSM組織,如Peloton的Tile。此種格式試圖結合NSM和DSM兩者的優點,達到處理混合負載(HTAP)的目的,但是同時也繼承了兩者的缺點。

所以做存儲引擎,一開始就要面臨選擇何種存儲格式的問題。即便選定了大類,每種格式中也有無數的細節需要考慮,每種數據類型的字段如何編碼(Encoding),行存中null/not null如何存儲,是否需要列索引加快project operation,是否需要對列值進行重排,列存如何進行數據壓縮,等等,都要存儲空間和存取速度中做平衡。

現代數據庫爲了應對複雜的應用場景,往往使用不只一種存儲格式,比如Oracle有In-memory Column Store在內存中將行存的頁面轉換爲列存方式的page,用來加速複雜查詢。

當數據選定存儲格式以後,還要選擇數據在磁盤和內存中的聚集方式。以按行存儲爲例,大部分存儲引擎使用固定大小的頁面(page)來存儲連續的若干行。當然,數據行如何連續排列,有堆表(隨機)和索引組織表(按索引序)兩種,現在較爲流行的LSM-Like的存儲引擎使用不定大小的頁面(稱爲DataBlock),只支持按主鍵索引序聚集;這兩種方式主要區別在於前者被設計爲可更新的,每個page中會留有空間,後者是隻讀的,數據緊密存儲不帶padding,便於壓縮。兩者的區別實際上是因爲事務處理機制有較大的區別導致的,後面再論。

對於In-Memory Database來說,數據組織的方式會有較大區別,因爲不需要在內存和持久化存儲中交換數據,內存中一般不會使用page形式,而是直接使用索引存儲結構(比如B+Tree)直接索引到記錄(tuples),無需page這一層間接引用,減少cpu cache miss。

緩存管理

緩存的粒度一般是page,關鍵在於緩存替換算法。目前用的比較廣泛的LRU,LFU,ARC..以及各種變種的算法都有在數據庫中使用。另外還有一個是如何更有效的管理內存的問題,這點上,定長的page會比不定長的更有優勢。

當然還要考慮各種query pattern對cache的影響,如果單行查詢較多,選用更細粒度(比如row)的cache會更有效率,但是淘汰的策略會更復雜,很多新的研究開始嘗試引入機器學習的方法來優化cache淘汰算法,以及有效的管理cache.

事務處理

存儲引擎之核心,保證數據庫的ACID。要保證D,大家的做法差不多,都是寫WAL(Write Ahead Log)來做recovery,關鍵是如何高效的實現ACI,也就是所謂的多版本併發控制(MVCC)機制。

MVCC的完整實現比較複雜,暫不詳細闡述,這裏面的關鍵在於如何處理併發執行過程中的數據衝突(data race),包括寫寫衝突,讀寫衝突;因爲數據庫的負載一般是讀多寫少的,要做到高效,只讀事務不能被讀寫事務阻塞,這就要求我們的寫不能直接去更新當前的數據,而是要有一套維護多版本數據的能力,當前的存儲引擎管理多版本數據的辦法無非兩種:

寫入數據原地更新,被更新的舊版本寫到undo鏈中,寫入代價大,事務處理複雜, 但是回收舊版本數據高效。
寫入數據不直接更新原來的數據,而是追加爲新版本,寫入代價小,但是讀,尤其是掃描需要讀取層次較多,更爲嚴重的問題是回收舊版本的數據需要做compact,代價很大。
前一種稱爲ARIES算法比大多數主流數據庫存儲引擎使用,後一種稱爲LSM-Tree的結構也被很多新存儲引擎使用,受到越來越多的關注。

Catalog

與KV store有區別的是,數據庫是有嚴格的schema的,所以多數存儲引擎中的記錄都是有結構的,很多KV store在作爲數據庫存儲引擎時,都是在中間做一層轉換,將上層處理的tuples以特定的編碼方式轉換爲binary key-value,寫入KVStore,並在讀取到上層後,依靠schema解釋爲tuples格式供上層處理。

這種方法當然可以工作,但是諸多優化無法實施:a. 數據迭代必須是整行,即便只需要其中一列,序列化/反序列化開銷是免不了的。b. project和filter的工作無法下放到存儲層內部進行處理; c. 沒有列信息,做按列編碼,壓縮也不可能。d. schema change只能暴力重整數據… 因此要做到真正的高效,越來越多的存儲引擎選擇完全感知schema,存儲細微結構。

總結

以上所探討的,還只是單機數據庫的存儲引擎幾個大的問題,而現代數據庫對存儲引擎提出了更高的要求,可擴展,高可用已經成爲標配,現在要考慮的是如何給你的存儲引擎加上分佈式的能力,而這又涉及到高可用一致性保證,自動擴展,分佈式事務等一系列更爲複雜的問題,這已遠超出本文的範疇,需要另開篇章。

最後介紹下我們正在開發的阿里自研分佈式數據庫X-DB,其中的存儲引擎就使用了我們自研的X-Engine。X-Engine使用了一種對數據進行分層的存儲架構,因爲目標是面向大規模的海量數據存儲,提供高併發事務處理能力和儘可能降低成本。

我們根據數據訪問頻度(冷熱)的不同將數據劃分爲多個層次,針對每個層次數據的訪問特點,設計對應的存儲結構,寫入合適的存儲設備。X-Engine使用了LSM-Tree作爲分層存儲的架構基礎,並在這之上進行了重新設計。

簡單來講,熱數據層和數據更新使用內存存儲,利用了大量內存數據庫的技術(Lock-Free index structure/append only)提高事務處理的性能,我們設計了一套事務處理流水線處理機制,把事務處理的幾個階段並行起來,極大提升了吞吐。而訪問頻度低的冷(溫)數據逐漸淘汰或是合併到持久化的存儲層次中,結合當前豐富的存儲設備層次體系(NVM/SSD/HDD)進行存儲。

我們對性能影響比較大的compaction過程做了大量優化,主要是拆分數據存儲粒度,利用數據更新熱點較爲集中的特徵,儘可能的在合併過程中複用數據,精細化控制LSM的形狀,減少I/O和計算代價,並同時極大的減少了合併過程中的空間放大。同時使用更細粒度的訪問控制和緩存機制,優化讀的性能。當然優化是無止境的,得益於豐富的應用場景,我們在其中獲得了大量的工程經驗。

X-Engine現在已經不只一個單機數據庫存儲引擎,結合我們的X-Paxos(分佈式強一致高可用框架), GMS(分佈式管理服務), 和X-Trx(分佈式事務處理框架),已經演變爲一個分佈式數據庫存儲系統。



本文作者:七幕

閱讀原文

本文爲雲棲社區原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章