JuiceFS 快取策略詳解

語言: CN / TW / HK

對於一個由物件儲存和資料庫組合驅動的檔案系統,快取是本地客戶端與遠端服務之間高效互動的重要紐帶。讀寫的資料可以提前或者非同步載入快取,再由客戶端在後臺與遠端服務互動執行非同步上傳或預取資料。相比直接與遠端服務互動,採用快取技術可以大大降低儲存操作的延時並提高資料吞吐量。

資料一致性

JuiceFS 提供「關閉再開啟(close-to-open)」一致性保證,即當兩個及以上客戶端同時讀寫相同的檔案時,客戶端 A 的修改在客戶端 B 不一定能立即看到。但是,一旦這個檔案在客戶端 A 寫入完成並關閉,之後在任何一個客戶端重新開啟該檔案都可以保證能訪問到最新寫入的資料,不論是否在同一個節點。

「關閉再開啟」是 JuiceFS 提供的最低限度一致性保證,在某些情況下可能也不需要重新開啟檔案才能訪問到最新寫入的資料。例如多個應用程式使用同一個 JuiceFS 客戶端訪問相同的檔案(檔案變更立即可見),或者在不同節點上通過 tail -f 命令檢視最新資料。

元資料快取

JuiceFS 支援在核心和客戶端記憶體(即 JuiceFS 程序)中快取元資料以提升元資料的訪問效能。

核心元資料快取

核心中可以快取三種元資料: 屬性(attribute) 檔案項(entry) 目錄項(direntry) ,可以通過以下掛載引數控制快取時間:

--attr-cache value       屬性快取時長,單位秒 (預設值: 1)
--entry-cache value      檔案項快取時長,單位秒 (預設值: 1)
--dir-entry-cache value  目錄項快取時長,單位秒 (預設值: 1)

JuiceFS 預設會在核心中快取屬性、檔案項和目錄項,快取時長 1 秒,以提高 lookup 和 getattr 的效能。當多個節點的客戶端同時使用同一個檔案系統時,核心中快取的元資料只能通過時間失效。也就是說,極端情況下可能出現節點 A 修改了某個檔案的元資料(如 chown ),通過節點 B 訪問未能立即看到更新的情況。當然,等快取過期後,所有節點最終都能看到 A 所做的修改。

客戶端記憶體元資料快取

注意:此特性需要使用 0.15.0 及以上版本的 JuiceFS。

JuiceFS 客戶端在 open() 操作即開啟一個檔案時,其檔案屬性(attribute)會被自動快取在客戶端記憶體中。如果在掛載檔案系統時設定了  --open-cache 選項且值大於 0,只要快取尚未超時失效,隨後執行的  getattr() 和  open() 操作會從記憶體快取中立即返回結果。

執行 read() 操作即讀取一個檔案時,檔案的 chunk 和 slice 資訊會被自動快取在客戶端記憶體。在快取有效期內,再次讀取 chunk 會從記憶體快取中立即返回 slice 資訊。

提示:您可以查閱 「JuiceFS 如何儲存檔案」 [1] 瞭解 chunk 和 slice 是什麼。

預設情況下,對於一個元資料已經被快取在記憶體的檔案,超過 1 小時沒有被任何程序訪問,其所有元資料快取會被自動刪除。

資料快取

JuiceFS 對資料也提供多種快取機制來提高效能,包括核心中的頁快取和客戶端所在節點的本地快取。

核心資料快取

注意:此特性需要使用 0.15.0 及以上版本的 JuiceFS。

對於已經讀過的檔案,核心會把它的內容自動快取下來,隨後再開啟該檔案,如果檔案沒有被更新(即 mtime 沒有更新),就可以直接從核心中的快取讀取該檔案,從而獲得最好的效能。得益於核心快取,重複讀取 JuiceFS 中相同檔案的速度會非常快,延時可低至微秒,吞吐量可以到每秒數 GiB。

JuiceFS 客戶端目前還未預設啟用核心的寫入快取功能,從 Linux 核心 3.15 開始,FUSE 支援「writeback-cache 模式」這意味著可以非常快速地完成 write() 系統呼叫。你可以在掛載檔案系統時設定  -o writeback_cache 選項開啟 writeback-cache 模式。當需要頻繁寫入非常小的資料(如 100 位元組左右)時,建議啟用此掛載選項。

客戶端讀快取

JuiceFS 客戶端會根據讀取模式自動預讀資料放入快取,從而提高順序讀的效能。預設情況下,會在讀取資料時併發預讀 1 個 block 快取在本地。本地快取可以設定在基於機械硬碟、SSD 或記憶體的任意本地檔案系統。

本地快取可以在掛載檔案系統時通過以下選項進行調整:

