C-Store: A Column-oriented DBMS(1)

 1.介紹

大部分的DBMS實現都採用面向記錄(record-oriented)的存儲方式,即把一條記錄的所有屬性(列)存儲在一起。在這種行存儲(row store)體系裏,單次磁盤寫操作就能把一條記錄的所有列刷到磁盤裏。這樣能優化寫操作,我們稱這種存儲體系爲寫優化(write-optimized)系統。

與之相反的是,面向ad-hoc大數據量查詢的系統應該是讀優化(read-optimized)的。例如數據倉庫,常常是短時間內寫入大批新數據,然後長時間的進行ad-hoc查詢操作。其他以讀操作爲主的應用系統包括customer relationship management (CRM)系統,electronic library card catalogs和其他ad-hoc查詢系統。在這些系統裏,採用列存儲體系,把同一列(或屬性)的數據連續存儲在一起,會更有效率。現有的數據倉庫產品如Sybase IQ [FREN95, SYBA04]Addamark[ADDA04]KDB [KDB04]都能證明這個觀點。在本文裏,我們討論一種叫做C-Store的列存儲系統的設計,以及它的一些新穎特性。

在列存儲體系裏,DBMS只需要讀取指定列的數據,避免了把不相干的數據帶到內存裏。在數據倉庫裏,查詢一般是針對大量數據元素的集合型操作,列存儲就有很大的性能優勢。不過,還是需要仔細看看讀優化系統和寫優化系統之間的一些本質區別。

當前的關係型DBMS都把屬性(列)填充到字節或雙字節的邊界,以本地格式存儲數據。原因是他們認爲在處理時進行數據的移位操作太耗時了。但是,CPU越來越快,而磁盤速度卻沒怎麼增長。所以,用CPU時間交換磁盤帶寬是有意義的,特別是在以讀爲主的環境裏。

採用列存儲,一般有2種途徑用CPU時間交換磁盤帶寬。第一種方法是採用高效的編碼技術。比如存儲一個客戶的住址,可以用6bits來編碼所有的US州名,如果採用州名縮寫,則需要16bits;如果用全稱則更多。第二種方法是濃縮存儲的數據。比如把N個數據,每個K bits,直接打包成N*K bits。列存儲裏,編碼和濃縮技術比在行存儲裏更有效,這一點[FREN95]已經指出來了。當然DBMS還是需要一些未壓縮的數據副本(即cache),儘量避免解壓操作。

商用關係型DBMS一般單獨存儲一行數據的所有列,用B樹來索引。索引可以是主的(primary),記錄會盡量按這個索引的順序、接近的存儲;或副的(secondary),記錄不需要任何特殊的順序。這些索引在OLTP的寫優化系統裏是高效的,但是在讀優化系統裏則不是。後者使用其他的數據結構會更好,比如bit map indexes [ONEI97]cross table indexes [ORAC04],和and materialized views[CERI91]。在讀優化的DBMS裏,只能使用讀優化的數據結構,而完全不進行寫優化。

所以,C-Store在物理上存儲列的集合,每個集合都按照一些屬性來排序。以同一個屬性進行排序的列,組成“projections”(不知道翻譯成什麼,就用“列族”吧)。同一個列可能在多個列族裏,按照不同的屬性進行了排序。我們用強力的壓縮技術,在得到多種排序方式的同時,避免存儲空間的激增。多種排序方式還爲我們打開了優化的大門。

顯然,大計算量和大存儲量應用系統最廉價的硬件和體系結構,就是普通PC機集羣。所有,任何新的DBMS體系都要支持網格環境,支持擁有自己磁盤和內存的G nodes。我們建議在“shared nothing”體系下對磁盤數據進行水平分區。未來的網格計算機集羣可能有成千上萬個節點,所以任何新系統都需要支持這樣的規模。當然,網格中的一個節點可能重合或由另一個計算機集羣組成。優化網格環境已經給數據庫管理員造成了很大壓力,所以爲網格節點分配資源必須要自動進行。在水平分區基礎上,我們按照Gamma[DEWI90]的方法實現了內部的並行查詢。

