【譯】利用uDepot獲得快速NVM存儲的性能

小Tao們在進行分佈式全閃存文件系統的預研工作,從學術圈和工業界尋找靈感,發現這篇前沿文章,並翻譯出來和大家學習分享。 “這是一篇KV存儲技術方向中少見的講述如何即高效又經濟地使用快速閃存設備的文章,觀點新穎,方法獨特,參考和使用價值很高。”    ——譯者

     

摘要

許多應用程序需要低延遲的KV存儲,爲了滿足這一需求,通常使用基於DRAM後端的KV存儲。然而,與傳統的SSD相比,最近基於新型NVM技術的存儲設備提供了前所未有的性能。如果存在一個KV存儲能夠充分發揮這些設備本身的性能,那麼將提供很多機會來加速應用程序並降低成本。然而,現有的KV存儲,都是爲較慢的SSD或HDD設備所構建,並不能充分利用快速NVM這類設備的性能。

本文提出了一個自底向上構建的KV存儲uDepot,它可以充分發揮基於塊的快速NVM設備的性能。爲了避免低效,uDepot經過精心的設計,使用兩級索引結構,可以根據插入存儲的條目數量來動態調整其所佔用的DRAM空間,並使用一種新的基於任務的IO運行時系統來最大化性能,這使得應用程序能夠充分利用快速NVM設備的原始性能。作爲一個嵌入式存儲,就吞吐量和延遲而言,uDepot的性能幾乎與快速NVM設備的原始性能相當,同時可以跨多個存儲設備和核心擴展。作爲一個存儲Server,uDepot在YCSB基準測試下顯著優於針對SSD設計的目前最先進的存儲。最後,通過使用基於uDepot實現的Memcache服務,證明了基於NVM設備的數據服務,可以以更低的成本,提供與基於DRAM的數據服務相當的性能。實際上,我們使用uDepot建立了一個Memcache雲服務,目前在公有云中作爲一個實驗性的產品提供使用。

1、引言

非易失性內存(NVM)技術的進步使得一類新的基於塊的存儲設備具有了前所未有的性能。這些設備被稱爲Fast NVMe Devices(以下簡稱FNDs),實現了每秒數十萬IO操作(IOPS)和低延遲,並構成DRAM和傳統SSD之間性能和成本折衷頻譜的一個離散點。爲了說明兩者的不同,對比了讀取4KiB數據的延遲,傳統的NVMe Flash SSD的延遲是80µs,相同的操作Optane Drive[88]需要7µs,Z-SSD [48,74]需要12µs。從另一個角度看,一個常見的萬兆以太網的TCP包的往返延遲是25µs-50µs,這意味着在商用數據中心使用FNDs的情況下,存儲將不再成爲性能瓶頸。

因此,相對於將所有數據放在內存中的數據存儲[26,35,72,73,78]流行架構趨勢來講,FNDs作爲一種平衡力量應運而生。具體來說,許多KV存儲將所有數據存儲在DRAM中[21,25,44,52,57,59,68,73],以滿足應用程序的性能要求。而基於FNDs的KV存儲,在成本和容量可擴展性方面爲基於DRAM的系統提供了一個有吸引力的替代方案。我們期望對於傳統SSD不能滿足其性能要求的一些應用,現在可以使用基於FNDs的KV存儲來滿足其性能要求。事實上,由於在許多常見的設置中,FNDs將性能瓶頸從存儲轉移到了網絡,所以基於FNDs的KV存儲可以提供與基於DRAM的存儲相當的性能。

然而,現有的KV存儲不能充分發揮FNDs的潛力。首先,將所有數據放在DRAM中的KV存儲,需要通過操作系統分頁功能來透明地使用FNDs,這導致了很差的性能[33]。其次,將數據存放在存儲設備中的KV存儲[8,24,31,50],即使是那些專門針對傳統SSD的存儲[3,19,20,58,60,84,87,91],在設計時也考慮到了不同的需求:較慢的設備,較小的容量,以及是否不需要針對設備和核心的擴展性。正如Barroso等人[7]指出的那樣,當存在耗時幾微秒的IO操作時,大多數現有的系統表現不佳。

基於以上的動機,我們提出了uDepot,一個爲了發揮FNDs原始性能而從頭開始設計的全新KV存儲。uDepot的核心部分是一個可以被應用程序作爲庫使用的嵌入式存儲。使用這個嵌入式存儲,構建了兩個網絡服務:一個是使用自定義的網絡協議的分佈式KV存儲;另一個是實現了Memcache[64]協議的分佈式緩存,可以作爲memcached[65]的完全替代,memcached[65]是一個廣泛使用的基於DRAM的緩存系統[5,70]。

在設計上,uDepot是有所側重的:它提供了高效數據訪問的流線型功能,側重於性能優化而不是更豐富的功能(例如,範圍查詢)。uDepot的高效表現在幾個方面:1)實現了低延遲,2)提供了每個核心的高吞吐量,3)存儲性能隨着設備和核心的增加而增加,4)就IO操作的數量和字節數而言,實現了較低的端到端的IO放大,最後,5)提供了一個較高的存儲容量利用率。要實現以上這些目標,需要在整個系統中進行多次的優化,但其中兩個方面尤其重要。首先,有效地訪問FNDs。大多數現有的KV存儲使用同步IO,這嚴重降低了性能,因爲同步IO依賴於內核調度的線程來處理併發性。相反,uDepot使用異步IO,如果可能的話,直接從用戶空間訪問存儲設備。爲此,uDepot建立在TRT之上,TRT是一個使用用戶空間協作調度的微秒級IO的任務運行時。其次,uDepot使用了一個高性能的DRAM索引結構,它能夠匹配FNDs的性能,同時保持它的內存佔用很小。(較小的內存佔用會導致高效的容量利用率,因爲索引相同的存儲容量所需的DRAM更少。)uDepot的索引結構是可調整大小的,可以根據存儲的條目數量來調整內存佔用。調整索引結構大小不需要任何IO操作,並且是增量執行的,因此造成的損耗最小。

總之,本文的貢獻有:

1)uDepot,一個KV存儲,可以發揮FNDs的原始性能,提供低延遲,高吞吐量,良好的擴展性,以及對CPU、內存和存儲設備的高效使用。

2)TRT,一個適用於微秒級IO的任務運行時系統,作爲uDepot的基石。TRT爲編寫充分利用快速存儲的應用程序提供了一個對程序員友好的框架。

3)uDepot的索引數據結構,能夠滿足其性能目標,同時具有較高的空間利用率,並且可以根據存儲的KV條目數量來動態調整其大小。

