一文詳解 JuiceFS 讀性能:預(yù)讀、預(yù)取、緩存、FUSE 和對象存儲

在高性能計(jì)算場景中,往往采用全閃存架構(gòu)和內(nèi)核態(tài)并行文件系統(tǒng),以滿足性能要求。隨著數(shù)據(jù)規(guī)模的增加和分布式系統(tǒng)集群規(guī)模的增加,全閃存的高成本和內(nèi)核客戶端的運(yùn)維復(fù)雜性成為主要挑戰(zhàn)。

JuiceFS,是一款全用戶態(tài)的云原生分布式文件系統(tǒng),通過分布式緩存大幅提升 I/O 吞吐量,并使用成本較低的對象存儲來完成數(shù)據(jù)存儲,適用于大規(guī)模 AI 業(yè)務(wù)。

JuiceFS 數(shù)據(jù)讀取流程從客戶端的讀請求開始,然后再經(jīng)過 FUSE 發(fā)送給 JuiceFS 客戶端,通過預(yù)讀緩沖層,接著進(jìn)入緩存層,最終訪問對象存儲。為了提高讀取效率,JuiceFS 在其架構(gòu)設(shè)計(jì)中采用了包括數(shù)據(jù)預(yù)讀、預(yù)取和緩存在內(nèi)的多種策略。

本文將詳細(xì)解析這些策略的工作原理,并分享我們在特定場景下的測試結(jié)果,以便讀者深入理解 JuiceFS 的性能優(yōu)勢及一些相關(guān)的限制,從而更有效地應(yīng)用于各種使用場景。

鑒于文章內(nèi)容的深度和技術(shù)性,需要讀者有一定操作系統(tǒng)知識,建議收藏以便在需要時(shí)進(jìn)行仔細(xì)閱讀。

01 JuiceFS 架構(gòu)簡介

JuiceFS 社區(qū)版架構(gòu)分為客戶端、數(shù)據(jù)存儲和元數(shù)據(jù)三部分。 數(shù)據(jù)訪問支持多種接口,包括 POSIX 、HDFS API、S3 API,還有 Kubernetes CSI,以滿足不同的應(yīng)用場景。在數(shù)據(jù)存儲方面,JuiceFS 支持幾十種對象存儲,包括公共云服務(wù)和自托管解決方案,如 Ceph 和 MinIO。元數(shù)據(jù)引擎支持多種常見的數(shù)據(jù)庫,包括 Redis、TiKV 和 MySQL 等。

企業(yè)版與社區(qū)版的主要區(qū)別在圖片左下角元數(shù)據(jù)引擎和數(shù)據(jù)緩存的處理。企業(yè)版包括一個(gè)自研的分布式元數(shù)據(jù)引擎,并支持分布式緩存,社區(qū)版只支持本地緩存。

02 Linux 中關(guān)于“讀”的幾個(gè)概念

在 Linux 系統(tǒng)中,數(shù)據(jù)讀取主要通過幾種方式實(shí)現(xiàn):

  • Buffered I/O:這是標(biāo)準(zhǔn)的文件讀取方式,數(shù)據(jù)會(huì)通過內(nèi)核緩沖區(qū),并且內(nèi)核會(huì)進(jìn)行預(yù)讀操作,以優(yōu)化讀取效率;
  • Direct I/O:允許繞過內(nèi)核緩沖區(qū)直接進(jìn)行文件 I/O 操作,以減少數(shù)據(jù)拷貝和內(nèi)存占用,適合大量數(shù)據(jù)傳輸;
  • Asynchronous I/O:通常與 Direct I/O 一起使用。它允許應(yīng)用程序在單個(gè)線程中發(fā)出多個(gè) I/O 請求,而不必等待每個(gè)請求完成,從而提高 I/O 并發(fā)性能;
  • Memory Map:將文件映射到進(jìn)程的地址空間,可以通過指針直接訪問文件內(nèi)容。通過內(nèi)存映射,應(yīng)用程序可以像訪問普通內(nèi)存一樣訪問映射的文件區(qū)域,由內(nèi)核自動(dòng)處理數(shù)據(jù)的讀取和寫入。

