論文詳譯:Fast Scans on Key-Value Stores

摘要

kv存儲引擎近些年越來越受歡迎,因為它可以彈性地擴縮容,對于get/put可以維持高吞吐量,有更低的延遲。這些得益于它的簡單,然而簡單也帶來一定的代價:目前的kv存儲系統不能很好的支持scan性能, 所以它不適用于處理復雜、分析型的query。分析型的query要求更好的數據局部性,然而get/put的高吞吐要求離散的索引。這篇paper展示了一種折中的方式可以兼具兩者。講述了分布式kv存儲系統TellStore,對于kv、分析型和混合場景都表現了不錯的性能。在論文最后的章節中展示了benchmark的測試結果。

1. 介紹

相比于傳統關系型數據庫,kv store由于它的彈性、可擴展性、部署和管理簡單而受到歡迎。而且它的性能也是可預期的:每條get/put請求花費確切的常量級時間。這些特性使得一些應用可以建立在kvs之上。

近期的一些工作展示了kvs可以以高效可擴展的方式run oltp的負載。這些工作采用了一種"SQL-over-NoSQL"的方式:數據持久化存儲在kvs(NoSQL),應用邏輯(with SQL support)放在一個分隔的處理層。在這個工作中我們要提出的一個重要問題是這樣一個"SQL-overNoSQL"的架構使用相同的kvs能否同時滿足OLAP和OLTP的負載?

這個問題是有價值的,因為oltp和olap的請求訪問方式是不同的。大多數kvs的get/put層是為oltp型負載所設計的,但對于分析型的請求并不是可行的,往往要涉及讀全部的數據或者大范圍的數據。分析型的系統提供適用于它們的訪問方式:允許一次性訪問到所有的數據(full table scan),下推select條件或者join條件到存儲層。大多數kvs是不具備這樣的能力的,所以對于scan的性能是不可接受的。

屏幕快照 2019-07-17 下午11.43.16.png