4)一個實驗評估,該評估表明uDepot與FNDs的性能是相當的,據我們所知,沒有任何現有的系統可以做到這一點。實際上,uDepot的性能大大超過了針對SSD優化的存儲,最高可達14.7倍,而且與基於DRAM的memcached服務的性能相當,這使得uDepot可以作爲Memcache的替代品,從而大大降低了成本。使用uDepot構建的Memcache雲服務已在公共雲[39]中作爲實驗產品使用。

本文的其餘部分組織如下。在第2節說明工作動機,在第3節討論TRT,並分別在第4節和第5節提出和評估uDepot。在第6節討論相關的工作,並在第7節進行總結。

 

2、背景與動機

在2010年,Ousterhout等人主張一種全內存的KV存儲,他們預測“在未來5-10年內,假設DRAM技術不斷進步,將有可能以低於5美元/GB的成本構建容量爲1-10PB的RAM雲存儲”[72]。從那以後,研究人員一直在進行着一場“軍備競賽”,以最大限度地提高全內存KV存儲的性能[10,21,44,52,57,68,73]。然而,與上述預測不同的是,DRAM的擴展規模正在接近物理極限[69],並且DRAM的成本也越來越高[22,40]。因此,隨着容量需求的增加,內存KV存儲依賴於橫向擴展能力,即通過添加更多的服務器來滿足所需的存儲容量。自然地,這是低效的並且代價高昂,因爲存儲節點的其餘部分(CPU、存儲)仍然未得到充分利用,而且爲了支持額外的節點,所需的其他資源也需要按比例增加(空間、電源供給、冷卻系統)。此外,儘管內存KV存儲的性能令人印象深刻,但許多這種存儲都依賴於高性能或專門的網絡(例如RDMA、FPGAs),無法部署在許多公共雲提供商提供的普通數據中心基礎設施中。

(a)當以2的冪爲單位增加隊列深度時,使用SPDK的perf基準工具測試兩種NVMe設備(NVMe Flash SSD和Optane)的4KiB隨機讀取的延遲和吞吐量[86]。

(b)使用不同的IO工具(aio,spdk)和存儲引擎(WiredTiger,RocksDB)測試的4KiB隨機讀取的總吞吐量。

圖1

 

       最近發佈的快速NVMe設備(FNDs)爲DRAM提供了一種經濟有效的替代方案,其性能顯著優於傳統SSD(圖1a)。具體來說,基於3D XPoint(3DXP)的Optane驅動器,可以提供一個接近0.6Mops/s的吞吐量,和7µs的讀取訪問延遲,這個延遲與具有80µs或更高延遲[34]的傳統SSD相比,低了一個數量級。此外,三星發佈了Z-SSD,這是一個利用Z-NAND[74]技術的新設備[89],和Optane設備有類似的性能特徵,獲得了12µs的讀取訪問延遲。因此,有效使用FNDs的KV存儲爲其DRAM對手提供了一個有吸引力的替代方案。特別是在使用商用網絡的環境中(例如10 Gbit/s以太網),FNDs將系統瓶頸從存儲轉移到了網絡,而基於DRAM的KV存儲的全部性能無法通過這種網絡獲得。

       現有的KV存儲在構建時考慮了較慢的設備,從而無法提供FNDs的原始性能。作爲一個令人激勵的例子,考慮一個多核心多設備的系統,目標是使用20個CPU核心和24個NVMe驅動器來降低成本,並將設備的性能與兩種常見的存儲引擎RocksDB和WiredTiger的性能進行比較。這些引擎代表了現代KV存儲設計的縮影,分別使用了LSM樹和B樹。我們使用Linux異步IO工具(aio)和SPDK(一個用於從用戶空間直接訪問設備的庫)的微基準測試工具來測量設備性能。對於兩個KV存儲,分別加載5千萬個4KiB的條目,並使用相應的微基準工具測試隨機GET操作的吞吐量,同時設置適當的緩存大小,以便將請求直接下發到存儲設備。一方面,即使將RocksDB和WiredTiger調到最佳狀態,其性能也無法分別超過1 Mops/s和120 Kops/s。另一方面,存儲設備本身可以使用異步IO獲得3.89 Mops/s的性能,使用用戶空間IO(SPDK)可以獲得6.87 Mops/s的性能。(關於這個實驗的更多細節以及uDepot如何在相同的設置中執行,可以在第5節中找到說明。)

       總的來說,這些KV存儲沒有充分發揮設備的性能,儘管專家們可能會調整它們以提高其性能,但它們的設計存在着根本問題。首先,這些KV系統是爲速度較慢的設備構建的,使用的是同步IO,而同步IO在微秒級別[7]上存在很大的問題。其次,使用了LSM樹或B樹,這是已知的會造成顯著的IO放大。例如,在之前的實驗中,RocksDB的IO放大爲3倍,WiredTiger爲3.5倍。再次,在DRAM中緩存數據,這需要額外的同步,但也由於內存需求而限制了可擴展性,最後,它們提供了許多額外的特性(例如事務),這可能會對性能造成影響。

       uDepot遵循了不同的設計思路:它是自底向上構建的,爲了發揮FNDs的原始性能(例如,通過消除IO放大),只提供KV存儲的基本操作,不緩存數據,並使用基於TRT的異步IO,接下來將對TRT進行說明。

 

3、TRT:用於快速IO的任務運行時系統

