MyRocks及其使用場景分析

來自專欄資料庫內核13 人贊了文章

MyRocks是一種經過空間和寫性能優化的MySQL資料庫,為您業務的資料庫選型提供一種靠譜的選擇。本文主要介紹什麼是MyRocks,包括其功能特性,重點講解MyRocks相比InnoDB的優勢,詳細分析MyRocks適用的各種場景。

RocksDB是FaceBook基於Google開源的LevelDB實現的,使用LSM(Log-Structure Merge)樹來存儲數據。Facebook開發工程師對RocksDB進行了大量的開發,使其符合MySQL的插件式存儲引擎框架的要求,移植到了MySQL上,並稱之為MyRocks。MyRocks支持基於SQL的數據讀寫、鎖機制、MVCC、事務、主從複製等MySQL絕大部分功能特性。從使用習慣考慮,使用MyRocks還是使用MySQL/InnoDB並沒有多大區別。

經過4年多的發展,MyRocks已經成熟,開源的MySQL分支版本Percona和MariaDB已將MyRocks遷移到自己的MySQL分支中,InnoSQL作為網易的MySQL分支,目前也已支持MyRocks,具體版本為InnoSQL 5.7.20-v4,在開源的MyRocks代碼基礎上,我們對其做了功能優化增強、bugfix,並支持對其進行本地和遠程在線物理備份。下面先簡要介紹MyRocks特性,讓大家對其有個基本認識。由於MyRocks只是將InnoDB替換為RocksDB,所以MySQL Server層的邏輯並沒有多大變化,包括SQL解析和執行計劃,基於Binlog的多線程複製機制等。我們討論的焦點主要是存儲引擎層,也就是RocksDB上。

本文主要包括3個部分:首先是通過RocksDB讀寫流程來介紹其整體框架、存儲後端和功能特性;接著分多維度分析其與InnoDB的不同點,這些差別所帶來的的好處;最後分析RocksDB的這些優勢能夠用在哪些業務場景上。文章較長,大家可以調自己感興趣的部分食用。

RocksDB讀寫流程

寫流程

上圖所示為RocksDB的寫請求示意圖,一個事務的修改在提交前先寫入事務線程自身的WriteBatch中(在上圖示例中事務僅執行一個Put操作,那麼WriteBatch中僅有該Put),在提交時被寫入RocksDB位於內存中的MemTable中,MemTable本質上是一個SkipList,裡面緩存的記錄是有序的。和InnoDB一樣,事務更改的數據(WriteBatch)在提交前也會先寫Write Ahead Log(WAL),事務提交後,只需保證WAL已經持久化即可,MemTable中數據不需要寫入磁碟上的數據文件中。當MemTable大小達到閾值後(比如32MB),RocksDB會產生新的MemTable,原來的MemTable會變為只讀狀態(Immutable),不再接收新的寫入操作。Immutable MemTable會被後臺的Flush線程dump成一個sst文件。在磁碟上,RocksDB通過一個個sst文件來保存數據,一個個log文件保存WAL日誌。在磁碟上,sst文件是分層的,每層多有一到多個sst文件,文件大小基本固定,層級越大,該層的文件數量越多,意味著該層允許的總大小越大,如下圖所示。

一般情況下,從內存中dump出來的文件放在Level0,Level0層的各個sst文件其保存的記錄區間是可能重合的,比如sst1保存了1.4.6.9,sst2保存了5.6.10.20。由於採用LSM樹技術存儲數據,所以一條記錄會有多個版本,比如sst1和sst2都有記錄6,只不過sst2中的版本更新。同樣的,不同層級間也會存在相同記錄的不同版本。跟Level0不同,Level1及更高層級的sst文件,同層的sst文件相互間不會有相同的記錄。

Compaction機制

既然存在多個不同的記錄版本,那麼就需要有個機制進行版本合併,這個機制就是Compaction。

