Exercises — Journaling — why, how it works
Before we start, three words we will lean on constantly. If any feel fuzzy, expand them:
Recall The three load-bearing words
Consistency ::: the filesystem's structures never contradict each other (no block claimed by two files, no directory pointing at nothing). Durability ::: a write you were told "succeeded" survives a power cut. Different from consistency! See fsync and Durability. Idempotent ::: doing it twice gives the same result as doing it once — writing the value into a box, then writing again, leaves .
Level 1 — Recognition
Exercise 1.1
In the transaction lifecycle, which single write flips a transaction from "ignore me" to "replay me" during recovery?
Recall Solution
The COMMIT block. It is written last and is a single small sector write (disks guarantee one sector is atomic). Recovery rule: commit present ⇒ replay; commit absent ⇒ discard. This is why the parent note calls it "the atomic go switch."
Exercise 1.2
State the Write-Ahead Logging invariant in your own words, and identify which of these two orderings obeys it:
- (A) write new inode to journal → write new inode in place
- (B) write new inode in place → write new inode to journal
Recall Solution
WAL invariant: ==the intent must be durably on the journal before any in-place (real) structure is touched== — written , where means "strictly completes before". (A) obeys it (journal first). (B) violates it — if a crash lands between the two steps in (B), the real inode is already broken and there is no journal record to fix it. See Write-Ahead Logging.
Level 2 — Application
Exercise 2.1
You append 1 block to log.txt. This touches the data block, the inode (size + pointer), and the bitmap — 3 in-place structures. In ordered mode (the default), how many of these 3 go into the journal? And what ordering guarantee applies to the one(s) that don't?
Recall Solution
Ordered mode journals only metadata: the inode and the bitmap → 2 structures enter the journal. The data block does not enter the journal; instead it is forced to disk before the commit block for the metadata is written. This prevents the inode from pointing at garbage. See the modes table in the parent note.
Exercise 2.2
A journal transaction consists of, in order: 1 descriptor block, metadata blocks being logged, and 1 commit block. A crash happens. Recovery finds the descriptor and metadata blocks present, but no commit block. What does recovery do, and what is the state of the real filesystem afterwards?
Recall Solution
Discard the transaction. Because the commit is missing, recovery cannot trust that all metadata blocks made it durably — it treats the whole thing as never-committed. Crucially, the checkpoint step (copying journal → in place) had not run, so the real inode/bitmap were never touched. The filesystem is byte-for-byte in its pre-transaction state → still consistent. The append simply "didn't happen." ✅
Level 3 — Analysis
Exercise 3.1 — recovery time scaling
A disk holds . The journal is a fixed circular region of size . Old-style fsck must scan the whole disk at a read rate of . Journal recovery only scans the journal at the same rate.
(a) How long does full fsck take? (b) How long does journal recovery take? (c) By what factor is journaling faster?
Recall Solution
Convert to megabytes: .
(a) .
(b) .
(c) Factor faster.
The point: journal recovery scales with work in flight (, fixed), while fsck scales with total disk size (). Bigger disks make fsck worse but leave journaling untouched.
Exercise 3.2 — the ordered-mode counterexample
In writeback mode, suppose the metadata commit lands (inode now says "file is 8 KB, blocks 40–41") but a crash happens before the new data blocks 40–41 were flushed. Blocks 40–41 previously held a deleted user's password file. What does a reader of the newly-extended file now see, and how does ordered mode prevent it?
Recall Solution
The reader sees the stale/garbage contents of blocks 40–41 — here, someone else's old password data. The committed metadata legitimately points at those blocks, but the intended new data never landed. This is a real security/integrity leak. Ordered mode's fix: it forces the data blocks to disk before committing the metadata (data metadata-commit). So if the metadata is durable, the data it points to is guaranteed already durable — no window for exposing stale blocks. This is why ordered mode is the default sweet spot.
Level 4 — Synthesis
Exercise 4.1 — write-amplification of data mode
In data (full) journal mode, both metadata and file data are written to the journal and checkpointed to their in-place homes. Ignoring the tiny descriptor/commit blocks, if you append of file data plus of metadata, how many total megabytes hit the disk? Express the "write amplification" (total bytes written ÷ useful bytes) as a factor.
Recall Solution
Useful payload (since ). In data mode every byte is written twice — once into the journal, once in place: Write amplification . Synthesis point: this exact 2× cost is why full data mode is "safest, slowest," and why ordered mode was invented — it journals only the of metadata, so amplification for the data is . This is the same tension studied in Databases with Write-Ahead Logging.
Exercise 4.2 — idempotency and the double checkpoint
A committed transaction TX#7 sets: inode.size , bitmap.bit[50] . During checkpoint, the inode is copied to disk, then a crash hits before the bitmap is copied. On reboot, recovery finds TX#7 committed and re-copies all of TX#7's journal blocks — including the inode that was already written. Prove that re-writing the already-correct inode causes no harm, and state the general property this relies on.
Recall Solution
Before the second copy, disk inode.size (from the first checkpoint). The journal's copy of inode.size is also . Re-copying writes over → the value is unchanged. The bitmap, which was missed, now gets bit[50] from the same replay. Final state: inode.size , bitmap.bit[50] — exactly the committed intent. General property: idempotency — replaying a committed transaction any number of times yields the same final state as replaying it once. This is what makes crash-during-recovery safe: recovery itself can crash and be re-run without limit. See Atomicity.
Level 5 — Mastery
Exercise 5.1 — designing around a lying disk
Some cheap disks reorder writes and report a write "done" while it still sits in the disk's own volatile cache. Explain, step by step, how this breaks the WAL invariant even when the OS issued writes in the correct order — and name the single primitive that repairs it.
Recall Solution
The OS issues: (1) journal metadata, (2) commit block, (3) checkpoint in place — correctly ordered. But if the disk reorders and pushes the commit block to platter before the journal metadata, then a crash can leave a committed transaction whose logged data is missing. Recovery sees the commit, trusts it, replays — and copies garbage/incomplete blocks in place. Corruption, despite perfect OS ordering.
Root cause: the disk's own cache broke the durability boundary the OS assumed. The fix: a cache flush / write barrier — the OS must call fsync() (or issue a FLUSH/FUA barrier) after writing the journal data and again after the commit, forcing the disk to durably persist each group before the next begins. Without an honest flush primitive, WAL's ordering cannot be enforced on physical media. See fsync and Durability.
Exercise 5.2 — journaling vs copy-on-write
Both journaling and copy-on-write (CoW) filesystems achieve crash consistency, but by opposite strategies. Contrast them in one sentence each, and state which one never writes user data twice.
Recall Solution
Journaling: update in place, but write intent to a side journal first; recovery replays or discards. In full data mode it writes data twice (journal + in place). Copy-on-write: never overwrite; write new data to fresh blocks, then atomically flip a single root pointer to the new tree — the old version stays valid until the flip. See Copy-on-Write Filesystems. CoW never writes user data twice — it writes each new block exactly once to a new location and just re-points to it. Journaling's data mode inherently double-writes; that's the fundamental trade the two designs make.
Exercise 5.3 — the consistency ≠ durability boundary (capstone)
A program writes 500 KB to a file and the write() call returns success. The OS buffers it in RAM. The journal has no transaction for this data yet. Power is cut. After reboot the filesystem passes every consistency check but the 500 KB is gone. Is journaling broken? Justify precisely, and state what the program should have done.
Recall Solution
Journaling is not broken. Its promise is consistency (structures never contradict), not durability of un-synced buffered writes. A returned write() only means "copied into a kernel RAM buffer," not "on the platter." Since that data never reached a committed journal transaction, recovery correctly has nothing to replay — and the on-disk filesystem is perfectly consistent, just without the 500 KB.
What the program should do: call fsync(fd) after write() to force the buffered data (and its journal transaction) durably to disk before treating it as saved. Consistency is the filesystem's job; durability of specific bytes is the application's, via fsync(). See fsync and Durability.
Recall Self-test summary (cloze)
The write that decides replay-vs-discard is the COMMIT block. WAL orders journal write strictly before in-place modification. Recovery is safe to re-run because replay is idempotent. Ordered mode journals only metadata but forces data before the metadata commit. Journaling guarantees consistency, not durability; use fsync for the latter.