廣義上講,有三種IO方式訪問存儲:同步IO、異步IO和用戶空間IO。大多數現有的應用程序通過同步系統調用(如pread、pwrite)訪問存儲。因爲已經建立了良好的網絡連接[45],同步IO並不會伸縮,因爲處理併發請求需要每個線程對應一個請求,這會導致上下文切換,當等待處理的請求的數量高於CPU核心的數量時,上下文切換會降低性能。因此,就網絡編程來說,要想利用快速IO設備的性能就需要利用異步IO[7]。例如,Linux AIO[43]允許從單個線程批量地發送(和接收)多個IO請求(及其完成響應)。然而,僅執行異步IO本身並不足以完全獲得FNDs的性能。爲了構建可高效訪問快速IO設備的應用程序,產生了一套新的原則。這些原則包括從數據路徑中移除內核,支持對中斷進行輪詢,最小化(如果不能完全排除的話)跨CPU核心的通信[9,75]。雖然上述技術設計之初主要針對快速網絡,但它們也適用於存儲[47,94]。與作爲內核工具的Linux AIO不同,SPDK[85]等用戶空間IO框架通過避免上下文切換、數據複製和調度開銷來實現性能最大化。另一方面,並不總是能夠使用這些方法,因爲它們需要直接訪問設備(在許多情況下這是不安全的),而且許多環境還不支持這些方法(例如,雲虛擬機)。

      因此,一個高效的KV存儲(或類似的應用程序)需要異步地訪問網絡和存儲,如果情況允許,需要儘量使用用戶空間IO來最大化性能。現有框架(如libevent[55])不適合此用例,因爲對於事件檢查來說,它們假定應用程序只檢查單個事件端點(例如,epoll_wait[46]系統調用)。在對存儲和網絡的組合訪問時,可能存在需要檢查的多個事件(和事件完成)端點。例如,可能將epoll_wait用於網絡套接字,將io_getevents[42]或SPDK的完成處理調用用於存儲。此外,這些框架中有許多是基於回調的,由於存在所謂的“stack ripping”問題,使得回調使用起來很麻煩[1,49]。

      爲了使訪問FNDs既高效又對程序員友好,我們開發了TRT(Task-based Run-Time),這是一個基於任務的運行時系統,其中的任務是協作調度的(即不可搶佔),每個任務都有自己的堆棧。TRT會產生許多線程(通常每個核心一個線程),並在每個線程上執行一個用戶空間調度器(scheduler)。調度器在自己的堆棧中執行。在調度器和任務之間切換是輕量級的,包括保存和恢復大量寄存器,而不涉及內核。在協作調度中,任務通過執行適當的調用自動切換到調度程序。一個到調度器的這種調用示例是yield,它將執行延遲到下一個任務。還有生成任務的調用和同步調用(用於等待和通知)。同步接口基於編程語言的Futures[29,30]實現。因爲TRT儘量避免跨CPU核心通信,所以它爲同步原語提供了兩種變體:核心內部和核心之間。核心內部原語的效率更高,因爲只要臨界區不包含切換到調度器的命令,就不需要同步調用來防止併發訪問。

       基於上述原語,TRT爲異步IO提供了基礎設施。在典型的場景中,每個網絡連接將由不同的TRT任務提供服務。爲了啓用不同的IO後端和工具,每個IO後端實現一個poller任務,該任務負責輪詢事件並通知其他任務來處理這些事件。爲了避免跨CPU核心通信,每個核心運行自己的poller實例。因此,當任務具有掛起的IO操作時,不能跨核心移動。Poller任務與任何其他任務一樣由調度器調度。

      TRT目前支持四種後端:Linux AIO、SPDK(包括單設備和RAID-0多設備配置)、Epoll以及正在開發中的用於RDMA和DPDK的後端。每個後端都提供了一個低級接口,允許任務發出請求和等待結果,並在此基礎上構建了一個高級接口,用於編寫類似於其同步實現對應的代碼。例如,trt::spdk::read()調用將向SPDK設備隊列發出read命令,並調用TRT調度器暫停任務執行,直到收到處理SPDK完成的poller任務的通知爲止。

      爲了避免同步,運行在不同核心上的所有後端的poller使用不同的端點:Linux AIO poller使用不同的IO上下文,SPDK poller使用不同的設備隊列,Epoll poller使用不同的控制文件描述符。

 

4、uDepot

uDepot支持對可變大小的key和value執行GET、PUT和DELETE操作(第4.5節)。最大的key和value大小分別是64 KiB和4 GiB,沒有最小限制。uDepot直接操作設備,並執行自己的(日誌結構的)空間管理(第4.1節),而不是依賴於文件系統。爲了最小化IO放大,uDepot在DRAM中使用一個兩級哈希表作爲一種索引結構(第4.2節),允許使用單個IO操作就能實現KV操作(如果不存在哈希衝突),但是缺乏對範圍查詢的高效支持。通過運行時調整索引結構的大小(resizing),以適應存儲的KV條目的數量,該索引結構可以管理PB級的存儲,同時仍然保持高效的內存使用。重新調整大小(第4.3節)造成的性能損耗很小,因爲它是增量的,不會引起額外IO。uDepot不緩存數據,使用持久化存儲(第4.4節):當PUT(或DELETE)操作返回時,數據已存儲在設備中(而不是在操作系統緩存中),在發生崩潰時其數據可以被恢復。uDepot支持多種IO後端(第4.6節),允許用戶根據自己的配置來獲得性能最大化。uDepot目前可以以三種方式使用:作爲可鏈接到應用程序的一種嵌入式存儲,作爲一種網絡分佈式存儲(第4.7節),或者作爲一個實現Memcache協議的緩存系統[64](第4.8節)。

 

4.1 存儲設備空間管理

     uDepot使用一種日誌結構的方法管理設備空間[67,79],即空間按順序分配並且通過垃圾收集器(GC)處理碎片。使用這種方法有三個原因。首先,它在NAND Flash等特殊存儲上取得了良好的性能。其次,即使對於DRAM等非特殊存儲[80],它也比傳統的分配方法更有效。第三,uDepot的一個重要用例是緩存,在協同設計GC和緩存時存在大量的優化機會[81,84]。空間分配是通過SALSA[41]的日誌結構的分配器的用戶空間端口實現的。設備空間被分割成片段(segments,默認大小爲1 GiB),這些片段又被分割成顆粒塊(grains,通常大小等於IO設備的塊大小)。有兩種類型的片段:用於存儲KV記錄的KV片段和用於沖刷(flushing)索引結構以加速啓動的索引片段(第4.4節)。uDepot調用SALSA(順序地)分配和釋放顆粒塊。SALSA執行GC功能並向上調用uDepot將特定的顆粒塊重新定位到空閒片段[41]。SALSA的GC[76]是基於貪婪算法[12]和環形緩衝區(circular buffer,CB)[79]算法的一般變體,該變體算法利用CB的老化因子來增強貪婪策略。

圖2:uDepot在DRAM中維護的索引結構(目錄和表)。FNDs空間被分割成兩種類型的片段:存儲內存索引表的索引片段和存儲KV記錄的KV片段。

 

4.2 索引數據結構

   uDepot的索引是一個全內存的兩級映射目錄,用於將key映射到存儲中的記錄位置(如圖2)。該目錄被實現爲一個原子指針,指向一個只讀的指針數組,其中的指針指向哈希表。

 

哈希表。每個哈希表實現了一個改進的跳房子(hopscoth)[37]算法,其中一個條目存儲在一個連續位置的範圍內,該範圍被稱爲鄰域(neighborhood)。實際上,跳房子算法的工作類似於線性探測,但限定探測的距離在鄰域內。如果一個條目被哈希到了哈希表數組中的索引i,並且H是鄰域大小(默認爲32),那麼該條目可以存儲在從i開始的任意H個有效條目位置。在接下來的段落中,將i稱爲鄰域索引。本文選擇跳房子算法是因爲其高佔用率(high occupancy)、高效的緩存訪問、有界的(bounded)lookup性能(即使在高佔用率的情況下)和簡單的併發控制[21]。

本文對原來的算法做了兩個修改。首先,使用條目數量的二次冪對哈希表進行索引,類似於組相聯緩存[38]:使用從key計算而來的指紋的最低有效位(LSB)來計算鄰域索引。這允許在重新調整大小期間有效地重建原始指紋,而不需要完全存儲指紋或執行IO來獲取key並重新計算。

