Crimson:高性能,高擴(kuò)展的新一代 Ceph OSD
? 背景
隨著物理硬件的不斷發(fā)展,存儲(chǔ)軟件所使用的硬件的情況也一直在不斷變化。
一方面,內(nèi)存和 IO 技術(shù)一直在快速發(fā)展,硬件的性能在極速增加。在最初設(shè)計(jì) Ceph 的時(shí)候,通常情況下,Ceph 都是被部署到機(jī)械硬盤上,能夠提供數(shù)百 IOPS 的讀寫和數(shù)十 G 的磁盤容量。但是,目前最新的 NVMe 設(shè)備可以提供數(shù)百萬的 IOPS 讀寫,并支持 TB 級的磁盤容量。DRAM 的容量在大約20年的時(shí)間內(nèi)增加了128倍。對于網(wǎng)絡(luò) IO 來說,網(wǎng)卡設(shè)備現(xiàn)在能夠提供超過 400Gbps 的速度,而幾年前只有 10Gbps。

另一方面,在大約十年的時(shí)間內(nèi),CPU 頻率和 CPU 內(nèi)核的單線程性能一直處于穩(wěn)定的狀態(tài),增長不明顯。相比之下邏輯核心的數(shù)量隨著晶體管規(guī)模的增加而迅速增長。

Ceph 的性能要跟上硬件發(fā)展的速度一直很有挑戰(zhàn)的,因?yàn)?Ceph 的架構(gòu)是十年前的——它對單核 CPU 性能的依賴使它無法充分利用不斷增長的 IO。特別是,當(dāng) Ceph 對象存儲(chǔ)守護(hù)程序(OSD)依賴線程池來處理不同的 IO 時(shí),跨 CPU 核心通信會(huì)產(chǎn)生了大量的延遲開銷。減少或消除這些開銷成本是 Crimson 項(xiàng)目的核心目標(biāo)。
Crimson 項(xiàng)目使用 shared-nothing? 設(shè)計(jì)和 run-to-completion 模型來重寫 Ceph OSD,以滿足苛刻的硬件與軟件系統(tǒng)的擴(kuò)展要求,同時(shí)也與現(xiàn)有的客戶端和組件兼容。
為了理解 Crimson OSD 如何針對 CPU 擴(kuò)展進(jìn)行重新設(shè)計(jì)的,我們比較了 傳統(tǒng) OSD 和 Crimson OSD 之間的架構(gòu)差異,來解釋架構(gòu)怎么以及為何這樣設(shè)計(jì)。然后我們討論了 Crimson 為什么建立在 Seastar 框架之上,以及每個(gè)核心組件是如何實(shí)現(xiàn)擴(kuò)展的。
最后,我們分享了實(shí)現(xiàn)這一目標(biāo)的最新情況,同時(shí)還提供了一個(gè)我們最終希望達(dá)到的性能結(jié)果。
Crimson 與傳統(tǒng) OSD 架構(gòu)
Ceph OSD 是 Ceph 集群的一部分,主要負(fù)責(zé)通過網(wǎng)絡(luò)來提供對象的訪問、維護(hù)數(shù)據(jù)冗余和高可用性以及將對象持久化到本地存儲(chǔ)設(shè)備。作為傳統(tǒng) OSD 的重寫版本,Crimson OSD 從客戶端和 OSD 的角度來看是與現(xiàn)有的 RADOS 協(xié)議兼容的,它提供相同的接口和功能。Messenger、OSD 服務(wù)和 ObjectStore 等 Ceph OSD 模塊化的功能沒有太大改變,但跨組件交互和內(nèi)部資源管理的形式進(jìn)行了大幅重構(gòu),以使用 shared-nothing 設(shè)計(jì)和自下而上的用戶空間任務(wù)調(diào)度。
傳統(tǒng) OSD 的架構(gòu)中,每個(gè)組件中都有線程池,針對多 CPU 核心場景下,使用共享隊(duì)列處理任務(wù)效率很低。在一個(gè)簡單的例子中,一個(gè) PG 操作需要先由一個(gè)messenger worker 線程處理,將原始數(shù)據(jù)流組裝或解碼成一條消息,然后放入消息隊(duì)列中進(jìn)行調(diào)度。之后由一個(gè)PG worker thread 來獲取消息,經(jīng)過必要的處理后,將請求以事務(wù)的形式交給 ObjectStore。
事務(wù)提交后,PG 會(huì)完成操作,再次通過發(fā)送隊(duì)列和 messenger worker 線程發(fā)送回復(fù)。盡管可以通過向池中添加更多線程來將工作負(fù)載擴(kuò)展到多個(gè) CPU,但這些線程默認(rèn)共享資源,因此需要鎖,這會(huì)引入爭用問題。

