開源數(shù)據(jù)庫 MatrixOne 的 HTAP 分布式架構演進

一、Matrixone 的早期架構與難題
MatrixOne 早期的架構與現(xiàn)在有很大區(qū)別。早期的架構可以總結(jié)為兩個詞:一個是 NewSQL,一個是 MPP。

NewSQL 是當年谷歌的幾篇論文衍生出來的分布式數(shù)據(jù)庫的一套理論體系。其中最重要的一點就是分布式架構,解決的是傳統(tǒng)數(shù)據(jù)庫的高可用以及水平擴展的難題。另外一點就是多引擎,用不同的引擎來做不同的事情。
MPP 或者叫大規(guī)模并行計算,主要的用途是通過分布式的方式將一些規(guī)模比較大的計算任務分布到不同的節(jié)點,并且在計算完成之后匯總。充分利用了分布式架構的算力資源。
我們早年的架構確實也是這個樣子。上面有一個負責分發(fā)負載均衡的 proxy。下面是 MatrixOne Server,每一個 Server 下面有自己的存儲。是一個存算一體的架構,每個節(jié)點看似是對等的。當然這里面存在著一些問題。

再來看一下組件詳解。
最上面這一層我們叫 SQL 前端,是為了兼容 MySQL 的語法協(xié)議,無論用 MySQL client,還是 JDBC 都可以直接連。
計算層是傳統(tǒng)的 SQL Parser,支持語法樹的解析,以及多方言的 SQL。
最下面就是我們自己寫的 MPP SQL 執(zhí)行器,針對基礎引擎做了一些向量化的加速,部分操作甚至用匯編語言做了改寫。當時使用了一個比較獨到的因子化加速的能力。
當時這套架構有著相當不錯的性能表現(xiàn)。

分布式框架這一層,叫做 MatrixCube,是一個開源的項目。提供了一個對于多臺機器的分布式存儲框架。具備高可用多副本,負載均衡強力制等基礎能力。當時設想是用它來為 Matrix 計算提供分布式事務的支持能力。里面有 Raft 協(xié)議,而且中間會有一個調(diào)度器叫做 Prophet。

最下面是計算層和存儲層。存儲層除了引擎接口之外,有三個引擎。
最中間的叫 AOE,不支持事務,可以往里寫數(shù)據(jù),但是對于事務、去重等基本上是不支持的。最左邊的叫 TPE,用來保存元數(shù)據(jù) catalog,它是一個非常繁忙的引擎。最右邊的叫TAE,是一個典型的列式存儲,能夠提供完整的 ACID,同時我們希望它能夠去支持比較大規(guī)模的 OLAP 能力。所以早期的一個問題是三個存儲運行,這也是后來我們?yōu)槭裁磸募軜媽用嬲麄€進行重構的一個原因。
接下來講一講原有架構存在的問題。

擴展性:
- 存算不分離,每擴展一個單位的節(jié)點,就要去擴展相應的存和算兩種資源。
 - 因為數(shù)據(jù)以3副本形式保存,只要一個節(jié)點加進去,想要真正接管計算任務的時候要先把整個存儲完成同步,預熱的時間非常長。比如我們大概有 1TB 的數(shù)據(jù),要先等著 1TB 數(shù)據(jù)的3副本完成在這個節(jié)點上的同步,才能去開始提供所有的計算負載。
 
性能:
- 因為 Raft 協(xié)議一定是有一個 leader, leader 節(jié)點容易成為熱點,很多的調(diào)度任務都從它這里走。
 - 在性能比較差的存儲下,整體性能下降會超過預期。比如我們用 SSD 做 benchmark 測試時,預計其性能是10,我們最早預想 HDD 能跑到5或者6,但是實際情況是我們在HDD 上可能跑出來的結(jié)果只有3或者4,這成為了一個性能上的瓶頸。
 - TPE、 AOE、TAE三個引擎,用途不同而且性能也不一樣,經(jīng)常會出現(xiàn)在某一個業(yè)務場景三個引擎當中的一個成為了整個性能的瓶頸。
 