其次,本文沒有維護每個鄰域的位圖,也沒有維護每個鄰域的所有條目的鏈表,而這兩者是原始算法所建議使用的[37]。後者將使默認配置的內存需求增加50%(8字節的條目,鄰域大小爲32,每個條目4字節)。一個鏈表至少需要兩倍的內存(假設使用8字節指針和單鏈表或雙鏈表);更不用說增加的複雜性了。本文不使用位圖或鏈表,而是直接對條目的lookup和insert操作執行線性探測。

同步。本文使用一組鎖來進行併發控制。這些鎖用於保護哈希表的不同區域(lock regions),其中每個區域(默認爲8192個條目)嚴格大於鄰域的大小。根據鄰域所在的區域獲取鎖;如果一個鄰域跨越兩個區域,則按順序獲取第二個鎖。(最後一個鄰域沒有環繞到哈希表的開頭,因此保持了鎖的順序。)此外,爲了避免跨越兩個以上鎖區域的insert操作,不會對超過兩個區域以外的條目進行替換。因此,一個操作最多使用兩個鎖,並且,假設具有良好的key分佈,那麼鎖爭用可以忽略不計。

哈希表條目。每個哈希表條目由8個字節組成:

 

其中pba字段包含了KV對所在存儲位置的顆粒塊偏移量。爲了能夠利用大容量設備,在這個字段使用了40比特,因此能夠索引PB級的存儲(例如,對於4KiB的顆粒塊可索引4PiB的存儲)。Value爲全1的pba表示一個無效的(空閒的)條目。

使用11比特表示存儲在顆粒塊中的KV對的大小(kv_size)。當使用4 KiB大小的顆粒塊時,對於KV對的GET操作,允許發出一個最大8MiB大小的讀IO。大於該值的KV對需要二次操作。一個KV大小爲0的有效條目表示一個已刪除的條目。

圖3:如何使用key指紋來確定一個key的鄰域。目錄d包含4個表,圖中只顯示了其中的兩個(ht00和ht10)。

 

其餘13比特按如下方式使用。內存索引對一個35比特的指紋進行操作,該指紋是key的一個64比特cityhash[14]哈希的LSBs(如圖3)。將指紋分爲一個索引(27比特)和一個標記(8比特)。指紋索引用於索引哈希表,允許每個表最多有227個條目(默認值)。從表的位置重構指紋需要:1)條目的鄰域偏移量,2)指紋標記。在每個條目上同時存儲了8比特的指紋標記(key_fp_tag)和5比特的鄰域偏移量,以允許每個鄰域存儲32個條目(neigh_off)。因此,如果一個條目在表中的位置爲λ, 則它的鄰域索引爲λ-neigh_off,並且它的指紋是key_fp_tag:(λ-neigh_off)

容量利用率。有效地利用存儲容量不但需要能夠對其尋址(pba字段),而且需要有足夠的哈希表條目。使用指紋標記的LSBs(總共8比特)來索引目錄,uDepot的該索引允許有28個表,每個表有227個條目,總共有235個條目。如果以增加衝突爲代價,還可以通過使用最多5個LSBs(從索引目錄的指紋)來進一步增加目錄大小,允許使用213個表。這樣可以使用最多5個鄰域位,因爲現有的跳房子算法的碰撞機制最終將填充哈希表中沒有鄰域開始的位置。如果考慮KV對的平均大小爲1 KiB,那麼這將允許使用最多1 PiB(235+5·210)的存儲。根據預期的工作負載和可用的容量,用戶可以通過配置相應的哈希表大小參數來最大化容量利用率。

操作。對於lookup操作,會生成一個key指紋。使用指紋標記的LSBs來索引目錄,併爲這個key找到其所在的哈希表(如果指紋標記還不夠,還可以使用前面描述的指紋LSBs)。接下來,使用指紋索引對哈希表進行索引,以找到鄰域(同樣參見圖3)。然後在鄰域中執行線性探測,並返回指紋標記(key_fp_tag)匹配的條目(如果存在的話)。

對於insert操作,哈希表和鄰域的定位過程與lookup操作一樣。之後對鄰域執行線性探測,如果沒有匹配指紋標記(key_fp_tag)的現有條目,那麼insert將返回第一個空閒條目,如果存在的話。然後用戶可能填充這個條目。如果不存在空閒條目,那麼哈希表將執行一系列的置換操作,直到在鄰域內找到一個空閒條目。如果查找空閒條目失敗,則返回一個錯誤,此時調用者通常會觸發重新調整大小操作。如果存在匹配項,那麼insert將其返回。調用者決定是更新現有的條目,還是從停止的地方開始繼續搜索一個空閒條目。

 

圖4:從具有2個哈希表的目錄(d)轉換到具有4個哈希表的目錄(d‘)的增量調整大小示例。在調整大小期間,插入操作將數據從ht0的鎖區域(包含插入條目的鄰域)複製到兩個哈希表(ht00和ht10)。

 

 

4.3 調整大小操作 

      索引數據結構的最優大小取決於KV記錄的數量。將索引數據結構的大小設置得太低會限制可以處理的記錄的數量。設置得過高則可能會浪費大量內存。例如,假設KV記錄平均大小爲1KiB,1 PiB的數據集將需要大約8TB的內存。

      uDepot通過根據工作負載動態調整索引數據結構大小來避免這個問題。重新調整大小操作速度很快,因爲不需要對設備執行任何IO,並且對正常操作的干擾最小,因爲是增量執行的。

        目錄以2的次冪擴容,因此在任意點上,索引保存n∗2m個條目,其中m是擴容(grow)操作的數量,n是每個哈希表中的條目數量。只需要指紋來確定新的位置,因此不需要IO操作來將哈希條目移動到其新位置。一種簡單的方法是一次移動所有條目,但是,這會導致用戶請求的嚴重延遲。相反,本文使用一種增量方法(如圖4)。在重新調整大小階段,新結構和舊結構都得到了維護。按照鎖區域的粒度將條目從舊結構遷移到新結構。每個鎖的“遷移”位表示該區域是否已經遷移。“重新調整大小”的一個原子計數器用於跟蹤是否完成了總的調整大小操作,並初始化爲鎖的總數。

       遷移是由無法找到空閒條目的插入操作觸發的。第一個插入失敗將觸發重新調整大小操作,並設置一個新的影子(shadow)目錄。隨後的插入操作將它們持有的鎖(一個或兩個)下的所有條目遷移到新結構,設置每個鎖的“遷移”位,並減少“重新調整大小”計數器(一個或兩個)。在重新調整大小期間,在單獨的線程中預先分配哈希表,以避免延遲。當所有條目都從舊結構遷移到新結構時(“resize”計數爲零),舊結構的內存將被釋放。在重新調整大小期間,根據lookup區域的“遷移”狀態,lookup操作需要檢查新結構或舊結構。

 