傳統(tǒng)架構(gòu)的一個(gè)主要挑戰(zhàn)是鎖競爭開銷隨著任務(wù)數(shù)和 CPU 核數(shù)的增加而迅速擴(kuò)大,在某些場景下每個(gè)鎖點(diǎn)都可能成為擴(kuò)展瓶頸。此外,這些鎖和隊(duì)列即使在沒有爭用的情況下也會(huì)產(chǎn)生延遲開銷。多年來,社區(qū)在分析和優(yōu)化更細(xì)粒度的資源管理和快速路徑實(shí)現(xiàn)以跳過隊(duì)列方面做了大量工作。未來,這類優(yōu)化的成果會(huì)越來越少,可擴(kuò)展性似乎會(huì)在當(dāng)前的設(shè)計(jì)架構(gòu)下達(dá)到了某個(gè)瓶頸。也還有其他挑戰(zhàn)。隨著在工作線程之間分配任務(wù),延遲問題將隨著線程池和任務(wù)隊(duì)列而惡化。鎖可以強(qiáng)制上下文切換,這會(huì)使事情變得更糟。
Crimson 項(xiàng)目希望通過 shared-nothing? 設(shè)計(jì)和 run-to-completion 模型來解決 CPU 可擴(kuò)展性問題。該設(shè)計(jì)的重點(diǎn)是強(qiáng)制每個(gè)內(nèi)核或 CPU 運(yùn)行一個(gè)固定線程并在用戶空間中分配非阻塞任務(wù)。因?yàn)檎埱笠约八鼈兊馁Y源可以被分配到各個(gè)核心,所以它們可以在同一個(gè)核心中被處理,直到處理完成。理想情況下,我們不再需要所有的鎖和上下文切換,因?yàn)槊總€(gè)正在運(yùn)行的非阻塞任務(wù)都使用到 CPU,一直到它完成任務(wù)。沒有其他線程可以在同一時(shí)間搶占任務(wù)。如果不需要與數(shù)據(jù)路徑中的其他分片通信,理想情況下,性能將隨著內(nèi)核數(shù)量線性擴(kuò)展,直到 IO 設(shè)備達(dá)到其極限。這種設(shè)計(jì)非常適合 Ceph OSD,因?yàn)樵?OSD 層面,所有 IO 都已經(jīng)被 PG 分片了。

雖然跨區(qū)通信不能完全消除,但那通常是用于 OSD 全局狀態(tài)的維護(hù),而不是用于數(shù)據(jù)路徑中。這里的一個(gè)主要挑戰(zhàn)是,最重要的改變是對 OSD 操作的基本要求——相當(dāng)一部分現(xiàn)有的鎖或線程代碼無法重用,需要重新設(shè)計(jì),同時(shí)保持向后的兼容性。
重新設(shè)計(jì)需要對代碼的整體理解,以及相關(guān)的注意事項(xiàng)。使用 shared-nothing? 架構(gòu)實(shí)現(xiàn)底層的one-thread-per-core和用戶空間調(diào)度是另一個(gè)挑戰(zhàn)。
Crimson 試圖在 Seastar 的基礎(chǔ)上重新設(shè)計(jì) OSD,Seastar 是一個(gè)異步編程框架,具有滿足上述目標(biāo)的所有理想特性。
Seastar Framework
Seastar 是 Crimson 項(xiàng)目的理想選擇,因?yàn)樗粌H在 C++ 中實(shí)現(xiàn)了 one-thread-per-core? 的 shared-nothing 架構(gòu),而且還提供了一套全面的功能和模型,這些功能和模型已被證明在其它應(yīng)用程序中對性能和擴(kuò)展有效。資源默認(rèn)情況下不在分片之間共享,Seastar 實(shí)現(xiàn)了自己的內(nèi)存分配器以進(jìn)行無鎖分配。該分配器還利用了 NUMA 拓?fù)浣Y(jié)構(gòu)的優(yōu)勢,將最近的內(nèi)存分配給分片。對于一些不可避免的跨核資源共享和通信,Seastar 強(qiáng)制要求明確地處理它們。如果一個(gè)分片擁有另一個(gè)核心的資源,它必須通過外部指針指向這些資源;如果一個(gè)分片需要與其他分片通信,它必須提交并轉(zhuǎn)發(fā)任務(wù)給他們。這就迫使程序限制其跨核的需求,并有助于減少對 CPU 擴(kuò)展性問題的分析范圍。Seastar 還為跨核通信實(shí)現(xiàn)了高性能的非阻塞通信。

