файловая система и менеджер томов слиты в один слой — отсюда сквозные чексуммы merkle-дерева, RAID-Z без write hole и снапшоты за секундыthe filesystem and the volume manager fused into one layer — hence merkle-tree end-to-end checksums, RAID-Z with no write hole, and snapshots in seconds
ZFS разработана в Sun Microsystems (первый релиз — Solaris 10, 2005 год) вокруг идеи, которая на момент выхода была почти еретической: файловая система и менеджер томов — это одна и та же программа, а не два независимых слоя (ФС поверх RAID-контроллера или mdadm/LVM), которые ничего не знают друг о друге. Из этого слияния следует всё остальное: пул хранения (pool) вместо раздела, copy-on-write вместо журнала, merkle-дерево чексумм, которое делает молчаливую порчу данных (bit rot) обнаружимой и часто самовосстанавливающейся, RAID-Z — первое программное решение классического write hole RAID5 — и снапшоты, создающиеся быстрее, чем успевает моргнуть курсор в терминале.ZFS was developed at Sun Microsystems (first shipped in Solaris 10, 2005) around an idea that was almost heretical at the time: the filesystem and the volume manager are the same program, not two independent layers (a filesystem sitting on top of a RAID controller or mdadm/LVM) that know nothing about each other. Everything else follows from that fusion: a storage pool instead of a partition, copy-on-write instead of a journal, a merkle-tree of checksums that makes silent data corruption (bit rot) detectable and often self-healing, RAID-Z — the first software-only fix for RAID5's classic write hole — and snapshots that finish faster than a terminal cursor can blink.
Классический стек хранения — это несколько независимых слоёв: RAID (аппаратный или mdadm) ничего не знает про файлы, файловая система ничего не знает про физическое расположение данных на дисках. ZFS убирает эту границу: она сама решает, на каком диске (или дисках, при зеркалировании/RAID-Z) физически разместить каждый блок, сама считает и хранит его чексумму, и сама знает, есть ли под рукой избыточная копия для восстановления при повреждении. Именно это единство даёт ZFS то, что классической связке ФС+RAID недоступно в принципе: RAID-Z без write hole (потому что ФС знает реальный размер записи и не пишет фиксированный страйп) и мгновенные снапшоты (потому что ФС и так уже пишет через copy-on-write, снапшот — это просто «не трогать старые блоки»).A classic storage stack is several independent layers: RAID (hardware or mdadm) knows nothing about files, and the filesystem knows nothing about the physical layout of data on the disks. ZFS removes that boundary: it decides itself which disk (or disks, under mirroring/RAID-Z) to physically place each block on, computes and stores its checksum itself, and knows itself whether a redundant copy is on hand to recover from on corruption. That unity is exactly what gives ZFS what a classic filesystem+RAID combo can't have in principle: RAID-Z with no write hole (because the filesystem knows the real write size and doesn't write a fixed stripe) and instant snapshots (because the filesystem already writes via copy-on-write anyway — a snapshot is just "don't touch the old blocks").
| параметрparameter | ext4 | Btrfs | ZFS |
|---|---|---|---|
| управление томамиvolume management | отдельно (LVM/mdadm)separate (LVM/mdadm) | частично встроено (chunk tree)partly built in (chunk tree) | полностью слито с файловой системой (pool/vdev)fully fused with the filesystem (pool/vdev) |
| чексуммыchecksums | только метаданныеmetadata only | данные и метаданныеdata and metadata | данные и метаданные, merkle-цепочка до корня пулаdata and metadata, a merkle chain up to the pool root |
| RAID5/6-аналогequivalent | — | есть, write hole официально не решёнexists, write hole officially unresolved | RAID-Z, write hole решён variable stripe widthRAID-Z, write hole solved via variable stripe width |
| снапшотыsnapshots | — | да, CoWyes, CoW | да, CoW; плюс send/receive для переноса между машинамиyes, CoW; plus send/receive for transferring between machines |
| кэширование чтенияread caching | общий page cache ядраthe kernel's shared page cache | общий page cache ядраthe kernel's shared page cache | собственный ARC в памяти + опциональный L2ARC на SSD, вне обычного page cacheits own in-memory ARC + optional L2ARC on SSD, outside the ordinary page cache |
| лицензия / статус в ядреlicense / kernel status | GPLv2, в мейнлайнеmainline | GPLv2, в мейнлайнеmainline | CDDL, несовместима с GPL — только отдельный модуль (OpenZFS), никогда не в мейнлайнеincompatible with the GPL — a separate module only (OpenZFS), never in the mainline |
Первое, что нужно перестать искать в ZFS — это «раздел» в привычном смысле. Вместо этого есть иерархия из четырёх уровней, где верхний (pool) объединяет физическое железо, а всё остальное — уже логические сущности внутри него, свободно делящие общее пространство:The first thing to stop looking for in ZFS is a "partition" in the usual sense. Instead there's a four-level hierarchy, where the top level (the pool) unifies the physical hardware, and everything else is a logical entity inside it, freely sharing the common space:
Каждый блок в ZFS адресуется не просто указателем, а block pointer-ом — структурой, которая помимо физического адреса содержит контрольную сумму содержимого этого блока. Решающая деталь: чексумма хранится не рядом с данными, а в родительском блоке, который на этот блок ссылается. Из-за этого чексуммы выстраиваются в цепочку вверх до корня — классическое merkle-дерево:Every block in ZFS is addressed not by a plain pointer but by a block pointer — a structure that, besides the physical address, holds a checksum of that block's contents. The decisive detail: the checksum isn't stored next to the data — it's stored in the parent block that references it. That's why checksums chain upward to the root — a classic merkle tree:
Отсюда прямо следует self-healing: если том избыточный (mirror, RAID-Z) и чексумма не совпала, ZFS читает ту же логическую позицию с другого диска/копии — и если её чексумма совпадает, молча подменяет повреждённые данные исправными прямо во время обычного чтения, без вмешательства администратора. zpool scrub — та же самая проверка, только принудительно прогнанная по каждому когда-либо записанному блоку пула, а не только по тем, что реально читают приложения: цель — найти и исправить повреждение, пока жива вторая копия, а не в момент, когда редко используемый файл наконец понадобился.Self-healing follows directly from this: if the volume is redundant (mirror, RAID-Z) and a checksum doesn't match, ZFS reads the same logical position from another disk/copy — and if that one's checksum matches, it silently swaps the corrupted data for the good copy right during an ordinary read, with no administrator involved. zpool scrub is the same check, just forced across every block the pool has ever written, not just the ones applications happen to read: the goal is to find and fix corruption while a second copy is still alive, not the moment a rarely used file is finally needed.
RAID-Z решает write hole классического RAID5 — рассинхронизацию данных и parity при сбое питания посреди записи страйпа — трюком, доступным только файловой системе, слитой с менеджером томов: вместо фиксированной ширины страйпа RAID-Z делает страйп переменной ширины, ровно под размер записываемого блока, так что каждая запись — это по определению полный страйп:RAID-Z solves classic RAID5's write hole — data and parity going out of sync on a power failure mid-stripe-write — with a trick only a filesystem fused with its volume manager can pull off: instead of a fixed stripe width, RAID-Z uses a variable-width stripe, sized to exactly the block being written, so every write is by definition a full stripe:
| классический RAID5 (mdadm/аппаратный)classic RAID5 (mdadm/hardware) | RAID-Z | |
|---|---|---|
| ширина страйпаstripe width | фиксированная, независимо от размера записиfixed, regardless of write size | переменная — каждый блок сам себе страйп, под свой реальный размерvariable — every block is its own stripe, sized to its actual size |
| частичная запись страйпаa partial stripe write | возможна — если запись меньше страйпа, требуется read-modify-write существующих данных и paritypossible — a write smaller than the stripe requires a read-modify-write of existing data and parity | не возникает в принципе — записывается ровно столько, сколько нужно данным плюс их собственная paritydoesn't arise in principle — exactly as much is written as the data needs, plus its own parity |
| сбой питания посреди записиpower loss mid-write | data и parity могут рассинхронизироваться навсегда — write holedata and parity can go out of sync permanently — the write hole | транзакция атомарна через CoW: либо блок целиком (с parity) применяется, либо ничего — старые данные остаются нетронутымиthe transaction is atomic via CoW: either the whole block (with parity) is applied, or nothing is — the old data stays untouched |
Это стало возможным только потому, что метаданные файловой системы содержат достаточно информации о модели избыточности нижележащего хранилища, чтобы работать с переменной шириной RAID-страйпа — то есть ровно то же самое архитектурное слияние ФС и volume manager-а, о котором шла речь выше. RAID-Z официально называют первым программным решением write hole RAID5, не требующим ни NVRAM-кэша контроллера, ни батарейки.This is possible only because the filesystem's metadata carries enough information about the underlying storage's redundancy model to work with a variable-width RAID stripe — exactly the fusion of filesystem and volume manager discussed above. RAID-Z is officially described as the first software-only fix for RAID5's write hole, needing neither a controller's NVRAM cache nor a battery.
Кэширование в ZFS тоже не полагается на обычный page cache ядра, а реализует собственный многоуровневый механизм — отдельно для чтения и для записи:Caching in ZFS also doesn't rely on the kernel's ordinary page cache — it implements its own multi-tier mechanism, separately for reads and writes:
| что этоwhat it is | рольrole | |
|---|---|---|
| ARC | Adaptive Replacement Cache — кэш чтения в оперативной памятиAdaptive Replacement Cache — a read cache in RAM | адаптивно балансирует между «недавно использованным» и «часто использованным», а не просто LRU; на активном сервере может занимать большую часть свободной памятиadaptively balances "recently used" against "frequently used" instead of plain LRU; on a busy server it can occupy most of the free memory |
| L2ARC | расширение ARC на быстром отдельном устройстве (обычно SSD/NVMe)an ARC extension on a separate fast device (usually SSD/NVMe) | принимает вытесненные из ARC «ещё довольно ценные» блоки — второй уровень кэша чтения, когда рабочий набор не помещается целиком в RAMabsorbs blocks evicted from ARC that are "still fairly valuable" — a second read-cache tier for when the working set doesn't fully fit in RAM |
| ZIL | ZFS Intent Log — журнал синхронных записейZFS Intent Log — a log of synchronous writes | не кэш записи в строгом смысле: временно удерживает подтверждённые (fsync) записи до их фактического попадания в пул отдельными транзакционными группами каждые ~5 секундnot a write cache in the strict sense: temporarily holds confirmed (fsync'd) writes until they actually land in the pool via separate transaction groups every ~5 seconds |
| SLOG | отдельное устройство, на котором физически размещён ZILa separate device the ZIL is physically placed on | быстрый защищённый от потери питания SSD в роли SLOG резко ускоряет синхронную запись (базы данных, NFS); из-за критичности для целостности рекомендуется зеркалироватьa fast, power-loss-protected SSD acting as SLOG sharply speeds up synchronous writes (databases, NFS); given how critical it is for integrity, mirroring it is recommended |
Снапшот в ZFS — прямое следствие copy-on-write, а не отдельно реализованная фича: он фиксирует состояние dataset-а в момент создания, продолжая указывать на те же самые физические блоки, что и «живая» версия. Пока данные не меняются, снапшот не занимает почти никакого дополнительного места — расход появляется только тогда, когда изменённые блоки в «живой» версии начинают расходиться со снапшотом через обычный механизм CoW.A snapshot in ZFS is a direct consequence of copy-on-write, not a separately implemented feature: it captures a dataset's state at creation time while continuing to point at the exact same physical blocks as the "live" version. As long as the data doesn't change, a snapshot uses almost no extra space — usage only appears once the "live" version's modified blocks start diverging from the snapshot through the ordinary CoW mechanism.
| операцияoperation | что происходит физическиwhat physically happens |
|---|---|
| zfs snapshot | фиксируется указатель на текущее дерево блоков dataset-а — ни один блок не копируется, операция занимает миллисекунды независимо от объёма данныхa pointer to the dataset's current block tree is captured — not a single block is copied, the operation takes milliseconds regardless of data volume |
| файл меняется в «живом» dataset-еa file changes in the "live" dataset | CoW создаёт новый блок для изменённых данных; снапшот продолжает ссылаться на старый — оба физически существуют, пока снапшот живCoW creates a new block for the changed data; the snapshot keeps referencing the old one — both physically exist as long as the snapshot lives |
| zfs rollback | «живой» dataset принудительно указывается обратно на состояние снапшота — все изменения после снапшота теряются; требует удаления более новых снапшотов между нимиthe "live" dataset is forcibly repointed to the snapshot's state — all changes made after the snapshot are lost; requires removing any newer snapshots in between |
| zfs clone | создаёт полноценный записываемый dataset поверх снапшота — разделяет с ним блоки, пока не начнёт расходиться, в отличие от rollback не уничтожает исходный «живой» datasetcreates a full writable dataset on top of a snapshot — shares blocks with it until it diverges, and unlike rollback doesn't destroy the original "live" dataset |
zfs send превращает снапшот (или разницу между двумя снапшотами) в поток байт — не файловый архив, а именно поток блочных изменений, который можно передать по сети или сохранить в файл. Полный send отправляет все данные снапшота целиком; инкрементальный (-i) — только блоки, отличающие один снапшот от другого, что делает регулярную репликацию на удалённую машину дешёвой по трафику даже для больших dataset-ов. Принимающая сторона (zfs receive) для инкрементального потока обязана уже иметь dataset, чей последний снапшот совпадает с начальной точкой потока — иначе применить разницу невозможно.zfs send turns a snapshot (or the difference between two snapshots) into a byte stream — not a file archive, but literally a stream of block-level changes that can be piped over the network or saved to a file. A full send ships an entire snapshot's data; an incremental one (-i) ships only the blocks that differ between two snapshots, which makes regular replication to a remote machine cheap in bandwidth even for large datasets. The receiving side (zfs receive) for an incremental stream must already have a dataset whose latest snapshot matches the stream's starting point — otherwise there's no way to apply the difference.
| командаcommand | что происходитwhat happens |
|---|---|
| zpool create tank mirror /dev/sdb /dev/sdc | создаёт пул tank из одного vdev-зеркала на двух дисках — уже в этот момент под пулом автоматически создаётся и монтируется корневой dataset tank в /tank, без отдельного mkfscreates pool tank from one mirror vdev on two disks — at this point a root dataset tank is already automatically created and mounted at /tank, with no separate mkfs |
| zfs create tank/home | создаёт вложенный dataset — новую «файловую систему» внутри того же пула, монтируемую отдельно (по умолчанию в /tank/home) и способную иметь свои собственные свойства (compression, quota, свои снапшоты)creates a nested dataset — a new "filesystem" inside the same pool, mounted separately (by default at /tank/home) and able to have its own properties (compression, quota, its own snapshots) |
| zfs set mountpoint=/srv/data tank/home | монтирование в ZFS — свойство dataset-а, а не запись в /etc/fstab: точка монтирования хранится в самих метаданных пула и переносится вместе с ним при экспорте/импорте на другую машинуmounting in ZFS is a dataset property, not an /etc/fstab entry: the mount point is stored in the pool's own metadata and travels with it when exported/imported to another machine |
| zpool export tank затемthen zpool import tank | аккуратно отсоединяет пул от текущей системы и подключает его на другой (или той же после переустановки) — все datasets, их точки монтирования и снапшоты восстанавливаются автоматически из метаданных, физически находящихся на самих дискахcleanly detaches the pool from the current system and attaches it on another one (or the same one after a reinstall) — every dataset, its mount point, and its snapshots are restored automatically from metadata that physically lives on the disks themselves |
| zfs snapshot tank/home@before-upgrade | создаёт снапшот конкретного dataset-а за миллисекунды — стандартная практика перед рискованной операцией; для доступа к содержимому снапшота не нужна отдельная команда — он виден напрямую как /tank/home/.zfs/snapshot/before-upgradecreates a specific dataset's snapshot in milliseconds — standard practice before a risky operation; no separate command is needed to access a snapshot's contents — it's directly visible as /tank/home/.zfs/snapshot/before-upgrade |
| zfs send tank/home@before-upgrade | zfs receive backup/home | полная копия снапшота (со всеми данными) переносится в другой dataset (может быть на другом пуле или другой машине через ssh) одним потокомa full copy of the snapshot (with all data) is transferred into another dataset (possibly on another pool or another machine over ssh) as a single stream |
| zfs send -i @before-upgrade tank/home@after-upgrade | zfs receive backup/home | передаётся только разница между двумя снапшотами — на удалённой стороне уже должен существовать dataset с первым снапшотом; типичная схема инкрементального бэкапа по crononly the difference between the two snapshots is transferred — the remote side must already have a dataset with the first snapshot; a typical cron-driven incremental backup setup |
| zfs set checksum=sha256 tank/home | переключает алгоритм чексумм для нового dataset-а с дефолтного fletcher4 на криптографически более сильный (используется, например, вместе с дедупликацией, где коллизии чексумм критичны)switches the checksum algorithm for a new dataset from the default fletcher4 to a cryptographically stronger one (used, e.g., alongside deduplication, where checksum collisions matter) |
| zfs get checksum,compressratio tank/home | показывает текущие значения свойств dataset-а — какой алгоритм чексумм реально используется и насколько эффективно работает сжатиеshows the dataset's current property values — which checksum algorithm is actually in use and how effective compression is |
| zpool scrub tank | запускает фоновую проверку чексумм абсолютно всех блоков пула когда-либо записанных данных; на избыточных vdev автоматически чинит найденные несовпадения из исправной копииstarts a background checksum verification of absolutely every block ever written to the pool; on redundant vdevs, automatically repairs found mismatches from a good copy |
| zpool status -v tank | показывает состояние каждого vdev и диска в пуле, счётчик READ/WRITE/CKSUM ошибок по каждому устройству — прямое наблюдение за тем, где именно чексуммы не совпадают чаще всего (ранний признак умирающего диска)shows the state of every vdev and disk in the pool, a READ/WRITE/CKSUM error count per device — a direct look at exactly where checksums mismatch most often (an early sign of a dying disk) |
Лицензия CDDL (под которой Sun/Oracle выпустили исходный код ZFS) считается юридически несовместимой с GPLv2 ядра — мнение самой FSF и Red Hat однозначное, хотя Canonical придерживается более мягкой трактовки и потому включает ZFS прямо в установщик Ubuntu (с пометкой «экспериментально» с 24.04 LTS). Попытка 2025 года договориться о двойном лицензировании CDDL+GPLv2 застопорилась: Oracle не ответила, а Линус Торвальдс не проявил интереса включать ZFS в мейнлайн. Практическое следствие: на любом дистрибутиве, кроме тех, что идут на прямой юридический риск, ZFS ставится как отдельный DKMS-модуль, который приходится пересобирать при каждом обновлении ядра — и именно здесь чаще всего что-то ломается.the CDDL license (under which Sun/Oracle released ZFS's source) is considered legally incompatible with the kernel's GPLv2 — the FSF's and Red Hat's own opinion is unambiguous, though Canonical holds a softer interpretation and so ships ZFS directly in the Ubuntu installer (flagged "experimental" since 24.04 LTS). A 2025 attempt to negotiate dual-licensing as CDDL+GPLv2 stalled: Oracle never responded, and Linus Torvalds showed no interest in mainlining ZFS. The practical consequence: on any distro besides the ones taking a direct legal risk, ZFS installs as a separate DKMS module that has to be rebuilt on every kernel update — and that's exactly where things most often break.
По умолчанию ARC может занять большую часть свободной оперативной памяти — так же, как page cache в обычном Linux, но это отдельный от page cache механизм со своими настройками, который новым администраторам ZFS часто ошибочно принимают за утечку памяти или неправильно настроенный сервис. Как и обычный кэш, ARC отдаёт память обратно при реальной потребности приложений — но по умолчанию делает это не так охотно, как page cache, и на памяти-ограниченных системах верхнюю границу (zfs_arc_max) часто приходится выставлять вручную.by default, ARC can occupy most of the free RAM — much like the page cache on ordinary Linux, but it's a separate mechanism with its own settings, which new ZFS administrators often mistake for a memory leak or a misconfigured service. Like an ordinary cache, ARC releases memory back when applications actually need it — but by default it does so less eagerly than the page cache, and on memory-constrained systems the upper bound (zfs_arc_max) often needs to be set manually.
Блочная дедупликация ZFS (zfs set dedup=on) требует держать таблицу хэшей всех уникальных блоков в оперативной памяти для приемлемой скорости — на практике это порядка нескольких гигабайт памяти на каждый терабайт уникальных данных. Включение дедупликации на системе с недостаточной памятью превращает её из оптимизации в источник катастрофического замедления записи; чаще всего разумной альтернативой оказывается обычное сжатие (compression=on, почти бесплатное по CPU на современных алгоритмах вроде lz4/zstd) без дедупликации.ZFS's block-level deduplication (zfs set dedup=on) requires keeping a hash table of every unique block in RAM for acceptable speed — in practice that's on the order of a few gigabytes of memory per terabyte of unique data. Enabling dedup on a system without enough memory turns it from an optimization into a source of catastrophic write slowdown; ordinary compression (compression=on, nearly free on CPU with modern algorithms like lz4/zstd) without dedup is usually the sensible alternative.