4.4 元數據和持久性 

uDepot在三個不同的級別上維護元數據:每個設備、每個片段和每個KV記錄。在設備級別,uDepot配置信息與唯一的種子以及校驗和一起存儲。在每個片段的頭部,它的配置信息(擁有的分配器、片段幾何結構等)與匹配設備元數據的時間戳和校驗和一起存儲。在KV記錄級別(如圖2),uDepot爲每個KV對前面加入一個6字節的包含key大小(2字節)和value大小(4字節)的元數據,在KV對後面追加(避免不完整頁問題)一個2字節的校驗和,該校驗和匹配片段元數據(未對數據進行計算)。設備和片段元數據分別需要128字節和64字節,它們存儲在按顆粒塊對齊的位置,其開銷可以忽略不計。主要開銷在於每個KV記錄元數據,這取決於KV的平均大小;對於平均1KiB的規模,總開銷爲0.8%。

爲了加快啓動速度,會將全內存的索引表刷新到持久存儲中,但不能保證它們是最新的:真正的最新持久來源是日誌。刷新到存儲發生在uDepot正常關閉期間,但也會定期執行用來加快恢復過程(recovery)。在初始化之後,uDepot遍歷索引片段,恢復索引表,並重新構造目錄。如果uDepot被正常地關閉(使用校驗和和唯一會話標識符來對此檢查),那麼索引就是最新的。否則,uDepot從KV片段中找到的KV記錄來重建索引。相同key的KV記錄(新value或tombstone)使用片段版本信息來消除歧義。因爲在恢復期間沒有讀取數據(只有key和元數據)操作,所以在崩潰後重新啓動通常需要幾秒鐘。

 

4.5 KV操作 

對於GET操作,會計算key的64位哈希值,並對關聯的哈希表區域進行鎖定。然後執行lookup(見第4.2節),這會返回零個或多個匹配的哈希條目。在lookup之後,表的區域會被解鎖。如果沒有找到匹配的條目,則該key不存在。否則,對於每個匹配條目,會從存儲中取出相應的KV記錄;或找到完整匹配的key並返回其value,或該key確實存在。

對於PUT操作,首先在非原地的(out-of-place)日誌中寫入KV記錄。隨後,使用insert(參見第4.2節)哈希表函數執行類似於GET(key哈希、鎖等)的操作來確定key是否已經存在。如果key不存在,則向跳房子表插入一個新條目(如果存在空閒空間)——如果不存在空閒條目,則觸發重新調整大小操作。如果一個key已經存在,將其之前條目的顆粒塊失效,並使用KV記錄的新位置(pba)和新記錄大小原地的(in-place)更新表條目。請注意,與GET一樣,在對匹配的哈希表條目執行讀取IO操作時,不需要持有表區域鎖。但是,與GET不同的是,如果找到了記錄,PUT將重新獲取鎖,並重復執行lookup以檢測同一key上的併發修改操作:如果檢測到這樣的併發修改操作存在,則第一個要更新哈希表條目的操作將獲得勝出。如果PUT失敗,那麼將其在lookup之前寫入的顆粒塊失效,並返回適當的錯誤信息。PUT默認情況下更新現有條目,但提供一個可選參數,用戶可以選擇:(1)僅在key存在時執行PUT,或(2)僅在key不存在時執行PUT。

DELETE操作幾乎與PUT相同,只是它寫的是一個tombstone條目,而不是KV記錄。Tombstone條目用於標識從日誌進行恢復的已刪除的條目,並在GC期間將其回收。

 

4.6 IO後端 

默認情況下,uDepot繞過頁面緩存直接(O_DIRECT)訪問存儲。這樣可以防止不受控制的內存消耗,同時避免了由於從多核併發訪問頁面緩存而導致的可擴展性問題[96]。uDepot支持通過同步IO和異步IO訪問存儲。同步IO是由uDepot的Linux後端實現的(這樣稱呼是因爲程序調度留給了Linux)。儘管該Linux後端性能很差,但它允許uDepot被現有的應用程序直接使用而無需做出修改。例如,我們實現了一個使用Linux後端的uDepot JNI接口。該實現很簡單,因爲大多數操作直接被轉換爲系統調用。對於異步IO和用戶空間IO,uDepot使用了TRT,從而可以利用SPDK或內核LinuxAIO工具。

 

4.7 uDepot Server 

嵌入式uDepot爲用戶提供了兩個接口:一個接口可以操作使用任意的(連續的)用戶緩衝區,另一個接口可以使用一個數據結構,用來保存從uDepot分配的緩衝區的鏈表。前一種接口在內部通過使用後一種接口實現,它更簡單,但本質上效率低。對於許多IO後端存在的一個問題是,需要在IO緩衝區和用戶提供的緩衝區之間進行數據複製。例如,執行直接IO需要對齊的緩衝區,而SPDK需要通過其運行時系統分配緩衝區。本文的服務端使用第二個接口,因此它可以直接從接收(發送)緩衝區執行IO。服務端使用TRT實現,並使用epoll後端進行聯網。首先,產生一個用於接收新的網絡連接的任務(譯者注:簡稱任務A)。任務A向poller註冊,並在有新的連接請求時得到通知。當有新的連接請求時,任務A將檢查是否應該接收新的連接請求並在(隨機選擇的)TRT線程上生成新任務(譯者注:簡稱任務B)。任務B將向本地的poller註冊,以便在其連接上有傳入數據時得到通知。任務B通過向存儲後端(Linux AIO或SPDK)發出IO操作來處理傳入的請求。發出IO請求後,任務B延遲其自身的執行,並且調度器將運行另一個任務。存儲poller負責在IO完成(completion)可用時喚醒延遲執行的任務B。然後被喚醒的任務B將發送正確的應答並等待新的請求到來。

 

4.8 Memcache Server 

uDepot還實現了Memcache協議[64],該協議廣泛用於加速從較慢的數據存儲(如數據庫)中檢索對象。Memcache的標準實現是使用DRAM[65],但是使用SSD的實現也存在[27,61]。

uDepot的Memcache實現類似於uDepot Server(第4.7節):它避免了數據複製,使用epoll後端進行聯網,使用AIO或SPDK後端訪問存儲。Memcache相關的KV元數據(例如,過期時間、標誌等)被附加在value的末尾。過期以一種惰性(lazy)的方式實現:在執行lookup時檢查是否過期(對於Memcache的GET或STORE命令)。

