摘要
現代塊尋址NVMe固態硬盤為隨機與順序的存取提供了更高的帶寬和相似的性能。持續鍵值存儲是為更早的存儲設備設計的,它使用了日志結構(LSM)或者B樹,並沒有充分利用這些新的設備。避免隨機存取的邏輯、為了保持數據在磁盤上有序存放的昂貴操作以及同步瓶頸使得這些千伏安處理器受限於NVMe固態硬盤。
我們將展示一種新的持續KV設計。與先前的設計不同,並沒有去做有序存儲的嘗試,並且數據並不是有序存儲在硬盤中的。我們采用無共享的理念來避免同步開銷。和設備訪問的批處理一起,這些設計決策使得讀寫性能接近設備帶寬。最終,在內存中維持一個廉價的部分排序可以產生足夠的掃描性能。
我們在KVell中實施了這個設計,第一個持續的KV能夠在最大的帶寬下利用現代塊尋址NVMe固態硬盤。我們將KVell與可用的最新技術的LSM和B樹KVs相比較,包括綜合基准測試和生產工作負載。在以讀取為主的工作負載上,KVell的吞吐量至少是其最接近的競爭對手的兩倍,在以寫為基礎的工作負載上為五倍。對於主要包含掃描的工作負載中,KVell比其競爭者表現相當甚至更好。KVell提供的最大延遲比其最好的競爭對手低一個數量級,即使在基於掃描的工作負載上也是如此。
關鍵詞:鍵值存儲,持續,性能,SSD,NVMe,B+樹,日志結構的合並樹(LSM)
1 介紹
鍵值存儲(KVs)已經成為在廣泛的雲應用中提供存儲的標准平台,其中包括緩存、元數據管理、消息傳遞和在線購物。在本文中,我們的目標是塊尋址存儲設備上的KVs,這些設備提供持久性保證(即,數據和更新在失敗時不能丟失),並且工作集不完全適合主存。
過去,存儲設備和CPU之間的速度差距是很大,以至於投資CPU周期來優化存儲訪問是一個有益的折衷。因此,一些KVs包含復雜的索引,比如B+樹。LogStructured Merge (LSM) KVs強制執行順序寫操作,因為它們比隨機寫操作效率更高。所有的KVs都包含某種形式的緩存。為了支持檢索給定鍵范圍內的所有項的高效掃描,KVs在內存和磁盤上以排序的順序維護項。盡管所有這些優化都需要一些CPU周期的投資,但存儲設備仍然是瓶頸,而這些優化被證明是有益的,因為它們緩解了這個瓶頸。
當我們測量運行在塊尋址NVMe SSD設備上的最先進的KVs的性能時,我們發現瓶頸是CPU,而不是存儲設備,這證實了早期的定性觀察。NVMe SSD在隨機和順序訪問方面表現出非常高的帶寬和相當的性能。因此,許多為傳統存儲設備開發的優化不再有用,實際上還會產生反效果,因為它們在CPU周期方面的成本加劇了CPU瓶頸。一個明顯的例子是LSM KVs強制執行順序寫操作的嘗試。
在設備訪問方面不再有任何好處,並且LSM KVs(主要是壓縮)所需要的維護操作會加重CPU的負擔,並且因此對性能造成負面的影響。一個更令人驚訝的例子是,在共享(內存中)數據結構上進行同步以進行緩存和維護順序會導致CPU成為瓶頸。我們得出的結論是,存儲設備特性的變化需要在KVs的設計中進行范型轉換,其中重點關注CPU的流線型使用。
在本文中,我們介紹了以下四個KVs設計原則,我們發現它們在此過程中非常有用。
\1. 無共享:所有數據結構在核之間分區,這樣每個核幾乎可以完全不同步地運行。
\2. 項目不在磁盤上排序。每個分區都維護一個內存中排序索引,以便進行高效掃描。
\3. 沒有嘗試強制順序訪問,但是對I/O操作進行批處理,以減少代價高昂的系統調用的數量。批處理還提供了對設備隊列長度的控制,使同時實現低延遲和高吞吐量成為可能。
\4. 沒有提交日志:只有在更新被持久化到磁盤上的最終位置后,更新才被確認。
除了提供更好的平均吞吐量之外,通過消除許多復雜的維護過程,這些原則還可以在吞吐量和延遲方面帶來更可預測的性能。有些設計決策並不是沒有權衡的。例如,無共享架構會有負載不平衡的風險。沒有排序順序會影響掃描。我們在評估中顯示,對於主要由中到大型鍵值對(400B+)組成的工作負載,精簡的CPU操作帶來的好處超過了這些缺點。
我們在KVell中實現了這些技術,我們展示了KVell可以與四個最先進的KVs相媲美:RocksDB和PebblesDB,它們都是LSM KVs,以及TokuMX和WiredTiger,它們是基於B樹的衍生物。我們使用行業標准的YCSB基准測試,包括統一的和傾斜的密鑰訪問分布。此外,我們還展示了吞吐量在整個實驗中保持穩定,而其他系統則經歷了性能較差的時期。我們通過對替代工作負載和平台的測量來確認這些結果。貢獻。本文的貢獻是:
1.對傳統的LSM和B樹型KVs進行了分析,論證了NVMe SSDs的CPU瓶頸問題。
2.持久KVs利用NVMe ssd的屬性的新范例,它重點關注CPU的流線型使用。
3.KVell KV包含了這個新范式。
4.將KVell與最新的LSM和B樹KVs在合成基准和生產工作負載上進行深入評估。
路線圖。本文的其余部分組織如下。第2節展示了SSD性能的發展。第三節討論當前KV設計的限制。第4節介紹KVell背后的設計原則。第5節將更詳細地描述KVell中采用的技術解決方案。第6節對KVell進行了深入的評價。第7節討論相關工作,第8節總結全文。
2 SSD性能的發展
硬件。我們考慮一下在過去5年中引入的三種設備:
• Config-SSD. A 32-core 2.4GHz Intel Xeon, 128GB of
RAM, and a 480GB Intel DC S3500 SSD (2013).
• Config-Amazon-8NVMe.An A WS i3.metal instance,
with 36 CPUs (72 cores) running at 2.3GHz, 488GB of
RAM, and 8 NVMe SSD drives of 1.9TB each (brand
unknown, 2016 technology).
• Config-Optane. A 4-core 4.2GHz Intel i7, 60GB of
RAM, and a 480GB Intel Optane 905P (2018).
IOPS(每秒輸入輸出次數,是一個用於計算機存儲設備(如硬盤(HDD)、固態硬盤(SSD)或存儲區域網絡(SAN))性能測試的量測方式). 表1顯示了這三種設備的最大讀寫IOPS數以及隨機和順序帶寬。首先,IOPS的數量和帶寬顯著增加。其次,在較老的設備上,隨機寫操作比順序寫操作要慢得多,但現在已經不是這樣了。類似地,混合隨機讀寫不再會像老式設備那樣導致性能低下。例如,Config-Optane使用在2018年Q3發布的Intel Optane 905P驅動器,不管讀寫的混合方式如何,它都能維持500K+ IOPS,而且隨機訪問也只比順序訪問稍微慢一點。
延遲和帶寬。表2展示了三個設備的延遲和帶寬測量值,它們是設備隊列深度的函數,其中一個核心執行隨機寫操作。上一代設備只能通過少量的同步I/O請求維持亞毫秒級的響應時間(在Config-SSD的情況下是32個)。Config-Amazon-8NVMe和Config-Optane支持更高的並行性,這兩個驅動器都能夠以亞毫秒級的延遲響應256個同時發生的請求。當設備隊列中的請求太少時,這兩個驅動器只能達到其帶寬的一小部分。因此,即使在現代設備上,也必須在發送太少的並發請求(導致次優帶寬)和發送太多(導致高延遲)之間保持良好的平衡。
吞吐量降低。圖1顯示了這三種設備上的IOPS隨時間的變化。這張圖也顯示了設備技術的進步。例如,配置ssd可以在40分鍾內維持50K的寫入IOPS,但隨后它的性能慢慢下降到11K IOPS。較新的ssd就不會有這個問題,而且隨着時間的推移,IOPS會保持在較高的水平。