--prefetch value          併發預讀 N 個塊 (預設: 1)
--cache-dir value         本地快取目錄路徑;使用冒號隔離多個路徑 (預設: "$HOME/.juicefs/cache" 或 "/var/jfsCache")
--cache-size value        快取物件的總大小;單位為 MiB (預設: 1024)
--free-space-ratio value  最小剩餘空間比例 (預設: 0.1)
--cache-partial-only      僅快取隨機小塊讀 (預設: false)

此外,如果希望將 JuiceFS 的本地快取儲存在記憶體中有兩種方式,一種是將 --cache-dir 設定為  memory ,另一種是將其設定為  /dev/shm/<cache-dir> 。這兩種方式的區別是前者在重新掛載 JuiceFS 檔案系統之後快取資料就清空了,而後者還會保留,效能上兩者沒有太大差別。

JuiceFS 客戶端會盡可能快地把從物件儲存下載的資料(包括新上傳的小於 1 個 block 大小的資料)寫入到快取目錄中,不做壓縮和加密。 因為 JuiceFS 會為所有寫入物件儲存的 block 物件生成唯一的名字,而且所有 block 物件不會被修改,因此當檔案內容更新時,不用擔心快取的資料失效問題。

當快取在使用空間到達上限(即快取大小大於等於 --cache-size )或磁碟將被存滿(即磁碟可用空間比例小於  --free-space-ratio )時會自動進行清理,目前的規則是根據訪問時間,優先清理不頻繁訪問的檔案。

資料快取可以有效地提高隨機讀的效能,對於像 Elasticsearch、ClickHouse 等對隨機讀效能要求更高的應用,建議將快取路徑設定在速度更快的儲存介質上並分配更大的快取空間。

客戶端寫快取

寫入資料時,JuiceFS 客戶端會把資料快取在記憶體,直到當一個 chunk 被寫滿或通過 close() 或  fsync() 強制操作時,資料才會被上傳到物件儲存。在呼叫  fsync() 或  close() 時,客戶端會等資料寫入物件儲存並通知元資料服務後才會返回,從而確保資料完整。

在某些情況下,如果本地儲存是可靠的,且本地儲存的寫入效能明顯優於網路寫入(如 SSD 盤),可以通過啟用非同步上傳資料的方式提高寫入效能,這樣一來 close() 操作不會等待資料寫入到物件儲存,而是在資料寫入本地快取目錄就返回。

非同步上傳功能預設關閉,可以通過以下選項啟用:

--writeback  後臺非同步上傳物件 (預設: false)

當需要短時間寫入大量小檔案時,建議使用 --writeback 引數掛載檔案系統以提高寫入效能,寫入完成之後可考慮取消該選項重新掛載以使後續的寫入資料獲得更高的可靠性。另外,像 MySQL 的增量備份等需要大量隨機寫操作的場景時也建議啟用  --writeback

警告:當啟用了非同步上傳,即掛載檔案系統時指定了  --writeback 時,千萬不要刪除  <cache-dir>/<UUID>/rawstaging 目錄中的內容,否則會導致資料丟失。

當快取磁碟將被寫滿時,會暫停寫入資料,改為直接上傳資料到物件儲存(即關閉客戶端寫快取功能)。啟用非同步上傳功能時,快取本身的可靠性與資料寫入的可靠性直接相關,對資料可靠性要求高的場景應謹慎使用。

總結

最後,分享一個使用者們經常會問到的問題 「為什麼設定了快取容量為 50 GiB,但實際佔用了 60 GiB 的空間?」

對於總量相同的快取資料,在不同的檔案系統上會有不同的容量計算規則。JuiceFS 目前是通過累加所有被快取物件的大小並附加固定的開銷(4KiB)來估算得到的,這與 du 命令得到的數值並不完全一致。為防止快取盤被寫滿,當快取目錄所在檔案系統空間不足時,客戶端會盡量減少快取用量。

通過上述介紹,我們對 JuiceFS 的快取機制的原理有了進一步瞭解。JuiceFS 本身作為底層檔案系統,提供了包括元資料快取、資料讀寫快取等多種快取機制,最大限度的保證了資料讀寫的效能與一致性。希望大家通過本文的瞭解能更好的應用 JuiceFS。

引用連結

[1] 「JuiceFS 如何儲存檔案」:  https://juicefs.com/docs/zh/community/how_juicefs_store_files

開源社群貢獻指南

JuiceFS 已於 2021 年 1 月開源,開源軟體的發展離不開每一個人的支援,一篇文章、一頁文件、一個想法、一個建議、報告或修復一個 Bug,這些貢獻不論大小都是推動開源專案不斷髮展的動力, 歡迎來 JuiceFS 的社群參與以上貢獻。 (https://github.com/juicedata/juicefs)

:point_down: 掃碼加群 :point_down:

:point_down: 關注「 Juicedata 」,獲取更多技術乾貨   :point_down: