AlgoMaster Logo

Journaling

26 min readUpdated August 7, 2026
Listen to this chapter
Unlock Audio

A service creates a file containing a newly generated invoice. One logical create can modify several file-system structures:

If power fails while those blocks are being updated in place, storage can contain an arbitrary mixture of their old and new versions. A full consistency checker can search for the damage, but scanning a large file system after every unclean shutdown is expensive.

A journal gives recovery a smaller and more precise source of truth. Before installing a related set of changes in their normal locations, the file system records the changes in a dedicated log. A persistent commit record marks the set as complete.

Journaling turns a multi-block file-system update into a recoverable transaction by writing enough information to a log before reusing or relying on the normal on-disk locations.

After a crash, recovery does not have to rediscover every interrupted operation from the entire file system. It examines the journal, replays complete transactions, and ignores incomplete ones.

Home Locations and the Journal

Every persistent metadata block has a normal location in the file system. This is its home location.

For example:

Without journaling, the file system writes modified versions directly to blocks 80, 241, and 910. A crash between those writes can leave them disagreeing.

A journal introduces a separate on-disk region:

The journal holds temporary recovery information. The home locations remain the long-term representation used during ordinary file-system access.

The journal can be stored inside the same file system, in a reserved region, or on a separate device. Its placement affects performance and failure behavior, but not the basic protocol.

Journal Transactions as Internal Recovery Units

A journal transaction groups updates that recovery must treat as one unit.

Creating invoice.txt might place these metadata changes in one transaction:

If the transaction commits, recovery can install all its recorded updates. If it does not commit, recovery ignores all of them.

An internal journal transaction is not necessarily:

  • One system call
  • One application request
  • One database transaction
  • One file

The file system can batch changes from many processes into one transaction to reduce commit overhead. One complex namespace operation can also affect several objects.

The important property is not which application caused each change. It is that the transaction contains a set of file-system updates whose recovery outcome is defined together.

The Records Inside a Redo Journal

A common design is a redo journal. It records the new versions, or after-images, of changed blocks. Recovery redoes committed updates by copying those versions to their home locations.

A simplified transaction can contain:

The descriptor record identifies the transaction and the home locations represented by its payload blocks. The payload carries the changes. The commit record says that the complete transaction is valid for replay.

Real journal formats also need block types, lengths, sequence numbers, checksums, and rules for wrapping around the journal. These fields let recovery distinguish valid records from stale, torn, or unrelated bytes.

Some file systems log operations or compact metadata deltas instead of complete block after-images. Others retain information for both redo and undo. The exact representation varies, but every correct design must let recovery identify transaction boundaries and decide which work is complete.

The Write-Ahead Rule

The journal is useful only when persistence follows a strict rule:

The complete transaction contents must be persistent in the journal before the commit record is allowed to become persistent.

For transaction 73, the required order is:

  1. The descriptor and payloads reach persistent storage.
  2. The commit record reaches persistent storage.
  3. Only then may the journal-covered home blocks be overwritten.

The order is the guarantee. A commit record that lands before its payloads would describe a transaction that cannot be replayed.

Submitting writes in this order is not enough. The storage stack can queue, cache, merge, and reorder requests. The file system uses ordering and cache-flush mechanisms supplied by lower layers to enforce the persistence dependency.

If the commit record could reach stable storage first, recovery might treat missing or torn payloads as a complete transaction. The log would then certify data it does not actually contain.

Checksums strengthen this boundary. Recovery should accept a transaction only when its records, sequence information, and commit record satisfy the journal format's validation rules. A recognizable commit block with a failed checksum is not a valid commit.

The Four Stages of a Transaction

The journal lifecycle has four main stages.

1. Build

The file system modifies cached blocks and associates them with a running transaction. It tracks which blocks and resources the transaction will need.

2. Log and commit

The transaction's journal records are written. Once those records are persistent, the commit record is made persistent.

At that point, the transaction is committed in the journal. Recovery has enough information to reproduce it even if none of its home blocks contain the new versions.

3. Checkpoint

Committed block versions covered by the journal are written from memory or the journal to their home locations. This is called checkpointing.

The home blocks do not all need to change atomically. If a crash interrupts checkpointing, recovery can replay the committed transaction again.

