SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

ZFS 在 FreeBSD 與 Linux 上的 RAM 取捨

ZFS 提供校驗和、快照、send/receive 複寫與壓縮,但 ARC 預設會佔用大量 RAM。本指南說明如何在 2 GB 或 4 GB VPS 上評估 ZFS。

ZFS 提供的功能,以及所需的資源

FreeBSD 與 Linux 上的 ZFS 現在共用同一套程式碼,也就是 OpenZFS,因此兩個系統的功能相同。執行 ZFS 的伺服器會取得具校驗和的資料、在資料變更前不佔用額外空間的快照、使用 zfs send 的複寫功能,以及只需設定一個屬性即可啟用的壓縮功能。所需的資源是記憶體:ARC(adaptive replacement cache)預設會使用大量 RAM;而在 2 GB 或 4 GB 的 VPS(virtual private server)上,這些記憶體正是應用程式所需的資源。

本指南從配備一或兩個虛擬磁碟的租用 VPS 角度評估 ZFS,而不是從擁有 40 個磁碟槽的儲存設備角度評估。經得起這種環境轉換的功能,才值得投入時間。無法經得起這種轉換的部分,則應在建立儲存池前先了解。

FreeBSD 與 Linux 上的 OpenZFS:同一份程式碼、兩種打包方式

FreeBSD 自 2008 年的 FreeBSD 7.0 起,就將 ZFS 納入 base system,最初是實驗性功能。自 2020 年 12 月的 OpenZFS 2.0 起,FreeBSD 與 Linux 都從同一份原始碼樹建置,因此 zfszpool 在兩者上的行為相同,而且在其中一個系統建立的 pool,也能在另一個系統匯入。

ZFS 在 Linux 上是套件,而在 FreeBSD 上屬於 base system,原因在於授權條款。OpenZFS 採用 CDDL(common development and distribution license)。Linux kernel 採用 GPL(general public license)version 2。kernel 專案將兩者視為不相容,因此 ZFS 程式碼不會合併至 mainline Linux,各個 distribution 也會自行決定如何提供。FreeBSD 沒有這項衝突,因此 ZFS 直接包含在系統中。實務上就是這麼簡單:只有打包方式不同,無須在兩者之間選邊。

SSD Nodes 不提供 FreeBSD images,因此在此租用的伺服器上,適用本指南的 Linux 部分。如果你在其他地方執行 FreeBSD,FreeBSD 伺服器可直接取得 ZFS,無須建置 module,也不必因應 kernel upgrade。

安裝 ZFS 並建立儲存池

在 Ubuntu 中,模組包含於 kernel 套件內,因此只需安裝指令。

sudo apt update
sudo apt install -y zfsutils-linux
zfs version

zfs version 會輸出兩行,分別是 userland 版本與 kernel 模組版本。若只輸出一行,表示模組未載入。套件位於 universe 元件中;Ubuntu server 映像檔預設會啟用此元件。若 apt 找不到套件,請先執行 sudo add-apt-repository universe

在 Debian 中,套件位於 contrib 元件中,模組會由 DKMS(dynamic kernel module support)在本機建置。將 contrib 加到 /etc/apt/sources.list.d/debian.sources 中的 Components: 行,執行 sudo apt update,然後:

sudo apt install -y linux-headers-$(dpkg --print-architecture) zfs-dkms zfsutils-linux

安裝程序會編譯模組並輸出 Building initial module for 6.12.0-...,這需要幾分鐘。請注意其含義:每次 kernel 升級都會重新建置模組;如果建置失敗,儲存池會維持未匯入狀態,直到修正問題為止。

在 FreeBSD 中,不會自動安裝任何項目。請啟用服務並啟動服務。

sysrc zfs_enable=YES
service zfs start

接著建立儲存池。先查看穩定的裝置路徑,因為 /dev/vdb 會依裝置的偵測順序指派,接上另一個磁碟區後可能變更。

ls -l /dev/disk/by-id/
sudo zpool create -o ashift=12 tank /dev/disk/by-id/virtio-abc123def456
zpool status tank

zpool status 應輸出 state: ONLINE,並在 tank 下列出您的裝置。ashift=12 會將儲存池的最小區塊大小固定為 4 KiB,這符合目前的 SSD,且建立後無法變更。