幾種主要的讀取模式及它們對存儲系統(tǒng)帶來的挑戰(zhàn):

  • 隨機(jī)讀取,包括隨機(jī)大 I/O 讀和隨機(jī)小 I/O 讀,主要考驗(yàn)存儲系統(tǒng)的延遲和 IOPS。
  • 順序讀取,主要考驗(yàn)存儲系統(tǒng)的帶寬。
  • 大量小文件讀取,主要考驗(yàn)存儲系統(tǒng)的元數(shù)據(jù)引擎的性能和系統(tǒng)整體的 IOPS 能力。

03 JuiceFS 讀流程原理解析

JuiceFS 采用了文件分塊存儲的策略。 一個(gè)文件首先邏輯上被分割成若干個(gè) chunk,每個(gè) chunk 固定大小為 64MB。每個(gè) chunk 進(jìn)一步被細(xì)分為若干個(gè) 4MB 的 block,block 是對象存儲中的實(shí)際存儲單元,JuiceFS 的設(shè)計(jì)中有不少性能優(yōu)化措施與這個(gè)分塊策略緊密相關(guān)。(進(jìn)一步了解 JuiceFS 存儲模式)。 為了優(yōu)化讀取性能, JuiceFS 采取了預(yù)讀、預(yù)取與緩存等多種優(yōu)化方案。

預(yù)讀 readahead

預(yù)讀(readahead):通過預(yù)測用戶未來的讀請求,提前從對象存儲中加載數(shù)據(jù)到內(nèi)存中,以降低訪問延遲,提高實(shí)際 I/O 并發(fā)。下面這張圖是一張簡化的讀取流程的示意圖。虛線以下代表應(yīng)用層,虛線以上是內(nèi)核層。

當(dāng)用戶進(jìn)程(左下角標(biāo)藍(lán)色的應(yīng)用層) 發(fā)起文件讀寫的系統(tǒng)調(diào)用時(shí),請求首先通過內(nèi)核的 VFS,然后傳遞給內(nèi)核的 FUSE 模塊,經(jīng)過 /dev/fuse 設(shè)備與 JuiceFS 的客戶端進(jìn)程通信。

右下角所示的流程是后續(xù)在 JuiceFS 中進(jìn)行的預(yù)讀優(yōu)化。系統(tǒng)通過引入“session” 跟蹤一系列連續(xù)讀。每個(gè) session 記錄了上一次讀取的偏移量、連續(xù)讀取的長度以及當(dāng)前預(yù)讀窗口大小,這些信息可用于判斷新來的讀請求是否命中這個(gè) session,并自動(dòng)調(diào)整/移動(dòng)預(yù)讀窗口。 通過維護(hù)多個(gè) session,JuiceFS 還能輕松支持高性能的并發(fā)連續(xù)讀。

為了提升連續(xù)讀的性能,在系統(tǒng)設(shè)計(jì)中,我們增加了提升并發(fā)的措施。具體來說,預(yù)讀窗口中的每一個(gè) block (4MB) 都會(huì)啟動(dòng)一個(gè) goroutine 來讀數(shù)據(jù)。 這里需要注意的是,并發(fā)數(shù)會(huì)受限于 buffer-size 參數(shù)。在默認(rèn) 300MB 設(shè)置下,理論最大對象存儲并發(fā)數(shù)為 75(300MB 除以 4MB),這個(gè)設(shè)置在一些高性能場景下是不夠的,用戶需要根據(jù)自己的資源配置和場景需求去調(diào)整這個(gè)參數(shù),下文中我們也對不同參數(shù)進(jìn)行了測試。

以下圖第二行為例,當(dāng)系統(tǒng)接收到連續(xù)的第二個(gè)讀請求時(shí),實(shí)際上會(huì)發(fā)起一個(gè)包含預(yù)讀窗口和讀請求的連續(xù)三個(gè)數(shù)據(jù)塊的請求。按照預(yù)讀的設(shè)置,接下來的兩個(gè)請求都會(huì)直接命中預(yù)讀的 buffer 并被立即返回。