傳統(tǒng)的帶有異步事件和回調(diào)的程序在實(shí)現(xiàn)、理解和調(diào)試方面是非常困難的。用戶空間的非阻塞任務(wù)調(diào)度需要實(shí)現(xiàn)普遍的異步性。Seastar 將 futures、promises 和 continuations (f/p/c) 作為構(gòu)建塊來組織邏輯。futures 和 promises 通過將邏輯上連接的異步結(jié)構(gòu)組合在一起,而不是將它們分散用于普通的回調(diào)中,這使代碼更更容易實(shí)現(xiàn)以及更好的可讀性。Seastar 還為循環(huán)、計(jì)時(shí)器以及基于未來控制生命周期甚至 CPU 份額提供了更高級別的工具。為了進(jìn)一步簡化應(yīng)用程序,Seastar 將網(wǎng)絡(luò)和磁盤訪問封裝到 shared-nothing 和基于 f/p/c 設(shè)計(jì)的模式中。采用不同 I/O 堆棧(如 epoll、linux-aio、io-uring、DPDK 等)的復(fù)雜性和細(xì)微控制對應(yīng)用程序代碼是透明的。

Run-to-completion performance
Crimson 團(tuán)隊(duì)已經(jīng)為 RBD 客戶端的讀寫工作負(fù)載實(shí)現(xiàn)了 OSD 的大部分關(guān)鍵特性。當(dāng)前完成的任務(wù)包括重新實(shí)現(xiàn) messenger V2 (msgr2), heartbeat, PG peering, backfill, recovery, object-classes, watch-notify, etc等,并不斷努力的增加一些 CI 測試組件。Crimson 已經(jīng)達(dá)到了一個(gè)里程碑,我們可以在具有足夠穩(wěn)定的單個(gè)分片中驗(yàn)證run-to-completion設(shè)計(jì)。
綜合考慮現(xiàn)實(shí)條件,在相同的隨機(jī) 4KB RBD 工作負(fù)載下,在沒有復(fù)制的情況下,通過將傳統(tǒng)和 Crimson OSD 與 BlueStore 后端進(jìn)行比較來驗(yàn)證 single-shard run-to-completion?。兩個(gè) OSD 都分配了 2 個(gè) CPU 資源。Crimson OSD 很特別,因?yàn)?Seastar 需要一個(gè)獨(dú)占 CPU 核心來運(yùn)行 single-shard OSD 邏輯。這意味著 BlueStore 線程必須固定到另一個(gè)核心,引入 AlienStore 來彌合 Seastar 線程和 BlueStore 線程之間的邊界,并在兩個(gè)邊界之間提交 IO 任務(wù)。相比之下,傳統(tǒng) OSD 沒有限制使用分配的 2 個(gè) CPU。

性能結(jié)果顯示,使用 BlueStore 時(shí),Crimson OSD 的隨機(jī)讀取性能大約提高了 25%,隨機(jī)寫入情況下的 IOPS 大約比傳統(tǒng) OSD 高 24%。進(jìn)一步的分析顯示,在隨機(jī)寫的情況下,CPU 的利用率很低,因?yàn)榇蠹s 20% 的 CPU 被消耗在頻繁的查詢中,這表明 Crimson OSD 應(yīng)該不是是當(dāng)前的瓶頸。