uDepot的Memcache利用了緩存回收和空間管理GC設計空間的協同效應:實現了一個合併的緩存回收和GC過程,在IO放大方面將GC清理的開銷降低到零。具體來說,在片段級別使用了基於LRU策略的GC(第4.1節):在緩存命中時,包含KV的片段被更新爲最近訪問的片段;當運行期間的空閒片段不足時,選擇最近最少使用的片段進行清理,並將該片段在uDepot目錄中的有效KV條目(過期的和未過期的)失效,此時這個片段可以自由地被重新填充,而不需要執行任何重定位IO。即使存在持續的隨機更新操作情況下,該方案也可以保持穩定的性能,同時也降低了在空間管理級別(SALSA)上的過度配置程度到最低限度(提供足夠的備用片段支持write-streams),從而在空間管理級別上最大化容量利用率。該方案的一個缺點是潛在地降低了緩存命中率[81,93];本文認爲這是一個很好的折衷,因爲緩存命中率會通過更大的緩存容量(由於減少了過度配置)來平攤。uDepot的Memcache Server是目前在公共雲[39]中一個實驗性的memcache雲服務的基礎。

 

4.9 實現說明 

uDepot採用C++11實現。值得注意的是,uDepot的性能需要許多優化:使用本地CPU核心的slab分配器從數據路徑消除堆分配,使用大內存頁面(huge pages),與動態多態相比更傾向於靜態,避免使用scatter-gather型IO帶來的數據複製,在IO緩衝區的適當位置存放網絡數據,使用批處理等等。

 

5、評估

本文在一臺具有2個10核的Xeon CPU(配置爲以最大頻率2.2GHz運行)、125 GiB的 RAM和運行Linux 4.14內核的機器上進行實驗(包括對KPTI[16]的支持——這是對增加了上下文切換開銷的CPU安全問題的一個緩解)。該機器配置了26個NVMe驅動器:2個Intel Optanes(P4800X 375GB HHHL PCIe)和24個Intel Flash SSD(P3600 400GB 2.5in PCIe)。

(a)5千萬條目的吞吐量(無擴容)(b)10億條目的吞吐量(4次擴容)(c)操作延遲

圖5 映射結構的性能結果

 

5.1 索引結構 

首先評估索引結構分別在有和沒有重新調整大小操作時的性能。使用512 MiB(226個條目)的哈希表,每個表有8192個鎖。實驗包括預先插入一些隨機key,然後對這些key執行隨機lookup。考慮兩種情況:1)插入5千萬(5·107)個條目,其中沒有發生重新調整大小操作;2)插入10億(109)個條目,其中發生了4次擴容操作。本實驗對比了libcuckoo[53,54],這是一種目前最先進的哈希表實現,通過運行它附帶的基準測試工具(universal_benchmark),爲5千萬和10億條目測試分別配置了226和230的初始容量。結果如圖5所示。對於5千萬條目,本文的實現取得了每秒8770萬次的lookup和每秒6400萬次的insert,分別比libcuckoo的性能快5.8倍和6.9倍。對於10億條目,由於存在重新調整大小操作,insert速率下降到了23.3Mops/s。爲了更好地理解重新調整大小的開銷,執行了另一次測試,在其中對延遲時間進行了採樣。圖5c顯示了由此產生的中值延遲和尾部延遲。需要複製條目的insert操作的延遲出現在99.99%的百分位數中,延遲爲1.17 ms。注意,這是一種更糟糕的情況,只執行insert而不執行lookup。可以通過增加鎖的數量來降低這些慢速insert操作的延遲,但是以增加內存使用爲代價。

 

5.2 嵌入式uDepot 

接下來,將研究uDepot作爲嵌入式存儲的性能。我們的目標是評估uDepot利用FNDs的能力,並比較三種不同IO後端的性能:使用線程的同步IO(Linux-directIO)、使用Linux異步IO的TRT(trt-aio)和使用SPDK的TRT(trt-spdk)。本文對兩個特性感興趣:效率和可擴展性。首先,應用程序被限制使用一個核心和一個驅動器(第5.2.1節)。其次,應用程序使用24個驅動器和20個核心(第5.2.2節)。

本文使用了一個自定義的微基準工具來爲uDepot生成負載。對微基準工具進行了標註,以對操作的執行時間進行採樣,並使用這些執行時間來計算平均延遲。在接下來的實驗中,使用8-32字節之間隨機大小的key和4KiB大小的value。執行5千萬次隨機PUT,並對已插入的key執行5千萬次隨機GET。

 

5.2.1 效率(1個驅動器,1個核心)

本節使用一個核心和一個Optane驅動器來評估uDepot及其IO後端的效率。將uDepot的性能與設備的原始性能進行了比較。

將所有線程綁定在一個核心上(該核心與驅動器在同一個NUMA節點上)。對5.2節中描述的工作負載分別使用長度爲1、2、4、…、128的隊列深度(qd)和三個不同的IO後端。對於同步IO(Linux-directIO),產生的線程數量等於qd。對於TRT後端,產生一個線程和等於qd的任務數量。Linux-directIO和trt-aio都使用直接IO來繞過頁面緩存。

(a)PUT操作(b)GET操作

圖6:運行在單個核心和單個存儲設備的uDepot。在不同IO後端和不同隊列深度下,在4KB的均勻隨機工作負載的中值延遲和吞吐量。

 

GET的結果如圖6b所示,PUT的結果如圖6a所示。Linux-directIO後端表現最差。在很大程度上,這是因爲它爲每個正在運行的請求使用一個線程,導致操作系統頻繁地切換上下文,以允許所有這些線程在單個核心上運行。Trt-aio後端通過使用TRT的任務來執行異步IO和對多個操作執行單個系統調用來改進性能。最後,trt-spdk後端顯示了(正如預期的)最佳性能,因爲避免了到內核的切換。

(a)隊列深度爲1的中值延遲(b)隊列深度爲128的吞吐量

圖7:運行在單個核心和單個存儲設備的uDepot,在4KB的均勻隨機GET操作負載下的情況。

 

本文考慮了在比較uDepot和設備性能過程中表現較好的GET操作。主要關注單個排隊請求(qd = 1)時的延遲和較高隊列深度時(qd=128)的吞吐量。圖7a顯示了每個後端在qd=1時的平均延遲。該圖包括兩行用來描述設備在類似工作負載下的原始性能,這些性能數值是使用與每個IO設施相適應的基準工具獲得的。也就是說,對於一個核心和一個設備,在qd=1時對整個設備執行4KiB大小的隨機讀操作,設備上的數據是隨機寫入的(預先設定好的)。fio raw線顯示了使用libaio(即Linux AIO)後端的fio[23]獲得的延遲,而對於spdk raw線,則是使用了SPDK的perf實用程序[86]獲得的性能。使用trt-spdk的uDepot取得了7.2µs的延遲,這非常接近直接使用SPDK取得的原始設備的延遲(6.8µs)。使用trt-aio後端獲得的延遲是9.5µs,與相之應的使用fio的原始設備延遲是9µs。一個trt-aio後端的初始實現使用了io getevents()系統調用來接收IO完成,這導致了更高的延遲(接近12µs)。通過在用戶空間中實現這個功能後提高了性能[17,28,77]。當使用這種技術時(fio的userspace_reap選項),fio的延遲保持不變。圖7b顯示了在較高隊列深度(128)時每個後端取得的吞吐量。Linux-directIO達到了200kops/s,trt-aio是272kops/s,trt-spdk是585kops/s。和前面一樣,fio raw線和spdk raw線分別顯示了在類似工作負載(4KiB隨機讀取,qd=128)下的設備性能,分別通過fio和spdk的perf程序報告而來。總之,uDepot的性能與設備原始性能非常接近。