成本:
- 節(jié)點規(guī)模越大,成本線性增長,負擔越來越重,到了公有云上,有的公有云提供了一個高可用的方案,就成了殼子套殼子,就不是線性而是指數(shù)級增長了??赡芪乙?個節(jié)點的時候,數(shù)據(jù)庫里存了9副本。然后在公有云又可能做了一個3層冗余那就是27層。到后面客戶所承擔的成本負擔實在是太重了。
 - 只有高配存儲才能發(fā)揮出預期,性能較差的存儲架構發(fā)揮不出來我們想要的性能特性的時候,只能通過不斷增加存儲成本的方式來滿足需求。
 
二、Matrixone 架構升級之路
面對這些問題,從2022年3月開始,我們對整個架構進行了升級。其實我們是從0.5開始的,0.1到0.4還是在探索,不斷思考不斷試錯,直到0.5的時候,我們終于意識到這個架構走不下去了。

原有架構存在三座大山。
第一座是分布式框架:
- 多副本存儲帶來存儲成本的飆升。
 - Leader 選舉,人為制造了熱點。
 
第二座是引擎眾多:
- 三個存儲引擎彼此之間的代碼復用率非常低,一個新的功能,代碼的維護量是3倍。
 - 因子化算法過于激進,除了主開能夠駕馭這個算法,其他同事參與度非常低,只能做一些輔助,加一個功能都會非常困難。
 
第三座是資源分配:
- 存算不分,做各種資源配比的時候,不同業(yè)務場景的隔離性非常差。
 - Share-nothing 架構,擴展性非常差。必須同時擴展存和算兩種資源。
 
在總結(jié)出這三大根本問題之后,我們開始做架構升級。

左側(cè)圖可以看到,一層套一層,各層之間又是強依賴的關系,所以第一步就要先把各層徹底打散,做一個更加靈活解耦的整體架構。
最后我們將存儲層單獨做了一層,可以使用各種存儲,并把所有的cache服務放在存儲層來做。
最上面是計算層。所有的 MPP 以上的無論是前端還是 Parser,或是執(zhí)行器,全都放到計算層。
中間我們開發(fā)了一個新的層,叫做事務層,可以看作 log service。與傳統(tǒng)關系數(shù)據(jù)庫中的 Redo 或者 PG 里的 WAL 日志非常類似。而我們還有一個事務節(jié)點 DN,專門用來做事務服務,因為我們是一個分布式數(shù)據(jù)庫,涉及到分布式的事務裁決,包括去重、落盤等任務。所以最終選擇開發(fā)一個單獨的事務層來專門處理這些東西。

三個存儲引擎中,我們認為留下 TAE 最合適,它能夠提供基于列存的 TP 引擎,并且可以做到完整的事務的原子性、一致性、隔離性和數(shù)據(jù)一致性,完整的 OLAP 能力,所以是最適合用于新架構的一個引擎,剩下兩個引擎的一些功能想辦法融到TAE里。所以最終我們看到的是 TAE,它是用列式的編碼來存 column family,可以在行和列之間靈活切換。這樣做的好處是,可以同時運行 TP 和 AP 的負載,因為我們經(jīng)常說行存更適合 TP,列存更適合 AP,現(xiàn)在做一個轉(zhuǎn)換之后,行和列就都能夠兼顧到了。另外,所有的表都能夠?qū)崿F(xiàn)表級別的快照事務隔離。并且支持主鍵、唯一鍵排序、外鍵索引。
前文提到,數(shù)據(jù)要保存三副本,而且每個副本在數(shù)據(jù)庫里面是以分片的形式保存,用操作系統(tǒng)自帶的 cache 來完成冷熱數(shù)據(jù)的管理。新架構有一個新的要求,就是冷熱數(shù)據(jù)盡量分離,讀寫請求分離,實現(xiàn)對存儲的精細化管理。所以最后我們選擇了 AWS 的 S3 對象存儲,私有化部署提供 S3 的協(xié)議兼容的對象存儲。熱數(shù)據(jù)保存在計算節(jié)點的緩存cache 上,所有的節(jié)點都實現(xiàn)了無狀態(tài)。并發(fā)能力可以通過多啟動幾個計算節(jié)點來線性提升。三個層級之間不再過度依賴。