4. Reclaim

After every home update covered by the transaction is safely checkpointed, its journal space is no longer needed. The circular journal can reuse that region for newer transactions.

The transition to Committed is the critical recovery boundary. Checkpoint completion determines whether the log space can be reused.

Crash Outcomes at Every Stage

Suppose transaction 73 updates three metadata blocks. The result depends on the crash point:

Crash pointJournal stateRecovery action
Before any log records persistNo transaction evidenceKeep the old home state
During descriptor or payload writesIncomplete or invalid transactionIgnore transaction 73
After all payloads but before commit persistsComplete data but no valid commitIgnore transaction 73
After the commit persistsValid committed transactionReplay transaction 73
During checkpointingSome home blocks are newReplay all of transaction 73
After checkpointing and reclamationHome blocks are completeNo replay is needed

The rule “no valid commit means ignore” may leave unused bytes in the journal, but they are not authoritative.

The rule “valid commit means replay everything” is equally important. Recovery does not need to determine which home blocks happened to reach storage before the crash. Replaying all recorded after-images produces the same final contents.

Loading simulation...

Idempotent Journal Replay

An operation is idempotent when repeating it has the same final effect as performing it once.

Copying an after-image to a fixed home block is naturally idempotent:

This matters because recovery can crash too.

Suppose recovery replays two of a transaction's three blocks, then the machine loses power again. At the next boot, recovery can replay the entire transaction from the beginning. Already-replayed blocks receive the same contents, and the missing block is installed.

Idempotence removes the need for recovery to persist a fragile “I copied exactly these blocks” progress list after every block.

Operation-based journals must design their replay operations with the same goal. A record that merely says “increment this link count” is unsafe to apply twice unless the transaction protocol also makes duplicate application detectable.

Checkpointing vs. Committing

The terms are easy to confuse.

A transaction is committed when its complete journal representation and commit record are persistent. The journal can recover it from that point onward.

A transaction is checkpointed when its changes have reached their normal home locations. The journal no longer needs to retain it.

Checkpointing can happen later and in the background. Delaying it lets the file system combine home writes and choose efficient write-back times.

However, checkpointing cannot be delayed forever. A finite journal eventually fills. If its oldest committed transaction is not fully checkpointed, that journal region cannot be overwritten safely.

Under sustained write load, the need to free journal space can force foreground writers to wait for checkpoint I/O.

Circular Journal Structure

A journal is commonly managed as a circular log with a logical head and tail.

The journal is a circular region. Space is reclaimed by advancing the tail, which is only possible once the oldest transaction's home blocks have been written.

The head advances as new records are appended. The tail advances when the oldest transaction has been completely checkpointed and its recovery records are no longer needed.

Eventually the head reaches the physical end of the journal and wraps to the beginning. Sequence numbers distinguish current records from older bytes left by a previous pass through the same locations.

The head must never overtake journal space still protected by the tail:

A larger journal can absorb longer bursts and give checkpointing more time to catch up. It does not eliminate the steady-state work of writing home blocks, and it is not a permanent history of file-system activity.

Journal-Based Recovery Scans

After an unclean stop, journal recovery follows a process resembling:

  1. Find the active range of the journal.
  2. Read records in transaction sequence order.
  3. Validate record types, lengths, sequence numbers, and checksums.
  4. Identify transactions with valid commit records.
  5. Replay committed transactions in the required order.
  6. Ignore incomplete transactions.
  7. Mark the recovered journal state so normal operation can resume.

Because the journal is bounded, recovery work is related to the outstanding journal contents rather than every inode and data block in the file system.

This is why a journaled file system can often recover quickly even when its total capacity is very large.

Journaling does not make full consistency checking obsolete. A checker may still be needed after storage corruption, journal damage, software bugs, unsupported hardware behavior, or failures outside the journal's assumptions. Normal crash recovery is simply much narrower.

Physical and Logical Journaling

Journals can describe changes at different levels.

Physical journaling

Physical journaling records block images or byte ranges destined for specific home locations:

Replay is straightforward and naturally idempotent. Recording an entire block can write more journal data than the few bytes that actually changed.

Logical journaling

Logical journaling records higher-level intentions or compact changes:

