Filesystem Implementation: Layout, Journaling, COW, and Recovery
On this page
A filesystem mounting successfully does not imply that two business writes performed before a crash are both preserved. Below, we expand on block layout, structural recovery, data verification, and historical recovery separately, avoiding the mistake of summarizing all guarantees under a single feature.
| Phenomenon to Explain | Reading Location |
|---|---|
| How does a file offset map to a device block? | Section 1: Manual extent calculation |
| How do journals and COW recover? | Sections 2–4: ext4, XFS, Btrfs |
| What coordination is needed for device tiers? | Section 5: bcachefs |
| How to compare under identical conditions? | Section 6: Capability boundaries and computational experiments |
1. Extents are contiguous range mappings, not constant-time magic
Assume a block size of 4 KiB. An extent describes "8 logical blocks starting from logical block 100, corresponding to 8 physical blocks starting from physical block 900." When accessing 102 × 4096 + 17:
| Step | Calculation |
|---|---|
| Locate relative block within extent | 102 − 100 = 2 |
| Map to physical block | 900 + 2 = 902 |
| Retain byte offset within block | Byte 17 within block 902 |
The physical block number still belongs to the filesystem layer; underneath, it may pass through volume management or device mapping.
What extents save is the mapping record for contiguous regions: 8 blocks can be described by a single record instead of one pointer per block. Indirect block designs do not linearly scan N pointers to find the Nth block; they can locate via indexes level by level. The cost of extent tree lookups also depends on tree height and fragmentation, so it cannot be generalized as "indirect blocks O(N), extents O(1)."
The ext4 inode contains a 60-byte region to hold the extent tree root: after a 12-byte header, it can accommodate 4 records of 12 bytes each. When the tree height is zero, these are leaf extents; as it grows, the root holds indexes, so one can no longer say "the first four segments of data always remain directly in the inode." The ee_len field also has encoding rules for initialized/unwritten states, not just an uninterpreted ordinary length. See ext4 inodes and block mapping.
Three types of "not yet written" have different occupancy semantics
| State | Physical Location and Read Semantics |
|---|---|
| Hole | Typically no data blocks yet |
| Unwritten extent | Blocks reserved; reads return zeros; converts to valid state after write |
| Delayed allocation | Physical location decision deferred during caching phase |
Delayed allocation aids aggregation, but subsequent writeback may still fail to allocate; pre-allocation does not guarantee that future metadata updates or COW operations will never run out of space.
Deferred allocation can fail later. Preallocation does not guarantee every future metadata or COW allocation.
Holes lack data blocks; unwritten extents reserve blocks but read as zeros. A write to a shared extent separates the affected range while the other file retains old contents.
2. ext4: What does the journal protect, and what does ordered add?
JBD2 writes a set of metadata modifications to the journal. After establishing a valid commit record, it can later checkpoint these to their final locations. Crash recovery replays valid transactions, preventing directory, allocation bitmap, inode, and other related modifications from stopping in an inconsistent intermediate state. A commit is the boundary for recoverable journal transactions, while checkpointing is the progression to final locations; the two do not happen at the same moment.
| Data Mode | Relationship Between User Data and Journal | What the Application Must Still Handle |
|---|---|---|
data=ordered | Typically only metadata is logged; order constraints are applied to the submission of related data and metadata | In-place rewrites and multiple writes do not automatically become application transactions |
data=writeback | Does not provide the data-first constraint of the ordered mode | Content after recovery may not be the new data expected by the application |
data=journal | Data also passes through the journal | Application commit points must still be defined, and sync results checked |
For example, if an application continuously overwrites two regions of the same file, even if the filesystem metadata is completely valid, one should not assume both regions are definitely updated to the new version after a crash just because of data=ordered. Journal consistency concerns the recoverable structure of the filesystem; a single business transaction from the application requires its own atomic update protocol. See ext4 journal documentation.
Delayed allocation and background writeback can expand the set of dirty data in memory that has not yet been persisted, but "crash losing dirty pages" is not unique to delayed allocation. Whether modifications must be retained here should be expressed via interfaces like fsync or synchronous opens, rather than guessing that the background will flush them after a few seconds.
3. XFS: Distributing allocation concurrency across AGs
AG stands for Allocation Group. Each group manages local free space and inode allocation information, reducing the pressure of all allocation requests competing for a single global structure. File data can be distributed across multiple AGs; AGs are not fixed exclusive partitions for each CPU; logs, concurrent modifications to the same inode, and device throughput can still become shared bottlenecks.
XFS uses different indexes to answer different questions: free space indexes organized by address and size are used to find suitable regions; the inode allocation tree describes inode chunks and their usage; the file's own extent mapping describes the mapping from logical offsets to physical regions. One cannot simply write the inode allocation tree as "each leaf stores a complete inode." Small-scale records may also exist in an embedded form, meaning it is not always necessary to traverse an external B+tree for every access.
Shared extents: Independent files can still share content
Filesystems supporting reflink allow two different inodes to share data extents, executing COW when modifying the shared range. Copying is not a hard link: their names and metadata are independent, and rewriting one will not affect the other according to hard link semantics. Both XFS reflink and Btrfs can share and split by range; they cannot be distinguished by "extent-level vs. whole-file-level COW."
Modern XFS self-describing metadata can include checksums, owning object, and location information to detect corruption or misplaced writes; this does not mean ordinary user file data has the same end-to-end checksums. See XFS self-describing metadata design.
4. Btrfs: Updating a reference path
The default COW data path writes modifications to a new location and then updates the relevant metadata references. Transaction commits must satisfy write ordering and root validity requirements; it is not "safe because each pointer is written atomically, so any intermediate state is safe." Old extents can only be reclaimed when they are no longer needed by the current tree, snapshots, or other references.
Snapshots typically share existing structures and data first; creation does not require copying the entire subvolume content, but it is not strictly O(1) under all scales and loads. The preserved old versions will gradually consume space; after deleting a file, snapshots may still retain the blocks it occupied. Subvolumes have their own file trees and can be mounted separately, but they are not independent physical volumes and do not automatically form mount namespace isolation. The content of nested subvolumes is not recursively included in parent-child volume snapshots.
COW does not require small modifications to always copy the original complete large extent; in practice, they are split/rewritten according to implementation and compression unit rules; compression, fragmentation, and sharing degree affect write amplification. Enabling configurations like NOCOW results in different data verification and shared write restrictions. Btrfs has a tree log for accelerating fsync, so the statement "COW filesystems do not need any journal" is also incorrect. See Btrfs feature documentation and Subvolume semantics.
Detection, Repair, and Historical Recovery are Three Capabilities
| Required Capability | What Must Be Present | What Cannot Be Assumed |
|---|---|---|
| Detect Corruption | Content and checksum records are comparable | Detection does not equal reconstruction capability |
| Repair Corruption | Another verifiable correct copy | Mirrors do not automatically retain history before deletion |
| Recover History | Snapshots or historical versions still exist | Snapshots in the same pool still share the fault domain |
| Handle Pool-Wide Loss | Backup in an independent fault domain | Recovery process must still be verified |
Therefore, "two disks," "has checksums," and "has snapshots" are different conditions; one cannot replace the others.
Btrfs's data checksum algorithm is configurable; not all files should be hardcoded to CRC32C; data and metadata profiles can also differ. "Having two disks" cannot replace checking the actual profile, device errors, and remaining available replicas.
5. bcachefs: Tiered placement and reclamation are also design priorities
bcachefs combines extent mapping, B-trees, journaling, checksums, compression, and multi-device management. Fast devices handle writes or caching, while slower devices store data migrated in the background, involving coordination between placement targets, replica counts, background migration, and space reclamation. copygc moves still-valid data out of reclaimable regions, so user write volume and total device write volume may differ.
The journal is not a decorative option that can be casually removed; "COW-centric, optional journal" does not describe its recovery protocol. Comparisons should look at persistent commit conditions, data and metadata replica policies, and recovery paths after device failures, rather than writing project promotional goals as proven performance conclusions.
Version and Deployment Boundaries
Version Check: 2026-09-08. bcachefs entered the mainline via Linux 6.7, but the official documentation currently states that it is no longer distributed with the mainline kernel from version 6.18 onwards, switching to an out-of-tree DKMS module. Deployment and upgrades require checking the compatibility of the module, tools, target kernel, and disk format simultaneously; one can no longer simply write "built-in to 6.7+ kernels." See bcachefs official documentation and official source repository.
6. Compare using capability boundaries, not fixed champions
| Mechanism | ext4 | XFS | Btrfs |
|---|---|---|---|
| Typical Metadata Recovery | JBD2 Journal | Metadata Journal | COW transactions and tree log mechanisms |
| File Data Sharing | Does not provide standard native reflink | Formats with reflink support available | reflink and subvolume snapshots |
| Ordinary Data Checksums | Not commonly provided | Not commonly provided | Usually provided, subject to configuration |
| Space Organization Focus | Block groups, extents, delayed allocation | AGs, extents, multiple allocation indexes | Block groups, profiles, shared references |
Capacity limits depend on block size, format features, kernel implementation, and tool versions; it is inappropriate to directly write the theoretical bit width of the format as the user-available upper limit. Performance comparisons should fix the dataset, concurrency, sync frequency, remaining space, and snapshot state; short-duration buffered writes only measure memory absorption speed and cannot demonstrate device sustained capabilities.
The following pure calculation example checks logical range mappings without modifying the device:
= # Logical start, length, physical start
, =
return + - ,
return None # Represents a hole in this model; unwritten/delalloc not modeled
assert ==
assert ==
assert is None
Persistent update application protocols are covered in VFS Advanced; storage pools, TXG, and ZIL are covered in ZFS.