許多數據倉庫系統(比如Walmart [WEST00])維護2份數據拷貝,因爲通過DBMS的日誌來恢復TB級的數據代價太高了。這個方案隨着磁盤效率的提高而越來越有吸引力。在網格環境裏,可以在多個節點裏放置數據副本,所以也支持Tandem風格的高可靠系統[TAND89]。但也不是非要這樣存儲多個備份。C-Store允許多餘對象按照不同的排序形式存儲,以得到好的讀取效率和高的可靠性。基本上,在列族裏存儲多餘的列能很好的改善性能,即使某個G site掛了也能從多餘的數據裏尋找。一個系統允許K個失敗,我們稱之爲K-safeC-Store通過可配參數調整K的範圍。

即使在讀頻繁的系統裏,也需要更新數據。數據倉庫需要在線更新來更正錯誤數據,同樣也不斷有數據需要加入實際的數據倉庫裏,而且數據更新的反應時間要儘量接近0。最終的需求就是數據倉庫的在線更新。顯然,讀頻繁的世界,比如CRM,需要通用的在線更新方案。

在“提供更新”和“優化讀取”之間,有一個矛盾。比如,在KDBAddamark裏,數據列按照插入的順序排列,這樣插入新元素就非常高效。但是代價就是讀取效率的下降,因爲更有效的讀取只有在另一種順序下才能達到。不過,如果不按照插入順序排列數據列,又會導致插入操作非常耗時。

C-Store用一種全新的視角解決了這個問題。我們結合了讀優化的列存儲系統和寫優化的存儲系統,用一個tuple mover來聯繫它們,如下圖所示。在頂層,有一個小的可寫存儲組件(WS),支持高性能的插入和更新。還有一個大些的讀優化組件(RS),支持大數據量讀取。RS對數據讀取進行了優化,只能進行特殊的插入,比如從WSRS的批量記錄轉移。這個轉移工作就是由tuple mover來完成的。

 

 

 

 

當然,數據訪問操作需要2個存儲系統的合作。插入操作交給WS,刪除操作則在RS裏做標記,然後由tuple mover來清理。更新操作分離爲插入和刪除2個操作。爲了提高tuple mover的速度,我們應用了LSM樹理論[ONEI96]的一個變體,採用merge out操作來從WSRS移動大量數據。merge out用有效的方式把排好序的WS數據對象合併到大的RS塊中,生成新的RS拷貝,在操作完成的時候使用。

上圖的體系必須支持多ad-hoc複雜查詢事務,小的更新事務,和可能的連續插入。顯然盲目的支持動態鎖定會導致阻塞和死鎖,進而導致讀寫衝突和性能下降。

所以我們希望只讀的查詢運行在historical模式下。在這種模式下,查詢操作選擇一個時間戳T,它應該比最後提交的事務時間戳小,然後查詢操作能保證產生的結果在那個時間點是正確的。爲了提供這種snapshot isolation [BERE95]特性,C-Store需要在元素插入時標記時間戳,並在程序裏小心的處理時間戳在T以後的元素。

最後,大部分商用的優化器和執行器(executors)都是建立在行存儲體系之上的,而WSRS都是面向列的,於是有必要實現面向列的優化器和執行器。我們將看到,這樣的軟件與當前的傳統設計完全不同。

本文描述一個可更新的列存儲系統,C-Store。它對數據倉庫式的查詢達到高性能的同時,還能在快速處理OLTP式的事務。C-Store是一個面向列的DBMS,旨在減少查詢時的磁盤訪問次數。C-Store的革新點包括:

1.  一種由WSRS混合的架構,前者優化頻繁的插入和更新,後者優化查詢;