分布式存儲完成之后,就是計算層。之前是因子化算法構建執(zhí)行計劃,做復雜的查詢加速,主要是提高AP性能。但表達式和節(jié)點的抽象與表述過于復雜,增加修改功能難度很大。并且多個引擎之間的代碼復用率太低,導致工作量成倍增長。所以我們開發(fā)了新的 MPP 執(zhí)行引擎,基于 DAG 來構建執(zhí)行計劃,能夠?qū)崿F(xiàn)節(jié)點內(nèi)和節(jié)點之間的自適應調(diào)度。同時能夠滿足并行和并發(fā)兩種請求,因為我們都知道并行是 AP,并發(fā)是 TP,通常大家會這么去處理。而且 SQL 能力得到了完善,具備了子查詢、窗口函數(shù)、CTE,還有內(nèi)存溢出等能力。未來的優(yōu)化空間更大,無論是我們的主開還是其他的計算組成員都可以進行優(yōu)化加新功能。

下面來看一下現(xiàn)在總體的架構。
最下面是 File Service,這是一個統(tǒng)一的文件讀寫的服務接口,它能夠從 S3 對象存儲去讀數(shù)據(jù),并且把這些數(shù)據(jù)推給日志、計算節(jié)點或者事務節(jié)點等等。而且事務節(jié)點和計算節(jié)點的日志又可以通過它去寫 S3。所有的節(jié)點只需要與 File Service 打交道,就能夠完成對存儲的讀寫。全量的存儲都可以保存在 S3 上,公有云版的 S3 成本非常低,并且可以無限伸縮。上面的事務層,有兩個 DN 節(jié)點專門負責管理日志服務和元數(shù)據(jù)。平時會在里面緩存元數(shù)據(jù),做一些事務裁決,并且會指揮 log service 的日志服務來落盤寫數(shù)據(jù)。它自己就是通過三副本的方式來保證從日志級別上的高可用,這里還有一個 Logtail,后面會詳細解釋 Logtail 和現(xiàn)在的落盤的數(shù)據(jù)之間是如何共同完成數(shù)據(jù)寫的過程的。
最上面是我們所有 Severless 的計算節(jié)點,叫 CN 節(jié)點。計算節(jié)點是完全無狀態(tài)的,每個計算節(jié)點有自己的 cache,好處是如果計算的負載比較高就可以多起幾個 CN,如果業(yè)務比較少,就可以把節(jié)點全都宕機,節(jié)省一些成本。

存儲層 TAE 完全實現(xiàn)了列存。大家可看到從數(shù)據(jù)庫到表,再到 segment,再往下是很多列,列的單位是列級別的 block。一次讀的時候,會從一個列里面去讀一些行作為一個 block,推給上面的計算節(jié)點或者日志節(jié)點。

大家可能會比較關心如何在 AP 和 TP 之間找到一個平衡點,現(xiàn)在默認建一張表,都是列。如果某些表想要強化一下行存的性能,我們建一個叫做 column family,對某一些行做一些特殊的優(yōu)化,對一些可能需要頻繁在上面做索引,或者需要更新的列,通過 column family 列的方式能夠大幅提升其TP性能,最終只需要存一個副本。我們在表上做好一些優(yōu)化之后,就可以實現(xiàn)行存和列存在各自性能上的優(yōu)勢。這個 column family 現(xiàn)在還正在開發(fā)當中,會在未來的一兩個版本迭代之后推出一個最初的版本。

計算方面,實現(xiàn)了節(jié)點之間的調(diào)度。上圖中可以看到,所有的計算節(jié)點之間都有個雙箭頭,含義是比如從最左邊的計算節(jié)點進來,需要去做一個數(shù)據(jù)的查詢,但是我發(fā)現(xiàn) cache 里面沒有想要的數(shù)據(jù),就會遍歷所有其它計算節(jié)點去找想要的數(shù)據(jù);如果找到,就直接在那個節(jié)點里面把計算任務完成,再把結(jié)果返回到最初的接受請求的節(jié)點。這樣的好處是最大限度地利用了不同節(jié)點緩存不同的熱數(shù)據(jù)。對于一些常用的查詢,會有非常大的性能提升。而且現(xiàn)在我們的計算節(jié)點,除了緩存以外,上面還有一個自己寫的pipeline,將很多的 SQL 請求拆解成為物理執(zhí)行計劃來執(zhí)行。上面是目前正在開發(fā)的一個優(yōu)化器,而最上面就是我們一直在迭代的代碼復用最多的 MySQL 級別的 Parser。能夠?qū)φZ法做一些解析,同時還能夠去做一些方言上的支持。比如對 PG 的語法和方言的支持,其實都是用 MySQL Parser 來做的。
三、Matrixone 架構升級的困難與收獲
接下來介紹我們在架構升級中遇到的困難,和解決方案。

