SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

小型 VPS pool 的 ZFS scrub 排程與頻率

了解 ZFS scrub 如何驗證每個已配置區塊,以及單一裝置 VPS pool 為何只能偵測、無法修復損壞,並掌握合適的執行頻率。

ZFS scrub 的實際作用

ZFS scrub 會讀取 pool 中每個已配置的區塊,重新計算其 checksum,並將結果與父區塊指標中儲存的 checksum 比對。如果兩者不一致,ZFS 會使用 pool 中的備援資料修復該區塊。ZFS 中沒有其他機制會執行這項工作。一般讀取只會驗證實際存取的區塊,因此兩年未開啟的檔案,必須等到 scrub 讀取它之後才會完成驗證。

scrub 不是 fsck,也不是其他檔案系統所需的離線修復程序。ZFS 不需要結構修復階段,因為它不會讓磁碟上的格式處於損壞狀態:每次寫入都會寫到新的位置,最後才更新 uberblock,也就是 pool 的根指標。scrub 也不會讀取整個裝置。它只會讀取已配置的區塊,因此幾乎空的 pool 只需幾分鐘即可完成 scrub,而同一個 pool 使用率達到 80% 時則需要更久。

scrub 會以 ZFS 可用的最低 I/O 優先權執行。在 Linux 上,zfs_vdev_scrub_max_active 預設為 2,因此每個 vdev(virtual device,ZFS 視為單一單位的磁碟群組)最多同時執行兩個 scrub 讀取作業。zfs_scrub_min_time_ms 預設為 750,表示每次 transaction group flush 之間,sync thread 至少會花費的 scrub 工作時間;transaction group flush 是 ZFS 將寫入批次提交的定期程序。在閒置的機器上,scrub 會使用整個磁碟的 I/O 資源。系統有負載時,scrub 會讓出資源。只有一或兩個裝置的 pool 幾乎沒有可讓出的空間,因此排程的重要性高於配備 60 顆磁碟的大型機箱。

無法自行修復的 pool 為什麼還要執行 scrub?

這句話決定了小型 pool 的所有後續做法。沒有 redundancy 時,scrub 只能偵測 corruption,無法修復。 VPS 中的單一 virtual disk 是沒有 mirror、也沒有 parity 的 pool。ZFS 會讀取損壞的 block、驗證 checksum 失敗、在 CKSUM 欄位中累計錯誤、指出檔案名稱,然後停止,因為沒有第二份 copy 可供重建。

有兩個部分例外值得了解。ZFS 預設會額外儲存一份 metadata(redundant_metadata=all),並將其寫入 device 的不同區域。因此,即使是單一 device 的 pool,scrub 仍可修復損壞的 directory entry 或 block pointer。dataset 設定為 copies=2 時,則會保留 data block 的兩份 copy,但空間成本會加倍。若 device 整個無法使用,這兩種方式都無法保留資料。copies property 的文件正是針對這點提出警告:不要建立 striped pool、設定 copies=2,就以為自己具備 redundancy。

因此,在單一 device 的 pool 上執行 scrub,只能提供一項功能:及早且準確地通知問題。當 backup 仍保有檔案的正確版本時,scrub 會將原本無聲的 corruption 轉換成 zpool status -v 中的檔案名稱。這是支持 backup 的理由,不是反對 scrub 的理由。如果你還沒有釐清 point-in-time image 與真正的 off-box copy 之間的差異,請先閱讀 為什麼 VPS snapshot 不是 backup,因為 scrub 的結果只有在其他位置仍保有完整 copy 時才有幫助。

scrub 沒有發現任何問題,同樣也是一項結果。這表示你即將信任的資料保持完整,而這正是執行 restore 或 migration 前需要確認的事項。

小型 VPS pool 應多久執行一次 scrub?

每月一次是合適的預設值,套件也已依此設定。Debian 和 Ubuntu 會提供 cron job,在每月第 2 個星期日對狀態正常的 pool 執行 scrub。FreeBSD 的 periodic system 會依天數門檻運作,而 daily_scrub_zfs_default_threshold 的預設值為 35;手冊將其說明為 5 週。