Crimson OSD 提交和完成 IO 任務(wù),以及在 Seastar 和 BlueStore 線程之間進(jìn)行同步,也有額外的開銷。因此,我們針對 MemStore 后臺(tái)重復(fù)了同一組實(shí)驗(yàn),兩個(gè) OSD 都分配了 1 個(gè) CPU。如下圖所示,Crimson OSD 在隨機(jī)讀取中提供了大約 70% 的 IOPS,在隨機(jī)寫入中比 傳統(tǒng) OSD 高 25%,這與之前實(shí)驗(yàn)中的結(jié)論一致,即 Crimson OSD 可以做得更好。

盡管上述場景僅涵蓋實(shí)驗(yàn)性 single-shard? 案例,但結(jié)果表明使用 Seastar 框架具有性能優(yōu)勢——消除鎖、通過用戶空間任務(wù)調(diào)度刪除上下文切換、分配更靠近 CPU 的內(nèi)存。此外,重要的是要重申,run-to-completion 模型的目標(biāo)是更好地?cái)U(kuò)展 CPU 并消除軟件使用高性能硬件而引起的性能瓶頸。
Multi-shard Implementation
實(shí)現(xiàn)多分片的路徑很明確。由于每個(gè)PG中的 IO 已經(jīng)在邏輯上被分片,所以對IO路徑?jīng)]有太大改變。主要的挑戰(zhàn)是確定無法避免的跨核通信,并設(shè)計(jì)新的解決方案,以盡量減少其對IO路徑的影響,這需要根據(jù)具體情況進(jìn)行分析。一般來說,當(dāng)從 Messenger 接收到一個(gè) IO 操作時(shí),它會(huì)根據(jù) PG-core 映射被定向到 OSD 分片,并在同一分片/CPU的上下文中運(yùn)行,直到完成。請注意,在當(dāng)前階段,為了簡單起見,設(shè)計(jì)上選擇不修改RADOS協(xié)議。