第一個難題是如何尋找一個能夠?qū)Ω咝阅苡嬎阋嫫ヅ涞拇鎯?。兩個核心的需求:
- 一個是更少的冗余。
 - 一個是更低的使用成本。
 
經(jīng)過很多的論證之后我們發(fā)現(xiàn),AWS 的 S3 對象存儲能夠完美地匹配我們這兩個核心需求。比如我們現(xiàn)在整個單副本,在AWA基本上是一點幾個副本。多了約20%的冗余,成本比起之前的三副本大幅下降。使用上,現(xiàn)在匹配 S3 的各種接口,各種方式開發(fā)都已經(jīng)慢慢的成熟起來,還有 S3 自帶的冷熱分離,一方面將冷數(shù)據(jù)放在 S3 里面降低成本,而熱數(shù)據(jù)放到計算節(jié)點上,基本上完成了用更低的成本來實現(xiàn)冷熱數(shù)據(jù)分離。

第二個難題就是事務層的分工,分布式數(shù)據(jù)庫的分布式事務始終是一個非常大的難點,一開始我們希望我們的 CN,即計算節(jié)點只負責計算,所有的事務 ID 生成,事務裁決,還有一致性隔離性,包括數(shù)據(jù)的讀寫全都由 DN 也就是事務節(jié)點來完成。所有的沖突檢測、約束完整性也都由 DN 來完成。但是后來發(fā)現(xiàn),DN 會成為瓶頸。因為我們平時啟動的事務節(jié)點的數(shù)量遠遠少于計算節(jié)點的數(shù)量,如果事務節(jié)點起的多了,在事務裁決上多個事物節(jié)點之間同步又會出現(xiàn)問題。所以當時 DN 成為了一個瓶頸。
于是我們做的第一件事情是引入了 Logtail 的概念。我們平時寫數(shù)據(jù)時首先把數(shù)據(jù)這個操作寫到日志里,然后再落盤去寫。這樣的好處是如果寫的過程當中發(fā)生宕機,我們只需要回放日志,就可以保證數(shù)據(jù)最終還是可以落盤的?,F(xiàn)在在日志里保存數(shù)據(jù) Logtail,然后 Logtail 會定期把這部分數(shù)據(jù)寫入到 S3 對象存儲,就不需要頻繁地去寫。這樣的好處就是我們在寫的時候,不再局限于整個 DN 的寫的性能。DN 只需要攢夠一大批一起往里寫一次。當 DN 不怎么忙的時候,可以選擇在自己不怎么忙的時候把它寫進去。忙的話就全都緩存在 Logtail 里,這樣 CN 只需要負責所有的事務和事務邏輯,還有計算。DN 既保留最近一段的數(shù)據(jù),同時又負責日志服務,這樣就把 DN 在一定程度上解放了出來,使得寫入動作的上限被打破了。但是還面臨一個問題,就是事務量非常大的時候怎么樣保證寫的性能。當時我們選擇一個新的策略,如果批量寫入一大批數(shù)據(jù),比如一下寫入幾百兆的數(shù)據(jù)的時候,我們不再通過日志,而是直接往對象S3里寫。只是告訴日志服務我要通過什么樣的操作,在哪個文件里寫什么東西。而那些比較小的事務,比如只是更新一兩行數(shù)據(jù),或者插入一個新數(shù)據(jù),仍然還是走原來的從計算節(jié)點到事務節(jié)點再到對象存儲這樣一個過程。并且現(xiàn)在我們將約束完整性和沖突檢測,都放在了 CN 來做。就在一定程度上讓事務 DN 節(jié)點更加的靈活,整個的負載更輕。寫入性能可以得到明顯的提升。