如果第一個(gè)和第二個(gè)請求沒有被預(yù)讀,而是直接訪問對象存儲,延遲會(huì)比較高(通常大于10ms)。而當(dāng)延遲降低在 100 微秒以內(nèi),則說明這個(gè) I/O 請求成功使用了預(yù)讀,即第三個(gè)和第四個(gè)請求,直接命中了內(nèi)存中預(yù)讀的數(shù)據(jù)。

預(yù)取 prefetch

預(yù)取(prefetch):當(dāng)隨機(jī)讀取文件中的一小段數(shù)據(jù)時(shí),我們假設(shè)這段數(shù)據(jù)附近的區(qū)域也可能會(huì)被讀取,因此客戶端會(huì)異步將這一小段數(shù)據(jù)所在的整個(gè) block 下載下來。

但是在有些場景,預(yù)取這個(gè)策略不適用,例如應(yīng)用對大文件進(jìn)行大幅偏移的、稀疏的隨機(jī)讀取,預(yù)取會(huì)訪問到一些不必要的數(shù)據(jù),導(dǎo)致讀放大。因此,用戶如果已經(jīng)深入了解應(yīng)用場景的讀取模式,并確認(rèn)不需要預(yù)取,可以通過 --prefetch=0 禁用該行為。

緩存 cache

在之前的一次分享中,我們的架構(gòu)師高昌健詳細(xì)介紹了 JuiceFS的緩存機(jī)制,或查看緩存文檔。 在這篇文章中,對于緩存的介紹會(huì)以基本概念為主。

頁緩存 page cache

頁緩存(page cache)是 Linux 內(nèi)核提供的機(jī)制。它的核心功能之一就是預(yù)讀(readahead),它通過預(yù)先讀取數(shù)據(jù)到緩存中,確保在實(shí)際請求數(shù)據(jù)時(shí)能夠快速響應(yīng)。

進(jìn)一步,頁緩存(page cache)在特定場景下的應(yīng)用也非常關(guān)鍵,例如在處理隨機(jī)讀操作時(shí),如果用戶能策略性地使用頁緩存,將文件數(shù)據(jù)提前填充至進(jìn)頁緩存,如內(nèi)存空閑的情況下提前完整連續(xù)讀一遍文件,可以顯著提高后續(xù)隨機(jī)讀的性能,從而極大地提升業(yè)務(wù)整體性能。

本地緩存 local cache

JuiceFS 的本地緩存可以利用本地內(nèi)存或本地磁盤保存 block,從而在應(yīng)用訪問這些數(shù)據(jù)時(shí)可以實(shí)現(xiàn)本地命中,降低網(wǎng)絡(luò)時(shí)延并提升性能。我們通常推薦使用高性能 SSD。數(shù)據(jù)緩存的默認(rèn)單元是一個(gè) block,大小為 4MB,該 block 會(huì)在首次從對象存儲中讀取后異步寫入本地緩存。

關(guān)于本地緩存的配置,如 --cache-dir--cache-size 等細(xì)節(jié),企業(yè)版用戶可以查看文檔。

分布式緩存 cache group

分布式緩存是企業(yè)版的一個(gè)重要特性。與本地緩存相比,分布式緩存將多個(gè)節(jié)點(diǎn)的本地緩存聚合成同一個(gè)緩存池,提高緩存的命中率。但由于分布式緩存增加了一次網(wǎng)絡(luò)請求,這導(dǎo)致其在時(shí)延上通常稍高于本地緩存,分布式緩存隨機(jī)讀延遲一般是 1-2ms,而本地緩存隨機(jī)讀延遲一般是 0.2-0.5ms。關(guān)于分布式緩存具體架構(gòu),可以查看官網(wǎng)文檔。

04 FUSE & 對象存儲的性能表現(xiàn)

JuiceFS 的讀請求都要經(jīng)過 FUSE,數(shù)據(jù)要從對象存儲讀取,因此理解 FUSE 和對象存儲的性能表現(xiàn)是理解 JuiceFS 性能表現(xiàn)的基礎(chǔ)。

關(guān)于 FUSE 的性能

