A service changes 20 bytes in a large database file. An in-place file system can overwrite the storage block containing those bytes, then update any related metadata.
A copy-on-write file system takes a different approach:
The old block is not modified while it remains part of the current persistent state.
In a copy-on-write, or CoW, file system, an update creates a new version of changed blocks and publishes that version through new references instead of overwriting the old reachable version in place.
This principle can provide crash-consistent tree updates, efficient snapshots, and block sharing. It also introduces extra allocation work, reference tracking, fragmentation, and write amplification.
The underlying idea is the same wherever CoW appears: share an existing object until a writer needs a different version, then preserve the original and create a new one for the writer.
The mechanism differs by layer.
A file-system write does not normally involve making the storage device raise a page fault. The file system itself controls allocation, tree construction, persistence ordering, and block ownership.
The important question in this chapter is therefore not “who faulted?” It is:
Modern file systems commonly organize metadata in trees. A simplified tree can contain:
Every change has to travel back up this tree. Modifying one data block means writing new versions of its leaf, its internal blocks, and finally the root.
The real layout can use several specialized trees rather than one universal tree. The central property is that a small set of persistent root references leads to the reachable file-system state.
Suppose root R0 reaches internal node P0, and P0 reaches leaves A, B0, and C:
Leaf B0 contains metadata that must change. Overwriting B0 in place would make the old tree depend on a block being modified. CoW instead writes a new leaf B1.
But P0 still points to B0, so the file system also needs a new parent P1 that points to B1. Finally, it needs a new root R1 that reaches P1.
Only the changed path needs new versions. Unchanged subtrees can remain shared.
The new tree reuses A and C. It allocates new storage only for the changed leaf and the metadata path required to reach it.
This is often called path copying:
The cost depends on tree depth, block size, and how many nearby changes the file system can combine into the same new nodes.
Suppose logical file block 12 currently maps to storage block D0:
An overwrite produces:
The file system must then update the extent or block-mapping metadata that used to point to D0. That metadata update can propagate through its tree toward a new root.
The old data block remains intact until the new version has been safely published. After publication:
D1 belongs to the new file version.D0 can be freed if no snapshot, clone, or older live root still reaches it.D0, it remains allocated and shared.CoW therefore applies not just to payload bytes, but to the references that give those bytes meaning.
Writing D1, a new leaf, and new parent blocks does not immediately change the visible persistent tree. The old root still selects the old state.
A safe publication sequence is conceptually:
The new root must not become authoritative before all blocks it reaches satisfy the file system's persistence rules. Otherwise, recovery could follow a new root into missing or incomplete children.
The root publication itself also needs protection. A root record can tear, and storage can reorder writes. CoW file systems use mechanisms such as:
The exact protocol varies. “Write a pointer last” is the conceptual core, not a complete implementation by itself.
Loading simulation...
Consider two root generations:
If power fails before R1 is validly published, recovery chooses R0. The blocks prepared for generation 41 are unreachable and can later be reclaimed.
If power fails after R1 is validly published, recovery can choose R1.
When several candidate root records exist, recovery validates their checksums and generations rather than blindly choosing the bytes in one fixed location. A newer record that is torn or points to an invalid tree must not displace an older valid record.
This provides an old-or-new recovery structure without copying new blocks back over old home locations.
The clean teaching model says every change creates new blocks. Real designs still need a small amount of carefully managed mutable state.
Examples can include:
Some structures can be updated by rotating among multiple locations. Others use journaling or their own atomic update protocol.
The useful classification is not whether literally every sector is immutable. It is whether the main persistent trees are updated by writing new versions and publishing new roots.
CoW and journaling are design tools that can coexist.
A snapshot preserves a point-in-time file-system view.
With a CoW tree, creating a snapshot can begin by retaining the current root:
Initially, the snapshot and live view share every reachable block. No eager copy of all file contents is needed.
When the live view changes leaf B0, it creates B1 and a new path:
The snapshot keeps reaching B0; the live tree reaches B1. Unchanged blocks stay shared.
Snapshot creation can therefore be fast and initially space-efficient. Its later space cost depends on how much the live view diverges.
Without a snapshot, old block D0 could become unreachable as soon as the new root containing D1 is safely published.
With a snapshot:
D0 is still live and cannot be returned to free space.
This leads to a common operational surprise:
Deleting the snapshot releases only blocks that no other root, snapshot, or clone references. Determining that set can require significant background work.
A snapshot is not a backup. It normally shares the same storage system, failure domain, and capacity pool as the live data. Device loss, administrative mistakes affecting the whole pool, or silent faults beyond the protection scheme can affect both.
A reflink creates a new file whose extents initially reference the same physical blocks as an existing file.
The inodes and names are distinct. Changing one file must not change the other's contents.
If the clone overwrites part of the shared extent, the file system allocates new blocks for that region and updates only the clone's mappings:
Unmodified regions can remain shared.
This makes large file cloning fast and initially space-efficient. Virtual-machine images, build trees, and test data sets can benefit when copies mostly remain unchanged.
The copy is logically independent but physically shared. Tools and applications should not infer physical allocation solely from logical file size.
An ordinary unshared block has one clear owner. Snapshots and reflinks let several roots or extents reach the same block.
The file system needs enough metadata to answer:
Implementations can use reference counts, backreferences, reachability trees, or combinations of these techniques.
A reference count records how many owners remain. A backreference identifies the owners. Backreferences support tasks such as integrity repair and relocation, but require more metadata than a count alone.
Ownership updates must themselves be crash-consistent. Freeing a block while a snapshot still refers to it can corrupt the snapshot. Failing to free an unreferenced block leaks space.
File systems therefore reserve space and carefully order delayed reference updates, allocation metadata, new tree blocks, and root publication.
A CoW update needs free space before it can publish new state.
Even replacing one existing block temporarily requires both versions:
Metadata path copying consumes more blocks. A nearly full file system can have enough logical space for the new user bytes but not enough reserved metadata space to complete the tree update safely.
Allocation metadata belongs to the versioned state too:
While building R1, the in-memory allocator reserves D1 so another writer cannot choose it. If a crash leaves R0 authoritative, its allocation state can treat the unreachable contents of D1 as disposable. If R1 is published, its allocation state records D1 as live.
This is why CoW file systems can report out-of-space conditions that surprise users looking only at total file sizes. Relevant consumers include:
A correct allocator must never solve low space by reusing an old block that the current committed root might still require.
CoW works well with checksummed trees because a parent can record integrity information for newly written children.
In one possible design:
Changing the child produces a new checksum, which produces a new parent version, and the change propagates toward a trusted root.
Other designs store checksums in a separate tree or place checksums inside metadata blocks. The exact arrangement differs, but CoW avoids overwriting the only known-good child before its replacement is validated and published.
A checksum provides detection:
Repair requires another valid copy, such as a mirror or redundant metadata replica. A checksum alone cannot reconstruct lost bytes.
Checksums also do not prove that the newest application update became durable. They prove that a selected block matches the integrity information for the selected tree version.
Suppose the file system uses 16 KiB data blocks and 16 KiB tree nodes. An application changes 100 bytes.
The update may require:
This is write amplification: physical bytes written exceed the application's changed byte count.
Compression can enlarge the rewrite unit further. A small edit inside one compressed region may require decompressing, modifying, recompressing, and writing a new region.
The actual amplification depends on allocation units, tree depth, batching, compression, checksumming, and whether nearby updates share metadata nodes.
CoW can still perform well because new writes can be allocated sequentially and several changes can be grouped into one transaction. The tradeoff is workload-dependent.
An initially contiguous file can have one large extent:
Repeated small CoW overwrites allocate new blocks elsewhere:
The logical file is unchanged as a byte sequence, but its physical layout now contains more extents.
Fragmentation can increase metadata size and reduce sequential I/O efficiency. It is especially relevant to large files that receive repeated small random overwrites, such as virtual-disk images and database files.
Allocators can cluster related writes, delay allocation, or defragment files. Defragmentation itself rewrites blocks and can break sharing with snapshots or clones, increasing space use.
One logical write can pass through several copy-on-write layers:
Four layers, each redirecting writes rather than overwriting in place. The costs compound, and a write pattern tuned for one layer can be defeated by the next.
Each layer can turn a small overwrite into allocation and metadata work. The combined write amplification and latency may be much larger than any one layer suggests.
Database engines sometimes overwrite large files repeatedly and already implement their own crash-recovery protocol. On some file systems, administrators can choose different CoW, compression, or checksumming policies for those files.
Such changes are not universal tuning rules. Disabling CoW for a workload can also disable or weaken snapshots, reflinks, compression, checksumming, or crash-consistency behavior. The exact tradeoff must be verified for the specific file system.
Both approaches aim to prevent a crash from exposing an invalid multi-block update.
| Aspect | Journaling | Tree-based CoW |
|---|---|---|
| Where new state is first recorded | Dedicated journal | Newly allocated final blocks |
| What marks a complete update | Valid journal commit | Valid publication of a new root |
| What recovery does | Replay committed log records | Select a valid committed root/tree |
| What happens to old main blocks | Usually overwritten during checkpointing | Retained until no root references them |
| Natural snapshot support | Not implied by the journal | Follows from retaining older roots and blocks |
| Typical extra writes | Journal copy plus home write | New data/metadata path plus root and reference updates |
This comparison describes broad designs, not mutually exclusive categories.
A file system can use CoW for its main trees and a log for selected operations. A journaling file system can also support CoW reflinks for shared file extents without making all metadata tree updates copy-on-write.
The feature names do not replace reading the actual persistence contract.
A snapshot captures the file-system state associated with a root generation. It cannot automatically understand application state still held in memory.
For a database, a snapshot can contain:
The result can resemble a sudden crash from the application's perspective. A database with a correct recovery log may recover that image, but a group of arbitrary files need not represent one completed business operation.
Workloads needing application-consistent snapshots coordinate with the application: pause writes, flush the necessary state, use an application-provided snapshot hook, or rely on a storage format designed for crash recovery.
The root publication makes the file-system tree internally coherent. It does not invent higher-level transaction boundaries.
Consider a 100 GiB live file with a snapshot.
The application overwrites 20 GiB of the live file:
The two logical views total 200 GiB, but physical data use is roughly 120 GiB before metadata and compression effects.
Now the application deletes the live file. The snapshot still reaches the original 100 GiB, so most of that space remains allocated.
Space monitoring must account for:
These values answer different questions. A simple sum of file sizes can double-count shared blocks, while a live-directory walk can miss blocks retained only by snapshots.
On Linux with GNU cp and a file system that supports reflinks, create a disposable test:
--reflink=always makes the command fail rather than silently performing a full byte-for-byte copy when reflinks are unavailable.
The two files have different inodes:
Modify only the clone:
The source retains source-version; the clone begins with clone-version!. The file system CoW-splits the affected allocation region, while unchanged extents can remain shared.
Some systems expose shared-extent flags through:
The availability and interpretation of this output depend on the file system and tool version. Different physical block numbers in the modified region are expected, but ordinary file tools are not a universal shared-space accounting interface.
All files in this example live under a newly created temporary directory. Verify the variables before modifying the commands.
Several production file systems use CoW techniques, but their on-disk protocols are not interchangeable.
ZFS groups updates into transaction groups and publishes new tree state through checksummed root records often called uberblocks. Btrfs uses copy-on-write B-trees, multiple tree roots, checksums, snapshots, and reflinks. APFS uses copy-on-write metadata and supports snapshots and clones.
Other file systems combine a traditional metadata journal with selected CoW features. For example, a file system can retain journal-based metadata recovery while supporting reflinked file extents.
The useful questions are more precise than “Is it CoW?”:
The answers determine recovery behavior and performance.
A copy-on-write file system updates persistent state by allocating new blocks instead of overwriting the blocks reachable from the current root. Changing a leaf creates a new leaf and new ancestors along the path to a new root, while unchanged subtrees remain shared.
The new root is published only after its reachable blocks satisfy the persistence protocol. A crash before publication leaves the old tree authoritative; a crash after valid publication selects the new tree. Checksummed generations and redundant root locations help recovery identify a valid state.
Retaining old roots makes snapshots efficient, while reflinks let independent files share extents. Both features require accurate reference tracking: a block can be reclaimed only when no live tree, snapshot, or clone reaches it.
The tradeoffs are additional metadata, temporary space requirements, fragmentation, and write amplification. Snapshots can retain unexpectedly large amounts of space, and small logical overwrites can rewrite data blocks plus several metadata levels.
The central update pattern is:
write new children → write new ancestors → publish a valid new root → reclaim unreachable old blocks
5 quizzes