內存數據庫解析與主流產品對比(一)

8月26日,星環邀請來自華東師範大學軟件工程學院的博士生導師宮學慶教授帶來《數據庫前沿技術系列講座》,分享數據庫業內前沿發展和研究熱點。現將宮學慶教授的培訓第一講內容:內存數據庫的技術發展分享給大家。

— 基於磁盤的數據庫管理系統 —

傳統的數據庫管理系統(DBMS)通常是採用基於磁盤的設計,原因在於早期數據庫管理系統設計時受到了硬件資源如單CPU、單核、可用內存小等條件的限制,把整個數據庫放到內存裏是不現實的,只能放在磁盤上。由於磁盤是一個非常慢的存儲設備(相對於CPU的速度),因此學術界和工業界發展出的數據庫管理系統在架構上都必須適應當時的硬件條件,沿用至今的Oracle和MySQL等數據庫管理系統仍然採用的是這種架構設計。

伴隨着技術的發展,內存已經越來越便宜,容量也越來越大。單臺計算機的內存可以配置到幾百GB甚至TB級別。對於一個數據庫應用來說,這樣的內存配置已經足夠將所有的業務數據加載到內存中進行使用。雖然大數據處理的數據量可能是PB級別的,但那些數據一般是非結構化的數據。通常來講,結構化數據的規模並不會特別大,例如一個銀行10年到20年的交易數據加在一起可能只有幾十TB。這樣規模的結構化數據如果放在基於磁盤的DBMS中,在面對大規模SQL查詢和交易處理時,受限於磁盤的I/O性能,很多時候數據庫系統會成爲整個應用系統的性能瓶頸。

如果我們爲數據庫服務器配置足夠大的內存,是否可以仍然採用原來的架構,通過把所有的結構化數據加載到內存緩衝區中,就可以解決數據庫系統的性能問題呢?這種方式雖然能夠在一定程度上提高數據庫系統的性能,但在日誌機制和更新數據落盤等方面仍然受限於磁盤的讀寫速度,遠沒有發揮出大內存系統的優勢。內存數據庫管理系統和傳統基於磁盤的數據庫管理系統在架構設計和內存使用方式上還是有着明顯的區別。

— 緩衝區管理方式 —

在傳統的數據庫管理系統中,數據的主存儲介質是磁盤。例如,邏輯上的一張表通常會被映射到磁盤上的一個文件,文件是以數據塊(Data Block,也稱作Page)的形式存儲在磁盤上。對於結構化數據來說,一條記錄會被保存在磁盤上的某個數據塊中,可以用數據塊ID和Offset/偏移量來表示該條記錄的具體位置。這種形式的數據塊也被稱作 Slotted Page,顧名思義是把數據塊劃分成很多槽位,然後一個Record放在某一個槽位上。在對某條記錄進行處理時,可以通過代表該記錄地址的Page ID + Offset從磁盤上獲取該記錄;隨後系統會把存儲有該條記錄的數據塊從磁盤讀到緩衝區(Buffer Pool分爲多個Frame,每個Frame可以保存一個磁盤塊),再從緩衝區將該條記錄讀到線程或事務的工作區進行處理;處理結束後將更新的記錄寫回緩衝區中的數據塊,再由數據庫管理系統將修改過的數據塊寫回到磁盤上。

基於磁盤的數據庫管理系統中的數據訪問示例

在基於磁盤的數據庫管理系統中,處理查詢時通常會把整個索引加載到內存,而B+樹索引中一個索引節點的大小通常是一個數據塊。每個被索引的key值在索引葉子節點中都有對應的索引項,索引項中包含該key值所對應記錄的存儲位置(Page ID + Offset);當一個數據塊被加載到內存中的緩衝區時,DBMS通過Page Table結構來維護Page ID + Offset的地址與內存緩衝區地址的轉換。在訪問數據時,先在Page Table中查找是否存在對應的Page ID + Offset,如果沒有則說明這條記錄仍然在磁盤上,需要先把磁盤上數據塊的讀進緩衝區,然後再在Page Table中維護好地址映射關係。具體的實現過程是,DBMS首先會在緩衝區中尋找可用的Frame,如果沒有就根據緩衝區替換算法選取髒頁(Dirty Page)替換出去;假如選中了某個髒頁進行替換,則需要對該位置加Latch鎖來保證在替換過程中該位置不會被其他事務訪問(Latch後面會介紹)。在髒頁寫回磁盤後,系統就可以把目標數據塊讀入到緩衝區中的該位置,再將其在緩衝區中的地址寫到Page Table,維護好地址映射關係;在這些操作完成後再將Frame上的Latch鎖釋放。

