做者:實驗室小陳 / 大數據開放實驗室前端
在上一篇文章《內存數據庫解析與主流產品對比(二)》中,咱們從數據組織和索引的角度介紹了內存數據庫的特色和幾款產品的技術實現。本文將繼續解析內存數據庫,從併發控制、持久化和查詢處理的角度介紹幾款技術,帶來更多維度、更細緻的內存數據庫技術討論。算法
— 數據庫管理系統中的併發控制—數據庫
1. 內存數據庫併發控制的兩種策略數組
a. 多版本的併發控制緩存
內存數據庫中的併發控制主要採用兩類策略:1. 多版本的併發控制;2. 分Partition處理。併發控制機制能夠分爲樂觀和悲觀兩種類型。悲觀併發控制則認爲進程競爭資源老是存在的,所以訪問時先加鎖,訪問完再釋放;樂觀併發控制認爲大多數狀況不須要競爭資源,只在最後提交前檢查是否存在衝突,有衝突就回滾,沒有就提交。性能優化
樂觀併發控制大多數不採用基於鎖的技術實現,而且一般是多版本的。多版本意味着每次更新都會產生新的版本,讀操做根據可見範圍選取合適的老版本,讀操做不阻塞寫操做,因此併發程度比較高。其缺點是會產生額外開銷,例如更新要建立新版本,並且隨着版本愈來愈多,還須要額外開銷收回老版本。內存數據庫多采用樂觀的多版本併發控制機制,相比於基於鎖的悲觀併發控制其優點是開銷較小,並且支持併發程度較高的場景;缺點是在有大量寫競爭的場景下,事務間衝突機率比較高時,大量事務會失敗和回滾。網絡
b. 分Partition處理數據結構
內存數據庫併發控制的另一類策略是把數據庫分紅多個Partition,每一個Partition採用串行方式處理事務。優點是單Partition業務的執行沒有用於併發控制的額外開銷,缺點是存在跨Partition事務時系統的吞吐率會直線降低。所以,若是不能保證全部業務都是單Partition進行,將致使性能不可預測。架構
2. 多版本併發控制之 Hekaton併發
Hekaton採用樂觀的多版本併發控制。Transaction開始時,系統爲事務分配讀時間戳,並將Transaction標記爲active,而後開始執行事務,在操做過程當中系統記錄被讀取/掃描/寫入的數據。隨後,在Pre-commit階段,先獲取一個結束的時間戳,而後驗證讀和掃描數據的版本是否仍然有效。若是驗證經過,就寫一個新版本到日誌,執行Commit,而後把全部的新版本設置爲可見。Commit以後,Post-Processing記錄版本時間戳,以後Transaction才真正結束。
a. Hekaton 的事務驗證
i) Read Stability:Hekaton系統可以保證數據的讀穩定性(Read Stability),好比交易開始時讀到的每條記錄版本,在Commit時仍然可見,從而實現Read Stability。
ii) Phantom Avoidances:Phantom指一個事務在開始和結束時執行相同的條件查詢,兩次結果不同。出現幻影的緣由是該事務執行過程當中,其餘事務對相同數據集進行了增長/刪除/更新操做。應該如何避免幻影現象呢?可經過重複掃描,檢查所讀取的數據是否有新版本,保證記錄在事務開始時的版本和在結束時一致。
Hekaton併發控制的好處在於,不須要對Read-Only事務作驗證,由於多版本可以保證事務開始時的記錄版本在結束時依然存在。對於執行更新的事務,是否作驗證由事務的隔離級別決定。例如若是快照隔離級別,就不須要作任何驗證;若是要作可重複讀,就要作Read Stability;若是是串行化隔離級別,既要保證Read Stability,又要保證Phantom Avoidance。
b. Hekaton的回收策略
Hekaton中的回收任務並不禁獨立的線程處理,而是每一個事務本身回收。以下圖所示,Transaction ID爲250的事務結束時間戳爲150且狀態爲terminated,此時會有一個Write Set獲取全部老版本,並判斷當前全部active的Transaction的開始時間戳是否大於ID爲250的事務結束時間,即150。若是都大於150,說明不可能再基於時間戳早於150的舊版本進行修改,於是由事務回收舊版本,這部分工做是每一個線程在處理Transaction時的額外工做。
3. 多版本併發控制之Hyper
Hyper的併發控制和Hekaton的區別主要有如下三點:1. 直接在記錄位置進行更新,經過undo buffer來保存對數據的修改,數據和全部修改被連接在一塊兒;2. 驗證是根據最近的更新與讀的記錄進行比較來實現(後續會涉及到);3. 串行化處理commit,對提交的事務進行排序,並依次處理。
在事務驗證方面,Hyper的驗證須要在日誌中記錄Read Predictates,包括查詢或Range Scan,並且要記錄插入、刪除和更新的記錄。在Hyper模式中,插入/刪除/更新經過對應的Undo Buffer獲悉被修改過的記錄,因此記錄改動對於Hyper而言是容易的。
對於每一個Transaction,只須要比較該事務從開始到Commit之間,是否存在其餘Transaction對知足搜索條件的數據集進行過增/刪/改,從而判斷是否存在幻影現象,若是存在,就直接終止事務。
4. 多版本併發控制之HANA和HStore/VoltDB
HANA並行控制方式比較簡單,採用悲觀的多版本控制,由行級鎖保護數據結構,每行由時間戳決定每一個版本的可見範圍。每一個Transaction在更新或刪除時都須要申請寫鎖,並且要作死鎖檢測。
HStore/VoltDB是一個Partition系統,鎖的粒度很粗,每一個Partition對應一把鎖,所以Transaction在某節點上執行時,須要拿到該節點全部資源。一旦一個事務可能涉及到兩個Partition,就須要把兩個Partition的鎖都拿到。因此Partition系統的優勢是單Partition處理速度很是快,缺點是多Partition效率很低。同時,系統對於負載的偏斜很是敏感,若是有熱點數據,那麼熱點數據就構成系統瓶頸。
5. 多版本併發控制之負載預知
假設一個工做負載中,事務須要讀和寫的數據集能夠提早得到,就能夠在執行前肯定全部事務的執行順序。Calvin就是基於這樣的假設設計的VLL (Very Lightweight Locking)超輕量級鎖數據庫原型系統。通用場景的工做負載是沒法提早知道讀寫集合的,但在存儲過程業務的應用中,能夠提早肯定讀寫集合,對於這些場景就能夠考慮相似Calvin的系統。
— 數據庫管理系統中的持久化技術—
對於內存數據庫而言,和基於磁盤的數據庫相同也須要日誌和Checkpoint。Checkpoint的目的是恢復能夠從最近的檢查點開始,而不須要回放全部數據。由於Checkpoint涉及寫入磁盤的操做,因此影響性能,所以要儘可能加快相關的處理。
一個不一樣是內存數據庫的日誌和Checkpoint能夠不包含索引,在恢復時經過基礎數據從新構造索引。內存數據庫中的索引在恢復時從新構造,構造完成後也放在內存中而不用落盤,內存索引數據丟失了再重構便可。另一個不一樣是內存數據庫Checkpoint的數據量更大。面向磁盤的數據庫在Checkpoint時,只須要把內存中全部Dirty Page寫到磁盤上便可,可是內存數據庫Checkpoint要把全部數據所有寫到磁盤,數據量不管多大都要全量寫一遍,因此內存數據庫Checkpoint時寫入磁盤的數據遠大於基於磁盤的數據庫。
Hekaton Checkpoint
對於持久化的性能優化,第一要保證寫日誌時的高吞吐量和低延遲,第二要考慮恢復時如何快速重構整個數據庫。Hekaton的記錄和索引存放在內存,全部操做寫日誌到磁盤。日誌只記錄數據的更新,而不記錄索引的更新。進行Checkpoint時,Hekaton會從日誌中恢復,並根據主鍵範圍並行處理。以下圖,分三個主鍵範圍:100~19九、200~29九、300~399,綠色表明數據,紅色表明刪除的記錄(單獨保存被刪除的文件)。在恢復時,Hekaton用並行算法在內存中重構索引和數據,過程當中根據刪除記錄過濾數據文件,去除被刪除的數據,而後從Checkpoint點開始,根據日誌回放數據。
其餘系統的Checkpoints
— 數據庫管理系統中的查詢處理—
傳統的查詢處理採用火山模型,查詢樹上的每一個節點是一個通用的Operator,優點在於Operator能夠任意組合。但Operator拿到的記錄只是一個字節數組,還須要調用另外一個方法來解析屬性以及屬性類型。若是這種設計放到內存數據庫中,屬性以及類型的解析都是在Runtime而非編譯時進行的,會對性能產生影響。
另外對於get-next,若是有百萬個數據就要調用百萬次,同時get-next一般實現是一個虛函數,經過指針調用,相比直接經過內存地址調用,這些都會影響性能。此外,這樣的函數代碼在內存中的分佈是非連續的,要不斷跳轉。綜上,傳統DBMS的查詢處理方式在內存數據庫當中並不適用,尤爲體如今在底層執行時。
內存數據庫一般採用編譯執行的方式,首先對查詢進行解析,而後優化解析後的語句,並生成執行計劃,而後根據模板對執行計劃進行編譯產生可執行的機器代碼,隨後把機器代碼加載到數據庫引擎,執行時直接調用。
下圖是對不一樣查詢方式的耗時分析,能夠看出編譯執行方式中Resource Stall的佔比不多。
另一張圖解釋了目前的CPU架構實現,L2 Cache和主存之間存在Hardware Pre-fetcher。L2 Cache分爲指令Cache和Data Cache,指令Cache會由Branch Prediction實現分支預測,Data Cache會由基於Sequential Pattern的Pre-fetcher實現預測。所以,數據庫系統的設計須要考慮該架構下如何充分發揮Pre-fetcher功能,讓Cache能夠不斷爲 CPU計算單元提供指令和數據,避免出現Cache Stall。
Hekaton的編譯採用T-SQL存儲過程,編譯的中間形式叫作MAT Generator,生成最終的C代碼在編譯器中執行。它產生的庫和通用Operator的區別在於:通用Operator須要在運行時解釋數據類型;而Hekaton編譯方式是把表的定義和查詢編譯在一塊兒,每一個庫只能處理對應的表而不能通用,天然就能拿到數據類型,這樣的實現能得到3~4倍的性能提高。
HyPer的編譯方式是把查詢樹按照Pipeline的分割點每段編譯。而MemSQL採用LLVM作編譯,把MPI語言編譯成代碼。
下圖是一個對MemSQL性能的測試。沒有采用編譯執行時,MemSQL兩次執行相同查詢的時間都是0.05秒;若是採用編譯執行,第一次耗時0.08秒,可是再執行時耗時僅0.02秒。
除了以前提到的幾種內存數據庫外,還有其餘一些著名的內存DBMS出現。
i) SolidDB:誕生於1992年的混合型數據庫系統,同時具有基於磁盤和內存的優化引擎,使用VTRIE(Variable-length Trie)樹索引和悲觀鎖機制進行併發控制,經過Snapshot Checkpoints恢復。
ii) Oracle Times Ten:早期是惠普實驗室名爲Smallbase的研究項目,在2005年被Oracle收購,如今多做爲大型數據庫系統的前端內存加速引擎。Oracle Times Ten支持靈活部署,具備獨立的DBMS引擎和基於RDBMS的事務緩存;在BI工做時支持Memory Repository,經過Locking進行併發控制;使用行級Latching處理寫衝突,採用Write-Ahead Logging和Checkpoint機制提升持久性。
iii) Altibase:於1999年在韓國成立,在電信、金融和製造業應用普遍。Altibase在Page上存儲記錄,以Page爲粒度進行Checkpoint且兼容傳統DBMS引擎;支持多版本併發控制,使用預寫日誌記錄和檢查點實現持久性和恢復能力,經過Latching-Free對Page的數據進行Checkpoint。
iv) PTime: 21世紀起源於韓國,2005年出售給SAP,如今是SAP HANA的一部分。PTime具有極佳的性能處理,對日誌記錄使用差分編碼(XOR),採用混合存儲佈局,支持大於內存(Larger-than-Memory)的數據庫管理系統。
— 本文小結—
每個數據庫系統都是針對特定的硬件環境設計,基於磁盤的數據庫系統面臨CPU單核、內存小、磁盤慢的場景設計。而內存數據庫之內存爲主存,不須要再重複讀寫磁盤,磁盤I/O再也不是性能瓶頸,而要解決其餘瓶頸,好比:1. Locking/Latching的開銷;2. Cache-Line Miss,即若是數據結構定義的不夠好或在內存中組織的很差,沒法匹配CPU的層級緩存,會致使計算性能變差;3. Pointer Chasing,即須要另外一個指針解釋,或者查另外的表才能查到記錄地址,也是主要的性能開銷。此外,還有Predicate Evaluation計算、數據遷移/存儲時的大量拷貝、分佈式系統應用與數據庫系統的網絡通訊等開銷。
在本專欄中,咱們介紹了傳統基於磁盤的DBMS和內存數據庫的特色,並從數據組織、索引、併發控制、語句處理編譯、持久化幾個方面對內存數據庫與基於磁盤數據庫的相同和差別性進行了介紹:
1. 數據組織:內存數據庫中,把記錄分紅定長和變長管理,不須要數據連續存儲,用指針替代了Page ID + Offset的間接訪問;
2. 索引優化:考慮面向內存中的Cach Line優化、快速的內存訪問等Latch-Free技術,以及索引的更新不記錄日誌等;
3. 併發控制:能夠採用悲觀和樂觀的併發控制方式,可是與傳統基於磁盤數據庫的區別是,內存數據庫鎖信息和數據綁定,而不用單獨的Lock Table管理;
4. 查詢處理:內存數據庫場景下的順序訪問和隨機訪問性能差異不大。能夠經過編譯執行提升查詢性能;
5. 持久化:仍然經過WAL(Write-Ahead Logging)作Logging,並採用輕量級的日誌,日誌記錄的內容儘可能少,目的是下降日誌寫入磁盤延遲。
內存數據庫從1970s開始出現經歷了理論成熟、投入生產、市場驗證等發展環節。隨着當前互聯網秒殺、移動支付、短視頻平臺等高併發、大流量、低時延的平臺出現,對於數據庫性能提出了巨大需求和挑戰。同時,內存自己在容量、單位價格友好度上不斷提高,以及近期非容失性存儲(NVM)的發展,促進了內存數據庫的發展,這些因素使得內存數據庫在將來有着廣闊的市場和落地機會。
注:本文相關內容參照如下資料:
1. Pavlo, Andrew & Curino, Carlo & Zdonik, Stan. (2012). Skew-aware automatic database partitioning in shared-nothing, parallel OLTP systems. Proceedings of the ACM SIGMOD International Conference on Management of Data. DOI: 10.1145/2213836.2213844.
2. Kemper, Alfons & Neumann, Thomas. (2011). HyPer: A hybrid OLTP&OLAP main memory database system based on virtual memory snapshots. Proceedings - International Conference on Data Engineering. 195-206. DOI: 10.1109/ICDE.2011.5767867.
3. Faerber, Frans & Kemper, Alfons & Larson, Per-Åke & Levandoski, Justin & Neumann, Tjomas & Pavlo, Andrew. (2017). Main Memory Database Systems. Foundations and Trends in Databases. 8. 1-130. DOI: 10.1561/1900000058.
5. Diaconu, Cristian & Freedman, Craig & Ismert, Erik & Larson, Per-Åke & Mittal, Pravin & Stonecipher, Ryan & Verma, Nitin & Zwilling, Mike. (2013). Hekaton: SQL server's memory-optimized OLTP engine. 1243-1254. DOI: 10.1145/2463676.2463710.