現(xiàn)在還面臨一個問題,就是我們?nèi)绾螌崿F(xiàn)不同業(yè)務類型的負載工作。工作負載的隔離,按照現(xiàn)在的架構,首先計算節(jié)點先把數(shù)據(jù)推給事務節(jié)點,事務節(jié)點再通過日志寫到 S3 里。如果是 OLAP 負載,可能直接從 S3 里面去讀數(shù)據(jù)來進行計算就可以了。我們現(xiàn)在選擇的方式是用不同的 CN 節(jié)點來跑不同的東西。比如成立第一個 CN 組,只跑 TP 業(yè)務,第二個 CN 組,只跑 AP 業(yè)務,實現(xiàn)計算節(jié)點之間的隔離。當然如果系統(tǒng)比較重要,預算比較充裕,那可以選擇用機器做服務器級別的隔離,用物理機來部署不同的計算節(jié)點。如果想用低成本的機器跑,我們也提供了容器級別的隔離,容器級別可以實現(xiàn)數(shù)據(jù)和負載的完全隔離。

我們現(xiàn)在首先做了標簽化。比如圖中有三個打了 AP 的標簽,一個打了 TP 的標簽。當一個會話進來的時候,優(yōu)化器會先去判斷它是一個 AP 請求還是 TP 請求。如果是 TP 請求,就進TP的計算節(jié)點,如果是 AP 就進 AP 的計算節(jié)點。這樣的好處就是不會出現(xiàn)兩種業(yè)務上的資源公用。哪邊業(yè)務更高,我就選擇對哪一邊分配更多的資源。未來還會實現(xiàn)自動的負載均衡。比如利用優(yōu)化器,通過某一段時間的統(tǒng)計信息來判斷最近可能TP業(yè)務更多一些,那么就自動擴容一些 TP 的計算節(jié)點,AP 更多的話就自動擴容一些AP的節(jié)點。目前公測的0.8版,主要提供給用戶的是手動的通過配置標簽的方式,讓用戶把自己的不同類型的負載打到不同的計算節(jié)點上來實現(xiàn)。

我們在整個升級過程中,進行了一些技術上的復盤,總結(jié)了其中的收獲。
我們對一條 SQL 從客戶端進入服務器再到完成執(zhí)行的整個過程進行了重構。對 SQL 的執(zhí)行有了更深刻的理解,對執(zhí)行計劃、SQL 標準有了更多的認識。
之前我們是多引擎,有的引擎開發(fā)的時候不需要考慮事務的 ACID。現(xiàn)在則不同,每一條都要考慮事務的四個特性。
在開發(fā)事務層的時候,對 CN 和 DN 的適配有了更多的經(jīng)驗積累。作為分布式事務到底該怎么分工,既能夠保證完成事務的 ACID,同時又能夠保證讓系統(tǒng)的架構和系統(tǒng)的負載不會出現(xiàn)明顯的短板。我們經(jīng)過反復的驗證,最終引入 Logtail。并且 CN 和 DN 一個只負責元數(shù)據(jù),另外一個負責計算和邏輯以及去重。我們還發(fā)現(xiàn) Logtail 還有一個好處,就是可以實現(xiàn)不同的計算節(jié)點對這一部分數(shù)據(jù)的共享,不需要再從對象存儲里直接 load。
存儲層,積累了對S3對象存儲的開發(fā)經(jīng)驗。另外,我們現(xiàn)在自己的file service文件服務基本上已開發(fā)完成,很多時候使用不同類型的存儲,不再需要考慮接口要怎么寫,或是兼容性和性能,統(tǒng)一交給file service去實現(xiàn)即可。
四、總結(jié)
最后進行一下總結(jié)。