大多數租用的映像檔都從 ext4 root 開機,因此這裡的 ZFS 是位於第二個磁碟區上的資料儲存池,而不是 root filesystem。在建立儲存池前,請確認裝置就是您預期的裝置,因為確認業者提供的 NVMe 磁碟只需一分鐘,而重建需要一個下午。

僅在儲存池具備冗餘時,校驗和才能修復資料

ZFS 寫入的每個區塊都會帶有校驗和,每次讀取時也都會驗證。偵測永遠可用,但修復需要第二份資料。

在單磁碟儲存池上,ZFS 只能如實回報問題。zpool status -v 會以這種方式顯示:

status: One or more devices has experienced an error resulting in data
        corruption.
action: Restore the file in question if possible.  Otherwise restore the
        entire pool from backup.
errors: Permanent errors have been detected in the following files:

        /tank/data/archive.tar

系統會指出損壞的檔案。ext4 會直接回傳那些位元組而不提供任何提示,因此這項功能本身就很有價值。但 ZFS 仍無法修復該檔案,因為儲存池中沒有第二份資料可供修復。

使用 mirror 時,同一個讀取要求會從正常的一側提供資料,損壞的區塊會被重新寫入,事件則會出現在 zpool status 的 CKSUM 欄位中。這就是自我修復,但需要兩個裝置。

sudo zpool create -o ashift=12 tank mirror /dev/disk/by-id/DISK1 /dev/disk/by-id/DISK2

在 VPS 上,主機的儲存設備通常已具備冗餘,常見情況是 hypervisor 下的 RAID 10。這能防止磁碟故障,但無法告訴你某個區塊回傳的資料是否錯誤,因為陣列無從判斷哪一份資料才是正確的。ZFS 則能做到,因為它會將資料與自己寫入的校驗和比對。

如果你只有一個虛擬磁碟,但希望具備一些修復能力,sudo zfs set copies=2 tank/important 會在該 dataset 的同一顆磁碟上儲存每個區塊的兩份副本。這會使該 dataset 使用的空間加倍,能在區塊損壞時進行修復,但無法在整個 volume 消失時發揮作用。

scrub 會讀取儲存池中的所有資料並進行驗證。

sudo zpool scrub tank
zpool status tank

健康的儲存池最後會顯示類似 scan: scrub repaired 0B in 00:04:11 with 0 errors 的一行。請排程定期執行;小型儲存池每月一次即可。

systemctl list-unit-files 'zfs-scrub*'
sudo systemctl enable --now zfs-scrub-monthly@tank.timer

資料集是原則設定的單位

資料集是 pool 內的檔案系統,而且建立成本很低,因此每項工作建立一個資料集。屬性會從 pool 向下繼承,表示只需設定一次預設值,再針對需要的資料集覆寫。

sudo zfs create tank/data
sudo zfs create tank/pg
sudo zfs set compression=lz4 tank
sudo zfs set atime=off tank
sudo zfs set quota=20G tank/data
sudo zfs set recordsize=16K tank/pg
zfs get -r compression,compressratio,quota tank

壓縮是許多人因為謹慎而停用的屬性,但這種做法不正確。lz4 只會增加少量 CPU 負載,並減少必須寫入磁碟的資料量,因此對可壓縮資料而言,通常能讓讀寫速度更快。zstd 會使用更多 CPU 進行更高程度的壓縮,適合很少回讀的日誌與封存資料。使用 zfs get compressratio tank 檢查實際壓縮效果,並記住壓縮比只會計算設定該屬性後寫入的資料。

recordsize 是資料集寫入的最大區塊大小,預設為 128K。資料庫若將 8 KiB 頁面寫入 128 KiB 記錄,單次小型寫入就會變成讀取整個記錄、修改後再寫回。請在載入資料前,於資料庫資料集設定 recordsize=16K,因為該屬性只會套用到新寫入的區塊。

quota 可防止單一資料集填滿 pool。ZFS pool 接近 100% 使用量時,效能會變慢,也難以清理,因此應主動保留足夠的剩餘空間。

Snapshot 在資料變更前不占用空間

ZFS 絕不會覆寫現有區塊。它會寫入新的區塊並更新指標,這就是寫入時複製(copy-on-write)。Snapshot 就像一則備註,表示「保留此資料集目前指向的區塊」,因此建立 snapshot 會立即完成,且不會占用額外空間。

sudo zfs snapshot tank/data@2026-08-11
zfs list -t snapshot -o name,used,refer -r tank/data