傳統DBMS中的內存地址映射

對於傳統基於磁盤的DBMS而言,即使內存緩衝區足夠大,可以將所有數據加載到內存中,但訪問數據過程中的地址映射和轉換依然存在,只是省掉了將數據塊從磁盤加載到內存的開銷。即使數據已經全部被加載到內存,基於磁盤的DBMS性能上與內存數據庫相比還是有很大差距,這是其中一個重要的原因。

總結來看,基於磁盤的DBMS和內存數據庫在實現技術上一個重要區別是:在訪問數據時,基於磁盤的DBMS需要通過地址映射將數據在磁盤上的地址轉換成在內存中地址,而內存數據庫在設計上則是直接使用數據在內存中的地址。

— 事務ACID屬性保證 —

在數據庫管理系統中,需要保證併發訪問場景下事務的ACID屬性,即事務的原子性、一致性、隔離性和持久性。事務的ACID屬性主要靠數據庫管理系統中的兩個機制實現,一個是併發控制,另一個是Logging/Recovery機制。

  • 併發控制

傳統基於磁盤的DBMS大部分是採用基於鎖(Lock)的悲觀併發控制,即事務在訪問數據時先加鎖,用完後再進行解鎖,其他事務在訪問數據時如果存在衝突則需要等待擁有鎖的事務釋放鎖。傳統DBMS一般會在內存中維護一個單獨數據結構——Lock Table來存放所有的鎖,由Lock Manager模塊進行統一管理,這樣在內存中鎖和緩衝區中的數據是分開存放和管理的。事務在訪問數據時先向Lock Manager申請數據所對應的鎖,然後再訪問數據;執行結束後通過Lock Manager把鎖釋放,Lock Manager能夠保證所有事務申請和釋放鎖都是遵循嚴格的兩階段封鎖協議(strict 2 phase locking protocol)。同時,併發控制機制所帶來的開銷與用戶的實際業務處理沒有直接關係,是用於保證事務一致性和隔離性的額外開銷。

內存數據庫在訪問數據時也需要加鎖,但和基於磁盤的DBMS不同,鎖和數據在內存中是存放在一起的,通常是將鎖信息保存在數據記錄Header中。爲什麼基於磁盤的DBMS要單獨將鎖信息放在Lock Table中,而內存數據庫就可以把鎖信息和數據存放在一起呢?因爲在基於磁盤的DBMS中,數據塊是有可能被系統從內存緩衝區中替換到磁盤上,如果鎖信息和數據放在一起,一旦數據塊被替換出去,Lock Manager和所有事務都無法獲得關於數據的鎖信息。所以說對於傳統基於磁盤的DBMS來講,鎖要單獨維護在內存中,且需要始終保持在內存中,不能被替換出去。而對於內存數據庫來說,不存在這樣的場景。

實際上,數據庫管理系統中有兩種鎖機制,分別被稱爲Lock和Latch,目的都是爲了保護數據的一致性不被併發訪問所破壞。Lock機制是對數據庫邏輯內容的保護,一般來說擁有持續時間長,通常是事務執行的整個過程;並且Lock機制要支持事務的回滾以撤銷事務對數據修改。而Latch機制是爲了保證內存中特定的數據結構不會因爲併發訪問而導致錯誤,比如在多線程編程時有一個共享隊列發生插入、刪除等操作時,需要Latch保證操作過程中的隊列不受其他線程的干擾。Latch的保持時長與操作有關,本次操作做完就結束,同時也不需要支持對數據修改的回滾。

所以傳統DBMS如果要對緩衝區中的一個Page做操作則需要加Latch;如果是修改數據庫的內容則需要加Lock,單獨放在Lock Table維護和管理。下圖是對Lock和Latch的一個簡單對比。

Lock和Latch特徵對比
  • Logging 和 Recovery

數據庫管理系統中,Logging和Recovery機制是日誌來保證事務的原子性和持久性的方式。原子性意味着一個事務中的所有操作必須同時成功或者撤銷,在執行一半做不下去時,可以按照日誌進行回滾;持久性意味着數據如果丟失,可以根據日誌來進行恢復。