我們對 FUSE 性能進(jìn)行了兩組測試。測試場景是當(dāng) I/O 請求到達(dá) FUSE 掛載進(jìn)程后,數(shù)據(jù)被直接填充到內(nèi)存中并立即返回。測試主要評估 FUSE 在不同線程數(shù)量下的總帶寬,單個(gè)線程平均帶寬以及CPU使用情況。硬件方面,測試 1 是 Intel Xeon 架構(gòu),測試 2 則是 AMD EPYC 架構(gòu)。

Threads Bandwidth(GiB/s) Bandwidth per Thread (GiB/s) CPU Usage(cores)
1 7.95 7.95 0.9
2 15.4 7.7 1.8
3 20.9 6.9 2.7
4 27.6 6.9 3.6
6 43 7.2 5.3
8 55 6.9 7.1
10 69.6 6.96 8.6
15 90 6 13.6
20 104 5.2 18
25 102 4.08 22.6
30 98.5 3.28 27.4

FUSE 性能測試 1, 基于 Intel Xeon CPU 架構(gòu)

  • 在單線程測試中,最大帶寬達(dá)到 7.95GiB/s,同時(shí) CPU 使用量不到一個(gè)核;
  • 隨著線程數(shù)增加,帶寬基本實(shí)現(xiàn)線性擴(kuò)展,當(dāng)線程數(shù)增加到 20 時(shí),總帶寬增加到 104 GiB/s;

此處,用戶需要特別注意的是,相同 CPU 架構(gòu)下使用不同硬件型號、不同操作系統(tǒng)測得的 FUSE 帶寬表現(xiàn)都有可能不同。我們使用過多種機(jī)型進(jìn)行測試,在其中一種機(jī)型上測得的最大單線程帶寬僅為 3.9GiB/s。

Threads Bandwidth(GiB/s) Bandwidth per Thread (GiB/s) CPU Usage(cores)
1 3.5 3.5 1
2 6.3 3.15 1.9
3 9.5 3.16 2.8
4 9.7 2.43 3.8
6 14.0 2.33 5.7
8 17.0 2.13 7.6
10 18.6 1.9 9.4
15 21 1.4 13.7

FUSE 性能測試 2, 基于 AMD EPYC CPU 架構(gòu)

  • 在測試 2 中,帶寬不能線性擴(kuò)展,特別是當(dāng)并發(fā)數(shù)量達(dá)到 10 個(gè)時(shí),每個(gè)并發(fā)的帶寬不足 2GiB/s;

在多并發(fā)情況下,測試 2( EPYC 架構(gòu))帶寬峰值約為 20GiBps;測試 1(Intel Xeon 架構(gòu))表現(xiàn)出更高的性能空間,峰值通常在 CPU 資源被完全占用后出現(xiàn),這時(shí)應(yīng)用進(jìn)程和 FUSE 進(jìn)程的 CPU 都達(dá)到了資源極限。

在實(shí)際應(yīng)用中,由于各個(gè)環(huán)節(jié)的時(shí)間開銷,實(shí)際的 I/O 性能往往會(huì)低于上述測試峰值 3.5GiB/s。例如,在模型加載的場景中,加載 pickle 格式的模型文件,通常單線程帶寬只能達(dá)到 1.5 到 1.8GiB/s。這主要是因?yàn)樽x取 pickle 文件的同時(shí),要進(jìn)行數(shù)據(jù)反序列化,還會(huì)遇到 CPU 單核處理的瓶頸。即使是不經(jīng)過 FUSE,直接從內(nèi)存讀取的情況下,帶寬也最多只能達(dá)到 2.8GiB/s。

關(guān)于對象存儲的性能

我們使用 juicefs objbench 工具進(jìn)行測試,測試涵蓋了單并發(fā)、10 并發(fā)、200 并發(fā)以及 800 并發(fā)的不同負(fù)載。用戶需要注意的是,不同對象存儲的性能差距可能很大。

上傳帶寬 upload objects- MiB/s 下載帶寬 download objects MiB/s 上傳平均耗時(shí) ms/object 下載平均耗時(shí) ms/object
單并發(fā) 32.89 40.46 121.63 98.85ms
10 并發(fā) 332.75 364.82 10.02 10.96
200 并發(fā) 5590.26 3551.65 067 1.13
800 并發(fā) 8270.28 4038.41 0.48 0.99