表1. IOPS和帶寬取決於3個SSD的工作負載。 新驅動器中的IOPS和帶寬已大大增加,並且隨機訪問和順序訪問之間的性能差異很小。

表2.根據隊列大小的平均延遲和帶寬,在單個內核上進行隨機寫入。隊列長度的適當選擇是必需的同時實現低等待時間和高帶寬利用率。

圖1. 3個SSD隨時間推移的IOPS數量。 圖2. AWS和Intel Optane上的4K
老式SSD僅在最大IOPS時支持短暫的 寫請求(隊列深度64)的延遲時間。
I / O突發。
延遲峰值。圖2顯示了Config-Amazon-8NVMe和Config-Optane上隊列深度為64的4K寫操作的延遲。由於內部維護操作,老一代ssd在寫量大的工作負載中受到延遲高峰的影響。在Config-SSD上,我們觀察到5小時后延遲峰值高達100ms,而正常的寫入延遲為1.5ms。圖2中沒有顯示這些結果,因為這個設備的峰值大小會掩蓋其他設備的結果。Config-Amazon-8NVMe驅動器也會受到周期性延遲高峰的影響。最大觀察到的延遲是15ms(而99.1%是3ms)。在Config-Optane驅動器上,潛伏期的峰值不規律地發生,它們的幅度通常小於1ms,觀察到的最大峰值為3.6ms(相比之下,99%的峰值為700us)。
3 NVMe ssd上當前KVs的問題
目前持久KVs主要有兩種范式:(1)LSM KVs,被廣泛認為是寫為主的工作負載的最佳選擇;(2)B樹KVs,被認為更適合讀密集型工作負載。LSM KVs在流行的系統中使用,如RocksDB和Cassandra。在MongoDB中使用了B樹及其變體。接下來,我們將證明這兩種設計在NVMe ssd上都成為了cpu瓶頸,並遭受嚴重的性能波動。我們超越了現有的工作,表明這些觀察結果也適用於B樹KVs,並提供了一個詳細的CPU開銷核算。
3.1 CPU是瓶頸

圖3. Config-Optane。 RocksDB(LSM KV)和WiredTiger(B樹KV)的I / O帶寬(左)和CPU消耗(右)時間線。 這兩個系統都無法利用整個設備的I / O帶寬。 工作負載:YCSB寫入50%–讀取50%,均勻密鑰分配,KV項大小為1KB。
圖3顯示了兩個最先進的KVs在Config-Optane上的磁盤利用率(左)和CPU利用率(右):RocksDB (LSM KV)和Wired Tiger (一個B樹KV) (其他KVs的結果類似)。在本例中,我們使用具有統一密鑰分發的YCSB核心工作負載A(寫密集型)。這兩個系統都使CPU飽和,並且沒有充分利用可用帶寬。現在我們來解釋這些行為。
CPU****是LSM KVs的瓶頸。LSM KVs通過吸收內存緩沖區中的更新來優化寫密集型工作負載。當緩沖區滿時,它被刷新到磁盤。然后,后台線程將刷新的緩沖區合並到持久存儲中維護的樹狀結構中。磁盤結構包含多個級別,級別大小不斷增加。每一層都包含多個不可變的排序文件,它們的鍵值范圍不一致(除了第一層,它是為寫內存緩沖區保留的)。為了在磁盤上保留這種結構,LSM KVs實現了稱為壓縮的CPU和I/O密集維護操作,該操作將LSM樹中較低級別的數據合並到較高級別的數據,維護項目排序並丟棄重復項。
在較舊的設備上,壓縮會爭奪磁盤帶寬,但是合並、索引和內核代碼也會爭奪CPU時間,而在較新的設備上,CPU已經成為主要的瓶頸。分析顯示,RocksDB在壓縮上花費了高達60%的CPU時間(28%用於合並數據,15%用於構建索引,剩下的用於讀取和寫入磁盤數據)。壓縮需求源於LSM的設計要求,即順序磁盤訪問和在磁盤上保持數據排序。這種設計對於舊的驅動器是有益的,在這些驅動器中,花費CPU周期來保持數據排序和確保長時間順序的磁盤訪問是值得的。
CPU是B樹KVs的瓶頸。有兩種變體B樹為持久性存儲設計:B +樹和Bϵ樹。B+樹在葉子中包含KV項。內部節點只包含鍵並用於路由。通常,內部節點駐留在內存中,葉子駐留在持久存儲中。每個葉節點都有一個已排序的KV項范圍,並且葉節點被鏈接到一個鏈表中,便於掃描。最先進的B+樹(例如,WiredTiger)依靠緩存來實現良好的性能。更新首先寫入每個線程的提交日志,然后寫入緩存。最終,當數據從緩存中被逐出時,樹將使用新信息進行更新。更新使用序列號,這是掃描所必需的。讀操作遍歷緩存,只有在項目未被緩存時才訪問樹。
有兩種類型的操作可以將數據持久化到樹中:(1)檢查點和(2)驅逐。檢查點定期發生,或者在日志中達到某個大小閾值時發生。檢查點對於保持提交日志的大小有限制是必要的。驅逐將臟數據從緩存寫入樹中。當緩存中的臟數據量超過某個閾值時,將觸發驅逐。
Bϵ樹是B +樹的變體,增強臨時存儲鍵和值在每個節點的緩沖區。最終,當緩沖區滿了時,KV項沿着樹結構向下延伸,並被寫入持久存儲。
B樹的設計容易產生同步開銷。WiredTiger中的剖析揭示了這一點:工作線程花費47%的時間忙於等待日志中的插槽(在__log_wait_for_earlier_slot函數中,該函數使用sched_yield系統調用來忙碌等待)。這個問題源於不能足夠快地推進序列號進行更新。在更新的主代碼路徑中,不包括內核所花費的時間,WiredTiger只花費18%的時間來執行客戶端請求邏輯,其余時間用於等待。WiredTiger還需要執行后台操作:從頁面緩存中清除臟數據占總時間的12%,管理提交日志占總時間的5%。內核中只有25%的時間用於讀寫調用,其余的時間用於futex和yield函數。
Bϵ樹也經歷同步開銷。因為Bϵ樹保持磁盤上的數據命令有序,工作線程最終修改共享數據結構,導致爭用。對TokuMX的分析表明,線程將30%的時間花費在鎖或用於保護共享頁面的原子操作上。緩沖也被證明是Bϵ樹中的一個主要開銷來源。在YCSB中,一個工作負載TokuMX花費超過20%的時間將數據從緩沖區移動到葉子中的正確位置。這些同步開銷大大超過了其他開銷。
3.2 LSM和B樹KVs的性能波動
除了受制於cpu之外,LSM和B樹KVs都受到了顯著的性能波動。圖4顯示了RocksDB和WiredTiger運行YCSB核心工作負載a的吞吐量隨時間的波動。吞吐量是每秒測量一次的。在LSM和B樹KVs中,基本問題是相似的:客戶端更新會因為維護操作而停止。
在LSM KVs中,吞吐量下降是因為有時更新需要等待壓縮完成。當LSM樹的第一級已滿時,更新需要等待,直到通過壓縮使空間可用。但是,我們已經看到,當LSM KVs在現代驅動器上運行時,壓縮會遇到CPU瓶頸。隨着時間的推移,吞吐量方面的性能差異會上升一個數量級:RocksDB平均維持63K /s的請求,但在寫入停止時下降到1.5K。分析顯示,寫線程大約有22%的時間被擱置,等待內存組件被刷新。內存組件的刷新被延遲,因為壓縮無法跟上更新的速度。
降低壓實對性能影響的解決方案已被提出,即延遲壓縮或者僅在系統空閑時運行它們,但是這些解決方案不適合在高端ssd上運行。例如,Config-Optane機器以2GB/s的速度刷新內存組件。延遲壓縮超過幾秒鍾會導致大量的工作積壓和浪費空間。
在B樹中,用戶工作負載中的暫停也會影響性能。用戶寫入被延遲,因為驅逐不能足夠快地進行。暫停導致吞吐量下降了一個數量級,從120 Kops/s下降到8.5 Kops/s。我們的結論是,在這兩種情況下,諸如壓縮和驅逐之類的維護操作嚴重干擾用戶的工作負載,導致持續數秒的暫停。因此,新的KV設計應避免維護操作。

