列式數據庫專欄——和磁盤趨勢一樣,CPU 趨勢將有利於傾向採用列式存儲

 
一個由多名專家撰稿的關於數據庫技術和創新的博客。
和磁盤趨勢一樣,CPU 趨勢將有利於傾向採用列式存儲

我們討論了海量存儲技術趨勢傾向於使用主要爲決策支持查詢的數據庫系統中採用的列式存儲體系結構。在本貼中,Sam Madden 和我思考爲什麼 CPU 趨勢會對數據庫設計的選擇產生類似的影響。


行式存儲中的片式頁會降低 CPU 性能

大多數行式存儲體系結構使用“片式頁”概念,將記錄保存在磁盤頁上。除了一些控制信息之外,可將片式頁大概分爲兩個主要區域,如下圖所示。

 

第一個區域一般從該頁的前面開始,它用來將記錄在頁面上緊密排列地存儲。第二個區域,也叫作“分片數組”,從該頁的底部開始,向前面伸展。該頁上的每個記錄在該數組中都有一個條目。每個條目包括相應記錄從該頁開始處的偏移量和記錄的長度。

利用這個佈局方案,通過一個由文件或卷標識符、文件/卷內頁號和分片號的組合來標記記錄。這種通常稱爲 TID(即元組 ID)或 RID(即記錄 ID)的地址提供了一定程度的間接性,這在以後變得非常重要。具體來說可以將記錄放在數據頁的不同位置上但保留相同的分片 ID。這就意味着可以進行插入、刪除和更新操作,而不會影響包含指向該頁中的記錄的條目的索引。

現在考慮一下,當對記錄文件應用選擇謂詞時會發生什麼情況。會用到兩個迭代器。第一個迭代器在文件的所有頁中循環。對於由該迭代器返回的每一頁,封裝了頁面邏輯的第二個迭代器將進行初始化,然後對頁中的每一個記錄都調用一次。每次調用時,它就會前進到分片數組的“下一個”位置,以獲取該頁上相關記錄的偏移量。
一旦獲取了記錄開始的偏移量,數據庫系統的記錄處理邏輯將計算正在應用謂詞的屬性偏移量。然後將這兩個偏移量添加到內存中該頁開始的地址裏,以獲得屬性地址。只要計算出這個地址,謂詞就得到了應用。

這一過程中,至少對每個記錄執行了兩次內存訪問一次是訪問分片數組條目,一次是訪問要執行選擇謂詞的屬性。對於當今的 CPU 而言,決定應用程序能運行多快的一個關鍵因素是內存在L2(數據)高速緩存中訪問結果未命中(即結果尚不在內存中)的頻率。
這類未命中通常會造成當今的 CPU 暫停運行 100 個週期(等待數據從主存傳輸到高速緩存中),在這種情況下,數據庫系統會顯示其運行速度僅爲百兆赫而非 CPU 的千兆赫速度。

當今 CUP 的高速緩存行通常爲 64 字節 (Intel Core 2 Duo) 128 字節Intel Xeon 提供 L2 L3 高速緩存。假設計算機的高速緩存行爲 64 字節。假設高速緩存器是“冷的”-即,我們最近沒有訪問過當前頁分片數組條目佔4 字節(偏移量和長度每個佔 2 字節)將造成每 16 次訪問分片數組都會出現一次 L2 高速緩存未命中,導致將其它 64 字節內存讀入該高速緩存中。另一方面,每個屬性訪問將造成一次 L2 高速緩存未命中當然,除非記錄的長度等於或少於 32 字節,在這種情況下,每個 L2 高速緩存未命中將把兩條記錄從內存拖到高速緩存中。如果空值是採用一個在記錄前的比特數組來實現的,情況可能會更遭,因爲可能需要額外內存訪問來訪問比特數組,以確認屬性是否爲空。這種訪問將造成 L2 數據高速緩存未命中。如果屬性爲非空,將引起另一個未命中,除非屬性和空比特數組都在同一 64 字節高速緩存行裏。

爲更具體說明這個問題,那就假設頁大小爲 32 KB,記錄爲 200 字節。每頁將保留大約 160 條記錄。掃描每頁將引起至少 170 L2 高速緩存未命中(10 次是掃描分片數組,160 次是掃描記錄本身)來處理這 160 條記錄。現代的處理器採用了“預取”功能,其能檢測出高速緩存行內連續的內存訪問,並在引用該緩存行之前自動開始加載下一個高速緩存行。但是,這僅可能在訪問分片數組(按順序遍歷)時纔有幫助,而以下從分片數組到記錄的指針引起了對整頁的隨機訪問,這是預取機制所無法預測的。


列式存儲避免了片式頁中的 CPU 停滯