在傳統DBMS的Logging和Recovery中,最重要的概念是WAL(Write-Ahead Log)——預寫式日誌。WAL是指系統中所有更新操作都有對應的日誌,而在日誌沒有落盤前,對數據的修改不允許落盤。系統中每條日誌都有一個LSN號(Log Sequence Number),所有的LSN號單調遞增,日誌落盤的過程是向磁盤的連續寫(順序寫)。但如果系統嚴格按照一條日誌對應一條操作,日誌落盤後馬上將操作對數據的更新結果落盤,那麼系統性能會受到很大影響。所以,大多數的DBMS會採用 Steal + No Force 的緩衝區管理策略。Steal是指DBMS可以將未提交事務的更新刷到磁盤,不必等事務提交時再把更新刷到磁盤,提高了系統刷盤的靈活性和性能;如果在事務未提交時發生crash,由於更新可能已經寫到磁盤,這時就需要通過對日誌的undo操作進行回滾。No Force是指在事務已經提交後,對數據的更新可以依然存放在內存緩衝區中不寫入磁盤,在合併其他事務的更新後再一次性寫入磁盤,爲系統提供優化空間。但No Force可能帶來的風險是:如果事務已經成功提交但更新沒有寫到磁盤,此時出現crash,則仍然在內存中的數據更新就會丟失,需要根據已經寫到磁盤的日誌(事務成功提交的前提是其所有日誌都必須已經落盤)進行redo操作。

有了WAL和Steal + No Force機制後,就可以給基於磁盤的DBMS提供最大的靈活性,來優化磁盤I/O。但對於內存數據庫而言,所有的數據放在內存裏,是否還需要這個機制呢?可以明確的一點是,內存數據庫還是需要Logging的,但和基於磁盤的DBMS有所區別,在日誌中只記載redo操作所需的信息,不記載undo所需的信息。 大家可以想一下這是爲什麼? 另一方面,內存數據庫在Logging過程中不記錄關於索引的更新,只記錄對於基礎表的更新,那Logging過程中所需寫盤的內容就少了很多。而在內存數據庫出現故障需要恢復時,首先從磁盤上保存的檢查點(Check Point)數據和日誌中恢復基礎表,然後在內存中重新構造索引。

— 面向磁盤的DBMS性能開銷 —

2008年,SIGMOD的一篇論文對面向磁盤的數據庫性能開銷做了分析,把整個數據庫系統的開銷做了劃分。分析發現:假設一次業務處理的總開銷是100%,實際上只有7%不到的資源是在真正處理業務邏輯;34%用於緩衝區管理如緩衝區的加載替換、地址轉化等;14%處理Latching;16%處理Locking;然後12%處理Logging;最後16%用於對B樹索引的處理。也就是說,機器資源跑滿負荷以後,真正用於處理業務邏輯的只有7%。

磁盤數據庫系統性能開銷

那麼是否可以將開銷大的部分去掉,來提高業務邏輯的資源佔比呢?如果數據庫是單用戶的,沒有併發競爭衝突,那麼可以省去Locking和Latching等方面的開銷。歷史上也有一些單線程的解決方案,例如將數據庫分成多個Partition,每個Partition由一個線程處理等。但這樣的方案具有明顯缺點:每個Partition是串行處理,假如有一個長的事務在執行,串行處理將導致後續事務全部被阻塞,直到該事務結束。而且面向磁盤的系統在進行大規模事務處理時瓶頸是磁盤I/O,如果單線程執行,在從磁盤讀取數據時CPU將處於空閒狀態。但對於內存數據庫來說,所有數據存儲在內存,磁盤I/O不是系統主要瓶頸,因此使用的技術與之前有了很大的差別。當然技術在發展過程中也經歷了各種各樣的嘗試,某些技術的發展不適合於現實背景,慢慢就被人忘記了。

可以看到,基於磁盤的數據庫管理系統做了很多額外的管理工作,這些工作雖然不處理業務邏輯,但在保證業務邏輯正確性上不可或缺。對於內存數據庫而言,面臨的問題是應該做哪些優化來得到最優的性能。和基於磁盤的系統相比,內存數據庫主存儲是內存,但依然需要磁盤來做Check Point和Logging,故障時要靠磁盤上的檢查點數據和日誌來恢復整個內存數據庫。

—內存數據庫技術歷史發展 —

內存數據庫的發展大致可以分成三個階段:1984年到1994年的10年;1994年到2005年的10年;2005年以後到現在。第一個階段出現了內存相關的處理技術;第二階段出現了一些內存數據庫系統;第三個階段就是我們現在面臨的場景。

  • 1984年 - 1994年

在1984年到1994年間,學術界針對內存數據管理提出了很多假設,比如內存緩衝區可以放進全部數據,可以採用組提交和快速提交優化技術等。同時也提出了面向內存的數據訪問方法,不再像基於磁盤的DBMS一樣採用Page ID + Offset方式進行訪問,而是在所有數據結構中都直接採用內存地址。還有面向內存的T-tree索引結構以及對系統按功能分成多個處理引擎,有的專門做事務處理,有的專門做恢復,相當於有兩個核,一個專門負責事務處理,另一個負責日誌處理。此外還有和Partition相關的主存數據庫,把數據庫分成很多個Partition,每個Partition對應一個核(或節點),進程間沒有競爭。可以看到,這個期間的數據庫技術發展已經在考慮如果數據全部放在內存,可以採用哪些技術。但受限於當時的硬件條件,這些技術並沒有得到大規模應用。

  • 1994年 - 2005年