圖4. Config-Optane. RocksDB和WiredTiger中的吞吐量波動。 工作負載:YCSB寫入50%–讀取50%,密鑰分配均勻,KV項大小為1KB。
4 KVell設計原則
為了有效地利用現代持久存儲,KVs現在需要強調低CPU開銷。我們將說明以下原則是在現代ssd上實現最高性能的關鍵。
-
不共享。在KVell中,這可以轉化為支持並行性和最小化KV工作線程之間的共享狀態,以減少CPU開銷。
-
不要在磁盤上排序,而是將索引保存在內存中。KVell將未排序的項目保存在磁盤的最終位置,避免了昂貴的重新排序操作。
-
目標是減少系統調用,而不是連續的I/O。KVell的目標不是順序磁盤訪問,它利用了這樣一個事實:隨機訪問幾乎和現代ssd上的順序訪問一樣高效。相反,它努力通過批處理I/O來最小化系統調用造成的CPU開銷。
-
不提交日志。KVell不緩沖更新,因此不需要依賴於提交日志,避免了不必要的I/O。
4.1不共享
對於單個讀和寫的常見情況,處理請求的工作線程不需要與其他線程同步。每個線程處理給定的鍵子集的請求,並維護一個線程私密的數據結構集來管理這組鍵。關鍵數據結構是:(i)一個輕量級內存B樹索引,用於跟蹤鍵在持久存儲中的位置,(ii) i / O隊列,從持久存儲負責有效地存儲和檢索信息,(iii)自由列表,部分內存磁盤塊列表包含用於存儲項和(iv)頁面緩存——KVell使用自己不依賴於os級結構的內部頁面緩存。掃描是在內存中的B樹索引上需要最小同步的惟一操作。
與傳統KV設計相比,這種無共享方法是一個關鍵的區別,在傳統KV設計中,所有或大多數主要數據結構都由所有工作線程共享。傳統的方法需要對每個請求進行同步,這是KVell完全避免的。分區請求可能會導致負載不平衡,但我們發現,如果使用合適的分區,這種影響很小。
4.2不要在磁盤上排序,而是將索引保存在內存中
KVell不會對工作線程的工作集中的數據進行排序。因為KVell不對鍵進行排序,所以它可以將項保存在磁盤上的最終位置。磁盤上完全沒有順序減少了插入項的開銷(即找到插入的正確位置),並消除了與磁盤上的維護操作(或者寫入磁盤之前的排序)相關的CPU開銷。在磁盤上以無序方式存儲密鑰尤其有利於寫操作,並有助於實現較低的尾延遲。
在掃描期間,連續的鍵不再在同一個磁盤塊中,這可能是一個缺點。然而,可能令人驚訝的是,對於具有中型和大型kv -項的工作負載(例如YCSB基准測試或生產工作負載中的掃描,如我們在第6節中所示),掃描性能不會受到顯著影響。
4.3目標是減少系統調用,而不是順序I/O
在KVell中,所有操作(包括掃描)都對磁盤執行隨機訪問。因為隨機訪問和順序訪問一樣有效,所以KVell不會浪費CPU周期來強制執行順序I/O。
與LSM KVs類似,KVell將請求批量發送到磁盤。然而,目標是不同的。LSM KVs主要使用批處理I/O和對KV項進行排序,以便利用順序磁盤訪問。KVell對I/O請求進行批處理,主要目標是減少系統調用的數量,從而減少cpu開銷。
批量是需要折中的,正如在第2節中看到的,磁盤需要一直處於忙碌狀態,以實現其峰值IOPS,但只有在硬件隊列中包含的請求數小於給定數量(例如Config-Optane上的256個請求)時,磁盤才會以亞毫秒級的延遲響應。一個高效的系統應該向磁盤推送足夠的請求,以使它們保持忙碌,但是不要讓大的請求隊列淹沒它們,這會導致高延遲。
在具有多個磁盤的配置中,每個worker只在一個磁盤上存儲文件。這種設計決策對於限制每個磁盤掛起的請求數量非常關鍵。實際上,由於worker之間不通信,所以它們不知道其他worker向給定磁盤發送了多少請求。如果worker將數據存儲在單個磁盤上,那么對磁盤的請求數量是有限的(批處理大小乘以每個磁盤上的worker數量)。如果工作者要訪問所有磁盤,那么磁盤可能有多達(批大小乘以工作者總數)的掛起請求。
4.4不提交日志
KVell只在更新被持久化到最終位置的磁盤上之后才承認更新,而不依賴於提交日志。一旦更新被提交給工作線程,它將在下一個I/O批中持久化到磁盤上。刪除提交日志允許KVell僅將磁盤帶寬用於有用的客戶機請求處理。
5 KVell的實現
盡管KVell的設計原則看起來很簡單,但要在實踐中正確地實現它們是很有挑戰性的。KVell的源代碼可以在https://github.com/ BLepers/KVell上獲得。
5.1客戶端操作界面
KVell實現了與LSM KVs相同的核心接口:寫Update(k,v)、讀Get(k)和范圍掃描Scan(k1,k2)。Update(k,v)將值 v與鍵 k關聯。Update(k,v)只在值被持久化到磁盤后返回。Get(k)返回k的最近值。掃描(k1,k2)返回k1和k2之間的KV項目范圍。
5.2磁盤數據結構
為了避免碎片化,適合相同大小范圍的項被存儲在相同的文件中。我們稱這些文件為slab。KVell以塊粒度訪問slab,也就是我們機器上的頁面大小(4KB)。
如果項小於頁面大小(例如,多個項可以容納在一個頁面中),KVell會在slab中的項前面加上時間戳、鍵大小和值大小。大於4K的項在磁盤上每個塊的開頭都有一個時間戳頭。對於小於頁面大小的項,將在適當的位置進行更新。對於較大的項,一次更新包括將項目附加到slab上,然后在項目曾經所在的地方寫一個墓碑。當一個項更改大小時,KVell首先將更新后的項寫入新slab,然后從舊slab中刪除它。
5.3內存中的數據結構
索引。KVell依賴於快速而輕量級的內存索引,這些索引具有可預測的插入和查找時間來查找磁盤上項的位置。KVell為每個worker使用一個內存中的B樹來存儲磁盤上項目的位置。項根據其鍵(前綴)建立索引。我們使用前綴而不是哈希來保持范圍掃描的鍵的順序。B樹在存儲中/大型數據時性能很差,但當數據(大部分)能放入內存且鍵很小時,速度很快。KVell利用了這個屬性,並且只使用B樹存儲查找信息(前綴和位置信息占13B)。
KVell的樹實現目前平均每個條目使用19B(存儲前綴、位置信息和B樹結構),相當於1.7GB的RAM來存儲100M個條目。在YCSB工作負載(1KB項)上,索引相當於數據庫大小的1.7%。我們發現這個值在實踐中是合理的。KVell目前不顯式地支持將B樹的一部分刷新到磁盤,但是B樹數據是從mmap-ed文件中分配的,可以由內核調出。
頁面緩存。KVell維護自己的內部頁面緩存,以避免從持久存儲中獲取頻繁訪問的頁面。緩存的頁面大小是一個系統參數。頁面緩存會記住哪些頁面緩存在索引中,並按照LRU順序從緩存中回收頁面。
確保索引中的查找和插入具有最小的CPU開銷對於獲得良好的性能至關重要。我們的第一個頁面緩存實現使用快速uthash哈希表作為索引。但是,當頁面緩存很大時,散列中的插入可能會花費高達100ms的時間(增長散列表的時間),從而提高了尾延遲。切換到B樹可以消除這些延遲峰值。
空閑列表。當從一個slab中刪除一個項時,它在該slab中的位置將插入到每個slab的內存堆棧中,我們稱之為slab的空閑列表。然后在磁盤上的項目位置寫入一個tombstone。為了綁定內存使用,我們只保留內存中最后的N個釋放的位置(目前N被設置為64)。其目標是限制內存使用,同時保持在不需要額外磁盤訪問的情況下重用每批I/O的多個空閑點的能力。
當(N + 1)th項被釋放時,KVell使其磁盤tombstone指向第一個被釋放的位置。然后KVell從內存堆棧中刪除第一個被釋放的位置,並插入(N +1)被釋放的位置。當(N +2)th項被釋放時,它的tombstone指向第二個被釋放的位置,以此類推。簡而言之,KVell維護N個獨立的堆棧,它們的頭駐留在內存中,其余的在磁盤上。這使得Kvell在每批I/O能重用最多N個空閑點。如果只有一個堆棧,KVell就必須從磁盤上依次讀取N個tombstone,以找到下一個被釋放的N個位置。
5.4有效地執行I/O
KVell依賴於Linux (AIO)的異步I/O API將請求發送到磁盤,最多分批發送64個請求。通過批量處理請求,KVell將系統調用的開銷分攤到多個客戶機請求上。我們選擇使用Linux異步I/O,是因為它為我們提供了一種通過單個系統調用執行多個I/Os的方法。我們估計,如果這樣的調用在同步I/O API中可用,性能將大致相同。
我們拒絕了兩個流行的執行I/O的替代方案:(1)使用依賴於OS頁面緩存的mmap (例如,RocksDB),和(2)使用直接讀寫I/O系統調用(例如,TokuMX)。這兩種技術的效率都不如使用AIO接口。表3總結了我們的發現,展示了在Config-Optane上可以實現的最大IOPS,隨機寫入4K塊(這需要對設備進行讀-修改-寫操作)。被訪問的數據集比可用RAM大3倍。

