OLTP&OLAP超融合,揭秘新一代云原生數據庫的設計之道
MatrixOne 是一款面向未來的超融合異構云原生數據庫管理系統。通過全新設計和研發的統一分布式數據庫引擎,能夠同時靈活支持 OLTP、OLAP、Streaming 等不同工作負載的數據管理和應用,用戶可以在公有云、自建數據中心和邊緣節點上無縫部署和運行。其優點包括:簡單易用,成本較低,性能優異,靈活擴展。本文將對 MatrixOne 存儲格式設計進行詳細解讀。
一、MatrixOne 存儲格式設計的初衷
1. MatrixOne 架構解讀
MatrixOne 整體架構分為三層,分別為:
計算層,設計為多節點結構,主要負責提取數據,完成計算功能。
中間層,事務處理與元數據層面、共享日志,主要功能是提供完整的日志服務以及元數據信息。
對接層,通過 File Service 對接底層各種不同的存儲,比如 S3、用戶的本地文件、NAS 等。
數據執行過程:
插入一條數據,系統接到請求后,這一條數據提交到事務處理層,之后系統將數據寫到共享日志中,再返回給用戶提示,表示操作成功。
有三點需要注意:
第一點:事務處理層(TN)剛開始寫數據會比較小,系統會先在內存里面做組織結構,等積累到足夠的量,比如幾千行或 1 萬行,再將這些數據落盤,落盤后的數據定義為一個 block。
第二點:剛開始用戶寫數據是比較小的,隨著數據量的增長,系統會把數據都 MER merge 起來,然后不停地 merge,方便用戶直接查數據,做分析。
第三點:遇到在寫入數據過程中,操作多及數據量大的時候,系統會直接將數據寫入 Database 或者寫入 S3,寫入完成后,通過發布 service,提交到系統后臺。
如上圖右邊的結構,從上至下,第一層是 DB,第二層是 table,DB 和 table 存在元數據里,具體的數據分為多個 object,一個 object 又可以分為多個 block。
2. MatrixOne 存儲格式設計的初衷
從 MatrixOne 0.5 開始,現在已經發布到 1.1,一直采用列存的結構,因為列存對 AP 容易優化,負載可以靈活適配;一個 Column 數據,和大多數數據庫的 Hive 文件排列結構比較相似,每一個列有獨立的 block,用戶可以通過這些列來查詢數據。
對于 MatrixOne 來說,需要滿足所有的業務類型,比如數據庫表的數據,table、DB、Catalog 的元數據管理、元數據落盤、再做 Replay。
業務數據在運行過程中,有很多 trace log、raw log,為了方便用戶查看 log,也要對這些數據做足夠的支撐。
遇到類似 AP 場景,有很大數據量的查詢,查詢結果后續可能還會調用,系統會把常用查詢結果存儲在本地文件或者是 S3 上,方便后續直接調用常用結果型數據。
為了滿足所有的業務數據,不同的數據類型方便去訪問,且不影響數據查詢時效,采用 File Server 中的 S3 共享存儲可以解決此問題。S3 的對象存儲,每一個對象都需要存儲指定的行,或者是指定 size,或者是指定的 block。block 有很多,存儲一個對象可能會有多種類型的 block,可能 schema 都不一樣,這些不同的 schema,需要哪些 block,需要用 block 的語言數據分析,之后更新 block 的源數據,才能得到真正的數據存放地址。這就是保證便捷高效的元數據請求訪問的原理。
用戶數據在正常運行一段時間之后,需要 scrub 任務來校驗一下數據正確性,MatrixOne 提供了一些工具,來滿足這些需求,幫助用戶做數據維護。
3. 數據結構解析
結構圖最上方 Header 會記錄對象的版本信息、數據格式、元數據的位置、校驗值位置,結構圖最下方的 footer 是 head 的鏡像。
結構圖 Header 以下是數據區,寫入的業務數據在內存打包處理后寫到這個數據區,在數據區里將寫入的數據稱為一個 block,結構圖再往下一層是整個對象的元數據區域。
首先是索引區域,比如現有的 feature 還有 Romec 都存在里面。
結構圖最后一層是整個對象的 Meta 標記,包括整個對象怎么解析,怎么去讀。
當用戶寫完數據成功后,系統會有一個 offset 指向整個 Meta data 區域。
Metadata 的結構如上圖所示。
Tombstone 就是用戶 data,用戶寫完后可能要刪某一行,因為寫入系統不能直接修改,需要刪的那一行,系統會再記錄一下,也就是軟刪。
SubMeta 會存儲 catalog、block、object 等,用戶讀數據的時候,需要這些元數據,簡稱為 checkpoint。Checkpoint 存在 SubMeta 里面,有 20 多種。為了保障性能,用戶要提取一套數據,系統只需要指針偏移,就可以去拿到對應的 Meta,記錄這些 Meta 存在的位置,就需要 SubMetaIndex。
DataMeta、TombstoneMeta、SubMeta,每一個 Meta 都有 head 去記錄整個 Meta 的信息。
Block 層結構如上圖。
每個 block 有不同的值,整個數據的 Meta 的checkpoint、columncnt,rows或者 Bloom Fitter 這些索引,都記錄在 supplement。還有一些 block 的 index 用來記錄 block 的偏移,block 的數量不是固定的,所以系統要記錄 index。否則如果先去讀有多少 block,然后通過這些 block 去算需要的 block 具體在哪里,這樣對整體性能要求很高,所以系統會記住 block index,記錄固定的起止位置來標記 block 都在哪里,從而便于讀取。
DBID、TableID、AccountID 等內容具體解釋如上圖所示。
ColumnMeta 記錄第幾列,當前列是什么 type;NDV 即 column 中有多少不同的值;NullCnt 記錄的是這一列中有多少 null 值;DataExtent 記錄的是數據位置,也會記錄壓縮前和壓縮后的數據大小;Chksum 是數據的 checksum;ZoneMap 是指這列的 MinMax 索引,然后通過 index 當前尋找的偏行地址就可以讀這個數據。
用戶寫完數據后,會返回一個記錄對象 Meta 位置的 extent,并保存到 catalog 也就是 Metadata 當中。當用戶使用系統去執行查詢數據所需要去讀數據的時候,可以通過這個 extent 去查詢整個對象的 Meta,從而通過 Meta 一層一層去看到具體的 column data。
通過一個 extent,就可以去讀取一個 IO,因為系統中一個對象里面最小的單位是 IO entry,根據不同的 IO 它可分成不同的 IO entry。比如一個 column data,column 是一列,就等于一個 IO,可以一列一列地讀,或者一個 Meta,也是一個 IO,或者 bloomfilter,也是一個 IO,可以通過一個 IO 讀出所有不同的 feed,方便過濾。
通過 IO entry,查詢到這個對象的 Meta,然后查詢真正的數據。通過 Meta 讀數據,需要先查找數據的 head,然后得到 block index 就可以很方便地去獲取到 block 的 Meta,每一列都在 block Meta 里面,包括如何方便地查詢一些 column,可以看到 column Meta,column Meta 里面就記錄著 data 的 extent,然后可以根據這些 IO 去讀數據,能夠很方便地獲得真正想要的數據。
當遇到有些數據不常用到但也需要去分析時,用戶決定讀哪些數據,對應對象的Meta 就需要不停地去查,系統會將對象的 Meta 放在常駐緩存中,解決數據讀取時效的問題。
二、性能的保證
需要訪問的數據多種多樣,包括元數據、Table data、索引、checkpoint 以及各種日志。為了保證低成本訪問,需要做 metadata cache、index cache 等等。拿到數據后,需要高效地解析。
為了實現對元數據的高效解析,我們也做了很多嘗試。比如采用 byte 的方式,放在內存里面也不需要序列化,直接去用就可以了。要去讀哪些字段時,靠指針偏移即可完成。
數據寫入的時候也很方便,set 一個 block ID,相當于通過 block ID 這個結構先做成 byte,可以直接拷貝,指針操作也很方便。
通過 Benchmark 做反序列化解析,只要拿到相應的 Meta data 的 ID,整個解析過程會比較快,是納秒級別的速度。
三、數據兼容性和使?場景
1. 數據兼容性
兼容性也是個很重要的問題,比如客戶已經用了一兩年,有些優化需要修改數據格式,那么新的代碼肯定要對以前的數據做好兼容。
我們的方案比較簡單,每個 IO Entry 會在 Header 中標記 Data Type,type 會決定如何 encode 和 decode。Version 是 type 的版本。版本切換時,注冊相應的 encode 和 decode 函數,讀到相應的 IO Entry Header,選擇對應的函數,解析數據,這樣就可以實現兼容。
以上是結構圖,IOEntryHeader 中記錄著 Type 和 Version。Type 包括對象的 Meta,column data,還有 index 等等。用戶寫數據時,系統會注冊一個 encode 函數,如果要寫入對象的 Meta,對象 Meta 是版本 2,就會標記 type 為 type1,版本是 version2,后面就是要寫入的 Meta的byte。
讀的時候,先讀到 header,因為系統去讀 IO Entry 是把所有的東西都讀上來,之后先拿前面兩個字段,通過 header 去做解析。
從開始到現在,MatrixOne 的版本只變過三次,每個版本會有簡潔的對應的函數,維護起來非常便捷。
2. 應用場景
系統有單獨的 log service,其余所有的數據結構都使用這種結構-落盤讀取,包括正常業務的讀寫 table 數據,查詢或者插入等等。
支持 checkpoint、catalog。系統隔一段時間會打一個 checkpoint,記錄增加了哪個 DB,DB 里面增加了幾個 table,如果是落盤的數據,增加哪個對象,以及具體的對象數據的里面 block。啟動的時候,系統會根據 checkpoint,replay 出來 catalog。用戶可以根據 Catalog 去查詢 DB 數據。
Metadata 掃描的主要功能是查看 scan 用戶存了多少數據,存了多少表 DB,這些 DB 具體有多少個對象文件,對象到底都有多大,壓縮前、壓縮后分別是多大等等。Metadata 掃描會用到對象的 metadata,常駐在緩存里面,效率很高。
查詢結果的 cache 的使用,在遇到比較大的查詢分析時,比如一個查詢分析要幾秒,耗費計算資源比較多,系統會將這些結果都存在 cache 里。這樣用戶下次查詢就不需要再重新去 load 那么多數據再重新計算。
四、問答環節
Q1:社區實現動態 catalog 會考慮任務修改?
A1:現在 Catalog 是通過 checkpoint 來做,問題已經得到解決,checkpoint 所有都是增量的,增量的所有的記錄都會記錄下來,增刪改查或 DDL,這些東西都記錄在 checkpoint 里面,只要 Replay 即可。Catalog 也是常駐在內存當中的,可以進行原子性的控制。
Q2:比如企業已有一個非常完善的,且有很多存量數據的集群,在這種情況下怎么使用 MatrixOne 呢?
A2:MatrixOne 對外提供了集群對接的小工具,MoDump,可以實現 load 這些數據。首先要做的是把這些數據打包 load 出來,然后再把這些數據 load 到產品里面。后期會開發一些對接各種數據庫可以實時拷貝的工具。