Snapshot 的 USED 欄位表示僅由該 snapshot 保留的空間。起初接近 0,當你變更或刪除資料時會逐漸增加,因為舊區塊無法再釋放。

取回檔案不需要執行還原步驟。

ls /tank/data/.zfs/snapshot/
cp /tank/data/.zfs/snapshot/2026-08-11/notes.txt /tank/data/notes.txt

在執行 sudo zfs set snapdir=visible tank/data 之前,即使是 ls -a 也看不到 .zfs 目錄。請在需要 snapshot 之前先建立它,因為沒有 snapshot 時,一個失誤的 rm -rf 會讓你進入 ext4 復原流程,而該流程必須先卸載磁碟,後續處理只會更加複雜。

回復會捨棄 snapshot 建立後寫入的所有內容。

sudo zfs rollback tank/data@2026-08-11

如果存在較新的 snapshot,回復會拒絕執行;-r 會刪除這些較新的 snapshot,讓回復得以繼續。按下 Enter 前,請再次確認資料集名稱。

Snapshot 不是備份。 它位於同一個 pool、同一個 volume 及同一台 server。Volume 故障或任何一個 zpool destroy 都會讓 snapshot 與資料一併遺失。Snapshot 可防止你自己的 rm 以及不良升級,這涵蓋許多實際事故;但對 pool 本身發生的問題完全沒有防護作用。完整說明請參閱:為什麼 VPS snapshot 不是備份

傳送與接收:以一個命令進行複寫

zfs send 會將快照轉換為標準輸出上的位元組串流,而 zfs receive 會將該串流還原為資料集。第一次複製會進行完整傳送。

sudo zfs snapshot tank/data@daily-2026-08-11
sudo zfs send tank/data@daily-2026-08-11 | ssh backup.example.com "sudo zfs recv -F backup/data"

之後只傳送兩個快照之間的變更內容。

sudo zfs snapshot tank/data@daily-2026-08-12
sudo zfs send -i tank/data@daily-2026-08-11 tank/data@daily-2026-08-12 | ssh backup.example.com "sudo zfs recv backup/data"

接收端仍必須保有你要傳送的來源快照。若接收端沒有該快照,接收會因 cannot receive incremental stream: most recent snapshot of backup/data does not match incremental source 而停止,因為 ZFS 沒有可套用差異內容的基準。請從雙方都保有的快照進行傳送,或重新開始完整傳送。

請在目標端授予權限,不要使用遠端 root:sudo zfs allow -u backupuser create,mount,receive backup/data

這是真正的異地備份,但有一項條件。遠端必須是 ZFS pool,因為物件儲存無法接收串流。若目標是 S3-compatible storage 或一般 Linux 主機,請使用能與其通訊的工具;從 VPS 使用 restic 進行備份涵蓋這種情況。

ZFS 為什麼使用這麼多 RAM?ARC

ARC(adaptive replacement cache)是 ZFS 的讀取快取。它位於 kernel memory,而不是一般的 Linux page cache,因此 free -h 不會在 buff/cache 下顯示它。它會被計入使用中的記憶體。看起來幾乎已用滿的 ZFS 主機,通常只是快取已經暖機,而這正是多數「ZFS 吃掉我的 RAM」報告的原因。

預設上限刻意設得很寬鬆。OpenZFS 2.3 將 ARC 大小上限設為 RAM 減去 1 GiB 與 RAM 的 5/8 兩者中較大者。OpenZFS 2.2 及更早版本在 Linux 上使用 RAM 的一半;FreeBSD 則已經採用較新的規則。執行 zfs version,即可確認目前適用的規則。

ChartDefault maximum ARC size by instance RAM, from the OpenZFS default rule (GiB)
The data behind this chart
[
  {
    "label": "2 GB VPS",
    "openzfs_2_2_linux_gib": 1,
    "openzfs_2_3_gib": 1.25
  },
  {
    "label": "4 GB VPS",
    "openzfs_2_2_linux_gib": 2,
    "openzfs_2_3_gib": 3
  },
  {
    "label": "8 GB VPS",
    "openzfs_2_2_linux_gib": 4,
    "openzfs_2_3_gib": 7
  },
  {
    "label": "16 GB VPS",
    "openzfs_2_2_linux_gib": 8,
    "openzfs_2_3_gib": 15
  }
]