上圖就是一個Level0的Compaction,將一到多個Level0的文件跟Level1的文件進行compaction的過程。不管是將內存的MemTable dump到sst文件,還是sst文件之間的Compaction,從IO角度都是順序讀寫,這不管在SSD還是HDD上都是有利的,對於HDD可以發揮順序性能遠高於隨機性能的特點,對於SSD,可以避免隨機寫帶來的Flash介質寫放大效應。

讀流程

聊完了RocksDB寫流程,我們再來看下跟讀相關的組件。如下所示:

資料庫中的讀可分為當前讀和快照讀,所謂當前讀,就是讀取記錄的最新版本數據,而快照讀就是讀指定版本的數據。在此我們僅討論當前讀,快照讀可做類似的分析。由於採用LSM樹存儲結構,所以RocksDB的讀操作跟InnoDB有較大的不同,這是由於LSM可能存在多個記錄的版本(且不像InnoDB那樣前後版本有指針相連),且無法通過(嚴格意義上)的二分查找。因此,在RocksDB中引入Bloom Filter(布隆過濾器)來進行讀路徑優化,在RocksDB中Bloom Filter可以選擇三種不同的方式,分別是基於data block的、基於partition的和基於sst文件的,Bloom Filter可以用來判斷所需查找的key一定不在某個block/partition/sst中。RocksDB默認基於data block,其粒度最小。

接下來結合上面2張圖簡要分析RocksDB讀流程。一個Get(key=bbb)請求首先在當前MemTable中通過Bloom Filter查找,若未命中,在進一步到只讀MemTable,如果還未命中,說明該key-vaule或者在磁碟sst文件中,或者不存在。所以需要搜索每個sst文件的元數據信息,找出所有key區間包含所請求key值的sst文件。並根據層級從小到大進行查詢。對於每個sst文件,通過Bloom Filter進一步查找,若命中,則將sst文件中的data block讀入BlockCache,通過二分法在block內部進行遍歷查找,最後返回對應key或NotFound,如下圖所示。

RocksDB列族

在RocksDB中列族(Column Family)就是在邏輯上獨立的一棵LSM樹,每個列族都有自己獨立的MemTable,所有列族共享一份WAL日誌。sst文件的Compaction是以列族為粒度進行的。

默認情況下一個MyRocks實例包括2個列族,分別為用於存放系統元數據的_system_和用於存放所有用戶創建的表數據的default。當然,用戶在定義表的時候,可以通過在索引後面加備註(comment)來聲明該索引使用的列族名,下面的例子即將rdbtable的主鍵和唯一索引都放在獨立的列族cf_pk和cf_uid上。

CREATE TABLE `rdbtable` (

`id` bigint(11) NOT NULL COMMENT 主鍵,

`userId` bigint(20) NOT NULL DEFAULT 0 COMMENT 用戶ID,

PRIMARY KEY (`id`) COMMENT cf_pk,

UNIQUE KEY `uid` (`userId`) COMMENT cf_uid,

) ENGINE=ROCKSDB DEFAULT CHARSET=utf8

MyRocks主要功能特性

並發控制

MyRocks基於行鎖(row locking)實現事務並發控制,鎖信息都保存在內存中。MyRocks支持shared和exclusive行鎖,MyRocks在事務中執行更新時使用RocksDB庫進行鎖管理。可通過設置unique_check=0來屏蔽行鎖和唯一鍵檢查,這樣在批量導入數據時會提高性能,但使用時要注意數據key是否有重複,所以一般的高可用實例的從庫關閉唯一性檢查以加快Binlog回放速度。目前MyRocks還沒有實現gap鎖,存在幻讀問題(phantom read),這與標準的RR隔離級別一樣,但弱於InnoDB的RR。

事務隔離級別

MyRocks目前支持2種事務隔離級別:read committed(RC)、repeatable reads(RR)。MyRocks使用快照(snapshot)實現這兩種隔離級別,在repeatable reads中,snapshot在整個事務中持有,事務中的語句將看到一致的數據。在read committed隔離級別中,snapshot將被每個語句持有,因此SQL語句可以看見該語句執行前的對資料庫的修改。與絕大多數資料庫實現一樣,在RR隔離級別下snapshot是在事務執行第一條sql時獲取而不是事務開始時(begin/start)獲取。