對忙碌的小型 pool 而言,每週執行 scrub 通常得不償失。只有 1 或 2 個裝置時,scrub 會與應用程式爭用相同的佇列,也沒有備用裝置可分擔負載。在 VPS 上,I/O 額度有限,因此 scrub 消耗的讀取量,就是資料庫無法使用的讀取量。相較之下,每週執行 scrub 最多只能提前 3 週發現無法修復的故障。只有在 scrub 成本很低時,這項取捨才合理。

先測量時間,再決定設定。手動執行一次 scrub,並觀察所需時間。

  1. 在負載較低的晚上執行 sudo zpool scrub tank,並從 zpool status 記錄總耗時。
  2. 如果在遠低於 1 小時內完成,且主機夜間處於閒置狀態,每週執行的成本可以接受。
  3. 如果 pool 在提供網路流量期間持續執行數小時,請維持每月一次,並交由套件提供的 job 管理。
  4. 每當 pool 明顯成長時,重新測量耗時,因為 scrub 時間取決於已配置的資料量,而不是磁碟容量。

無論選擇哪種週期,都應將其記錄在其他定期伺服器維護工作旁。scrub 應與套件升級和 log rotation 列在同一份清單中:請參閱 每月 Linux 伺服器維護檢查清單

啟動、暫停與停止 scrub

sudo zpool scrub tank
sudo zpool status tank

暫停與停止是不同的操作。選錯操作可能會讓你多花數小時重複執行工作。

sudo zpool scrub -p tank
sudo zpool scrub tank
sudo zpool scrub -s tank

-p 會暫停 scrub。暫停狀態與進度會定期同步至磁碟,因此暫停中的 scrub 即使在匯出 pool 或重新開機後仍會保留:pool 重新載入後,scrub 會維持暫停,等待你繼續執行。再次執行 zpool scrub,會從上次寫入磁碟的檢查點繼續。-s 則會停止 scrub,下一次啟動的 scrub 會從頭開始。需要暫時釋放磁碟一小時時,使用 -p。想完全結束 scrub 時,使用 -s

另外還有 2 個值得知道的旗標。-w 會等到 scrub 完成後才返回。若要在 script 中使用,應選擇此旗標,這樣下一個步驟不會過早開始。-e 只會 scrub zpool status -v 回報為已知資料錯誤的檔案。若要快速確認從備份還原的檔案現在是否完整無誤,這是適合的方式。

每個 pool 一次只能執行 1 個 scrub 或 resilver。resilver 是更換裝置後執行的重建程序,這 2 項作業都會大量使用 I/O。如果某個裝置正在 resilver,scrub 就必須等待。

執行中 scrub 時如何讀取 zpool status

執行 sudo zpool status tank,並根據自己的數值判讀,不要拿來與他人的輸出比較。scrub 執行期間,scan: 列會顯示已掃描數、已發出數、總數、已修復數、完成百分比,以及預估剩餘時間。

Scanned 是中繼資料階段:ZFS 走訪區塊樹,收集需要讀取的位址。Issued 是資料階段:實際送往裝置的讀取作業,並依磁碟順序排序。Sorted scrub 會有兩個計數器,原因就在這裡;而 issued 才能反映實際進度。開始執行時,scanned 會遠遠超前 issued,因此時間估算幾乎沒有參考價值。請等完成第一個 10% 後再判讀。

Repaired 計算從良好副本重新寫入的位元組數。在沒有備援的 pool 上,不論 scrub 找到什麼問題,這個數值都會維持 0。這也以可監看的數字重述了前面提到的限制。

接著查看各裝置的欄位。READ 和 WRITE 計算裝置本身回報的 I/O 錯誤。CKSUM 計算未通過 checksum 驗證的區塊,而 scrub 的用途之一就是填入 CKSUM。裝置看似正常但 CKSUM 不為 0 時,這仍是真實錯誤:資料已經讀回,但內容不正確。

最後一列是判定結果。errors: No known data errors 表示通過。其他任何結果都表示需要執行 sudo zpool status -v tank;該命令會列出上次完整 scrub 以來的所有資料錯誤,包括受影響的檔名。從備份還原這些檔案,執行 sudo zpool clear tank 重設計數器,然後再次執行 scrub。每次完整 scrub 都會重新建立這份清單,因此檔名在完整且無錯誤的 scrub 後消失,就代表該錯誤確實已排除。