1994年到2005年間出現了一些商業內存數據庫系統,比如貝爾實驗室研發的Dali、Oracle Times Ten的前身Smallbase等。同時,也出現了一些面向多核的優化系統如P*-Time(現在是SAP-HANA事務處理引擎)。當時也有一些Lock-free的實現技術被應用於內存數據庫系統,即無鎖的編程技術和數據結構。

  • 前兩階段小結

前兩個階段的技術大致可以分成這樣幾類:

1、解決Buffer Pool的In-Direction訪問: 把間接訪問替換掉,換成直接的內存地址訪問;索引的葉子節點不再放Page ID 和Offset,而直接是內存地址。

2、Data Partition: 切分數據,不做併發訪問控制的一類技術。

3、Lock-free和Cache-Conscious: 相較於面向磁盤的數據庫管理系統把一個索引節點存儲在一個數據塊中,內存數據庫中一個索引節點是一個或幾個Cache Line的長度。

4、粗粒度的鎖: 一次鎖一張表或一個Partition,而不是一條記錄,但這種技術現在使用較少,因爲多核場景訪問競爭激烈,粗粒度鎖可能導致併發程度降低。(目前使用較少)

5、Functional Partition: 把系統按照功能進行切分,每一個線程負責特定的功能等。(目前使用較少)

DBMS歷史技術總結

— 數據庫系統的現代化發展 —

在現在的環境中,硬件條件基本有三個特點:1. 內存大而便宜;2. 多核CPU(從主頻提升轉變到內核數的提升);3. Multi-Socket即多核多CPU,意味着處理的併發程度可以越來越高。這些都是數據庫系統研發在當下所面臨的情況。

現代硬件環境

對於內存數據庫而言,CPU和磁盤I/O不再是主要瓶頸,因此優化技術目前主要從以下角度來考慮:

  • 去掉傳統的緩衝區機制 :傳統的緩衝區機制在內存數據庫中並不適用,鎖和數據不需要再分兩個地方存儲,但仍然需要併發控制,需要採用與傳統基於鎖的悲觀併發控制不同的併發控制策略。
  • 儘量減少運行時開銷: 磁盤I/O不再是瓶頸,新的瓶頸在於計算性能和功能調用等方面,需要提高運行時性能。
  • 採用編譯執行方式: 傳統數據庫多采用火山模型執行引擎,每一個Operator都被實現爲一個迭代器,提供三個接口:Initial、Get-Next、Closed,從上往下依次調用。這種執行引擎的調用開銷在基於磁盤的數據庫管理系統中不佔主要比重(磁盤I/O是最主要瓶頸),但在內存數據庫裏可能會構成瓶頸。假設要讀取100萬條記錄,就需要調用100萬次,性能會變得難以忍受,這就是內存數據庫中大量採用編譯執行方式的原因。直接調用編譯後的機器代碼,不再需要運行時的解釋和指針調用,性能會有效提升。
  • 可擴展的高性能索引構建: 雖然內存數據庫不從磁盤讀數據,但日誌依然要寫進磁盤,需要考慮日誌寫速度跟不上的問題。可以減少寫日誌的內容,例如把undo信息去掉,只寫redo信息;只寫數據但不寫索引更新。如果數據庫系統崩潰,從磁盤上加載數據後,可以採用併發的方式重新建立索引。只要基礎表在,索引就可以重建,在內存中重建索引的速度也比較快。

— 本文小結 —

本篇主要介紹了基於磁盤的數據庫管理系統與內存數據庫管理系統在幾個實現方面存在的主要異同,以及內存數據庫從1984年開始到現在的技術發展。後面會繼續分享關於內存數據庫技術的發展,從數據組織、索引、併發控制、編譯查詢和持久化角度出發,介紹並對比幾款主流內存數據庫產品的實現技術。

:本文部分材料來自於:

  1. VLDB 2016會議上的現代主存數據庫系統教程(Modern Main-Memory Database Systems Tutorial)
  2. CMU(卡耐基梅隆大學)Andy Pavlo教授的高級數據庫系統(Advanced Database Systems)課程

本文轉載自公衆號大數據開放實驗室(ID:gh_2362567b4e0e)。

原文鏈接

在 FIFA 20 將技能相似球員進行分組(2):層次聚類">內存數據庫解析與主流產品對比(一)

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