File System Journaling and Crash Recovery
Why a crash mid-update corrupts a file system and how a journal fixes it with write-ahead logging. In a crash simulation, 2 of 4 crash points left damage without a journal and none did with one.
File system journaling writes intended metadata or data changes to a log, adds a commit record, then applies them to their real locations. After a crash, recovery replays committed transactions and ignores uncommitted ones, so the file system stays consistent. Interviewers expect the contrast with fsck: journal recovery reads only the log, so it takes seconds regardless of disk size.
Appending to a file needs several separate disk writes, and a crash between them leaves the file system inconsistent. Journaling writes the intended changes to a log first and marks them committed, so after a crash the OS either replays the whole transaction or ignores it. In a simulation of every crash point, journaling left no inconsistent state.
- Appending a block to a file needs at least three separate disk writes, the data block, the inode and the allocation bitmap, so a crash between them leaves the file system inconsistent.
- fsck repairs a file system after an unclean shutdown by scanning all metadata, so its running time grows with disk size and can take hours on a multi-terabyte volume.
- In a journaling file system, a crash before the commit record means the transaction is ignored, and a crash after it means recovery replays the logged updates.
- In a crash simulation, 2 of 4 crash points left inconsistent state without a journal, while all 8 crash points recovered consistently with a journal.
- In ext4's default ordered mode, only metadata is journaled, but file data is forced to disk before the metadata commit, so an inode never points at unwritten data.
- Journaling keeps the file system structure consistent but does not guarantee application data reaches disk; programs that need durability must call fsync.
01The crash consistency problem
Appending a block to a file touches at least three things on disk: the data block itself, the inode (which records the file's size and block pointers), and the bitmap that marks the block as allocated. These are separate writes, and the disk can complete them in any order. If power fails after some but not all, the structures disagree.
Depending on which write was lost you can get a block marked used that no file owns (a leaked block), an inode pointing at a block that was never written (garbage or someone else's old data), or a block claimed by two files. None of these are visible until later, when something breaks.
02The old fix: fsck
Early file systems tolerated this by running a checker (fsck) after an unclean shutdown. It scans all metadata, finds inconsistencies and repairs what it can. That works, but the time is proportional to the size of the disk. Checking a multi-terabyte volume can take hours, and some problems cannot be repaired automatically, such as data written to the wrong place. It also only restores consistency, not the lost update.
03Journaling: write the plan first
A journaling file system keeps a small log area. To apply a change it does the following:
- Write the intended updates (bitmap, inode, data or metadata) to the journal.
- Write a commit record that marks the transaction complete.
- Apply the updates to their real locations on disk (the checkpoint).
- Free the journal space.
If the crash happens before step 2, the commit record is missing, so recovery ignores the half-written transaction as if it never happened. If the crash happens after step 2, recovery replays the logged updates, which are safe to repeat. Either way the file system ends up consistent, and recovery reads only the journal, so it takes seconds regardless of disk size. This is write-ahead logging, the same idea databases use.
04A crash simulation
def consistent(d): return len(set(d.values())) == 1 # all 0 or all 1
def no_journal():
bad = 0
for crash in range(4): # crash after k home-location writes
d = {'bitmap': 0, 'inode': 0, 'data': 0}
for k in list(d)[:crash]: d[k] = 1
bad += not consistent(d)
return bad, 4
def journal():
seq = [('log','bitmap'), ('log','inode'), ('log','data'), ('commit',None),
('home','bitmap'), ('home','inode'), ('home','data')]
bad = 0
for crash in range(len(seq) + 1):
d = {'bitmap': 0, 'inode': 0, 'data': 0}
log, committed = [], False
for kind, k in seq[:crash]:
if kind == 'log': log.append(k)
elif kind == 'commit': committed = True
else: d[k] = 1
if committed: # recovery: replay the log
for k in log: d[k] = 1
bad += not consistent(d)
return bad, len(seq) + 1
print(no_journal(), journal()) # (2, 4) (0, 8)Without a journal, 2 of the 4 possible crash points (after one write, after two) left the three structures disagreeing. With the journal, all 8 crash points, including the ones in the middle of logging, recovered to a consistent state: either everything applied or nothing. The model assumes the commit record reaches the disk only after the log entries, which real systems enforce with write barriers or cache flushes. If the disk reorders those writes, the guarantee breaks.
05Journaling modes and their cost
Writing all data twice (journal, then home) doubles the I/O, so file systems offer choices. In ext4 the default ordered mode journals only metadata but forces the file data to disk before the metadata commit, so a crash never exposes an inode pointing at unwritten data. writeback mode journals metadata without ordering data, which is faster but can expose stale data after a crash. data=journal logs everything and is the safest and slowest.
Note what the guarantee covers. The file system structure stays consistent. Your application data, such as the last few edits in a document, can still be lost if not yet flushed. Programs that need durability call fsync explicitly.
06Other approaches
Some file systems avoid in-place updates altogether. Copy-on-write designs (ZFS, Btrfs, APFS) write new versions of changed blocks elsewhere and then flip a single pointer to the new root, which is an atomic step, so there is no half-updated state to repair. Log-structured file systems treat the whole disk as an append-only log. These designs trade the journal's double write for other costs, such as fragmentation and the need to clean old versions.
07Common interview questions
Q: What problem does journaling solve?
A: A crash during a multi-write update leaves metadata inconsistent. The journal makes the update atomic.
Q: Why is journal recovery fast compared with fsck?
A: It reads only the log of recent transactions, not all metadata on the disk.
Q: What if the crash happens before the commit record?
A: The incomplete transaction is discarded and the file system is as before the update.
Q: What if it happens after the commit but before checkpointing?
A: Recovery replays the logged updates to the real locations.
Q: What does ext4's ordered mode guarantee?
A: Data is written before the metadata that points to it, so metadata never references unwritten data.
Q: Does journaling protect my file's contents?
A: Not fully. It protects structure. Use fsync for durability of application data.