與InnoDB相同,MyRocks支持基於MVCC的快照讀,快照讀無需加鎖。MVCC通過RocksDB快照實現,方法類似於InnoDB的read view。

備份與恢復

與InnoDB一樣,MyRocks支持進行在線物理備份和邏輯備份。邏輯備份通過mysqldump或mydumper等現有MySQL備份工具。物理備份則通過MyRocks實現的myrocks_hotbackup工具進行遠程備份,或者使用mariadb提供的mariabackup工具進行本地備份。

與InnoDB的比較優勢

熟悉MySQL的同學們都知道,InnoDB目前是在MySQL上佔統治地位的存儲引擎。其具備了一個關係型資料庫存儲引擎應該擁有的絕大部分特性,如強大而完整的事務機制等,MySQL官方已經將InnoDB作為MySQL不可分割的一部分,新加入的MySQL系統表均使用InnoDB而不是MyISAM。那麼為什麼Facebook不使用InnoDB而另起爐竈基於RocksDB開發MyRocks呢。顯然,RocksDB肯定有他過人之處,下面將從多個維度進行對比分析。

更小的存儲空間

先來看看InnoDB在存儲空間利用上存在的問題,我們知道InnoDB是基於B+樹的,避免不了樹節點的SMO操作,下面是個葉子節點分裂示意圖。

葉子節點Block1在插入user_id=31後觸發了節點分裂條件,被從中間拆分為2個Block,每個塊佔用原Block1約一半的空間,顯然每塊的填充率不到50%,也就是說此時有一半內碎片。

對於順序插入的場景,塊的填充率較高。但對於隨機場景,每個塊的空間利用率就急劇下降了。反映到整體上就是一個表佔用的存儲空間遠大於實際數據所需空間。

但基於LSM樹的RocksDB不會有該問題,其每次數據插入、更新和刪除都是在一個新的sst文件中追加寫入,只需在文件內部保證有序即可,不需要通過檢索找到B+樹的全局有序的某個遷移位置插入或更新,這樣就解決了B+樹節點的填充率問題,提高了空間利用率。

更進一步,RocksDB的sst文件是分層的,上下層總大小比值最大了10,在大數據量情況下,最壞也只有約10%的空間放大,這相比InnoDB是個很大的提升。

此外,如上圖所示,RocksDB在存儲時對記錄列採用前綴編碼。對每行的元數據也採取類似的處理方式。這更進一步減小的所需的存儲空間。

更高效的壓縮方式

在之前的文章中我們介紹過InnoDB基於記錄的壓縮機制,大概的實現方式是將16KB頁(Page)中每條記錄的部分欄位進行壓縮,再將壓縮後的所有記錄按照指定的頁大小進行存放。比如設置的key_block_size為8,即壓縮後按照8KB進行存放,若壓縮後頁大小為5KB,則浪費了3KB的存儲空間。InnoDB在MySQL 5.7版本引入透明頁壓縮,但仍存在上述的問題。

RocksDB在記錄壓縮時不是基於頁的,無需按key_block_size進行對齊,只需每個sst文件在壓縮後按照文件系統塊大小(一般為4KB)對齊即可,每個數MB的sst文件對齊開銷不超過4KB,遠遠小於InnoDB壓縮的對齊開銷。

綜合比較,MyRocks相比InnoDB能夠節省一半以上的存儲空間。

舊版本回收優化

在對記錄進行頻繁更新的場景下,若存在長時間的一致性快照讀,InnoDB會因為記錄舊版本無法purge導致undo空間急劇增大。但RocksDB可以有效緩解還問題。下面通過一個示例進行說明。

假設對MySQL進行一致性邏輯備份,開啟事務但還未對錶t執行select操作前對該表主鍵為1值為0的記錄進行100萬次增一操作。根據原理,本次備份需要讀到值為0的原始記錄。