5.2.2  可擴展性(24個驅動器,20個核心)

接下來,將研究uDepot在使用多個驅動器和多個核心時的可擴展性,以及不同IO後端在這些情況下的表現。

爲了最大化聚合吞吐量,在系統中使用了24個基於Flash的NVMe驅動器,以及所有的20個核心。(儘管這些驅動器不是FNDs,但使用了大量的驅動器來實現高聚合吞吐量,並檢查uDepot的可擴展性。)對於在塊設備(Linux-directIO和trt-aio)上操作的uDepot IO後端,本文創建了一個軟件RAID-0設備,使用Linux md驅動程序將24個驅動器組合成一個驅動器。對於trt-spdk後端,使用了基於RAID-0的uDepot的SPDK後端。本文使用了5.2節中描述的工作負載,並對不同數量的併發請求進行測量。對於Linux-directIO,每個請求使用一個線程,最多1024個線程。對於TRT後端,GET請求的每個線程使用128個TRT任務,PUT請求使用32個TRT任務(之所以爲不同的操作使用不同的任務數量,是因爲它們在不同的隊列深度處達到飽和)。我們將線程的數量從1增加到20。

(a)GET操作(b)PUT操作

圖8:使用24個NVMe驅動器用於不同的併發度時,uDepot後端的GET/PUT總吞吐量。

 

結果如圖8所示。其中還包括兩行用來描述最大聚合吞吐量的性能統計,這是使用SPDK 的perf和基於libaio(LinuxAIO)後端的fio在相同驅動器上取得的性能數值。我們關注GET,因爲這是最具挑戰性的工作負載。Linux-directIO後端最初的吞吐量更好,因爲它使用了更多的核心。例如,對於256個併發,它使用256個線程,然後使用機器的所有核心;對於TRT後端,相同的併發性使用2個線程(每個線程128個任務),然後使用機器的所有20個核心中的2個。然而,它的性能上限是1.66Mops/s。使用trt-aio後端的最大吞吐量爲3.78 Mops/s,非常接近fio的性能3.89Mops/s。最後,使用trt-spdk的性能達到了6.17 Mops/s,約爲原始SPDK性能的90%(6.87Mops/s)。由於服務器上的PCIe插槽有限,我們使用了普通SSD來達到比使用Optane驅動器更大的吞吐量。因爲測量了吞吐量,所以這些結果可以推廣到FNDs,不同之處在於FNDs實現相同的吞吐量需要更少的驅動器。此外,測量的原始SPDK性能(6.87 Mops/s)接近服務器的IO子系統能夠交付的最大吞吐量6.91 Mops/s。後者是SPDK基準測試在使用未初始化的驅動器時獲得的吞吐量,這些驅動器在不訪問Flash的情況下返回0。服務器的PCIe帶寬是30.8 GB/s(或者說對於4KiB塊大小的吞吐量爲7.7 Mops/s),如果考慮到PCIe和其他開銷,這與本文的結果是一致的。

總的來說,兩個uDepot後端(trt-aio,trt-spdk)在效率和可擴展性方面都與設備能爲每個不同的IO設備提供的性能非常接近。相反,使用阻塞的系統調用(Linux-directIO)和多線程,在吞吐量和延遲方面有顯著的性能限制。

 

5.3 uDepot Server/YCSB 

在本節中,將評估uDepot Server性能和兩個NoSQL存儲性能的對比:Aerospike[2]和ScyllaDB[82]。儘管uDepot(在設計上)的功能比這些系統少,但選擇這兩個系統是因爲它們是基於NVMe優化的,並且據我們所知,這是目前可以利用FNDs性能的最好的選擇。

爲了便於進行公平的比較,使用了YCSB[15]基準工具,並運行以下工作負載:A(update heavy:50/50)、B(read most:95/5)、C(read only)、D(read latest)和F(read-modify-write),使用了1千萬條記錄,默認記錄大小爲1KiB。(排除了workload E,因爲uDepot不支持範圍查詢。)將所有系統配置爲使用兩個Optane驅動器和10個核心(這足夠驅動兩個Optane驅動器),並使用通過10Gbit/s以太網連接的單個客戶端機器生成負載。對於uDepot,我們開發了一個YCSB驅動程序,使用uDepot的JNI接口作爲客戶端。由於TRT與JVM不兼容,客戶端使用uDepot的Linux後端。對於Aerospike和ScyllaDB,使用它們可用的YCSB驅動程序。本文使用了YCSB版本0.14、Scylla版本2.0.2和Aerospike版本3.15.1.4。對於Scylla,將cassandracql驅動程序的核心和maxconnections參數至少設置爲YCSB的客戶端線程數量,並將其內存使用限制爲64GiB,以減少由於內存分配而導致的YCSB在客戶端高線程數上運行失敗的情況。

圖9:爲不同的KV存儲使用256個YCSB客戶端線程時的總體吞吐量。

 

圖9給出了所有工作負載下的256個客戶機線程的吞吐量。uDepot使用trt-spdk後端提高了YCSB的吞吐量,相比Aerospike分別提高1.95倍(工作負載D)和2.1倍(工作負載A),相比ScyllaDB分別提高10.2倍(工作負載A)和14.7倍(工作負載B)。圖10關注的是繁重的更新工作負載A(50/50),描述了所有被測試的存儲在不同數量的客戶端線程(最多256個)下的總吞吐量、更新和讀取延遲報告。對於256個客戶端線程,使用SPDK的uDepot的讀/寫延遲達到345µs/467µs,Aerospike爲882µs/855µs,ScyllaDB爲4940µs/3777µs。

圖10:使用YCSB基準測試的不同的KV存儲,在工作負載A(讀寫各佔50%)和不同數量的客戶端線程情況下的總體吞吐量、更新和讀取延遲。

 

我們分析了在工作負載A下的執行情況,以瞭解Aerospike、ScyllaDB和uDepot之間性能差異的原因。Aerospike的限制在於它使用了多個IO線程和同步IO。實際上,由於多線程造成的爭用,同步函數佔用了大量的執行時間。ScyllaDB使用異步IO(通常有一個高效的IO子系統),但是它有顯著的IO放大。分別測量了讀取用戶數據(YCSB的key和value)與從FNDs讀取數據的讀取IO放大倍數,結果如下:ScyllaDB爲8.5倍,Aerospike爲2.4倍,uDepot(TRT-aio)爲1.5倍。