This can reduce log volume, but replay logic is more complex. It must understand metadata formats and handle repeated or partially applied operations correctly.

File systems can combine both techniques. The choice changes journal format and performance, not the core requirement: recovery must be able to recognize complete transactions and reconstruct a valid state.

Metadata Journaling and File Data

Logging every modified file-data block can be expensive. A workload that writes 1 GiB of file data could write roughly that data once to the journal and again to its home locations.

Many file systems therefore journal metadata while handling regular file data separately.

The distinction matters during an append. Let:

Three broad policies are common:

PolicyJournal contentsRequired relationship
Data journalingFile data and metadataD and M are logged before the transaction commits
Ordered metadata journalingMetadata onlyD reaches its home location before the journal commits M
Writeback metadata journalingMetadata onlyNo general persistence order between D and the commit of M

These names describe policy families. Exact guarantees vary by file system.

Data Journaling

With data journaling, both data and related metadata are part of the journal transaction.

For the append:

After a valid commit, recovery can replay both the file contents and the metadata that exposes them.

This provides the strongest consistency relationship of the three policies, but can add substantial write traffic. The new data may travel through storage twice: once into the journal and once to its final location.

Data journaling still does not turn arbitrary application activity into a database transaction. The file system chooses transaction boundaries according to its own rules, and application records may span several writes or files.

Ordered Metadata Journaling

With ordered metadata journaling, only metadata is copied into the journal. File data goes directly to its home location.

The protocol enforces:

If a crash occurs before the metadata commit, recovery ignores M. The newly written data may occupy storage, but the old metadata does not expose it as part of the file.

If recovery sees a committed M, the ordering rule ensures that the data it exposes was written first.

This avoids logging file data twice while preventing committed metadata from pointing to data that the protocol has not prepared. It does not imply that every recent application write has committed merely because its system call returned.

Writeback Metadata Journaling

With writeback metadata journaling, metadata is journaled, but file-data write-back is not generally ordered before the associated metadata transaction commits.

This allows more scheduling freedom:

After a crash, file-system structures can be consistent while recently modified file contents are old, partial, or otherwise not the version the application expected.

This mode can reduce ordering waits, but applications must not mistake metadata consistency for current file data.

Modern file systems can contain additional safeguards against exposing data from unrelated prior allocations. Those safeguards are separate from promising that the application's newest bytes survived.

Multi-Operation Journal Commits

Forcing a storage commit for every small metadata change would be expensive. File systems commonly keep a transaction open briefly and allow many operations to join it.

Unrelated work from three processes shares one commit. That batching is good for throughput, and it means one process's fsync can end up waiting on another's work.

This is transaction batching. When several waiters benefit from one commit, the result is similar to group commit in a database log.

Batching improves throughput because commit-related ordering and cache-flush work is shared. It introduces tradeoffs:

  • A larger transaction consumes more journal space.
  • Waiting longer can improve batching but delays the commit point.
  • One busy workload can create contention around the running transaction.
  • Writers may stall when checkpointing cannot reclaim space quickly enough.

The transaction remains a file-system recovery unit. Two application operations appearing in the same journal transaction does not give the application a supported way to commit or abort them as one business operation.

Repeated Block Updates

A hot metadata block can change repeatedly while transactions progress. For example, many creates can update the same directory block.

The journal must coordinate:

Recovery must replay them in transaction order so the final home version is B.

Checkpointing also must not let an older version overwrite a newer one. Implementations track ownership, pin modified buffers, copy versions, or otherwise coordinate concurrent transaction and write-back activity.

This bookkeeping explains why journaling is more than appending bytes to a log. The system must maintain a correct relationship among in-memory versions, journal versions, home versions, and transaction sequence order.

Revocation or Tracking for Block Reuse

Block reuse creates another subtle problem.

Suppose transaction 73 logs an old metadata update for block 500. Before transaction 73 is checkpointed, a later transaction frees block 500 and reuses that location for a different purpose.

Blindly replaying transaction 73 after a crash could overwrite the newer object with the older block image.

Journal implementations prevent this with sequence-aware reuse rules. Some use revoke records that tell recovery not to replay an older logged image for a particular home block. Others use equivalent version or ownership tracking.

Conceptually:

