JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Journaling and Crash Consistency

A single file rename can touch three different places on disk. If the power dies between the first write and the third, what does your filesystem look like the next morning? This guide builds, from the ground up, how a journal turns a pile of risky little writes into one all-or-nothing promise.

Why one logical change is many physical writes

From the previous guide you carry a precise picture of the on-disk layout: a file's data lives in blocks, its metadata lives in an inode, directories map names to inode numbers through directory entries, and a block allocation bitmap records which blocks are free. Hold onto that, because the trouble we are about to meet comes straight from it. A change that feels atomic to a program — "append 100 bytes to this file" — is not one write to the disk. It is several writes to several different, often far-apart, locations.

Walk that append through. First the filesystem finds a free block in the bitmap and flips its bit from 0 to 1. Then it writes your 100 bytes into that block. Then it updates the inode: a new block pointer, a larger size field, a fresh modification time. Three separate disk locations, three separate writes. The disk firmware and the block I/O layer are free to commit them in any order, and crucially, a write is not durable the instant write() returns — it may sit in the page cache in RAM for a while first. The hardware promises only one thing: a single disk sector write (typically 512 bytes, or 4 KiB on modern drives) either fully lands or does not. It makes no promise at all about groups of writes.

What a crash in the middle actually breaks

Let us make the danger concrete by crashing at the worst moment. Suppose the bitmap write and the data write landed, but the inode write did not. Now a block is marked used, holds your bytes — but no inode points at it. It is a lost block: occupied forever, reachable by nothing, a slow leak of disk space. Annoying, but not catastrophic. Now flip it: suppose the inode update landed (it claims a new block) but the bitmap write did not (the block still reads free). The far more dangerous case. The filesystem now believes that block is both free and part of your file. The next file created may be handed the same block, and two files silently share storage — write to one, corrupt the other.

Before journaling existed, the answer to this was fsck — "filesystem check" — a repair tool that, after a crash, scanned the entire disk looking for exactly these inconsistencies: blocks marked used but unreferenced, inodes pointing at blocks marked free, link counts that disagree with the directory entries. It would patch what it could. Two honest problems with that. First, fsck is slow: it must read the whole volume, so on a multi-terabyte disk a crash could mean an hour of downtime before the machine even boots. Second, fsck can only restore structural sanity, not your data — it may resolve a double-allocated block by simply throwing one file's claim away. The filesystem becomes consistent again; a file of yours just vanished.

The journal: write your intent down first

The idea that fixes this is borrowed, almost unchanged, from databases, and it is beautifully simple once you see it. Before touching the real, scattered locations on disk, first write a compact description of the entire change to one dedicated, contiguous area — the journal. "I am about to: set bitmap bit 4711, put these bytes in block 4711, update inode 92 like so." Only after that whole description is safely on disk do you go and perform the real updates in their scattered homes. This discipline — record your intent durably before you act on it — is the write-ahead log, and it is the engine of filesystem journaling.

The magic is in one little record at the end. After the description is written, the filesystem writes a final commit record — a tiny marker that says "this transaction is complete and valid." Because that marker is one small, single-sector write, the hardware's one guarantee applies to it: it either lands fully or not at all. So the commit record becomes the single point that decides everything. If it is present after a crash, the whole transaction counts; if it is absent, the whole transaction is ignored. We have converted a messy multi-write update into one all-or-nothing decision, hinged on a single durable bit. This is precisely the atomic behavior you wished the disk gave you natively — the journal manufactures it.

TIME --->

  [ describe change in journal ]   then   [ COMMIT record ]   then   [ apply to real locations ]
        (the intent)                       (the deciding bit)         (the "checkpoint")

  Crash BEFORE commit lands  =>  on reboot, recovery sees no commit  =>  DISCARD. Disk untouched, clean.
  Crash AFTER  commit lands  =>  on reboot, recovery sees commit     =>  REPLAY journal -> finish real writes.

  Either way the filesystem ends consistent. The commit record is the all-or-nothing hinge.
The write-ahead ordering. The only fragile instant is the commit write itself, and because it is a single sector it inherits the hardware's one atomicity guarantee.

Recovery, and the cost it buys back

Now reboot after a crash and watch recovery work. The filesystem does not scan the whole disk like fsck. It reads only the journal — a small, contiguous region — and replays each committed transaction it finds, re-doing the real writes to make sure they all completed. Transactions without a commit record are simply ignored, as if they never happened. Recovery time depends on the size of the journal, not the size of the disk, so it takes seconds instead of an hour. That speed is the headline reason journaling won: not that it prevents corruption fsck could not eventually fix, but that it makes recovery fast and bounded.

Replay must be safe to run more than once, because a machine could crash during recovery and reboot into recovery again. Re-doing a write like "set bitmap bit 4711" or "store these exact bytes in block 4711" lands on the same result whether you do it once or five times — applying it again is harmless. That property is idempotence, and it is what makes crash recovery trustworthy: the journal records final values to install, not increments to apply, so a half-finished replay can simply be run from the top again with no double-counting.

Nothing is free, and journaling's honest cost is right here: every change is now written twice — once to the journal, once to its real home. That is real bandwidth and real wear. Most production filesystems take a deliberate shortcut: they journal only the metadata (the inode, the bitmap, the directory entry — the structures whose corruption is catastrophic) and write file data just once, straight to its location. This is metadata-only journaling, the default mode of ext4. It keeps the filesystem's structure always recoverable for half the write cost. Be honest about the trade it makes, though: after a crash the structure is guaranteed sane, but a file's contents may be a mix of old and new bytes. The skeleton is protected; the flesh is not.

What the journal does and does not promise to you

Here is the misconception to kill before it bites you: a journaling filesystem does not promise that your data is on disk when write() returns. The journal guarantees the filesystem will be consistent after a crash — never internally contradictory, never a double-allocated block. It says nothing about which version of your file survives. Recent writes still sitting in the page cache, not yet pushed to the journal, are simply lost on a crash, and that is fully correct behavior. Consistency is not durability. A losslessly consistent filesystem can still cheerfully forget your last ten seconds of work.

When you genuinely need a particular write to survive a crash, you must ask for it explicitly with fsync(): a system call that tells the kernel "do not return until this file's data and metadata are durably on the disk." That is the bridge from consistency to durability, and the exact promises of fsync() — what it flushes, what it forgets, and why even fsync() has sharp edges — are the whole subject of the last guide in this rung. For now, fix the boundary in your mind: journaling guarantees consistency for free; durability of a specific write is something you must request.