這些數值是將文件記載的預設規則套用到常見 instance 大小所得的結果,不是從執行中的主機測得的數值。在 4 GB instance 上,2.3 規則允許 ARC 使用 3 GiB。同一台主機在 2.2 上則會限制為 2 GiB。2 GB instance 套用 2.3 規則時,仍允許使用 1.25 GiB。應用程式只能使用剩餘的記憶體。

請直接從自己的 server 讀取實際數值,不要只依賴下表:

grep -E '^(size|c_max) ' /proc/spl/kstat/zfs/arcstats
arc_summary | head -n 20

第三欄的單位是 bytes。c_max 是目前生效的上限,size 是 ARC 目前持有的記憶體量。

ARC 確實會釋放記憶體。kernel 偵測到記憶體壓力後,ARC 就會縮小。問題在於時機,因為 ARC 是由該壓力觸發縮減。因此,若某個 process 一次要求數百 MiB,可能會在 ARC 尚未完成釋放時遭遇 OOM(out of memory)killer。在執行 database 與 web server 的 2 GB 主機上,這並不是罕見情況。OpenZFS manual 對手動變更也有相同說明:降低上限「不會在沒有記憶體壓力促使 ARC 縮減的情況下,使 ARC 自動縮小」。

如何在小型 VPS 上限制 ARC

先評估工作負載所需的記憶體。加總資料庫與應用程式的需求,為作業系統保留餘裕,再將剩餘記憶體配置給 ARC。在執行 Postgres 與一個 Web 應用程式的 4 GB 執行個體上,將 ARC 設為 512 MiB 至 1 GiB 是合理的起始值。

以位元組為單位立即套用設定。以下設定為 1 GiB。

echo 1073741824 | sudo tee /sys/module/zfs/parameters/zfs_arc_max

讓設定在重新開機後仍然有效。

echo 'options zfs zfs_arc_max=1073741824' | sudo tee /etc/modprobe.d/zfs.conf
sudo update-initramfs -u

initramfs 步驟很重要,因為模組可能會在根檔案系統掛載前從 initramfs 載入,因此不會讀取剛才寫入的檔案。重新開機後,使用 arcstats 中的 c_max 行確認設定。

手冊本身還指出兩點限制。系統執行期間無法將此值設回 0,因此若要撤銷設定,必須編輯檔案並重新開機。此外,降低數值不會立即縮小目前已配置的大型 ARC。

在 FreeBSD 上,相同的限制是 vfs.zfs.arc 下的 sysctl。執行 sysctl vfs.zfs.arc 查看目前數值,以及該版本使用的確切名稱,再將上限寫入 /boot/loader.conf

小型伺服器還有兩項記憶體配置原則。請停用 deduplication,因為 dedup table 會佔用記憶體;常見的經驗法則是每 TB 唯一資料需要 1 至 3 GB RAM。此外,不要將 swap 放在 zvol 上。zvol 是從 pool 切出的 block device。透過負責釋放記憶體的檔案系統進行 swapping,可能導致機器 deadlock。請將 swap 放在 pool 外部的純 partition 或 swap file 上。

ext4 或 XFS 搭配 restic 是更好的選擇

ZFS 適合記憶體充足且有第二個磁碟區的伺服器。除此之外,使用一般檔案系統搭配正式的備份工具通常更合適。符合以下情況時,請選擇 ext4 或 XFS:

  • 執行個體只有 2 GB 或 4 GB RAM,而工作負載需要使用全部記憶體。
  • 只有一個虛擬磁碟,沒有第二份副本,因此 ZFS 只能偵測問題,無法修復。
  • 備份目的端是物件儲存或一般 Linux 主機,無法接收 zfs send 串流。
  • 您使用 Debian 搭配 DKMS,無法承擔核心升級後模組未成功建置的風險。
  • 您需要在根檔案系統上使用 ZFS,但服務供應商提供的映像檔只有 ext4。

如果您有獨立的資料磁碟區、足夠的剩餘記憶體(8 GB 以上較為充裕),並且有實際使用快照與 zfs send 的計畫,請保留 ZFS。其他情況下,使用 ext4 搭配 restic,將加密且去重的備份寫入伺服器無法控制的儲存空間,即可涵蓋大多數相同需求,而且不會佔用額外記憶體。

故障模式與實際顯示的字串