主機上有哪些定期 scrub 工作?

不要假設一定存在,也不要假設只有一個。執行機制會因平台與套件而異。所有平台的 pool 格式都相同,因此很容易忘記周邊工具並不相同;FreeBSD 與 Linux 提供 ZFS 的方式正是這裡需要注意的差異。

在 FreeBSD 上,工作由 periodic 系統執行。請在 /etc/periodic.conf 中設定以下項目:

daily_scrub_zfs_enable="YES"
daily_scrub_zfs_pools="tank"
daily_scrub_zfs_default_threshold="35"

daily_scrub_zfs_pools 是以空格分隔的 pool 名稱清單;留空時會 scrub 所有 pool。daily_scrub_zfs_default_threshold 是未設定 pool 專屬門檻時,兩次 scrub 之間的天數;手冊列出的預設值為 35。daily 工作每天執行,但只有在超過門檻後才會啟動 scrub。

在 Linux 上,執行方式取決於發行版提供的 ZFS 套件,而且部分系統可能同時具備兩種機制。每個 pool 都有個別的 systemd timer,分別是 zfs-scrub-monthly@tank.timerzfs-scrub-weekly@tank.timer,必須逐一啟用。Debian 與 Ubuntu 也提供 /etc/cron.d/zfsutils-linux;它會執行一個 script,在每月第二個星期日 scrub 所有 ONLINE pool。新增任何設定前,先確認目前的配置:

systemctl list-timers 'zfs-*'
ls /etc/cron.d/ | grep -i zfs
sudo zpool history tank | grep scrub

zpool history 才是可靠的判斷依據,因為它會記錄 pool 實際啟動 scrub 的日期。每月執行兩次 scrub 表示兩種機制都在運作,其中一種應予停用。若要啟用 timer:

sudo systemctl enable --now zfs-scrub-monthly@tank.timer

可用空間比任何可調參數都重要

小型 pool 的 scrub 執行時間取決於已配置的資料量,以及資料分散的程度。pool 越滿,這兩項情況都會惡化。

OpenZFS 建議 pool 的可用空間維持在 10% 以上。低於此值後,metaslab(allocator 作業所使用的資料區塊)會開始低於 4% 的可用空間門檻,allocator 也會從 first-fit 切換為 best-fit。best-fit 需要更多 CPU 資源。寫入延遲會上升,接著產生更多 fragmentation;下一次 scrub 也會更慢,因為相同資料量會以更多、更小的讀取要求送入。

因此,第一個處理方法不是調整參數,而是刪除資料。在 ZFS 主機上,最常見的原因是舊 snapshot,其次是無人清理的 Docker images 與 layers以及升級後留下的 kernel packages。先執行 zfs list -o space,再處理其他項目,因為這能區分由 snapshot 保留的空間與 live data 使用的空間。

接著簡要說明可調參數。在 Linux 上,可以讀取目前的值:

cat /sys/module/zfs/parameters/zfs_scrub_min_time_ms
cat /sys/module/zfs/parameters/zfs_vdev_scrub_max_active

FreeBSD 透過 sysctl 提供相同參數,因此可使用 sysctl -a | grep scrub 找出目前的值。提高這些參數的值會讓 scrub 更快完成,但應用程式的速度會變慢。降低這些參數的值則相反。在只有一或兩個裝置的 pool 上,任何設定都無法同時滿足這兩項需求,因為只有一個佇列可供分配。可調參數很少能修正設計問題。如果每月一次的 scrub 造成影響,合理的判斷是 pool 太滿,或裝置速度太慢;可調參數只能轉移問題。

Scrub 時間就是 resilver 的預覽

resilver 與 scrub 執行相同的掃描:讀取已配置的區塊、驗證區塊,並將遺失的區塊寫入替代裝置。因此,scrub 所需的時間,是重建所需時間,以及 pool 在重建期間維持降低備援狀態時間的最可靠估計。