表3. Config-Optane. 最大IOPS取決於磁盤訪問技術。
第一種方法是依賴os級頁面緩存。在單線程情況下,這種方法的性能不是最優的,因為當發生頁面錯誤時,它一次只能發出一次磁盤讀取(由於數據是隨機訪問的,所以提前讀取值設置為0)。此外,臟頁只定期刷新到磁盤。這在大多數情況下會導致次優化的隊列深度,然后是I/Os的爆發。當數據集不能完全裝入RAM時,內核還必須從進程的虛擬地址空間映射和取消映射頁出頁,這會帶來顯著的CPU開銷。對於多線程,在刷新LRU時,頁面緩存會受到鎖開銷的影響(平均每32KB刷新一個鎖到磁盤),以及系統使遠程核心的TLB項失效的速度。實際上,當一個頁面從虛擬地址空間中取消映射時,需要在所有訪問過該頁面的核心上使虛擬到物理的映射失效,這將導致IPI通信[33]帶來巨大的開銷。
第二種方法是依賴於直接I/O。然而,當請求以同步方式完成時(每個線程有一個掛起的請求),直接I/O讀/寫系統調用不會填充磁盤隊列。因為沒有必要處理從虛擬地址空間映射和取消映射頁面的復雜邏輯,此技術優於mmap方法。
相反,批處理I/O每個批處理只需要一個系統調用,並允許KVell控制設備隊列長度,以實現低延遲和高帶寬。盡管理論上I/O批處理技術可以應用於LSM和B樹KVs,但實現將需要很大的努力。在B樹中,不同的操作可能會對I/O產生沖突的影響(例如,由插入引起的葉的分裂,然后是兩個葉的合並)。此外,數據可能由於重新排序而在磁盤上移動,這也使得異步批處理請求難以實現。寫請求的批處理已經通過內存組件在LSM KVs中實現。然而,批處理略微增加了讀取路徑的復雜性,因為工作人員必須確保壓縮線程不會刪除所有需要讀取的文件。