Messenger
Messenger 在確保解決方案可擴(kuò)展方面發(fā)揮著重要作用。有一些限制需要認(rèn)真考慮。一個(gè)限制來自 RADOS 協(xié)議,它只為每個(gè)客戶端或 OSD 定義一個(gè)連接。連接必須存在于特定核心上才能根據(jù)其狀態(tài)高效且無鎖地解碼和編碼消息。與 OSD 對等體的共享連接意味著在當(dāng)前階段跨核消息傳遞到多個(gè) PG 分片是不可避免的,除非可以調(diào)整協(xié)議以允許到每個(gè)分片的獨(dú)占連接。
Seastar 框架的另一個(gè)限制是它不允許在 Seastar 套接字被 accept()ed? 或 connect()ed 之后移動(dòng)到另一個(gè)核心。這對無損連接 (msgr2) 來說是一個(gè)挑戰(zhàn),因?yàn)樗鼤?huì)影響 Messenger 和 OSD 服務(wù)之間的交互,在這種情況下,由于網(wǎng)絡(luò)故障重新連接,連接可能會(huì)預(yù)先跳轉(zhuǎn)到另一個(gè)核心。
擴(kuò)展 Messenger 的大部分工作是在將 IO 操作分派到 PG 分片之前將消息傳遞工作負(fù)載(編碼、解碼、壓縮、加密、緩沖區(qū)管理等)優(yōu)化擴(kuò)展到多個(gè)內(nèi)核,并最小化跨內(nèi)核消息沿 IO 路徑傳遞,理想情況下,在上述約束下,對于每個(gè)消息發(fā)送和接收操作,它最多保持 1 跳。
OSD
OSD 負(fù)責(zé)維護(hù) PG 分片之間共享的全局狀態(tài)和活動(dòng),包括心跳、身份驗(yàn)證、客戶端管理、osdmap、PG 維護(hù)、訪問 Messenger 和 ObjectStore 等。
多核 Crimson OSD 的一個(gè)簡單原則是將所有與共享狀態(tài)相關(guān)的處理保持在專用內(nèi)核上。如果一個(gè) IO 操作要訪問共享資源,要么按順序訪問專用核,要么訪問保持同步的共享信息的獨(dú)占副本。
實(shí)現(xiàn)這一目標(biāo)有兩個(gè)主要步驟。第一步是讓 IO 操作根據(jù) PG 分片策略運(yùn)行在多個(gè) OSD 分片中,包括 PG 狀態(tài)在內(nèi)的所有全局信息都維護(hù)在第一個(gè)分片中。此步驟在 OSD 中啟用分片,但需要在第一個(gè)分片中做出有關(guān) IO 調(diào)度的所有決策。即使這一步 Messenger 可以在多核中運(yùn)行,消息仍然需要傳遞到第一個(gè)分片進(jìn)行準(zhǔn)備(例如 PG peering)并在提交到該分片之前確定正確的 PG 分片。這會(huì)導(dǎo)致額外的開銷和不平衡的 CPU 使用(第一個(gè) OSD 分片使用率高,其他分片很低,等等)。因此,下一步是將 PG-core 映射擴(kuò)展到所有 OSD 分片。
ObjectStore
Crimson 支持三種 ObjectStore 后端:AlienStore、CyanStore 和 SeaStore。AlienStore 提供與 BlueStore 的向后兼容性。CyanStore 是用于測試的虛擬后端,由易失性內(nèi)存實(shí)現(xiàn)。SeaStore 是一種新的對象存儲(chǔ),專為 Crimson OSD 設(shè)計(jì),采用 shared-nothing 設(shè)計(jì)。根據(jù)后端的具體目標(biāo),實(shí)現(xiàn)多分片支持的路徑是不同的。
1AlienStore
AlienStore 是 Seastar 線程中的一個(gè)瘦代理,用于與使用 POSIX 線程的 BlueStore 進(jìn)行通信。對于多個(gè) OSD 分片沒有特別的工作要做,因?yàn)?IO 任務(wù)通信同步了。BlueStore 中沒有為 Crimson 定制其他內(nèi)容,因?yàn)椴豢赡苷嬲龑?BlueStore 擴(kuò)展到 shared-nothing 設(shè)計(jì),因?yàn)樗蕾囉诘?三 方 RocksDB 項(xiàng)目,而 RocksDB 仍然是線程的。但是,在 Crimson 能夠拿出一個(gè)足夠優(yōu)化和足夠穩(wěn)定的原生存儲(chǔ)后端解決方案(SeaStore)之前,合理的開銷來換取復(fù)雜的存儲(chǔ)后端解決方案是可以接受的。
2CyanStore
Crimson OSD 中的 CyanStore 與傳統(tǒng) OSD 中的 MemStore 相對應(yīng)。對多分片支持的唯一改變是為每個(gè)分片創(chuàng)建獨(dú)立的 CyanStore 實(shí)例。一個(gè)目標(biāo)是確保虛擬 IO 操作能夠在同一個(gè)內(nèi)核中完成,以幫助識別 OSD 級別的可擴(kuò)展性問題(如果有的話)。另一個(gè)目標(biāo)是在 OSD 層面上與傳統(tǒng) OSD 做直接的性能比較,而不受 ObjectStore 的復(fù)雜因數(shù)影響。
3SeaStore
SeaStore 是 Crimson OSD 原生的 ObjectStore 解決方案,采用 Seastar 框架開發(fā),采用相同的設(shè)計(jì)原則。
雖然很有挑戰(zhàn)性,但是 Crimson 必須建立一個(gè)新的本地存儲(chǔ)引擎,這有多種原因。存儲(chǔ)后端是主要的 CPU 資源消耗者,如果 Crimson OSD 的存儲(chǔ)后端不改變,那么它就不能真正地隨核心擴(kuò)展。我們的實(shí)驗(yàn)也證明了 Crimson OSD 不是隨機(jī)寫入場景中的瓶頸。
其次,BlueStore 中具有事務(wù)支持的 CPU 密集型元數(shù)據(jù)管理基本上由 RocksDB 提供,如果不重新實(shí)現(xiàn),它無法在原生的 Seastar 線程中運(yùn)行。與其為 BlueStore 重新實(shí)現(xiàn)通用的鍵值事務(wù)存儲(chǔ),不如在更高的層次上重新思考和定制相應(yīng)的架構(gòu)——ObjectStore。問題在原生的解決方案中比在 第三方項(xiàng)目中更容易解決,因?yàn)榈谌巾?xiàng)目必須保證使用與通用的場景。
第三個(gè)考慮是為異構(gòu)存儲(chǔ)設(shè)備和硬件加速器提供原生支持,讓用戶可以根據(jù)自己的需求平衡成本和性能。如果 Crimson 能夠更好地控制整個(gè)存儲(chǔ)堆棧,那么 Crimson 將更靈活地簡化部署硬件組合的解決方案。
SeaStore 在單分片讀寫方面已經(jīng)可以正常使用,盡管在穩(wěn)定性和性能改進(jìn)方面仍有待努力。目前的努力仍然集中在架構(gòu)上,而不是極端情況下的優(yōu)化。它針對多分片 OSD 的設(shè)計(jì)很明確。與 CyanStore 一樣,第一步是為每個(gè) OSD 分片創(chuàng)建獨(dú)立的 SeaStore 實(shí)例,每個(gè)實(shí)例都在存儲(chǔ)設(shè)備的靜態(tài)分區(qū)上運(yùn)行。第二步是實(shí)現(xiàn)一個(gè)共享磁盤空間平衡器來動(dòng)態(tài)調(diào)整分區(qū),它應(yīng)該可以在后臺(tái)異步運(yùn)行,因?yàn)?PG 已經(jīng)以偽隨機(jī)方式分配了用戶 IO。SeaStore 實(shí)例可能不需要等于 OSD 分片的數(shù)量,根據(jù)性能分析,調(diào)整這個(gè)比例是后期工作的第三步。
摘要和測試配置
在這篇文章中,我們介紹了為什么以及如何對 Ceph OSD 進(jìn)行重構(gòu)以跟上硬件的發(fā)展。另外我們也給出了我們所做的詳細(xì)設(shè)計(jì)、 一個(gè)簡單的性能測試結(jié)果。也提供了 Crimson OSD 真正實(shí)現(xiàn)多核可擴(kuò)展的所要考慮的大部分因素。
測試結(jié)果可能會(huì)根據(jù)不同的 commit 版本、軟件和硬件配置而有所變化。為了確保我們的測試是可重復(fù)的,可復(fù)現(xiàn)的,并可在以后場景中作為參考,我們列出了所有可能產(chǎn)生影響的設(shè)置和注意事項(xiàng)。
我們?yōu)?Crimson 和 傳統(tǒng) OSD 部署了本地 Ceph 集群,并使用 CBT 執(zhí)行了 FIO 測試。Crimson 在使用 tcmalloc 時(shí)仍然存在問題,因此為了公平起見,我們將兩個(gè) OSD 配置為使用 libc*。我們使用 BlueStore。RBD 緩存被禁用。BlueStore 線程數(shù)設(shè)置為 4 以獲得更好的結(jié)果。部署 Crimson 時(shí),需要指定*ceph-osd_cmd ( crimson-osd )。CPU 綁定通過 CBT 配置文件中的 crimson_cpusets 指定,BlueStore 線程通過 crimson_alien_thread_cpu_cores 和 crimson_alien_op_num_threads 配置。要部署傳統(tǒng) OSD,numactl 用于控制 CPU 綁定。根據(jù) CBT 存儲(chǔ)庫,部署過程的其余部分沒有變化。
測試場景:
- Client: 4 FIO clients
- IO mode: random write and then random read
- Block size: 4KB
- Time: 300s X 5 times to get the average results
- IO-depth: 32 X 4 clients
- Create 1 pool using 1 replica
- 1 RBD image X 4 clients
- The size of each image is 256GB
測試環(huán)境:
- Ceph 版本 (SHA1):7803eb186d02bb852b95efd1a1f61f32618761d9
- Ubuntu 20.04
- GCC-12
- 1TB NVMe SSD 作為 BlueStore 塊設(shè)備
- 50GB 內(nèi)存用于 MemStore 和 CyanStore


