ZFS 排程 resilver 工作時通常比 scrub 更積極,因此相同 pool 的重建通常會比 scrub 更快完成。請將 scrub 時間視為保守的上限。如果 scrub 需要 9 小時,請按照相近的時間規模規劃重建時段,並注意在這段期間內若第二個裝置故障,pool 將會遺失。這也是採用鏡像配對,而不是單一寬幅 raidz 群組的實際理由,因為 raidz 是 ZFS 用來取代 RAID 5 的同位元配置,重建時會讀取所有仍存活的裝置。

單一裝置 pool 完全沒有 resilver。裝置故障,pool 也會隨之遺失。你的復原時間就是還原時間,因此應改為測量還原所需的時間。從未執行過的還原,不能算是復原計畫。

租用磁碟後的變化

在 VPS 上,區塊裝置是虛擬的。hypervisor 會提供一個 volume,而底層可能是本機 NVMe,也可能是具備自身 parity 的複寫網路 volume。這會對 scrub 產生兩項影響。

第一,平台的備援機制對 ZFS 不可見,ZFS 也無法使用。如果平台在底層修復媒體錯誤,ZFS 永遠不會看到這個問題。如果平台向上層提供錯誤的區塊,ZFS 可以偵測到,但無法修復,因為正確副本位於這個邊界的另一側。

第二,您通常無法讀取虛擬磁碟底層裝置的 SMART(self-monitoring, analysis and reporting technology)資料,因此 VPS 上的磁碟健康監控所依賴的早期警告可能完全無法取得。scrub 產生的 CKSUM 計數器,會成為您能掌握的主要訊號。

如果您希望 ZFS 能夠修復問題,而不只是回報問題,pool 內需要有一個以上的裝置。這是規劃層面的決策,不是調校設定。選擇儲存型 VPS,而不是一般 VPS可以取得所需容量,但是否提供兩個獨立裝置,取決於方案內容。建立 mirror 前,先執行 lsblk 並確認裝置,避免把實際上屬於同一個 volume 的兩個切片誤當成獨立裝置。我們租用 Linux 和 FreeBSD 伺服器,不提供代管的 ZFS appliance,因此 scrub 排程與備份都由您負責執行。這就是取捨:完全控制 pool,也完全負責維護。

FAQ

在 VPS 上應多久 scrub 一次 ZFS pool?

每月一次適合大多數小型 pool,也符合套件的預設行為:Debian 與 Ubuntu 會在每月第 2 個星期日執行 cron job,而 FreeBSD 的 periodic system 預設門檻為 35 天。只有在測量過 scrub 時間,並確認其能在閒置主機上快速完成後,才適合改為每週執行。對只有 1 或 2 個裝置的忙碌 pool 而言,每週 scrub 都會消耗實際的應用程式 I/O,但只能將警告時間提前幾週。

scrub 單磁碟 ZFS pool 沒有意義嗎?

不是,但前提是你必須清楚它能提供什麼。沒有 redundancy 時,scrub 可以偵測損毀,但無法修復;metadata 除外,因為 ZFS 預設會保留額外副本。你會在 zpool status -v 取得受損檔案的具名清單,並能在其他位置仍有良好副本時及早還原檔案。正確的做法是改善備份,因為 scrub 會明確指出要還原哪個檔案。

可以暫停 ZFS scrub,之後再完成嗎?

可以。zpool scrub -p tank 會暫停 scrub,而暫停狀態與進度會定期寫入磁碟,因此即使執行 export 或重新開機,scrub 仍會保持暫停。再次執行 zpool scrub tank,即可從最後一個 checkpoint 繼續。不要使用 zpool scrub -s tank 來完成這項操作:-s 會停止 scrub,下一次執行時會從頭開始。

為什麼我的 ZFS scrub 很慢?可以加快嗎?

scrub 所需時間取決於已配置的資料量與 fragmentation,而不是磁碟容量。pool 使用率超過 90% 時會變慢,因為可用空間低於 4% 的 metaslab 會讓 allocator 從 first-fit 改用 best-fit,後續產生的 fragmentation 會使 scrub 變成大量小型讀取。釋放空間通常比調整任何 tunable 更有效。你可以提高 zfs_scrub_min_time_mszfs_vdev_scrub_max_active,讓 scrub 取得較大的 queue 配額;但對只有 1 或 2 個裝置的 pool 而言,這部分資源會直接排擠應用程式。