現在考慮一下,用列式存儲法將表中的每一列都單獨儲存在一個文件裏會發生什麼情況。現在爲了簡化討論,假設沒有使用壓縮,而且謂詞正在應用的屬性爲一個 4 字節整數。我們假設列式存儲通過將它們寫入一個單獨的寫優化存儲中來處理更新、插入和刪除操作,並定期將它們歸併到主要的數據存儲。這個歸併過程改寫了主存儲以及任何關聯索引。這意味着,主數據存儲中的屬性值將逐個“緊密排列”,而且主數據存儲裏也不需要獨立於頁位置的記錄標識符。因此,列式存儲不需要主存儲裏的分片數組。

考慮一下掃描每頁上的值以應用選擇謂詞的過程。利用 64 字節高速緩存行和 4 字節整數屬性值 16 次訪問屬性將引起一次 L2 數據高速緩存未命中。與行存儲引起的 170 次未命中相比,處理 160 個屬性將只引起 10 次未命中再次假設,沒有高速緩存預取。預取可能對列存儲比對行存儲更加有用,因爲現在都是按順序訪問數據值,而不是根據以下片式頁數組的指針產生的隨機訪問模式來進行訪問。

實際上,高速緩存行越大,性能差異就越大。例如,利用 128 字節高速緩存行,每處理 160 條記錄,行存儲將出現 165 次未命中,而列存儲將只出現 5 次未命中。

正如我們在上一個貼子所說,列式存儲具有高度的可壓縮性。假設對一列整數屬性使用 RLE 壓縮而且對列的壓縮產生了壓縮因子 10。每個壓縮的列條目將包括一個三維值(值、位置、計數),佔 10 個字節。利用 64 字節高速緩存行,每次未命中會將6 RLE 三維值引入高速緩存行。因爲 160 條記錄將平均壓縮爲 16 RLE 三維值,所以處理 160 條記錄將僅引起 3 次高速緩存未命中。與沒有進行壓縮的列存儲相比,未命中減少了 3 倍以上,而與沒有進行壓縮的行存儲相比,減少了 30 倍以上。


PAX
是行式存儲的一個選項但還沒得到利用

爲了符合廣告真實性”,有另一種方法來在稱爲 PAX (Partition Attributes Across) 的磁盤頁上分配行記錄存儲。由 Ailamaki DeWitt 共同開發的 PAX 將每個數據頁分割成爲 n 個更小的頁,每頁用來存儲記錄的 n 個屬性(請閱讀 2001 VLDB 會議發表的論文Weaving Relations for Cache Performance(爲緩存性能編織關係)》,請單擊此處)。由於每個記錄都已插入到每頁中,所以其每個屬性都將保存在相應的小頁面中。整體的效果就是一個由 n 列組成的數據頁。而這樣的設計從本質上有着與列式存儲一樣的高速緩存行爲,並有與傳統方式組織的使用片式頁的行存儲一樣的 I/O 性能。據我們所知,還沒有一個數據庫供應商在其產品中採用 PAX


比較行式存儲和列式存儲的影響

比較行式存儲與列式存儲的體系結構將意味着什麼類型的性能差異呢?假設對於每個屬性的內存訪問,我們花費 500 CPU 週期來“計算”(這與近期數據庫論文的估算相一致,比如上文提到的 PAX 論文)。如果內存時延爲 50 ns,那麼一個 L2 高速緩存未命中將消耗 3 GHz 處理器大約 300 週期。在 Core 2 Duo,訪問 L2 高速緩存中的記錄需要 14 個週期。在非面向行的數據庫中,利用 64 字節緩存行、200 字節的空頭記錄、以及 4 字節分片數組,內存延遲按每個記錄增加大約 660 週期(共 1160 週期)即,每16 L2中的 2 + 1 次未命中,加上 3 L2 訪問。相反,在列存儲中,內存延時按每條記錄只增加 45 週期(共 545週期)16 L2中的 1 次未命中加上 2 L2 訪問。因此,如果數據庫有 CPU 限制這很正常,因爲許多生產數據庫按每個 CPU 都有足夠數量的磁盤來確保這一點列式存儲將有比行存儲高出兩倍以上的記錄吞吐量。

總之,列式存儲除了比行式存儲的壓縮能力更強並能節省大量的 I/O之外,更重要的是,它與現代 CPU 的設計有更好的兼容性,並能大大地提高受 CPU 約束的查詢的 CPU 吞吐量。因此,隨着處理器性能與內存延時的差距變得越來越大,列式存儲將繼續比行式存儲表現得更好。此外,我們認爲列式存儲還能夠更好地利用將來帶有數十至數百個內核的 CPU,不過我們將留在未來的貼子中討論這方面的內容。

發佈了13 篇原創文章 · 獲贊 0 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章