為了說明目前kvs不適合分析型的應用,table1展示了簡單的查詢5000wkv對數據的時間,返回了特定字段(YCSB# Query 1 in Section 7.2.1)的最大值(?最大值是什么意思?)。對于這個實驗,論文中用了四種機型(在7.1章節中展示了配置)。因為rocksdb是一個單機存儲引擎,測試它只用了1/4的數據量。Cassandra花費了大約19m處理這個簡單的查詢。RAMCloud和HBase需要大約半分鐘。對于這三種kvs,使用服務端聚集數據,因為傳輸數據在客戶端聚集會使得性能更差(???)。即使將這些數據集全部放在內存中,性能也是不可接受的。在這個實驗中可以接受的系統是rocksdb,memsql和kudu。rocksdb是一個適用于oltp系統的開源數據庫存儲引擎,被facebook所使用。MemSQL是分布式的、內存關系型數據庫系統,經過很好的優化(相比與其它的有很快表達式編譯??)。kudu 是一種基于列存的kvs,適用于混合負載(分析型和get/put)。但是如果有實時的延遲的要求,這7種系統都還遠達不到要求。這篇論文將提出TellStore的不同變種,可以實現更低的延遲,更快的響應時間。

kudu的性能表現說明了即使為這種查詢做了特定優化的系統也不容易在kvs實現快速查詢。這里的問題在于同時支持get/put和scan操作是一個矛盾的目標。有效的scan要求很好的數據局部性,而get/put要求離散的索引。多版本和回收機制的實現也會很大的影響性能,是需要考慮的點。這篇論文展示了一種折中的方式,說明在相同kvs上支持混合負載是可能的。

這篇論文主要貢獻了以下幾個點:
第一,論文提到了Sql-over-NoSql架構,展示了它如何處理混合負載的。
第二,論文提出了開發一個同時支持點查和范圍查詢的kvs的存儲格式設計(列存/行存等)。
第三,論文介紹了TellStore,一個分布式的內存kvs和兩種不同kvs的實現(Sections 4 to 6)。
最后,論文給出了用擴展的YCSB benchmark測試的實驗性能和工業界(the telecommuni- cations industry )使用變體實現kvs支持有效查詢的負載。
這個工作的主要思想是構建一個支持混合型負載的kvs是可能的,而且性能是可接受的。相比于從文獻中簡單提出概念,從整體上考慮和羅列這些設計問題是比較重要的。

REQUIREMENTS

2.1 SQL-over-NoSQL架構

屏幕快照 2019-07-19 下午10.35.20.png

Figure 1 描述了在kvs之上支持OLTP/OLAP混合負載的SQL-OVER-NOSQL架構。在這種架構中,數據持久化在分布式的包含get/put和scan接口的kvs中,事務和查詢的邏輯在單獨的處理層。處理層也需要處理并發的查詢和事務。在整個過程中,我們使用SI(Snapshot Isolation)作為隔離級別,使用Figure 1 中展示的Commit Manager來實現。SI(或者多版本并發控制的其它形式)被作為是默認的隔離級別實現,尤其是在分布式系統和數據庫混合負載的場景下,因為oltp事務從來不會阻塞或者干擾olap的查詢。在SI隔離級別下,Commit Manager 簡化設計了事務的時間戳,保留了對活躍、已提交和回滾事務的追蹤,因此幾乎不會成為系統的瓶頸。
SQL-Over-NoSQL架構的一個主要優勢是elastic(彈性)。機器可以獨立的增加到存儲層或者處理層(計算層)。比如,可以新增OLAP節點放在處理層來處理復雜的分析型查詢。當query完成時,這些節點可以關閉或者更改屬性來處理其它任務。這種結構也能有效地支持混合型的事務/分析型的負載:兩種負載都可以并發的跑在單獨的數據副本上。
為了高效地實現SQL-OverNoSQL架構,分布式kvs必須滿足以下的要求:

Scans
除了get/put請求,kvs還必須支持高效地scan操作。為了減少網絡傳輸的代價,kvs應該支持selections、projections和簡單的聚合,以實現只有相關的數據從存儲層傳輸到處理層。另外,支持sharded scan對于許多應用來講是一大優勢。

Versioning
為了支持多版本的并發控制,kvs必須要每條記錄的不同版本,根據事務的時間戳來返回正確的數據版本。Versioning需要有舊版本回收來釋放舊版本數據占用的空間。

Batching and Asynchronous Communication
為了實現高oltp性能,oltp處理節點將多個請求打包發給存儲層是關鍵的。使用這種方式,從處理層到存儲層傳輸信息的往返代價就被分攤到了多個并發事務中。而且,這種打包請求的方式需要異步執行,使得處理層在等待前一次請求被kvs處理完成之前可以收集下一輪的請求。

2.2 實現的難點

實現這三個需求的最大難點在于它們是矛盾的。這也是為什么今天大多數kvs只支持get/put(例如Cassandra 和 HBase),有的實現了多版本(例如RAMCloud 和 HBase),有些實現了異步傳輸。所有的這些特征都能很好的支持離散的數據結構,為get/put操作而設計。當查找一行特定版本的記錄時,是否它是緊湊的存儲是不重要的。然而對于scan,要求很高的數據局部性和數據緊密的存儲使得每一次存儲層的訪問能夠返回盡可能多的相關數據。數據局部性對于磁盤和內存中的scan都很重要。而且,加入scan的特征也帶來如下的矛盾點:
Scan vs. Get/Put
大多數的分析型系統使用列向存儲層來增加數據的局部性。然而,kvs,不需要物化數據,典型的使用行向存儲來處理get/put請求。
Scan vs. Versioning
不想關的多版本記錄會減少數據的局部性而降低scan的性能。而且,scan過程中check記錄的版本相關,也會花費高昂的代價。
Scan vs. Batching
像get/put請求那樣打包scan請求并不是有利的。OLTP的負載要求常量級的返回時間和對get/put請求可預期的返回時間。相反,scan會帶來返回時間的多變性,取決于謂詞的選擇性和需要處理的復雜query涉及的字段數量。

幸運地,如我們所描述的,這些矛盾點是可以以一種折中的方式來解決的。這篇論文的目的是去研究kvs的存儲結構設計和通過實驗證明哪種方式最有效。

3. DESIGN SPACE

這部分給出了一個整體的概括,關于構建一個支持bulk操作和scan的kvs的大多數重要的設計問題。

3.1 Where to Put Updates?

這里有三種可能的設計方式來實現updates(put, delete和insert):update-in-place, log-structured, and delta-main。
update-in-place是大多數關系型數據庫系統所采用的方式。新的記錄(insert)存儲在已存在的空閑頁上,更新已有記錄(puts or deletes)通過在原有記錄的地方進行覆蓋寫來實現。如果記錄是fixed,這種方式是很好的,因為這樣幾乎就不會有碎片產生。然而,這種方式對于多版本維護是tricker的。如果多版本數據是在原地進行維護,則會引起明顯的存儲空間碎片和失去局部性。這種方式的另一個問題是限制來并發:為了創建一條記錄的新版本,整個page都要被latched(與鎖的區別??)(a latch是lock-term的鎖,一旦page被更新完就可以釋放)。

Log-structured的存儲設計第一次被提出是在文件系統的應用中【40】。這種方式被一些kvs所使用,例如RAMCloud。主要想法是通過追加寫的方式實現所有的update操作。 Log-structured的存儲有兩個重要的優勢:(1) 不會有空間碎片產生。(2)不會有并發問題,因為追加寫可以以一種不阻塞的方式實現【29】。一個主要的缺點是如果沒有做優化的話,對于scan是很費的,scan需要讀舊版本的數據。而且,如果一個記錄很少被更新,是很難去做舊版本回收的。一個被稱為LSM-Tree的數據結構被用在LevelDB [23], RocksDB [16], and Kudu [19]中。這種變體會定期重組數據來提升讀的性能。

第三種方式delta-main,是被SAP Hana所提出的【17】,也被用在幾個research 工作中,例如AIM【10】。這種方式以一種寫優化的數據結構來收集所有的update請求(called delta),并且以一種讀優化的數據結構來維護大部分的數據(called main)。這兩種數據結構會定期的進行merge,這種方式嘗試將log-structed的優點(fast get/put)和update-in-place(fast scan)的優點結合起來。

3.2 How to Arrange Records?

最流行的兩種設計方式是row-major和column-major。row-major以連續的字節流方式將數據存儲在頁中[24],這種布局通常對于get/put在整條記錄上很有效。column-major將數據垂直分區,將表的整列存儲為連續的字節流。這種column-major組織方式對于分析型的query是有益的,這種query經常會涉及到scan列的一個子集[25, 3, 2, 45, 9, 8]。另外,column-major支持向量操作(SIMD)來加速批量操作和在現代硬件上的scan [47, 49]。一種column-major的變體稱作PAX[5],在每個page中存儲多條記錄,而在頁內,所有的記錄以一種column-major的方式存儲。PAX是一個在純列向設計和行向設計之間很好的折中。

Column-major在定長的value上表現比較好。這就是state-of-the-art系統避免使用變長值的原因,要么簡單的不允許使用它們(as AIM[10),要么使用在堆上分配內存,存儲對應指針的方式(as in MonetDB [8] and HyPer [33]),或者用一個字典并存儲固定大小的字典代碼(as e.g. in SAP/HANA [18] and DB2/BLU [39]))。

3.3 How to Handle Versions?

如第二部分所描述的,我們需要支持記錄的多版本來實現多版本并發控制。這里有兩種主要的實現方式:(a) 存儲一條記錄的所有版本在相同的位置 (b) 將記錄的多個版本使用鏈表鏈起來。第一種變體通常與update-in-place結合使用,用這種方式創建新版本的成本更低,但是合并頁面做垃圾回收時會花費高昂的代價。第二種變體更適合log-structured的存儲,clustering versions in an append-only data structure would require a costly copy of all previous versions to the head.
鏈接記錄的指針會消耗空間,遍歷鏈表會涉及到很多cache miss也會花費很大的代價。好的一方面,第二種方式簡化了垃圾回收,因為它可以以一種舊版本數據日志流截斷的方式來實現。而且可以減少空間的碎片化。

3.4 When to do Garbage Collection?

回收舊版本數據的方式有兩種:(a) 使用單獨的線程做周期的回收。(b) scan的過程中做piggy-back回收。方法b增加了scan的時間,但是也換回了垃圾回收的收益:相比其它table,scan頻繁從垃圾回收中受益的table會更頻繁的做垃圾回收。piggy-back回收的另一個優點是數據處理過程中隨時隨地做回收,也避免了拿取數據引起的cache miss。

3.5 Summary

屏幕快照 2019-07-24 下午11.59.27.png

table 2給出了在存儲效率(碎片)、并發、實現多版本和垃圾回收的花費和scan、get/put操作的效率的前三個緯度之間權衡的方式。第四個緯度垃圾回收正是實現這些性能特征的基礎。一個kvs的性能是由這些技術的組合所決定的。總而言之,構建一個kvs總共有24種不同的方式:
(update-in-place vs. log-structured vs. delta-main)
×(row-major vs. column-major / PAX)
×(clustered-versions vs. chained-versions)
×(periodic vs. piggy-backed garbage collection)
另外,還有混合的方式,例如periodic和piggy-backed回收方式可以組合實現。幸運的是,僅僅只有幾種變體方式是有意義的:Log-structured方式的updates 和 列向的存儲結構設計是沒有意義的,因為明顯“delta-main & column-major”的組合方式更有效。另外“log-structured & chained-version”的方式明顯比"log-structured & clustered-versions"更優。

這其中最有的兩個組合方式是log- structured with chained-versions in a row-major的方式和使用delta-main的數據結構with clustered-versions in a column-major的格式。接下來的兩個章節將會描述我們使用這兩種方式在TellStore上的實現,叫做TellStore-Log 和 TellStore-Col。 第六章給出了TellStore的實現細節,對于所有的TellStore的變體都是很重要的。第七章包含了通過比較不同變體之間的權衡得出的性能評估,使用已存在的一些kvs作為baseline。出了tellstore-log和tellstore-col,第七章節包含了第三種變體的結果,tellstore-row,與tellstore-col是相似的,但是使用row-major的存儲格式可以更好的研究對于混合oltp/olap的負載權衡行存和列存。

4. TELLSTORE-LOG

TellStore-Log的實現由RAMCloud所啟發,并加入了新的修改以支持有效的scan。

Where to Put Updates?

屏幕快照 2019-07-29 下午10.50.41.png

Figure 2描述了TellStore-Log的主要數據結構:詳細描述了記錄的布局(kv對)以及使用hashtable來索引日志中的記錄。日志本身是被分為多個片段使用鏈表鏈起來的多個pages來存儲所有的kv對。

日志是使用追加寫的數據結構:內存分配以一種無鎖的方式自動增加到page的頭部。一旦記錄追加到日志中,將變為不可變的。這個屬性使得恢復和復制日志變得簡單,因為復制過程只需要監視頭部。因為無鎖的特性,相同key的記錄可以并發的追加到日志中。哈希表始終是同步點,一條記錄只有等指向它的指針成功地插入或者更新到哈希表中才認為生效。以防沖突,數據變為immutable之前在日志中是無效的狀態。delete以一種特殊標記的沒有數據的update的形式被寫入。

hashtable使用鎖實現會變成性能點。并發無鎖的hashtable設計對于特定的路徑是一種常用的優化手段。尤其,在實現擴容時面臨點查和update性能的權衡。TellStore預先分配一個定長大小的hashtable,為所有在存儲節點上的表共同使用。實現上采用線性探測的開放尋址算法以解決數據沖突的問題。不利的是,開放尋址在高負載下往往表現不佳。為了節省內存以使得可以使用較小的內存保存更大的表,哈希表只記錄表的id、記錄的key和指向記錄的指針。

How to Arrange Records?

追加寫的方式本質上與行向存儲結構相關聯。為了支持有效的scan,日志中的記錄必須是完全獨立的。我們尤其想要避免去哈希表中查找來確定一條記錄是否有效(沒有被delete或者覆蓋寫)。這個約束對版本控制有一些影響,將在下一節中提到。

TellStore-Log在存儲節點上為每個表分配一段單獨的log。這使得scan可以只處理相關的pages,提高了數據的局部性。sca對有效的記錄的數量是敏感的,這對局部性的要求有影響,4.4節將會提到。

4.3 How to Handle Versions?

日志的不可變性使得一條記錄的新版本只能追加到日志的頭部。為了能夠定位到舊版本的數據,我們在每個記錄旁邊存儲指向前一個版本的指針來形成多版本鏈。另外事務為每條記錄分配一個時間戳保存在元信息中,多版本的鏈表總是根據snapshot由新到舊嚴格排序的,哈希表總是指向最新的記錄。給定一個snapshot,get請求會沿著鏈表查找直到遇到第一個滿足條件的記錄。遍歷鏈表涉及到cache miss的代價,與fast put是一種權衡。
這種設計看起來是與所有數據都必須獨立的要求是矛盾的:當scan日中中記錄時,只有記錄的時間戳可以拿到,不能判斷是否數據是過期了的。為了避免哈希表的查找,我們為每條記錄在元信息中增加了過期的時間戳((valid-to)作為不可變字段。一條記錄被成功寫之后,舊版本的記錄的valid-to將會更新(lazily updated)為新版本的valid-to。給定一個snapshot, scan 可以通過比較兩個時間戳來確定記錄是否滿足條件。

哈希表仍然是唯一的同步點,始終指向最新的元素。更新哈希表和設置valid to字段之間沒有競爭條件,TellStore的SI實現不能確保在線事務的可見性(???)。

When to do Garbage Collection?

scan的性能被不再被任何活躍事務所看見的過期數據的數量所影響。為了更頻繁合并那些經常被scan的table,垃圾回收放在正常scan的一部分中。掃描一個頁面時,其運行狀況按照頁面中有效記錄的數量與總記錄數量的比率來計算,一旦該值降到某個閥值以下,頁面將會被標記為垃圾回收。被標記的頁面將在下一次scan時將剩余有效數據copy到log的頭部并將回收后的頁面放入空閑頁面的池子中。一條記錄被copy到日志頭部之后,該鍵的多版本鏈的指針需要重新調整。為此,我們需要查找哈希表,沿著版本鏈找到正確的位置。這個操作由于很差的緩存局部性而成本高昂。為了從非頻繁掃描的表中回收空間,由后臺線程定期去掃描回收。

Summary

盡管Ramcloud是日志結構kvs的典型實現,但其scan性能較差。同時構建一個支持快速get/put請求、版本控制和scan的log-structed kvs是可能的。主要的要點是日志中多版本的仔細組織、有效的垃圾回收策略、延遲的元信息/時間戳實現來使得記錄獨立,無鎖算法和良好的工程設計。

TELLSTORE-COL

屏幕快照 2019-07-31 下午10.42.34.png

TellStore-Col采用delta-main方式的核心理念,維護兩種數據結構:main for read, delta for update。如Figure3所示,我們的實現實際涉及到4種數據結構:page list用來維護主要的數據,兩個log存儲delta(一個for insert, 一個for update)和一個哈希表來索引數據。

5.1 Where to Put Updates?
除了查詢元信息字段,在main中的數據只用于read,所有update都被寫入一個追加寫的存儲中。與TellStore-Log不同,delta被分成兩個獨立的log:update已存在的記錄被寫到update-log中,update不存在的記錄被寫到insert-log中。如第5.4節所示,這種分離使得從delta構建main更容易。索引中保留了一個標志位來標示指針指向delta或者main。除了這個標志位,索引表使用與TellStore-Log相同的哈希表。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容

  • 一、簡介 Hbase:全名Hadoop DataBase,是一種開源的,可伸縮的,嚴格一致性(并非最終一致性)的分...
    菜鳥小玄閱讀 2,400評論 0 12
  • lnnoDB是事務安全的MySQL存儲引擎, 設計上采用了類似于Oracle數據庫的架構。 通常來說,InnoD...
    好好學習Sun閱讀 1,529評論 0 5
  • 原創文章,轉載請注明原作地址:http://www.lxweimin.com/p/52881714d786 論文地址...
    EchoZhan閱讀 4,441評論 0 6
  • 第一章 MySQL 體系架構和存儲引擎 mysql是數據庫也是數據庫實例 mysql 是一個單進程多線程架構的數據...
    snail_knight閱讀 3,546評論 0 6
  • 今天看到一位朋友寫的mysql筆記總結,覺得寫的很詳細很用心,這里轉載一下,供大家參考下,也希望大家能關注他原文地...
    信仰與初衷閱讀 4,747評論 0 30