2.  同一列以不同的順序在多個列族裏存在,這樣查詢能選擇最有效的順序;

3.  強力的列壓縮技術;

4.  面向列的優化器和執行器,和獨有的特性;

5.  通過列族的重疊調整K-safe級別,達到高可靠性和高性能;

6.  使用snapshot isolation避免2PC和查詢阻塞;

這裏需要強調的是,雖然這些特點在過去可能被獨立的研究過,但是現在我們把它們合在了一起,於是讓C-Store變得有趣和與衆不同。

本文第2部分展示C-Store的數據模型,第3部分介紹RS組件,第4部分介紹WS組件,第5部分考慮對網格節點的數據結構分配,第6部分是C-Store的更新和事務處理,第7部分介紹tuple mover組件,第8部分介紹查詢優化器和執行器。第9部分給出C-Store和流行的商用行存儲系統和列存儲系統之間的性能比較。對於TPC-H式的查詢,C-Store比其他系統明顯要快。不過需要指出的是性能比較並不完整,我們沒有整合WStuple mover,他們的耗時不可忽略。最後,第1011部分討論未來的改進工作和結論。

2.數據模型

C-Store支持標準的關係型邏輯數據模型(logical data model),即一個由表格組成的數據庫,每個表格都有一些屬性(列)。和其他多數關係型系統一樣,C-Store裏的屬性(或屬性集合)能作爲唯一的主鍵,或作爲外鍵引用其他表的主鍵。C-Store的查詢語言是標準的SQL語言。但是C-Store裏的數據在物理上不是按邏輯數據模型存儲的。大多數行存儲系統以直觀的方式實現表格,然後加入許多索引來提高訪問速度,但是C-Store只實現列族。比如,一個C-Store列族由一個邏輯表格T錨定,且包含一個或多個T的屬性(列)。一個列族甚至可以包含任意個表格裏的任意個屬性,只要從錨定表格到包含其他屬性的表格之間有一系列的n:1(即外鍵)關係。

生成列族時,我們從T裏選出感興趣的屬性,保留重複的行,然後通過外鍵從非錨定表格裏得到相應的屬性。這樣,一個列族和它的錨定表格有相同數目的行。當然,可以有多個精心設計的列族。我們相信這個簡單的方案能滿足高性能的要求。同時要說明的是,我們對錨定表格並不進行存儲。

 

 

 

我們把對錶格t的第i個列族記爲ti,後面跟列族裏的屬性名稱。其他表的屬性名稱前還要加上邏輯表名。我們使用一個經典的表格組合:EMPnameagesalarydept)和DEPTdnamefloor)。EMP的示例數據在Table 1裏。下面是一種可能的列族分佈:

EMP1 (name, age)

EMP2 (dept, age, DEPT.floor)

EMP3 (name, salary)

DEPT1(dname, floor)

列族裏的元素按列存儲,所以如果列族裏有K個屬性,則有K個數據結構,每個結構存儲一列,並按照同樣關鍵字排序。關鍵字可以是列族裏的任意列或列的組合。列族元素按照關鍵字排序,並從左到右排列。

我們在列族後面用豎線加屬性名的方式表示排序關鍵字。例如下面的表示方式:

EMP1(name, age| age)

EMP2(dept, age, DEPT.floor| DEPT.floor)

EMP3(name, salary| salary)

DEPT1(dname, floor| floor)

最後,每個列族都水平分區成一個或多個組,每組都有標示符SidSid > 0C-Store只支持基於關鍵字的分區,所以一個列族的每個組都對應了一個關鍵字範圍,而關鍵字範圍的集合則把整個關鍵字空間進行了“分區”。

顯然,爲了完成一個SQL查詢,C-Store需要一個覆蓋集合(covering set),比如某個表的某個列在哪些列族裏。同時,C-Store也必須能從不同的存儲組合裏重現表格的整個行。所以它需要利用storage keysjoin indexes來聯合不同列族裏的不同組。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章