對於InnoDB,由於備份事務id小於更新100萬次增一的事務id,因此,這100萬個舊版本記錄(即undo)都不會被purge,這意味著在對該記錄進行備份時,需要執行100萬次版本回溯,每次都是基於記錄上的undo指針對undo頁進行隨機讀,效率很低。

RocksDB針對InnoDB存在的舊版本記錄purge問題進行了優化,假設原始記錄的sequence number為2,該版本即為備份事務可見版本,對於比它更大的版本,在RocksDB將MemTable dump為sst文件,或對sst文件進行Compaction時會刪除中間版本,僅保留當前活躍事務可見版本和記錄最新的版本。這樣既滿足MVCC要求,又提高了快照讀效率,同時也減少了需佔用的存儲空間。

更小的寫放大

在InnoDB上,一次記錄更新操作需要先將當時記錄版本寫到undo日誌中用於進行事務回滾和MVCC(寫undo頁前也需要先寫undo的redo),再寫一份更新後記錄的redo用於進行宕機恢復,然後才能將更新操作寫到對應的數據頁上(可能會觸發B+樹節點分裂),為了避免在刷盤時宕機導致數據頁損壞,還需要再寫一份到Doublewrite磁碟緩存中。

可以看出,一次更新需要寫的東西非常多,特別的,如果是隨機更新場景,在寫數據頁和Doublewrite時,寫放大的比率是頁大小/記錄大小,非常驚人。

RocksDB寫放大與其sst文件總層級相關,最壞的寫放大情況約為(n-2)*10,其中n為總層數。顯然,相比InnoDB會好很多。

寫放大變小了,意味著有限的存儲寫能力能夠得到更高效的發揮,可以說在達到存儲IO性能瓶頸時,RocksDB能夠寫更多記錄。

另一方面,RocksDB每次數據插入、更新和刪除都是追加寫入而不是原地更新。這樣表現在存儲後端上就全都是順序寫,沒有隨機寫。對基於NAND Flash實現的SSD,在不考慮SSD內部對寫放大優化的前提下,同樣一塊SSD,在RocksDB下能夠比在InnoDB下用得更久。

更快的寫入性能

前面已經提到,InnoDB對記錄是原地更新的這意味著在隨機DML場景下對每條記錄操作都是隨機寫(即使對二級索引的先刪除再寫入新記錄的情況,也是隨機的),如下圖所示。

而RocksDB不同,將隨機寫轉換為順序寫,後臺進行記錄新舊版本合併的多線程Compaction也是批量的順序寫操作。對於批量插入場景,RocksDB也可以關閉記錄唯一性檢查來進一步加速數據導入速度。

在HDD上,這樣的優化能夠發揮機械盤順序讀寫性能遠優於隨機讀寫的特點。即使在SSD上,這樣的優化對資料庫的性能也是有幫助的。

更小的主從延遲

相比InnoDB,RocksDB還提供了更多的從庫DML優化選擇。

由於在從庫上能夠並行回放的事務肯定是沒有衝突的,也就是說不存在事務間的鎖等待關係,所以,RocksDB引入了一個優化參數rpl_skip_tx_api用來調過對記錄加鎖等保障事務隔離性的操作,加快了事務回放速度。

類似的,針對從庫上事務特點,可以跳過記錄插入操作的唯一鍵約束檢查,對於更新和刪除操作,可以跳過記錄查找操作,因為只要沒有實現上的Bug,所操作的記錄肯定是滿足事務約束的。

其他InnoDB沒有的特性

MyRocks在MySQL 5.6/5.7就實現了逆序索引,基於逆序的列族實現,顯然,逆序索引不能使用默認的default列族。基於LSM特性,MyRocks還以很低的成本實現了TTL索引,類似於HBase。相比MongoDB遍歷記錄進行批量刪除的TTL實現方式,LSM存儲下的TTL特性除了需要保存時間戳外,沒有額外的維護性能損耗代價,直接在Compaction時合併處理即可。

MyRocks適用場景

根據上面的描述,可以總結出MyRocks適用的業務場景,包括:

大數據量業務