當(dāng)我們增加對象存儲 GET 操作的并發(fā)數(shù)到 200 和 800 后,才能夠達(dá)到非常高的帶寬。這說明直接從對象存儲上讀數(shù)據(jù)時(shí),單并發(fā)帶寬非常有限,提高并發(fā)對整體的帶寬性能至關(guān)重要。

05 連續(xù)讀與隨機(jī)讀測試

為了給大家提供一個(gè)直觀的基準(zhǔn)參考,我們使用 fio 工具測試了 JuiceFS 企業(yè)版在連續(xù)讀取和隨機(jī)讀場景下的性能。

連續(xù)讀

從下圖可以看到 99% 的數(shù)據(jù)都小于 200 微秒。在連續(xù)讀場景下,預(yù)讀窗口總能很好地發(fā)揮作用,因此延遲很低。

同時(shí),我們也能通過加大預(yù)讀窗口,以提高 I/O 并發(fā),從而提升帶寬。當(dāng)我們將 buffer-size 從默認(rèn) 300MiB 調(diào)整為 2GiB 后,讀并發(fā)不再受限,讀帶寬從 674MiB/s 提升到了 1418 MiB/s,此時(shí)達(dá)到單線程 FUSE 的性能峰值,進(jìn)一步提高帶寬需要提高業(yè)務(wù)代碼中 I/O 并發(fā)度。

buffer-size 帶寬
300MiB 674MiB/s
2GiB 1418MiB/s

不同 buffer-size 帶寬性能測試(單線程)

當(dāng)提高業(yè)務(wù)線程數(shù)到 4 線程時(shí),帶寬能達(dá)到 3456MiB/s;16 線程時(shí),帶寬達(dá)到了 5457MiB/s,此時(shí)網(wǎng)絡(luò)帶寬已經(jīng)達(dá)到飽和。

buffer-size 帶寬
1線程 1418MiB/s
4線程 3456MiB/s
16線程 5457MiB/s

不同線程數(shù)量下帶寬性能測試 (buffer-size:2GiB)

隨機(jī)讀

對于小 I/O 隨機(jī)讀,其性能主要由延遲和 IOPS 決定,由于總 IOPS 能夠通過增加節(jié)點(diǎn)線性擴(kuò)展,所以我們先關(guān)注單節(jié)點(diǎn)上的延遲數(shù)據(jù)。

“FUSE 數(shù)據(jù)帶寬”是指通過 FUSE 層傳輸?shù)臄?shù)據(jù)量,代表用戶應(yīng)用實(shí)際可觀察和操作的數(shù)據(jù)傳輸速率;“底層數(shù)據(jù)帶寬”則指的存儲系統(tǒng)本身在物理層或操作系統(tǒng)層面處理數(shù)據(jù)的帶寬。

從表格中可以看到與穿透到對象存儲相比,命中本地緩存和分布式緩存的情況下,延遲都更低,當(dāng)我們需要優(yōu)化隨機(jī)讀延遲的時(shí)候就需要考慮提高數(shù)據(jù)的緩存命中率。同時(shí),我們也能看到使用異步 I/O 接口及提高線程數(shù)可以大大提高 IOPS。

不同于小 I/O 的場景,大 I/O 隨機(jī)讀場景還要注意讀放大問題。如下表所示,底層數(shù)據(jù)帶寬高于 FUSE 數(shù)據(jù)帶寬,這是因?yàn)轭A(yù)讀的作用,實(shí)際的數(shù)據(jù)請求會(huì)比來自于應(yīng)用的數(shù)據(jù)請求多1-3倍,此時(shí)可以嘗試關(guān)閉 prefetch 并調(diào)整最大預(yù)讀窗口來調(diào)優(yōu)。

分類 FUSE 數(shù)據(jù)帶寬 底層數(shù)據(jù)帶寬
1MB buffered IO 92MiB 290MiB
2MB buffered IO 155MiB 435MiB
4MB buffered IO 181MiB 575MiB
1MB direct IO 306MiB 306MiB
2MB direct IO 199MiB 340MiB
4MB direct IO 245MiB 735MiB

JuiceFS(開啟分布式緩存) 大 I/O 隨機(jī)讀測試結(jié)果

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容