重新開機後 pool 消失。 zpool status 會顯示 no pools available。import service 會讀取 /etc/zfs/zpool.cache,因此未列在該檔案中的 pool 不會在開機時匯入。sudo zpool import 會列出可匯入的項目,sudo zpool import tank 會將其匯回,而 sudo zpool set cachefile=/etc/zfs/zpool.cache tank 會使設定持續生效。若 pool 未從其他系統正常匯出,會顯示 cannot import 'tank': pool may be in use from other system;確認沒有其他主機使用該 pool 後,可用 sudo zpool import -f tank 覆寫這項限制。

Debian 在 kernel upgrade 後出現 modprobe: FATAL: Module zfs not found in directory /lib/modules/6.12.0-... DKMS 未針對新的 kernel 建置,通常是因為未安裝相符的 headers。dkms status 會顯示哪些模組已針對哪些 kernel 建置。接著執行 sudo apt install -y linux-headers-$(uname -r),由 sudo dkms autoinstall 重新建置,最後以 sudo zpool import tank 恢復 pool。

pool 已滿,但你已刪除檔案。 只要 snapshot 仍參照已刪除的資料,該資料就會繼續占用磁碟空間,因此 dudf 的結果會不一致。zfs list -o space -r tank 會將使用量拆分為 USEDDS 與 USEDSNAP;USEDSNAP 數值偏大就是原因。使用 sudo zfs destroy tank/data@2026-06-01 刪除舊 snapshot 後,空間就會釋出。

zpool status 中的 CKSUM 計數持續增加。 ZFS 底層的某個元件回傳了錯誤資料。在 mirror 上,這代表警告,且該區塊已經修復。在單磁碟 pool 上,檔案已遺失;zpool status -v 會指出檔案名稱,接著從不在此 pool 中的 backup 還原該檔案。

伺服器速度變慢並開始使用 swap。 先依前文設定 ARC 上限,再執行 arc_summary 並查看 hit ratio。若 ARC 太小,無法容納 working set,每次讀取都必須存取磁碟。此時,使用 page cache 的一般 filesystem 反而能提供更好的效能。

FAQ

ZFS 在 VPS 上需要多少 RAM?

ZFS 可在 2 GB 的執行個體上運作。真正的問題是應用程式還剩多少記憶體可用。在未調整設定的情況下,OpenZFS 2.3 會讓 ARC 增長至 RAM 減去 1 GiB 與 RAM 的 5/8 兩者中較大的值,因此 4 GB 的主機可將 3 GiB RAM 交給快取。將 zfs_arc_max 設為工作負載可釋出的數值,然後從 /proc/spl/kstat/zfs/arcstats 讀取 c_max 行加以確認。

ZFS 快照是備份嗎?

不是。快照與資料位於同一個 pool。它可在發生 rm 或升級失敗時保留,但 pool 或執行個體損毀時也會一併遺失。使用 zfs send 將快照傳送到另一台機器,或執行會將資料寫入本伺服器無法控制之儲存設備的備份工具,才能將快照轉為備份。

ZFS 在 FreeBSD 和 Linux 上的運作方式相同嗎?

自 OpenZFS 2.0 於 December 2020 發布後,兩者使用相同的程式碼基礎、相同的命令與相同的磁碟格式,pool 也能在兩者之間移轉。差異在於套件提供方式。FreeBSD 將 ZFS 包含在 base system 中。Linux 則由各發行版自行決定:Ubuntu 將模組編入其 kernel 套件,而 Debian 使用 DKMS 在你的機器上建置模組,因此 kernel 升級後,在重新建置成功前可能會暫時沒有模組。

ZFS 能在只有一顆磁碟的 VPS 上修復損毀嗎?

ZFS 能偵測損毀並指出檔案,但無法修復,因為修復需要該 block 的第二份副本。在 dataset 上設定 zfs set copies=2,即可用兩倍的空間提供第二份副本。這能處理損毀的 block,但無法處理遺失的 volume。跨兩個 volume 建立 mirror,才是能實際修復資料的方案。

壓縮會讓伺服器變慢嗎?

lz4 通常會讓伺服器更快。壓縮後的 block 需要寫入與讀取的位元組較少,而每個 block 的 CPU 成本相較於因此省下的磁碟 I/O 很低。在 pool root 設定 compression=lz4,讓每個 dataset 繼承此設定,然後在寫入實際資料後檢查一次 zfs get compressratio tank