存儲的未來
對于某些用例,當(dāng)前存儲設(shè)計是次優(yōu)的。我們相信可以通過在”heap”操作和存儲之間添加一個抽象層來進(jìn)行改進(jìn)。當(dāng)前,存儲設(shè)計基于按行組織頁的假設(shè):heapam.h假設(shè):每個tuple只有一個元組頭和一個數(shù)據(jù)區(qū)域,即包括HeapTuple及tuple邏輯操作的代碼,比如delete、update、加鎖。類似,執(zhí)行器代碼表示TupleTableSlot抽象層的元組,該抽象層下面是HeapTuple。2015年2ndQuadrant致力于在PG中實施列式存儲項目,以下是根據(jù)實施過程中吸取的經(jīng)驗得出的計劃。
項目大綱
1) 垂直分區(qū)
2) 執(zhí)行器批處理
3) 執(zhí)行器向量化
4) 列索引
5) 表的可拔插存儲
6) 列式存儲插件
當(dāng)將向量化執(zhí)行引擎集成到列式存儲中時,才能獲得最高性能。列式存儲不用向量化當(dāng)然也可以,但是獲得的收益卻不是最大。因為CPU仍然是一次僅操作一個元素。也可以不在列式存儲上做向量化,但收益也很小,因為要使向量化,必須將基于行的數(shù)據(jù)轉(zhuǎn)換成基于列的數(shù)據(jù),這是一個緩慢的操作。
垂直分區(qū)
將表的存儲區(qū)域拆分為多個部分的能力,將列的子集放入每個存儲區(qū)域。這有幾點:
1) 跳過讀取查詢中不使用的列存儲區(qū)域
2) 不同列使用不同存儲策略(基于行或基于列;基于列的不同實現(xiàn):實驗、壓縮或非壓縮等)
3) 在具有多個存儲區(qū)域的元組上讀取元組,用于他們之間的join
挑戰(zhàn):
1) 表和存儲區(qū)域之間進(jìn)行join需要單獨處理
2) Join消除是關(guān)鍵
3) 邏輯/物理元組表示需要改變(尤其是單個atrrelid值的pg_attribute不再表示一個表的元組描述符)
批量執(zhí)行
指執(zhí)行器在單個節(jié)點一次處理多個元組的能力,而不是當(dāng)前一次僅處理一個。需要大改TupleTableSlot結(jié)構(gòu)以及節(jié)點執(zhí)行流程。這適用于9.7.
向量化執(zhí)行
執(zhí)行器在CPU級別使用SIMD指令用于函數(shù)操作的能力。這基于執(zhí)行器批量執(zhí)行。聚合操作需要提供專用代碼。
列式索引
這個項目關(guān)于列存儲的新索引訪問方法。一個明顯的輸出是深入了解哪種列存儲方法最有效。好處:索引比標(biāo)準(zhǔn)索引更加緊湊,因此掃描速度更快。
表的可拔插存儲
這個項目關(guān)于為表存儲創(chuàng)建一個類似訪問方法的接口。目前,所有存儲都通過heapam.c。這使編寫不同實現(xiàn)成為可能。PG12開始已支持表訪問方法的可拔插。Heapam.c接口假定用于有一個表和一個TID。目前TID只是關(guān)系中元組的物理位置。該項目可能需要更高元組標(biāo)識符以適應(yīng)不同的存儲實現(xiàn)。同時,當(dāng)前heapam.c實現(xiàn)返回一個包含元組的HeapTuple結(jié)構(gòu),但不同的實現(xiàn)可能有完全不同的方式來表示存儲中的元組。因為我們希望利用元組的不同表示而不是heapify他們。所以可能需要進(jìn)行更多修改,以便可以將元組傳遞給執(zhí)行程序代碼。這如何工作,還不清楚,需要更多研究。執(zhí)行器批處理可以依靠他一次對多個元組進(jìn)行操作。
Tom Lane的警示
我們需要避免DDL代碼的重寫。目前所有utility代碼都假設(shè)HeapTuples可傳遞到任何地方。對于不同存儲格式,這種假設(shè)就會失效。我們需要一些方法來避免這個項目陷入無休止的utility代碼重構(gòu)中。
解決方案似乎很簡單:不需要在system catalog中立即解決這個問題,如果我們禁止對system catalog使用不同存儲格式,我們就不需要邊界大量utility代碼。
將來有人可以重構(gòu)涉及單個catalog的代碼,以允許將可拔插(非堆)存儲用于該catalog。這可以零碎地完成,取消對一個特定catalog的限定。
列存的插件
面向列存儲的可拔插存儲引擎。
現(xiàn)有用例分析
上面介紹的是PostgreSQL的,分析其他數(shù)據(jù)庫也很有用。
MySQL/MariaDB
MySQL和MariaDB提供可拔插存儲引擎,請參考其手冊。
MongoDB
mongoDB也提供可拔插存儲,參考其手冊。