總的來說,uDepot對於FNDs性能的發揮明顯優於Aerospike和ScyllaDB。我們注意到YCSB的效率很低,因爲它使用同步IO來同步Java線程,因而並沒有充分體現uDepot的性能。在下一節中,將使用一個性能更好的基準測試工具來更好地說明uDepot的高效。

 

5.4 uDepot Memcache 

最後,本節評估了uDepot的Memcache實現的性能,並研究它是否能夠提供與基於DRAM的服務相當的性能。

本文使用了memcached[65](版本1.5.4),這是使用DRAM的Memcache的標準實現,作爲使用Memcache的應用程序的期望標準。MemC3[25](commit:84475d1)是最先進的一個Memcache實現。Fatcache[27](commit:512caf3)是SSD上的一個Memcache實現。

 

6、相關工作      

Flash KV存儲。兩個早期的特別針對Flash的KV存儲是:一個是FAWN[3],這是一個分佈式的KV存儲,使用了低功耗的CPU和少量的Flash構建;另一個是FlashStore[19],使用了DRAM、Flash和磁盤的多層KV存儲。這些系統與uDepot類似,它們在DRAM中以哈希表的形式保存索引,並使用一種日誌結構的方法。它們都使用6字節的條目:其中4字節用於對Flash尋址,2字節用於key指紋,而這些工作的後續演化[20,56]進一步減少了條目大小。uDepot將條目增加到8個字節,並啓用了上述系統不支持的特性:1)uDepot存儲KV條目的大小,允許它通過單個讀請求同時獲得key和value。也就是說,一次GET操作只需單次訪問存儲。2)uDepot支持在線重新調整大小,而不需要訪問NVM存儲。3)uDepot使用40比特而不是32比特對存儲進行尋址,最多支持1PB的顆粒塊。此外,uDepot可以有效地訪問FNDs(通過異步IO後端),並且可以擴展到多個設備和多個核心上,而其他系統則是爲速度較慢的設備構建的,不支持這些特性。有一些構建Flash KV存儲或高速緩存[81,84]的工作[60,91]依賴於非標準的存儲設備(如open-channel固態硬盤)。uDepot不依賴於特殊的設備,使用更豐富的存儲接口來改進uDepot是未來的工作。

高性能DRAM KV存儲。大量工作的目標是最大化基於DRAM的KV存儲的性能,這些KV存儲可以使用RDMA[21,44,68,73]、直接訪問網絡硬件[57]或FPGAs[10,52]。另一方面,uDepot通過TCP/IP進行操作,並將數據存儲在存儲設備中。然而,許多這種系統使用一個哈希表來維護它們的映射,並在可能的情況下從客戶端通過單方面的RDMA操作來訪問它。例如,FaRM[21]識別了cuckoo哈希的問題,並且與uDepot類似,使用了“跳房子”哈希的一種變體。FaRM和uDepot的一個基本區別是前者關心最小化對哈希表訪問的RDMA操作,而uDepot不關心這一點。此外,uDepot的索引結構支持在線重新調整大小,而FaRM爲每個桶(bucket)使用一個溢出鏈(overflow chain),這可能會因爲檢查該溢出鏈而導致性能下降。

NVM KV存儲。最近的一些工作[4,71,92,95]提出了NVM KV存儲。這些系統的根本不同之處在於,它們操作位於內存總線上的可字節尋址的NVM。相反,uDepot在存儲設備上使用NVM,因爲該技術廣泛可用而且更經濟。MyNVM[22]還使用NVM存儲來減少RocksDB的內存佔用,其中的NVM存儲被引入做爲二級塊緩存。uDepot採取了一種不同的方法,它構建了一個KV存儲,將數據完全放在NVM上。Aerospike[87]針對NVMe SSD,採用了與uDepot類似的設計,將其索引保存在DRAM中,數據保存在存儲的日誌中。然而,由於它的設計考慮了普通SSD,因此不能充分發揮FNDs的性能(例如,它使用同步IO)。與uDepot類似,Faster[11]是一個最新的KV存儲,它維護一個可調整大小的內存哈希索引,並將其數據存儲到一個日誌中。與uDepot相比,Faster使用的是同時駐留在DRAM和存儲器中的混合日誌。

Memcache。Memcache是一個被廣泛使用的服務[5,6,32,65,70]。MemC3[25]使用一個併發的cuckoo哈希表重新設計了memcached。與原始的memcached類似,不能動態地重新調整哈希表的大小,必須在服務啓動時定義使用的內存量。uDepot支持哈希表的在線重新調整大小,同時也允許在服務重新啓動時加快預熱時間,因爲數據存儲在持久存儲中。最近,在memcached中使用FNDs被嘗試用來降低成本和擴展緩存[66]。

基於任務的異步IO。關於使用線程和事件來編寫異步IO的爭論由來已久[1,18,49,51,90]。uDepot建立在TRT上,使用基於任務的方法,每個任務都有自己的堆棧。對TRT的一個有用的擴展是爲異步IO[36]提供一個可組合的接口。Flashgraph[97]使用異步的基於任務的IO系統來處理存儲在Flash上的圖形。Seastar[83]是ScyllaDB使用的運行時,它遵循與TRT相同的設計原則,但是(目前)不支持SPDK。

 

7、結論與未來工作     

本文提出了uDepot,這是一個KV存儲,充分利用了像Intel Optane這樣的快速NVM存儲設備的性能。實驗證明了uDepot獲得了它所使用的底層IO設備的可用性能,並且與現有系統相比可以更好地利用這些新設備。此外,本文還展示了uDepot可以使用這些設備來實現一個緩存服務,該服務的性能與基於DRAM實現的緩存類似,但成本要低得多。實際上,已經使用uDepot的Memcache實現作爲一個實驗性公有云服務[39]的基礎。

uDepot有兩個主要的限制,計劃在未來的工作中加以解決。首先,uDepot並不(高效地)支持一些已經被證明對應用程序有用的一些操作,比如範圍查詢、事務、檢查點、數據結構抽象等[78]。其次,有很多機會可以通過支持多租戶[13]來提高效率,而uDepot目前並沒有利用到這些機會。

 

8、致謝      

感謝匿名審稿人,特別是我們的指導老師Peter Macko,感謝他們寶貴的反饋和建議,也感謝Radu Stoica對論文的初稿提供的反饋。最後,要感謝Intel讓我們可以提前體驗Optane測試平臺。

 

原文地址

https://www.usenix.org/system/files/fast19-kourtis.pdf

(TaoCloud團隊原創翻譯)

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