5.5客戶端操作實施
算法1總結了KVell架構。為簡單起見,該算法只顯示單頁KV項。當一個請求進入系統,根據它的鍵被分配給worker(第3行和第5行,算法1). worker線程執行磁盤I/O並處理客戶端請求的邏輯。
得到(k)。讀取一個項(第17-22行,算法1)包括從索引中獲取它在磁盤上的位置,並讀取相應的頁。如果已經緩存了頁面,則不需要訪問持久存儲,並且值將同步返回給客戶機。如果沒有,則處理請求的worker將其推入其I/O引擎隊列。
更新(k、v)。更新一個項(第24-35行,算法1)包括首先讀取存儲它的頁面,修改值,然后將該頁面寫入磁盤。刪除一個項目包括寫入一個tombstone值和在slab的空閑列表中添加項目位置。在添加新項時重用釋放的位置,如果不存在空閑位置,則追加項目。KVell僅在更新的項完全持久化到磁盤時(即io_getevents系統調用通知我們,與更新對應的磁盤寫操作已經完成(算法1的第37行),才確認更新已經完成。臟數據會立即刷新到磁盤,因為KVell的頁面緩存不用於緩沖區更新。通過這種方式,KVell提供了比最先進的KVs更強的持久性保證。例如,RocksDB只在提交日志同步到磁盤的粒度上保證持久性。在典型的配置中,同步只發生在幾批更新中。
掃描(k1, k2)。掃描包括(1)從索引中獲取鍵的位置,(2)讀取對應的頁面。為了計算鍵的列表,KVell掃描所有的索引:一個線程簡單地依次鎖定、掃描和解鎖所有worker的索引,最后合並結果以獲得一個要從KV讀取的鍵的列表。然后使用Get()命令執行讀取,該命令繞過了索引查找(因為KVell已經訪問了索引)。掃描是唯一需要在線程之間共享的操作。KVell返回與掃描觸及的每個鍵相關聯的最新值。相比之下,RocksDB和WiredTiger都在KV快照上執行掃描。
5.6故障模型與恢復
KVell目前的實現是為無故障運行而優化的。在崩潰的情況下,所有的平板被掃描,內存中的索引被重新構建。即使掃描使順序磁盤帶寬最大化,在非常大的數據集上恢復仍然需要幾分鍾。
如果一個項在磁盤上出現了兩次(例如,在將一個項從一個slab遷移到另一個slab的過程中發生了崩潰),那么只有最近的項保存在內存索引中,而另一個項插入到空閑列表中。對於大於塊大小的項,KVell使用時間戳標頭來丟棄僅被部分寫入的項。
KVell是為能夠自動寫入4KB頁面的驅動器設計的,即使在電源故障的情況下也是如此。通過避免對頁面進行就地修改,在新頁面中寫入新值,然后在第一次寫入操作完成后在slab的空閑列表中添加舊位置,可以解除該約束。
6評價
6.1目標
我們用各種生產和合成工作負載來評估KVell,並將其與最先進的KVs進行比較。評估旨在回答以下問題:
\1. 在吞吐量、性能波動和讀取、寫入和掃描的尾部延遲方面,KVell與現代ssd上現有的KVs相比如何?
\2. KVell如何在大型數據庫和生產工作負載上執行?
3.在超出其設計范圍的工作負載中使用KVell的利弊和限制是什么(小型項目、內存限制極端的環境和較舊的驅動器)?
6.2實驗設置硬件
硬件。我們使用第2節中描述的三種硬件配置。我們的重點是Config-Optane,因為它是三個驅動器中最近的配置。我們還在Config-Amazon-8NVMe中評估KVell,目的是展示大型配置中的系統行為。最后,我們評估了Config-SSD中的KVell,展示了在舊硬件上使用KVell的權衡。
工作負載。我們使用YCSB[10]和Nutanix的兩個生產工作負載。我們的重點是YCSB基准測試,因為它包含各種類型的工作負載,提供了KVell行為的更完整視圖。表4顯示了YCSB核心工作負載的摘要。我們評估所有YCSB工作負載的uniform和Zipfian密鑰分發。KV項目大小為1024B,小測試的數據集大小約為100GB (100M keys),大測試的數據集大小約為5TB (5B keys)。生產工作負載是兩個寫密集型工作負載,配置文件的為寫:讀:掃描的比例為57:41:2。KV項目大小在250B ~ 1KB之間,中位數400B。生產工作負載的總數據集大小是256GB。這兩種工作負載之間的差異是數據傾斜:生產工作負載1更接近統一的鍵分布,而生產工作負載2的傾斜程度更大。
現有的KVs, 我們將Kvell與四種最先進的系統相比:(1) RocksDB 6.2[15],一個由Facebook開發並在產業上大量使用的LSM KV存儲,(2)PebblesDB[43],一個最近的學術性LSM KV存儲,在LSM管理開銷上做出了顯著的改進(3)TokuMX [42],一個作為存儲引擎被MongoDB[35]支持的B epsilon樹[7],(4)WiredTiger 3.2[48]配置為使用B+樹。我們使用它的LevelDB接口來查詢WiredTiger,這個接口是從WiredTiger 3.1移植過來的。我們還嘗試了CouchDB[2],這是一種優化的B+樹實現,但在評估中沒有報告結果,因為它始終比TokuMX慢。

表4 YCSB核心工作負載描述。

圖****5. Config-Optane。 具有統一和Zipfian密鑰分配的YCSB工作負載的平均吞吐量。 KVell在寫入密集型工作負載方面高達5.8倍,在讀取密集型工作負載方面高達2.2倍,勝過次優競爭對手,同時提供了出色的掃描性能(與性能最佳的統一系統相比,而Zipfian則高出32%)。
系統配置。所有系統都被分配了相同數量的內存,用cgroups來設置。內存大小是數據集大小的三分之一,以確保從內存和持久存儲同時為請求提供服務。當數據庫大於可用RAM的3倍時,我們不使用cgroups(也就是說,應用程序可以使用整個可用RAM)。對於B樹,我們將塊大小配置為4KB。兩個LSM KVs配置為最多有5個級別和兩個128MB內存組件(一個活動的和一個不可變的)。此外,我們將write-ahead-log設置為1MB的緩沖區,這樣它只會偶爾被刷新,而不會對LSM KVs造成不利影響。
6.3主實驗設置的結果
6.3.1吞吐量
圖5顯示了KVell和競爭者系統在Config-Optane上的統一和Zipfian密鑰分布下YCSB的平均吞吐量。
寫密集型工作負載(YCSB A和****F)。在YCSB A工作負載上,KVell的性能比TokuMX高15倍,比WiredTiger和RocksDB高8倍,比PebblesDB高5.8倍(50%讀,50%寫)。在這個工作負載中,未緩存的讀產生1個I/O,緩存的讀產生0個I/Os,未緩存的寫產生2個I/Os(1個讀+ 1個寫),緩存的寫產生1個I/O(1個寫)。由於頁面緩存包含數據庫的1/3,平均1個請求在磁盤上產生1.17個I/Os,這意味着最大的理論吞吐量為500K IOPS/1.17 = 428K請求/s。KVell的平均吞吐量為420K請求/s。因此,KVell在不受cpu限制的情況下利用了其峰值帶寬的98%的磁盤,如圖6所示。在這個工作負載中,KVell將20%的時間花在由頁面緩存和內存中索引完成的B樹查找上,20%的時間花在I/O函數上,60%的時間花在等待上。
正如在第3節中看到的,LSM KVs受到壓縮成本的限制。WiredTiger受到日志爭用的限制(占總周期的50%)。TokuMX被共享數據結構的爭用和不必要的緩沖(兩者都占總周期的50%)所限制。
讀取密集型工作負載****(YCSB, C, D). KVell在YCSB上比現有的KVs性能高出2.2倍,在YCSB上比現有KVs高出2.7倍,在接近完全IOPS的情況下使用磁盤。在YCSB C上,KVell花費40%的時間在查找上,20%的時間在I/O函數上,40%的時間在等待上。在這些工作負載中,現有的KVs執行得不是很理想,因為它們共享緩存,或者因為它們沒有將讀請求批處理到磁盤(每個讀一個系統調用)。例如,RocksDB將41%的時間花費在pread()系統調用上,並且是cpu綁定的。
掃描工作負載****(YCSB E)。令人驚訝的是,KVell在掃描密集型工作負載中表現良好,無論是在uniform還是Zipfian密鑰分發中。注意,為了公平起見,我們修改了YCSB工作負載,以便它在KVell中以隨機順序插入鍵。默認情況下,YCSB按順序插入鍵。
在Zipfian工作負載上,KVell的性能至少比所有系統高出25%。由於工作負載傾斜,熱數據被捕獲在KVell的緩存中。無共享的設計,加上低開銷的緩存實現,使KVell比其競爭對手具有優勢。
在統一工作負載上,KVell的性能比PebblesDB高出5倍,比WiredTiger高出33%,並且提供了與RocksDB相當的性能(13.9K掃描/s vs. 14.4K掃描/s)。由於KVell並不在磁盤上對數據進行排序,所以它平均訪問每個掃描項的一個頁面。相比之下,對數據進行排序的RocksDB大約訪問每個頁面上的三個項(頁面大小為4K,項大小為1024K)。因此,RocksDB的最大吞吐量大約是KVell的三倍。圖7顯示了每個系統的吞吐量時間線,證實了這種推理。對於YCSB E, RocksDB的峰值吞吐量達到55Kops/s,而KVell的最大吞吐量為18Kops/s。然而,RocksDB的維護操作會干擾客戶端負載,導致大的波動,而KVell的吞吐量保持穩定在15Kops/s左右。因此,平均而言,RocksDB和KVell的表現相似。

圖****6. Config-Optane。 YCSBA上的KVell I / O帶寬(左)和CPU利用率(右)一致。 KVell使用整個磁盤I / O帶寬,而不會成為CPU綁定的。
6.3.2吞吐量隨時間變化
圖7展示了KVell、RocksDB、PebblesDB和WiredTiger的吞吐量隨時間的變化。吞吐量是每秒測量一次的。除了提供高平均吞吐量之外,KVell不會受到維護操作帶來的性能波動的影響。在YCSB A中,RocksDB的吞吐量降至最低1.4K請求/秒,PebblesDB的吞吐量降至10K請求/秒,而WiredTiger的吞吐量降至8.5K請求/秒。在只包含5%更新的掃描密集型工作負載中,RocksDB的吞吐量下降到1.8K掃描/s, PebblesDB下降到1.1K掃描/s。這些下降經常出現。
相比之下,KVell的性能在一個短暫的爬升階段(頁面緩存被填滿的時間)之后保持不變。KVell在YCSB a上至少維護400K /s的請求,在過渡階段之后維護15K /s的掃描。
6.3.3尾延遲
表5給出了KVell、RocksDB、PebblesDB和WiredTiger的第九十九百分位數和最大延遲。LSM KVs的尾部延遲超過9秒,而WiredTiger的最大延遲為3秒。這是由於維護操作直接影響了LSM和B樹KVs的尾部延遲。這樣的數字在LSM KVs中並不少見,在之前的工作[6]中也有報道。相比之下,KVell提供了較低的最大延遲(3.9ms),同時提供了強大的持久性保證。

表****5. YCSB A工作負載上99%的請求延遲和最大請求延遲(寫密集型)。

圖****7. Config-Optane。 KVell,RocksDB,PebblesDB和WiredTiger的吞吐量時間線,用於YCSB A(寫密集型),YCSB B(讀密集型),YCSB C(只讀)和YCSB E(掃描密集型),且密鑰分布均勻。 每秒測量。 與LSM和B樹KV相比,KVell提供高且穩定的吞吐量,而LSM和B樹KV由於維護操作而有很大的波動。
6.4備選配置和工作負載
6.4.1 Config-Amazon-8NVMe上的YCSB
圖8顯示了YCSB用於統一密鑰分發的平均吞吐量。所有系統都配置為使用30GB緩存(數據庫大小的1/3)。在YCSB A上,KVell的平均性能比RocksDB高6.7倍,PebblesDB高8倍,TokuMX高13倍,WiredTiger高9.3倍。對於YCSB E, KVell的性能略優於RocksDB,速度也比其他系統快。雖然結果的概要類似於上面的Config-Optane,但是我們可以看到KVell和競爭對手之間的性能差距是如何拉大的,因為KVell能夠更好地利用磁盤,而其他系統則是cpu綁定的。
AWS磁盤有不同的最大IOPS取決於讀/寫比率。KVell在混合讀寫操作的工作負載上最大化磁盤IOPS。在這些工作負載上,KVell將50%的時間用於等待,20%用於查找,10%用於管理回調(malloc和free), 20%用於I/O函數。KVell以3.8m req/s的速度在均勻密鑰分發中成為cpu綁定,使用了磁盤能夠維持的3.3m只讀IOPS的75%。然而,它仍然比現有的KVs快5.6倍。在Config-Amazon-8NVMe機器上,這樣的工作負載會受到cpu限制。當內核在I/O函數之外花費的時間超過其時間的一小部分時,這台機器就無法達到只讀IOPS的峰值。在一個微基准測試中,我們測量了每個I/O請求使用超過3us的CPU周期將可達到的IOPS限制為最大IOPS的75%。
盡管存在這些限制,但KVell甚至在掃描工作負載上也優於現有的KVs因為它的開銷較低(例如,沒有爭用頁面緩存和批處理請求到磁盤)。
與Config-Optane的情況一樣,LSM和B樹KVs受到吞吐量波動和高尾延遲(10秒以上)的困擾。在YCSB E中,RocksDB的每秒吞吐量經常低於6K掃描/s(相比之下,平均為57K掃描/s)。在運行維護操作時,RocksDB、PebblesDB和WiredTiger只能維持它們平均速度的一小部分(例如,RocksDB的YCSB a平均速度只有5%)。
我們在KVell中觀察到的最大延遲是YCSB A的20ms (99p 12ms), YCSB C的12ms (99p 2.9ms)。

圖****8. Config-Amazon-8NVMe。 YCSB平均吞吐量,統一密鑰分配。 KVell全面超越競爭對手系統。

圖****9. A. Config-Optane。 生產工作負載平均吞吐量。 在兩種工作負載下,KVell的性能均比所有競爭對手的系統高出約4倍。 B.Config-Amazon-8NVMe。 具有統一密鑰分配的YCSB 5TB數據集上的KVell吞吐量。 在所有工作負載中,KVell隨數據集的大小擴展。
6.4.2 Nutanix生產工作負載
圖9 A展示了RocksDB、PebblesDB、TokuMX、WiredTiger和KVell在Nutanix的兩個寫密集型生產工作負載上的性能。在第一個生產工作負載中,21%的讀請求來自緩存,而在第二個工作負載中,99%的讀請求來自緩存。就平均吞吐量而言,KVell在生產工作負載方面都比RocksDB(性能僅次於它的競爭對手)高出大約4倍。KVell還設法在第一個生產工作負載中最大化磁盤帶寬,並在第二個工作負載中使用78%的磁盤帶寬(在第二個工作負載中,磁盤僅用於更新,其他所有請求都來自緩存)。
與之前的實驗類似,RocksDB的吞吐量隨着時間的推移出現了很大的波動,低至3K請求/秒。另一方面,KVell在吞吐量方面表現出最小的波動。在延遲方面,KVell在最大延遲方面比RocksDB多3個數量級(RocksDB是8秒,KVell是2.5毫秒),在99百分位上比RocksDB多一個數量級(RocksDB是12毫秒,KVell是1.7毫秒)。
6.4.3配置在Config-Amazon-8NVMe上的YCSB 5TB數據庫
圖9 B顯示了在包含5B (5TB)密鑰的數據庫上,YCSB基准測試中的KVell的性能。與前面的實驗一樣,KVell被配置為使用30GB的頁面緩存。我們執行這個實驗是為了測試KVell在數據集大小方面的可伸縮性。
在這個實驗中,KVell只保存了0.6%的項。由於鍵的訪問是統一的,所以大多數讀和寫都是從磁盤提供的。在YCSB A中,這導致每個請求平均有1.5個I/Os,即,最大1.4m IOPS/1.5 = 935K請求/s。KVell實現866K請求/秒,92%的峰值帶寬。在YCSB C和E, KVell執行高達2.7m請求/秒和52K掃描/秒;數字比100M項數據集的數字略低,因為緩存的數據更少,並且在內存索引中查找平均要多花25%的時間。
6.5權衡和限制
6.5.1平均延遲
KVell分批提交I/O請求。在飽和IOPS(大批次)和最小化平均延遲(小批次)之間進行權衡。對於Config-Optane上的YCSB A, KVell可以最大限度地利用磁盤帶寬,每個worker處理64個批次的請求,平均延遲為158us。當批處理大小為32個請求時,平均延遲降低到76us,磁盤占用最大帶寬的88%。
6.5.2項規模的影響
對於未緩存的項,項大小不會影響Get()和Update()請求的性能。但是,對於保持條目有序的KVs,條目大小會影響掃描速度。圖10顯示了RocksDB,在執行壓縮時的RocksDB和KVell的平均吞吐量,其中YCSB E上的項大小不同(掃描占優勢)。由於KVell不對磁盤上的項進行排序,因此無論項的大小如何,它平均為每個掃描項讀取一個4KB的頁面,並且性能穩定。對於小條目,RocksDB優於KVell,因為它讀取的頁面比KVell少(64B項少64倍)。隨着項大小的增長,保持項排序的好處就會減少。
在運行壓縮時,對於所有的項大小,RocksDB只能維持其吞吐量的一小部分。雖然KVell不是為處理小項目而設計的,但它在所有配置中都提供可預測的性能。

圖****10. Config-Amazon-8NVMe。 RocksDB和KVell的YCSB E(掃描為主)吞吐量。
6.5.3內存大小的影響
表6給出了索引可以執行的查詢次數,這取決於Config-Amazon-8NVMe上索引大小與可用RAM的比率。當索引適合RAM時,工作人員可以在一個統一的工作負載上進行總共1500萬次/秒的查找(如果以Zipfian方式訪問索引,有更好的數據緩存,則為24M)。當索引比分配的RAM大5倍時,在統一的工作負載上,這個數目下降到109K /s。因此,當索引無法裝入RAM時,它們就會成為瓶頸。KVell只支持將索引部分刷新到磁盤,以避免索引超過可用RAM時崩潰,但沒有優化到在這種情況下工作。在實踐中,索引的內存開銷非常低,足以讓它們在RAM中適合大多數工作負載(例如,對於YCSB, 100GB數據集索引為1.7GB)。

表****6. Config-Amazon-8NVMe. 內存索引在具有100M鍵的受限內存環境中可以維持的操作數(查找或插入)。
6.5.4較舊的驅動器(Config-SSD)
在Config-SSD上,KVell在讀寫方面與LSM KVs相當,但是在掃描方面提供了相對較低的平均性能(KVell是3K掃描/s, RocksDB是15K掃描/s, PebblesDB是5K掃描/s)。在較舊的驅動器上,花費CPU周期來優化磁盤訪問通常是有益的,而且沒有系統是CPU綁定的。壓縮仍然會爭奪磁盤資源,造成延遲高峰(18s以上)和吞吐量波動(最極端的是11掃描/秒,而RocksDB的平均掃描次數是15K)。KVell的延遲僅受峰值磁盤延遲(在配置- ssd上為100ms)的限制。因此,在舊驅動器上使用KVell vs. LSM KVs是一種權衡:如果穩定性和延遲的可預測性很重要,那么KVell是比LSM KVs更好的選擇,但是對於掃描為主的工作負載,LSM KVs在舊驅動器上提供更高的平均性能。
6.6恢復時間
KVell的恢復時間取決於數據庫的大小,而其他系統的恢復時間主要取決於提交日志的最大大小。對於所有系統,我們都使用默認的提交日志配置,並測量YCSB數據庫上的恢復時間(100M密鑰,100GB)。我們通過在YCSB a工作負載中間終止數據庫來模擬崩潰,並測量Config-Amazon-8NVMe上數據庫的恢復時間。KVell需要6.6秒掃描數據庫並從崩潰中恢復,最大限度地提高磁盤帶寬。RocksDB和WiredTiger平均恢復時間分別為18s和24s。這兩個系統主要花費時間回放來自提交日志的查詢和重新構建索引。盡管KVell針對無故障操作進行了優化,並且必須掃描整個數據庫才能從崩潰中恢復,但它的恢復時間比現有系統的恢復時間要短。
7相關工作
7.1 ssd的KVs
LSM[36, 39]是最流行的書面優化KVs設計之一,例如,在LevelDB [11], RocksDB [15], HyperLevelDB [18], HyperDex[13]和Cassandra[16]中使用。將最初為硬盤驅動器設計的LSM kv改造為ssd已經做了很多工作。WiscKey[30]和HashKV[8]通過在LSM樹中保持鍵的排序來進行ssd優化,而值則分別存儲在日志中。與KVell類似,它們探索了打破順序性的想法,然而,兩個系統仍然執行昂貴的后台壓縮,這與客戶端操作相競爭。
PebblesDB[43]使用片段LSM樹,通過將片段LSM樹延遲到LSM樹的最后一層來減少壓縮的影響。SILK為LSM KVs提出了一個I/O調度器,以減少壓縮對客戶端請求延遲[6]的影響。三聯[5]使用不同技術的組合來減少寫放大。在高負載下,這三個系統最終都需要運行壓縮,從而導致客戶機操作的延遲峰值和吞吐量下降。
SlimDB[45]利用ssd,改進了LSM KVs中的索引和過濾方案,以獲得良好的讀取性能。NoveLSM [21], PapyrusKV[22]和NVMRocks[14]采用LSM設計來適應持久化存儲器,平滑了RAM和SSD之間的過渡。與KVell不同,這些LSM增強仍然保留了密鑰順序性。
BetrFS[19]和TokuMX[42]利用Bϵ樹[7]t o write-optimized之間取得平衡和讀取最優化的數據結構,仍然利用鑰匙的前后順序。正如第3節中所討論的,雖然樹對於小的鍵很有效,但是對於中、大的鍵,它們的性能很差,而這正是KVell的目標工作負載。
更普遍的是,在以前的工作中已經注意到CPU開銷限制了高速磁盤上的性能,並提出了新的KV設計。淤泥[28]i是一個KV的閃存驅動器設計。淤泥探索了使用小內存索引在磁盤上執行高效查找的想法,但是依賴於昂貴的后台操作將數據轉換和合並到磁盤上的HashStores和排序stores中。相反,KVell不需要后台操作就可以將數據持久化到磁盤上。Udepot[24]使用內存中的哈希表來查找存儲在NVMe驅動器上的數據。Udepot使用鎖來防止磁盤頁和內存結構上的競爭,使用垃圾收集器來避免磁盤碎片,並且不支持掃描。Papagiannis等人[40,41]提出了支持隨機I/O的備選KV設計,以減少SSD和NVMe驅動器上的CPU開銷。在杜鵑座[40],Papagiannis稱Bϵ樹等人修改,刪除緩沖指數水平。Tucana仍然依賴於葉級緩存來獲得良好的性能,也依賴於頁面緩存來緩存數據。在KVell中,我們說明了緩沖在高速驅動器上不再有用,頁面緩存嚴重限制了性能,單個共享數據結構上的爭用可能導致不必要的開銷。在Kreon[41]中,Papagiannis等人修改了LSM KVs以減少壓縮的開銷。它們只對磁盤上的鍵進行排序,並使用小型隨機I/Os將它們合並到LSM樹的不同級別。Kreon依靠緩沖來實現良好的性能,並且每分鍾只刷新它的10到磁盤來實現峰值吞吐量。與傳統的LSM設計相似,維護工作的積累會導致CPU使用量的激增,從而可能導致性能的波動。相反,KVell不使用任何維護操作,並提供穩定的吞吐量。
LOCS[47]、BlueCache[51]和NVMKV[32]是ssd上向操作系統公開FTL操作的有效KVs。這樣的ssd很少見,而且優化是與特定的硬件設計相關聯的。在這項工作中,我們說明了沒有必要進行特定於底層硬件的優化,並且可以通過通用系統調用以低延遲和最大磁盤帶寬執行I/O操作。Mickens等人[34]和Klimovic等人[23]采用了存儲分解,以便能夠利用數據中心的全部磁盤帶寬。KVell的目標是利用單機上的全部磁盤帶寬。
7.2KVs for byte-addressable persistent memory
大量的工作提出了為字節尋址持久性內存(PM)優化的數據結構[4,17,50,53,54]。HiKV[50]是一個混合KV商店。HiKV的重點是改進對PM中存儲的散列表執行的請求的延遲。哈希表用於快速查找,存儲在RAM中的全局B+樹用於加速范圍查詢。
Kvell面臨在塊設備上存在不同的延遲挑戰,在磁盤上無序存儲數據以避免昂貴的遷移,並使用perthread結構來避免系統中的爭用點。Bullet[17]使用交叉引用日志在KV存儲庫中創建DRAM和PM訪問之間的無縫過渡。Zuo等人[54]提出了一種針對PM優化的寫優化哈希索引方案。該技術優化了點查詢,但不支持有效的范圍查詢。各種樹型算法也已被調整,可以直接在PM中持久化數據,而不需要DRAM結構[3,9,25,38,46,52]。相比之下,KVell關注的是快速塊尋址ssd,我們相信這對於大型數據存儲來說仍然是更划算的選擇。SLM-DB[20]結合了LSM設計和B樹來利用PM。與KVell一樣,它維護B樹索引快速查找(SLM-DB的索引在PM中,而KVell在DRAM中維護)。SLM-DB依賴壓實操作對持久化數據進行排序。
7.3用於內存數據存儲的KVs
與KVell類似,MICA[29]和Minos[12]采用分區設計,將KV項目的不重疊碎片分配給每個系統線程。但是,Minos不是按鍵范圍分區,而是按KV項大小分區。Masstree[31]使用並發B+樹的組合,強調緩存的有效使用。與KVell不同,Masstree假設整個工作集適合內存。RAMCloud[37]是一個基於戲劇的KV存儲,強調快速並行恢復。Li等人[27]開發了一個全棧內存KV存儲,實現高吞吐量,考慮到硬件特性來創建一個高效的設計——硬件特性也被KVell考慮在內。
8.結論
現有的KV存儲設計在老一代ssd上是有效的,但在現代ssd上執行不是最優的。我們已經展示了一種依賴於sharenothing架構、磁盤上未排序的數據和隨機I/Os批的流線化方法在快速驅動器上的性能優於現有的KVs,甚至對於掃描工作負載也是如此。我們已經在KVell中建立了這些想法的原型。KVell提供了高且可預測的性能和強大的延遲保證。
致謝。我們要感謝我們的牧者邁克爾·卡明斯基(Michael Kaminsky)和匿名審稿人提供的所有有幫助的評論和建議。這項工作部分得到了瑞士國家科學基金會第513954和514009號撥款以及Nutanix公司的饋贈。