首先,實現(xiàn)了從存算一體到計算、事務、存儲三層解耦。存算一體的分布式架構有著其自身的優(yōu)勢,但是存在一些問題,比如容易制造熱點,成本較高等等。我們完成了三層的解耦之后,每一個層級可以自行進行擴縮容,不再依賴于其它層面。這種靈活解耦的架構,在不同的業(yè)務需求上,可以得到不同的最佳實踐。比如有些業(yè)務,可能需要更多的計算資源,可以直接加計算資源。
第二,實現(xiàn)了從多引擎到單一 TAE 的 HTAP 融合引擎。多引擎要維護大量代碼,并且要考慮每個引擎的特性之間如何搭配。而單引擎,無論工作量還是成本都有所降低。
第三,實現(xiàn)了因子化算法到 DAG。
第四,實現(xiàn)了從多副本存儲到對象存儲與 Logtail 的引入。存儲成本降到了原來的1/3左右。
第五,實現(xiàn)了靈活調(diào)整節(jié)點分配帶來的資源隔離。一方面存算分離,可以更加靈活地分配資源。另外通過標簽的方式,將一些請求強制隔離到不同的節(jié)點上,避免了不同業(yè)務類型對資源的征用。
如果希望進一步了解或探討,歡迎大家關注我們的企業(yè)服務號或加入微信群,會有很多干貨,以及我們最新的進展在上面發(fā)布。

最后,介紹一下我們公司現(xiàn)在在做的 Beta Program 用戶體驗計劃。這是我們?yōu)橐恍┘磳⒂泻献饕庀虻目蛻籼峁┑囊粋€專屬的計劃。參與該計劃,可以獲得最新的功能發(fā)布信息;并且可能得到匹配您的業(yè)務場景的定制;甚至可以參與到產(chǎn)品的設計當中。
目前0.8版本,處于 Beta program 階段,我們會在第三季度發(fā)布正式版。我們現(xiàn)在也提供了公有云版,處于公開招募階段。如果感興趣,也可以申請使用。目前我們使用的是serverless計劃,可以在上面跑一些 TBC 或者 TBCC 這種比較常見的 benchmark。如果您有一些基于 MySQL 開發(fā)的應用程序,可以在我們的產(chǎn)品上面進行測試。
以上就是本次分享的內(nèi)容,謝謝大家。
五、Q&A
Q1.:后期有沒有計劃接入更多的存儲引擎,比如 minio,或者是 HDFS 之類的引擎?
A1:我們現(xiàn)在私有化的場景就是以 minio 作為私有化部署的方案,當然整個對象存儲也會越來越多。在 minio 比較成熟以后,我們也會選擇更多的存儲對象來支持?,F(xiàn)階段我們的標準的私有化版本是 minio,公有云版本是 S3 或者阿里云的 OSS。
Q2:對企業(yè)級用戶是否有定制化的支持,比如對于解決方案的設計,運維的設計等等。
A2:是有的。首先我們現(xiàn)在企業(yè)付費用戶,會有一個單獨的運維工具,可以更好地進行集群管理、私有管控等等。如果需要定制化的設計開發(fā),或者一些應用程序的優(yōu)化等需求,也可以聯(lián)系我們。
Q3:為什么設計成單獨的存儲,Logtail 不放在統(tǒng)一的存儲層?
A3:我們給 Logtail 配的是比較好的存儲,直接在里面緩存的時候,往里寫的性能比S3要更好。相當于起到一個中轉(zhuǎn)作用。另外,Logtail 存在 DN 里面的話,不同的 CN 只要數(shù)據(jù)沒有落盤沒有被 truncate 之前,所有的 CN 都可以共享。比如恰好就需要 Logtail 數(shù)據(jù)的時候,直接從 Logtail 里讀不需要再走 S3,這樣在一定程度上也就實現(xiàn)了對 CN 的加速。
Q4:怎么體現(xiàn)冷熱跟讀寫分離?
A4:首先,S3 自身提供了一個冷熱數(shù)據(jù)分離的機制。它可能讀取速度會比冷數(shù)據(jù)稍微快一些。其次,我們在每一個節(jié)點上,不管是 DN 還是 CN,也放了自己的緩沖區(qū)。允許用戶把一些常用的數(shù)據(jù)放在自己的內(nèi)存里,如果除了內(nèi)存以外,在每個節(jié)點上再配一塊高性能盤,再緩存一些數(shù)據(jù),其實也可以做。實際上冷熱數(shù)據(jù)實現(xiàn)了多機分離。















 
 
 














 
 
 
 