相比InnoDB,RocksDB佔用更少的存儲空間,還具備更高的壓縮效率,非常適合大數據量的業務。下圖為Facebook公開的RocksDB與InnoDB空間佔用對比。

下圖為網上的RocksDB和InnoDB、TokuDB壓縮對比數據

結合上圖可以發現,RocksDB所需的存儲空間遠小於InnoDB,甚至比以高壓縮比著稱的TokuDB還要好一點。

在網易內部的業務測試中也得到了驗證,某個熱門業務的DDB實例由於數據量增長很快,DBA不得不頻繁進行分表擴容操作。使用MyRocks替換InnoDB發現,啟用壓縮(key_block_size=8)的165GB的InnoDB單表,在MyRocks壓縮下僅為51GB,該DDB實例一共有8個MySQL高可用實例,每個DBN包含10個InnoDB表,統計下來,替換MyRocks後實例所需存儲空間從26TB降為不到9TB。這一方面節省了三分之二(約17TB)的存儲開銷,同時也延長了DBA需要分表擴容的週期,假設DBA之前需要每個季度進行一次擴容操作,現在只需要每三個季度擴容一次即可。

寫密集型業務

MyRocks採用追加的方式記錄DML操作,將隨機寫變為順序寫,非常適合用在有批量插入和更新頻繁的業務場景。下圖為阿里雲發布的批量插入場景下的性能對比圖,相比基於InnoDB的AliSQL,MyRocks獲得了近一倍的性能提升。

在網易內部的某更新密集型業務場景下,也獲得了較好的性能表現,除了有不弱於KV存儲系統的寫入性能外,在讀性能上還佔據了一定的優勢。對比如下:

上圖是在只讀,1:1和2:1混合讀寫情況下,測試10分鐘獲取的結果,可以發現MyRocks在性能和延遲兩個方面均有較好的表現。

上圖是1:1混合讀寫和只寫場景下,寫性能和延遲情況。可以發現在20寫並發情況下,MyRocks也有上佳的表現。

緩存持久化方案

由於MyRocks具有高效的空間利用率,相比InnoDB,同樣大小的內存可緩存更多的數據量;相比pika等Redis替代方案,具有成熟的故障恢復機制和主從複製架構;此外其更低的複製延遲有利進行讀能力擴展。因此,MyRocks也是較合適的Redis緩存替代方案。

替換TokuDB

相比TokuDB,RocksDB/LevelDB擁有好不遜色的寫入性能和壓縮比,具有更好的讀性能;作為存儲引擎被MySQL、MongoDB、Kudu和TiDB等主流資料庫系統所使用,有更好的開源社區支持,更快的問題定位和BugFix可能性,更具可讀性的源碼。在TokuDB越來越不被看好的情況下,MyRocks可用於替換目前線上的TokuDB實例。

低成本低延遲從庫

MyRocks的較好的寫入性能,再配合從庫針對性參數優化,可實現比InnoDB更低的複製延遲。再加上更小的存儲空間佔用優勢,適合用於搭建特殊用途的從庫,比如防止線上數據誤刪除的延遲從庫,用於進行大數據統計和分析的從庫等。

總結

總的來說,相比InnoDB,MyRocks佔用更少的存儲空間,能夠降低存儲成本,提高熱點緩存效率;具備更小的寫放大比,能夠更高效利用存儲IO帶寬;將隨機寫變為順序寫,提高了寫入性能,延長SSD使用壽命;通過參數優化降低了主從複製延遲。因此,在數據量大、寫密集型等業務場景下非常適用。此外,作為同樣的MySQL寫和空間優化方案,MyRocks具有更好的社區生態,適合用於替換TokuDB實例。MyRocks高效的緩存利用率,成熟的故障恢復和主從複製機制,使得其也可以作為Redis的持久化方案。

參考資料:

1、RocksDB實現分析 ks.netease.com/blog?

2、RocksDB wiki github.com/facebook/roc

3、Facebook、Percona、Alibaba公開的RocksDB相關文檔和PPT


推薦閱讀:
相關文章