基於 Tile 連線 Row-Store 和 Column-Store

PingCAP發表於2017-05-30

繼 Kudu 文章之後,我司首架唐劉同學又一力作,共享給大家~

在之前的 Kudu 的文章裡面,我已經提到過,行列混存是一個非常有意思的研究方向,因為不同的儲存方式有不同的針對應用場景,但作為技術人員,折騰是天性,所以大家都在研究如何融合行存和列存,讓一個服務能儘量滿足大部分應用需求,而這也是 TiDB 在努力的方向。

在 Kudu Paper 裡面說到,Kudu 首先在 mem 裡面使用行存,但刷到硬碟之後,則使用的是列存,這當然是一個可以嘗試的方式,但我覺得應該還有更多種的解決方式,於是找到了 CMU 的 Peloton 【cmu-db/peloton】以及相關的 Paper,覺得有必要研究記錄一下。

Storage Model

很多時候,我喜歡用行存和列存,但看 Paper 的時候,發現都喜歡使用 NSM 和 DSM 來說明,這裡就簡單說明一下。

NSM

NSM 是 N-ary storage model 的簡稱,當然就是通常的行存了。NSM 主要針對 OLTP 場景,因為需要高效能的隨機寫入,NSM 的儲存方式如下:640-1.jpg-19.8kB

NSM 不適用需要讀取大量資料,並分析特定 column 的場景,因為 NSM 需要把整個 record 給讀出來,在拿到對應的 column 資料分析,資料資料量很大,整個開銷會很大。

DSM

DSM 是 decomposition storage model 的簡稱,也就是列存。DSM 主要針對 OLAP 場景,因為需要對一些特定的 column 進行快速掃描分析,DSM 的儲存方式如下: 640-2.jpg-19.9kB

DSM 當然就不適用與需要頻繁隨機更新的情況,因為任何寫入,DSM 需要將 record 分開寫入到不同的地方,寫開銷會很大。

FSM

為了解決這個問題,就有了一個 FSM flexible storage model 來融合 NSM 和 DSM,在 Peloton 裡面,它把這套系統叫做 HTAP (Hybrid Transactional/Analytical Processing), 640-3.jpg-19.5kB 不同於 NSM 按照每行存放,以及 DSM 按照每列存放,FSM 將資料分成了多個區塊,Peloton 裡面叫做 Tile,上面的圖顯示的是兩個 Tile,一個 Tile 包含了 ID,Image ID 以及 Name,而另一個 Tile 裡面則是包含了 Price 和 Data。各個 Tile 裡面資料是連續存放的。就是說,我們使用 Tile 來模擬了 DSM,在 Tile 裡面則是 NSM。

Tile-Based Architecture

Peloton 使用 tiles 來抽象了 storage 這一層,在上面的 FSM 例子我們可以看到,Tile 可以將一個 table 進行垂直和水平切分。Peloton 使用 physical tile 來處理實際的 storage,然後用另一個 logical tile 來隱藏了 physical tile 的實現,讓外面更容易使用。

Physical Tile

Physical tile 的最小儲存單位是 tile tuple,一批 tile tuple 形成了一個 physical tile。而一批 physical tile 則組成一個 tile group。一個 table 則是有多個 tile group 組成。640-4.jpg-41.2kB

在上面的例子中,table 被分成了三個 tile group (A, B, C),每個 group 都有不同的 physical tiles。譬如 group A 就是由 tile A-1 和 A-2 組成,tile A-1 包含 table 前面三個 column ID,IMAGE-ID,和 NAME,而 tile A-2 則包含了 PRICE 和 DATA。

使用 tile group,我們可以非常方便的支援 FSM。對於新插入的資料,通常是熱點資料,我們會直接放到一個 OLTP 友好的 group 中,也就是 group 裡面只有一個 tile(NSM)。當資料變冷之後,我們就將當前的 tile group 轉成更 OLAP 優化的佈局,也就是 group 裡面可能有幾個 tile 了。當 group 裡面每個 tile 都只有一個 column 的時候,這其實就變成了 DSM 了。

Logical Tile

使用 physical tile 的好處在於我們可以根據不同的情況將資料切分成不同的佈局,但是這對於查詢並不友好,因為資料在不同的 tile 裡面。為了解決這個問題,Peloton 引入了 logical tile。

Logical tile 隱藏了 physical tile 的具體實現,logical tile 的每個 column 可以指向一個或者多個 physical tiles 的 columns,每個 logical tile column 裡面儲存的是 tuple 在 physical tiles 裡面的偏移位置。 640-5.jpg-36.1kB 在上面的例子中,logical tile X 指向了兩個 physical tiles A-1 和 A-2。 X 的第一個 column 指向了 physical tile A-1 的 ATTR-1 和 ATTR-2。而第一個 column 裡面存放的 1,2,3 則是對應的 tuple 在 tile A-1 裡面的偏移。譬如 1 就對應的是 (101, 201)。

一個 logical tile column 也可以對映不同的 physical tile 的 columns。譬如上面 X 的第二個 column,就是對應的 tile A-1 的 ATTR-3 和 A-2 的 ATTR-1。當然一些 physical tile column 也可能不會有任何對映,譬如上面的 A-2 的 ATTR-2。

