Crimson:高性能,高擴展的新一代 Ceph OSD
? 背景
隨著物理硬件的不斷發(fā)展,存儲軟件所使用的硬件的情況也一直在不斷變化。
一方面,內(nèi)存和 IO 技術(shù)一直在快速發(fā)展,硬件的性能在極速增加。在最初設計 Ceph 的時候,通常情況下,Ceph 都是被部署到機械硬盤上,能夠提供數(shù)百 IOPS 的讀寫和數(shù)十 G 的磁盤容量。但是,目前最新的 NVMe 設備可以提供數(shù)百萬的 IOPS 讀寫,并支持 TB 級的磁盤容量。DRAM 的容量在大約20年的時間內(nèi)增加了128倍。對于網(wǎng)絡 IO 來說,網(wǎng)卡設備現(xiàn)在能夠提供超過 400Gbps 的速度,而幾年前只有 10Gbps。
另一方面,在大約十年的時間內(nèi),CPU 頻率和 CPU 內(nèi)核的單線程性能一直處于穩(wěn)定的狀態(tài),增長不明顯。相比之下邏輯核心的數(shù)量隨著晶體管規(guī)模的增加而迅速增長。
Ceph 的性能要跟上硬件發(fā)展的速度一直很有挑戰(zhàn)的,因為 Ceph 的架構(gòu)是十年前的——它對單核 CPU 性能的依賴使它無法充分利用不斷增長的 IO。特別是,當 Ceph 對象存儲守護程序(OSD)依賴線程池來處理不同的 IO 時,跨 CPU 核心通信會產(chǎn)生了大量的延遲開銷。減少或消除這些開銷成本是 Crimson 項目的核心目標。
Crimson 項目使用 shared-nothing? 設計和 run-to-completion 模型來重寫 Ceph OSD,以滿足苛刻的硬件與軟件系統(tǒng)的擴展要求,同時也與現(xiàn)有的客戶端和組件兼容。
為了理解 Crimson OSD 如何針對 CPU 擴展進行重新設計的,我們比較了 傳統(tǒng) OSD 和 Crimson OSD 之間的架構(gòu)差異,來解釋架構(gòu)怎么以及為何這樣設計。然后我們討論了 Crimson 為什么建立在 Seastar 框架之上,以及每個核心組件是如何實現(xiàn)擴展的。
最后,我們分享了實現(xiàn)這一目標的最新情況,同時還提供了一個我們最終希望達到的性能結(jié)果。
Crimson 與傳統(tǒng) OSD 架構(gòu)
Ceph OSD 是 Ceph 集群的一部分,主要負責通過網(wǎng)絡來提供對象的訪問、維護數(shù)據(jù)冗余和高可用性以及將對象持久化到本地存儲設備。作為傳統(tǒng) OSD 的重寫版本,Crimson OSD 從客戶端和 OSD 的角度來看是與現(xiàn)有的 RADOS 協(xié)議兼容的,它提供相同的接口和功能。Messenger、OSD 服務和 ObjectStore 等 Ceph OSD 模塊化的功能沒有太大改變,但跨組件交互和內(nèi)部資源管理的形式進行了大幅重構(gòu),以使用 shared-nothing 設計和自下而上的用戶空間任務調(diào)度。
傳統(tǒng) OSD 的架構(gòu)中,每個組件中都有線程池,針對多 CPU 核心場景下,使用共享隊列處理任務效率很低。在一個簡單的例子中,一個 PG 操作需要先由一個messenger worker 線程處理,將原始數(shù)據(jù)流組裝或解碼成一條消息,然后放入消息隊列中進行調(diào)度。之后由一個PG worker thread 來獲取消息,經(jīng)過必要的處理后,將請求以事務的形式交給 ObjectStore。
事務提交后,PG 會完成操作,再次通過發(fā)送隊列和 messenger worker 線程發(fā)送回復。盡管可以通過向池中添加更多線程來將工作負載擴展到多個 CPU,但這些線程默認共享資源,因此需要鎖,這會引入爭用問題。
傳統(tǒng)架構(gòu)的一個主要挑戰(zhàn)是鎖競爭開銷隨著任務數(shù)和 CPU 核數(shù)的增加而迅速擴大,在某些場景下每個鎖點都可能成為擴展瓶頸。此外,這些鎖和隊列即使在沒有爭用的情況下也會產(chǎn)生延遲開銷。多年來,社區(qū)在分析和優(yōu)化更細粒度的資源管理和快速路徑實現(xiàn)以跳過隊列方面做了大量工作。未來,這類優(yōu)化的成果會越來越少,可擴展性似乎會在當前的設計架構(gòu)下達到了某個瓶頸。也還有其他挑戰(zhàn)。隨著在工作線程之間分配任務,延遲問題將隨著線程池和任務隊列而惡化。鎖可以強制上下文切換,這會使事情變得更糟。
Crimson 項目希望通過 shared-nothing? 設計和 run-to-completion 模型來解決 CPU 可擴展性問題。該設計的重點是強制每個內(nèi)核或 CPU 運行一個固定線程并在用戶空間中分配非阻塞任務。因為請求以及它們的資源可以被分配到各個核心,所以它們可以在同一個核心中被處理,直到處理完成。理想情況下,我們不再需要所有的鎖和上下文切換,因為每個正在運行的非阻塞任務都使用到 CPU,一直到它完成任務。沒有其他線程可以在同一時間搶占任務。如果不需要與數(shù)據(jù)路徑中的其他分片通信,理想情況下,性能將隨著內(nèi)核數(shù)量線性擴展,直到 IO 設備達到其極限。這種設計非常適合 Ceph OSD,因為在 OSD 層面,所有 IO 都已經(jīng)被 PG 分片了。
雖然跨區(qū)通信不能完全消除,但那通常是用于 OSD 全局狀態(tài)的維護,而不是用于數(shù)據(jù)路徑中。這里的一個主要挑戰(zhàn)是,最重要的改變是對 OSD 操作的基本要求——相當一部分現(xiàn)有的鎖或線程代碼無法重用,需要重新設計,同時保持向后的兼容性。
重新設計需要對代碼的整體理解,以及相關(guān)的注意事項。使用 shared-nothing? 架構(gòu)實現(xiàn)底層的one-thread-per-core和用戶空間調(diào)度是另一個挑戰(zhàn)。
Crimson 試圖在 Seastar 的基礎(chǔ)上重新設計 OSD,Seastar 是一個異步編程框架,具有滿足上述目標的所有理想特性。
Seastar Framework
Seastar 是 Crimson 項目的理想選擇,因為它不僅在 C++ 中實現(xiàn)了 one-thread-per-core? 的 shared-nothing 架構(gòu),而且還提供了一套全面的功能和模型,這些功能和模型已被證明在其它應用程序中對性能和擴展有效。資源默認情況下不在分片之間共享,Seastar 實現(xiàn)了自己的內(nèi)存分配器以進行無鎖分配。該分配器還利用了 NUMA 拓撲結(jié)構(gòu)的優(yōu)勢,將最近的內(nèi)存分配給分片。對于一些不可避免的跨核資源共享和通信,Seastar 強制要求明確地處理它們。如果一個分片擁有另一個核心的資源,它必須通過外部指針指向這些資源;如果一個分片需要與其他分片通信,它必須提交并轉(zhuǎn)發(fā)任務給他們。這就迫使程序限制其跨核的需求,并有助于減少對 CPU 擴展性問題的分析范圍。Seastar 還為跨核通信實現(xiàn)了高性能的非阻塞通信。
傳統(tǒng)的帶有異步事件和回調(diào)的程序在實現(xiàn)、理解和調(diào)試方面是非常困難的。用戶空間的非阻塞任務調(diào)度需要實現(xiàn)普遍的異步性。Seastar 將 futures、promises 和 continuations (f/p/c) 作為構(gòu)建塊來組織邏輯。futures 和 promises 通過將邏輯上連接的異步結(jié)構(gòu)組合在一起,而不是將它們分散用于普通的回調(diào)中,這使代碼更更容易實現(xiàn)以及更好的可讀性。Seastar 還為循環(huán)、計時器以及基于未來控制生命周期甚至 CPU 份額提供了更高級別的工具。為了進一步簡化應用程序,Seastar 將網(wǎng)絡和磁盤訪問封裝到 shared-nothing 和基于 f/p/c 設計的模式中。采用不同 I/O 堆棧(如 epoll、linux-aio、io-uring、DPDK 等)的復雜性和細微控制對應用程序代碼是透明的。
Run-to-completion performance
Crimson 團隊已經(jīng)為 RBD 客戶端的讀寫工作負載實現(xiàn)了 OSD 的大部分關(guān)鍵特性。當前完成的任務包括重新實現(xiàn) messenger V2 (msgr2), heartbeat, PG peering, backfill, recovery, object-classes, watch-notify, etc等,并不斷努力的增加一些 CI 測試組件。Crimson 已經(jīng)達到了一個里程碑,我們可以在具有足夠穩(wěn)定的單個分片中驗證run-to-completion設計。
綜合考慮現(xiàn)實條件,在相同的隨機 4KB RBD 工作負載下,在沒有復制的情況下,通過將傳統(tǒng)和 Crimson OSD 與 BlueStore 后端進行比較來驗證 single-shard run-to-completion?。兩個 OSD 都分配了 2 個 CPU 資源。Crimson OSD 很特別,因為 Seastar 需要一個獨占 CPU 核心來運行 single-shard OSD 邏輯。這意味著 BlueStore 線程必須固定到另一個核心,引入 AlienStore 來彌合 Seastar 線程和 BlueStore 線程之間的邊界,并在兩個邊界之間提交 IO 任務。相比之下,傳統(tǒng) OSD 沒有限制使用分配的 2 個 CPU。
性能結(jié)果顯示,使用 BlueStore 時,Crimson OSD 的隨機讀取性能大約提高了 25%,隨機寫入情況下的 IOPS 大約比傳統(tǒng) OSD 高 24%。進一步的分析顯示,在隨機寫的情況下,CPU 的利用率很低,因為大約 20% 的 CPU 被消耗在頻繁的查詢中,這表明 Crimson OSD 應該不是是當前的瓶頸。
Crimson OSD 提交和完成 IO 任務,以及在 Seastar 和 BlueStore 線程之間進行同步,也有額外的開銷。因此,我們針對 MemStore 后臺重復了同一組實驗,兩個 OSD 都分配了 1 個 CPU。如下圖所示,Crimson OSD 在隨機讀取中提供了大約 70% 的 IOPS,在隨機寫入中比 傳統(tǒng) OSD 高 25%,這與之前實驗中的結(jié)論一致,即 Crimson OSD 可以做得更好。
盡管上述場景僅涵蓋實驗性 single-shard? 案例,但結(jié)果表明使用 Seastar 框架具有性能優(yōu)勢——消除鎖、通過用戶空間任務調(diào)度刪除上下文切換、分配更靠近 CPU 的內(nèi)存。此外,重要的是要重申,run-to-completion 模型的目標是更好地擴展 CPU 并消除軟件使用高性能硬件而引起的性能瓶頸。
Multi-shard Implementation
實現(xiàn)多分片的路徑很明確。由于每個PG中的 IO 已經(jīng)在邏輯上被分片,所以對IO路徑?jīng)]有太大改變。主要的挑戰(zhàn)是確定無法避免的跨核通信,并設計新的解決方案,以盡量減少其對IO路徑的影響,這需要根據(jù)具體情況進行分析。一般來說,當從 Messenger 接收到一個 IO 操作時,它會根據(jù) PG-core 映射被定向到 OSD 分片,并在同一分片/CPU的上下文中運行,直到完成。請注意,在當前階段,為了簡單起見,設計上選擇不修改RADOS協(xié)議。
Messenger
Messenger 在確保解決方案可擴展方面發(fā)揮著重要作用。有一些限制需要認真考慮。一個限制來自 RADOS 協(xié)議,它只為每個客戶端或 OSD 定義一個連接。連接必須存在于特定核心上才能根據(jù)其狀態(tài)高效且無鎖地解碼和編碼消息。與 OSD 對等體的共享連接意味著在當前階段跨核消息傳遞到多個 PG 分片是不可避免的,除非可以調(diào)整協(xié)議以允許到每個分片的獨占連接。
Seastar 框架的另一個限制是它不允許在 Seastar 套接字被 accept()ed? 或 connect()ed 之后移動到另一個核心。這對無損連接 (msgr2) 來說是一個挑戰(zhàn),因為它會影響 Messenger 和 OSD 服務之間的交互,在這種情況下,由于網(wǎng)絡故障重新連接,連接可能會預先跳轉(zhuǎn)到另一個核心。
擴展 Messenger 的大部分工作是在將 IO 操作分派到 PG 分片之前將消息傳遞工作負載(編碼、解碼、壓縮、加密、緩沖區(qū)管理等)優(yōu)化擴展到多個內(nèi)核,并最小化跨內(nèi)核消息沿 IO 路徑傳遞,理想情況下,在上述約束下,對于每個消息發(fā)送和接收操作,它最多保持 1 跳。
OSD
OSD 負責維護 PG 分片之間共享的全局狀態(tài)和活動,包括心跳、身份驗證、客戶端管理、osdmap、PG 維護、訪問 Messenger 和 ObjectStore 等。
多核 Crimson OSD 的一個簡單原則是將所有與共享狀態(tài)相關(guān)的處理保持在專用內(nèi)核上。如果一個 IO 操作要訪問共享資源,要么按順序訪問專用核,要么訪問保持同步的共享信息的獨占副本。
實現(xiàn)這一目標有兩個主要步驟。第一步是讓 IO 操作根據(jù) PG 分片策略運行在多個 OSD 分片中,包括 PG 狀態(tài)在內(nèi)的所有全局信息都維護在第一個分片中。此步驟在 OSD 中啟用分片,但需要在第一個分片中做出有關(guān) IO 調(diào)度的所有決策。即使這一步 Messenger 可以在多核中運行,消息仍然需要傳遞到第一個分片進行準備(例如 PG peering)并在提交到該分片之前確定正確的 PG 分片。這會導致額外的開銷和不平衡的 CPU 使用(第一個 OSD 分片使用率高,其他分片很低,等等)。因此,下一步是將 PG-core 映射擴展到所有 OSD 分片。
ObjectStore
Crimson 支持三種 ObjectStore 后端:AlienStore、CyanStore 和 SeaStore。AlienStore 提供與 BlueStore 的向后兼容性。CyanStore 是用于測試的虛擬后端,由易失性內(nèi)存實現(xiàn)。SeaStore 是一種新的對象存儲,專為 Crimson OSD 設計,采用 shared-nothing 設計。根據(jù)后端的具體目標,實現(xiàn)多分片支持的路徑是不同的。
1AlienStore
AlienStore 是 Seastar 線程中的一個瘦代理,用于與使用 POSIX 線程的 BlueStore 進行通信。對于多個 OSD 分片沒有特別的工作要做,因為 IO 任務通信同步了。BlueStore 中沒有為 Crimson 定制其他內(nèi)容,因為不可能真正將 BlueStore 擴展到 shared-nothing 設計,因為它依賴于第 三 方 RocksDB 項目,而 RocksDB 仍然是線程的。但是,在 Crimson 能夠拿出一個足夠優(yōu)化和足夠穩(wěn)定的原生存儲后端解決方案(SeaStore)之前,合理的開銷來換取復雜的存儲后端解決方案是可以接受的。
2CyanStore
Crimson OSD 中的 CyanStore 與傳統(tǒng) OSD 中的 MemStore 相對應。對多分片支持的唯一改變是為每個分片創(chuàng)建獨立的 CyanStore 實例。一個目標是確保虛擬 IO 操作能夠在同一個內(nèi)核中完成,以幫助識別 OSD 級別的可擴展性問題(如果有的話)。另一個目標是在 OSD 層面上與傳統(tǒng) OSD 做直接的性能比較,而不受 ObjectStore 的復雜因數(shù)影響。
3SeaStore
SeaStore 是 Crimson OSD 原生的 ObjectStore 解決方案,采用 Seastar 框架開發(fā),采用相同的設計原則。
雖然很有挑戰(zhàn)性,但是 Crimson 必須建立一個新的本地存儲引擎,這有多種原因。存儲后端是主要的 CPU 資源消耗者,如果 Crimson OSD 的存儲后端不改變,那么它就不能真正地隨核心擴展。我們的實驗也證明了 Crimson OSD 不是隨機寫入場景中的瓶頸。
其次,BlueStore 中具有事務支持的 CPU 密集型元數(shù)據(jù)管理基本上由 RocksDB 提供,如果不重新實現(xiàn),它無法在原生的 Seastar 線程中運行。與其為 BlueStore 重新實現(xiàn)通用的鍵值事務存儲,不如在更高的層次上重新思考和定制相應的架構(gòu)——ObjectStore。問題在原生的解決方案中比在 第三方項目中更容易解決,因為第三方項目必須保證使用與通用的場景。
第三個考慮是為異構(gòu)存儲設備和硬件加速器提供原生支持,讓用戶可以根據(jù)自己的需求平衡成本和性能。如果 Crimson 能夠更好地控制整個存儲堆棧,那么 Crimson 將更靈活地簡化部署硬件組合的解決方案。
SeaStore 在單分片讀寫方面已經(jīng)可以正常使用,盡管在穩(wěn)定性和性能改進方面仍有待努力。目前的努力仍然集中在架構(gòu)上,而不是極端情況下的優(yōu)化。它針對多分片 OSD 的設計很明確。與 CyanStore 一樣,第一步是為每個 OSD 分片創(chuàng)建獨立的 SeaStore 實例,每個實例都在存儲設備的靜態(tài)分區(qū)上運行。第二步是實現(xiàn)一個共享磁盤空間平衡器來動態(tài)調(diào)整分區(qū),它應該可以在后臺異步運行,因為 PG 已經(jīng)以偽隨機方式分配了用戶 IO。SeaStore 實例可能不需要等于 OSD 分片的數(shù)量,根據(jù)性能分析,調(diào)整這個比例是后期工作的第三步。
摘要和測試配置
在這篇文章中,我們介紹了為什么以及如何對 Ceph OSD 進行重構(gòu)以跟上硬件的發(fā)展。另外我們也給出了我們所做的詳細設計、 一個簡單的性能測試結(jié)果。也提供了 Crimson OSD 真正實現(xiàn)多核可擴展的所要考慮的大部分因素。
測試結(jié)果可能會根據(jù)不同的 commit 版本、軟件和硬件配置而有所變化。為了確保我們的測試是可重復的,可復現(xiàn)的,并可在以后場景中作為參考,我們列出了所有可能產(chǎn)生影響的設置和注意事項。
我們?yōu)?Crimson 和 傳統(tǒng) OSD 部署了本地 Ceph 集群,并使用 CBT 執(zhí)行了 FIO 測試。Crimson 在使用 tcmalloc 時仍然存在問題,因此為了公平起見,我們將兩個 OSD 配置為使用 libc*。我們使用 BlueStore。RBD 緩存被禁用。BlueStore 線程數(shù)設置為 4 以獲得更好的結(jié)果。部署 Crimson 時,需要指定*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 存儲庫,部署過程的其余部分沒有變化。
測試場景:
- 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 塊設備
- 50GB 內(nèi)存用于 MemStore 和 CyanStore