Recovery evaluates these records in transaction order. It must not interpret a valid but obsolete log record outside the lifetime in which that block had its earlier role.

Costs of Journaling

Journaling improves recovery, but it is not free.

Metadata is usually written at least twice:

Data journaling can double-write regular file data too.

The journal also introduces transaction management, checksumming, commit ordering, and contention. A workload can pause because the journal is full even when the file system has plenty of ordinary free space.

Several properties offset these costs:

  • Journal writes are append-oriented and can be easier to schedule efficiently than scattered home writes.
  • Batching spreads commit overhead across many operations.
  • Checkpointing can reorder and combine home writes.
  • Fast bounded recovery avoids routine full-volume scans after crashes.

The resulting performance depends on workload shape, storage latency, journal policy, transaction size, and checkpoint pressure. “Journaled” alone does not predict whether a workload will be fast or slow.

File-System Journals vs. Database Logs

The two mechanisms share the write-ahead idea:

Their responsibilities differ.

A file-system journal understands inodes, directories, allocation structures, and other file-system metadata. It normally does not know that three files together represent one customer order.

A database log understands database pages, transactions, records, and commit semantics. It can preserve application-level invariants the file system cannot infer.

A database stored on a journaled file system may therefore use its own write-ahead log. The two logs are not redundant: they protect correctness at different abstraction layers.

What Journaling Guarantees, and What It Does Not

Journaling can provide:

  • Atomic recovery of the updates covered by one committed journal transaction
  • Fast recovery by examining a bounded log
  • Safe replay after a crash interrupts checkpointing
  • Protection for the metadata relationships included in the journal policy

It does not automatically provide:

  • The newest version of every file after a crash
  • Application transaction semantics across arbitrary operations
  • Protection from permanent device loss
  • A backup or historical audit trail
  • Detection of every possible corruption
  • Correct behavior when hardware violates the persistence contract

The coverage question is essential:

Which bytes are journaled, which bytes are merely ordered, and what event makes the transaction valid for recovery?

Two file systems that both advertise “journaling” can answer those questions differently.

Inspecting an ext4 Journal Safely

On Linux, inspect the mounted file-system type and options for a path with:

The output identifies the file-system implementation and explicit mount options. An omitted option can still have a file-system default, so mount output alone is not a complete journal-policy specification.

For safer experimentation, create disposable regular-file images rather than modifying a real device:

Verify both variables before running either mkfs command. They must name newly created disposable regular files, never devices containing needed data.

Inspect their feature sets:

The first image should list has_journal among its features and report journal information. The second demonstrates that ext4-format metadata can exist without that feature, though such a configuration has different recovery behavior.

Run non-repairing checks:

Both new images should be structurally clean. This comparison exposes an on-disk feature flag; it does not simulate a mid-transaction power failure or measure recovery time.

Never run formatting or repairing tools against a mounted production file system as an experiment.

Reading a Journal Timeline

When reasoning about a journaling bug, label every relevant event by persistence state rather than by source-code order.

For one metadata transaction:

Then place the crash:

This timeline avoids two common sources of confusion:

  1. An I/O request being submitted is not the same as it becoming persistent.
  2. A committed transaction need not yet be present at every home location.

The same model scales from a three-block teaching example to transactions containing thousands of metadata changes.

Summary

Journaling records recoverable file-system updates before overwriting their normal home locations. Related updates form an internal transaction containing enough information for recovery to identify its destination blocks and completion state.

The write-ahead rule requires all transaction records to become persistent before the commit record. A valid commit makes the transaction replayable. Checkpointing later copies the committed changes to their home locations, and only then can the circular journal reclaim that space.

After a crash, recovery ignores incomplete transactions and replays committed ones in order. Redo operations are designed to be idempotent, so replay remains safe after partial checkpointing or even an interrupted recovery.

Data journaling logs file data and metadata. Ordered metadata journaling writes data to its home location before committing metadata that exposes it. Writeback metadata journaling provides weaker ordering for file contents. In every mode, the journal protects only the state covered by its policy; it is neither a backup nor a substitute for application-level transactions.

The core lifecycle is:

log the updates → persist the commit → checkpoint home blocks → reclaim journal space

Quiz

Journaling Quiz

5 quizzes