使用 logical tile 的好處是很明顯的,主要包括:

  • Layout Transparency:logical tile 隱藏底層底層實際的儲存實現,所以我們可以用不同的 engine 來滿足不同的場景。

  • Vectorized Processing:使用 logical tile,我們可以一次進行一批向量處理,在一些場景下能極大的提升 CPU 效能。

  • Flexible Materialization:我們可以提前或者推遲的物化。在執行 query plan tree 的時候,甚至都能夠動態去選擇一個物化策略。

  • Caching Behavior:我們可以根據不同的維度去建立不同的 tile group,放到 cache 了,用來處理後續的查詢。

Logical Tile Algebra

Peloton 提供 algebra operators 來讓外面更方便的使用。Operators 主要包括:

  • Bridge:Bridge operators 連線的 logical tile 和 physical tile。譬如我們可以使用 sequential scan 和 index scan operators 去訪問實際的資料,然後生成對應的 logical tile。而 materialize operator 則是將實際的 logical tile 轉成實際的 physical tile。

  • Metadata:Logical tile 的 metadata 存放的一些關於底層 physical tile 的資訊,以及一些 bitmap 來表明哪些 rows 在執行的時候必須被檢查。Metadata 的 operators 只會改變 logical tile 的 metadata,並不會改變底層的 physical tile 的資料。譬如 projection operator 如果發現上層的 query plan 並不需要一些 attributes 了,就可以在 logical tile 裡面移除。

  • Mutators :Mutator operators 會改變 table 的實際儲存資料。譬如 insert operator 首先會重新構建 logical tile 的 tuple,然後在插入到對應的 table 裡面,而 delete operator 則是刪除 table 裡面的資料,update operator 則是先在 logical tile 裡面刪除,在通過之前的 tuple 重新構建一個新版本的 tuple,在插入到 table。Mutators 同時也會控制 tuple 在 transaction 裡面的可見性。

  • Pipeline Breakers:當我們給一個 query plan 生成對應的 query plan tree 之後,在 tree 上層的 operators 需要等待 children 的操作完成返回了,才能繼續進行。譬如 join operator 需要處理多個 logical tiles,並且在這些 tiles 上面執行 predicate。首先,join operator 會構建一個 output logical tile,它的 schema 是根據輸入的 logical tile 來構建的。然後 join operator 會遍歷 input logical tile,如果發現滿足 predicate,就將結果放到 output logical tile,下面是 join 的一個例子: 640-6.jpg-35.1kB

Layout reorganization

雖然基於 Tile-Based Architecture 看起來很美好,但如果對於不同的 query,如果沒有好的 tile 與其對應,那麼其實根本就沒啥用。 Peloton 採用的方法是定期對每個 table 計算出最優化的 storage layout,然後在根據這個 layout 重新組織 table。

Peloton 使用一個輕量級的 monitor 來記錄每個 query 訪問的 attributes,用來確定哪一些 attributes 應該在新的 physical tile 裡面放在一起。通常 Peloton 會收集 query 裡面的 SELECT 和 WHERE 上面的 attributes。

為了減少監控帶來的開銷,monitor 會隨機的對 query 進行取樣統計,並且 monitor 還需要保證不能只關注頻繁的 transactional query,還需要關注一些不頻繁的 analytical query,所以 Peloton 會也會記錄 query 的 plan cost,並通過這些來給 analytical query 生成特定的 storage layout。

Peloton 使用增量的方式進行 data layout 的 reorganization。對於一個給定的 tile group,Peloton 首先會將 data 拷貝到一個新的 layout 上面,同時會原子地用一個新的 tile group 來替換。任何併發的 delete 或者 update 操作都只會更新metadata 資訊。新的 tile group 會有舊的 tile group metadata 的引用。如果一個 physical tile 沒有被任何 logical tile 引用,那麼 Peloton 就會將其回收。

對於一個熱點 tile group 來說,因為很可能一直在被 OLTP 的事務持續訪問,所以 Peloton 並不會對這種 tile group 做 reorganization,只會等到資料變冷之後才會進行。因為 Peloton 使用的是通用的 MVCC 模式,所以一段時間之後,老版本的資料一定會變成冷資料,那麼就可以開始 reorganization 了。Reorganization 通常就是將 layout 從 OLTP 友好的,逐漸變成 OLAP 友好的。

小結

上面僅僅是介紹 Peloton 的一些實現 FSM 的機制,這裡並沒有介紹 MVCC,transaction 這些,因為這些對於資料庫產品來說都幾乎是標配的東西,原理上面差不多。這裡僅僅是關注的是 Peloton 是如何做到行列混存的。

簡單來說,Peloton 使用 physical tile 將資料切分成了不同的單元,同時用 logical tile 來隱藏了相關的實現,並提供 algebra 讓上層更方便的操作。在後臺,統計 query 等資訊,並定期增量的對 layout 進行 reorganization。不得不說,整個設計思路還是很巧妙的。TiKV 後面也會考慮行列混存,我們一直想同時搞定 OLTP + OLAP,但究竟採用哪些方案,還需要我們慢慢研究和思考,也歡迎對這塊感興趣的同學加入。(文/唐劉)

延展閱讀 Kudu:一個融合低延遲寫入和高效能分析的儲存系統

相關文章