CrackedRuby CrackedRuby

Overview

Journaling file systems record file system operations in a dedicated log or journal before applying them to the main file system data structures. This technique addresses the fundamental problem of file system corruption during system crashes, power failures, or unexpected shutdowns. When a system crashes mid-operation, traditional file systems can be left in an inconsistent state requiring lengthy consistency checks. Journaling eliminates this problem by maintaining a sequential record of pending changes.

The journal acts as a transaction log, similar to write-ahead logging in database systems. When the system recovers from a crash, it replays the journal to complete any interrupted operations or rolls them back to maintain consistency. This approach reduces recovery time from hours (for full file system checks) to seconds or minutes (for journal replay).

Modern operating systems implement journaling in their primary file systems: ext3/ext4 on Linux, NTFS on Windows, APFS on macOS, and XFS on many Unix systems. Each implementation makes different trade-offs between data safety, metadata consistency, and performance.

Consider a file write operation that requires updating the file data, the inode metadata, and the directory entry. Without journaling:

1. Write file data blocks
2. Update inode (size, timestamps)
3. Update directory entry
[CRASH] - file system inconsistent

With journaling:

1. Write operation details to journal
2. Mark journal entry committed
3. Write file data blocks
4. Update inode
5. Update directory entry
6. Mark journal entry complete
[CRASH at any point] - recoverable state

The journal maintains enough information to either complete or safely discard the operation during recovery.

Key Principles

Journaling file systems operate on three fundamental principles: atomicity, ordering, and recoverability.

Atomicity ensures that file system operations either complete entirely or not at all. The journal groups related changes into transactions. For example, creating a file requires allocating an inode, writing directory entries, and updating free space maps. These operations form a single atomic transaction in the journal. If the system crashes partway through, recovery either completes all changes or discards all changes, preventing partial operations that leave the file system inconsistent.

Ordering determines which changes the journal records and in what sequence. File systems implement three primary journaling modes:

Journal mode (full data journaling) records both metadata and file data in the journal. This provides maximum data protection because all changes go through the journal. If a crash occurs, no data is lost beyond uncommitted transactions. The cost is performance—every write requires two disk operations (journal and final location).

Ordered mode (default in most systems) journals only metadata but enforces ordering constraints. File data writes complete before the metadata transaction commits. This prevents stale data exposure: the file system never points to unwritten disk blocks. Ordered mode offers a balance between safety and performance for most workloads.

Writeback mode journals only metadata without ordering constraints. File data and metadata writes occur independently. This maximizes performance but creates a window where metadata can point to stale data after a crash. Files modified before a crash might contain a mix of old and new data.

Recoverability defines how the system restores consistency after crashes. The journal contains sufficient information to replay or undo incomplete operations. Each journal entry includes:

  • Transaction identifier and sequence number
  • Operation type and parameters
  • Block addresses and checksums
  • Commit markers indicating completion

During recovery, the system scans the journal for uncommitted transactions. Transactions without commit markers are discarded. Committed transactions are replayed to ensure changes reach their final locations. Modern implementations use checksums to detect corrupted journal entries and prevent replaying damaged transactions.

The circular buffer design of most journals means old transactions are overwritten as new ones arrive. The file system tracks which journal entries correspond to persisted changes. Once changes reach their final locations, the journal space becomes available for reuse. This bounded space requirement distinguishes journaling from full transactional systems.

Journal placement affects both performance and reliability. Internal journals reside within the file system partition, simplifying management but competing for disk I/O. External journals use separate storage devices, improving performance through parallel I/O but adding configuration complexity. Some implementations support journal mirroring for additional reliability.

Implementation Approaches

File systems implement journaling through several architectural strategies, each with distinct characteristics for transaction management, journal structure, and recovery mechanisms.

Logical Journaling records high-level operations rather than raw block changes. The journal contains operation descriptions like "create file X in directory Y" along with parameters. During replay, the system re-executes these operations. This approach produces compact journals because operation descriptions are smaller than block data. However, replay logic becomes complex—the system must handle cases where target structures changed after the original operation. Logical journaling works well for metadata-only journaling where operations are well-defined and limited in scope.

Physical Journaling records actual disk block contents. Each journal entry contains copies of modified blocks. Replay simply writes these blocks to their destinations. This approach simplifies recovery—no interpretation needed, just block copying. The journal size increases proportionally to change volume, but recovery becomes deterministic and fast. Most modern file systems use physical journaling for its reliability and simplicity.

Hybrid Approaches combine logical and physical techniques. The journal records physical blocks for data changes and logical operations for complex metadata updates. This balances journal size against recovery complexity. Some systems use logical journaling for space-efficient metadata tracking while physical journaling protects critical structures like inodes and allocation bitmaps.

Write-Ahead Logging requires journal commits before applying changes to final locations. The sequence is: write to journal, force journal to disk, update main structures, checkpoint completed transactions. This ordering guarantees that recovery always finds either completed changes or journal entries describing pending work. The performance cost comes from forced journal flushes, which prevent write combining and require additional disk seeks.

Shadow Paging avoids in-place updates entirely. Modified blocks are written to new locations; the file system tracks the current version through root pointers. While not strictly journaling, shadow paging achieves similar crash consistency. Recovery simply discards uncommitted versions. This approach eliminates the journal but requires extensive metadata to track block versions and free space.

Soft Updates maintain dependency information between operations rather than journaling. The system tracks which writes must complete before others and schedules disk I/O to respect these dependencies. This provides crash consistency without journaling overhead. However, recovery complexity increases significantly, and the approach handles only metadata consistency, not data protection.

Compound Transactions group multiple file system operations into single transactions. For example, renaming files across directories becomes one atomic operation rather than separate unlink and link operations. This reduces journal traffic and improves consistency by making complex operations atomic. The file system must carefully define transaction boundaries to avoid deadlocks while maximizing grouping opportunities.

Ruby Implementation

Ruby programs interact with journaling file systems through standard file I/O operations while monitoring and configuring journaling behavior through system interfaces.

File system operations benefit from journaling automatically. When Ruby writes to a file, the underlying file system ensures durability through its journal:

# Standard file write - journaling happens transparently
File.write('/var/data/config.json', JSON.generate(config))

# The file system ensures:
# 1. Metadata updates are journaled
# 2. Data consistency based on mount options
# 3. Atomic visibility of the complete write

Ruby can check file system type and mount options to understand journaling behavior:

def filesystem_info(path)
  stat = File.stat(path)
  
  # Get mount information
  mounts = File.readlines('/proc/mounts')
  mount_info = mounts.find do |line|
    parts = line.split
    path.start_with?(parts[1])
  end
  
  return nil unless mount_info
  
  fs_type, mount_point, options = mount_info.split[2..4]
  
  {
    filesystem: fs_type,
    mount_point: mount_point,
    options: options.split(','),
    device: mount_info.split[0]
  }
end

info = filesystem_info('/var/data')
# => {:filesystem=>"ext4", :mount_point=>"/var", 
#     :options=>["rw", "relatime", "data=ordered"], 
#     :device=>"/dev/sda1"}

The data= mount option indicates the journaling mode. Ruby applications can verify their data safety requirements match the file system configuration.

Force data synchronization to ensure journal commits:

class PersistentStore
  def initialize(path)
    @path = path
    @file = File.open(path, File::RDWR | File::CREAT)
  end
  
  def write_atomic(data)
    # Write to temporary file
    temp_path = "#{@path}.tmp"
    File.open(temp_path, File::WRONLY | File::CREAT | File::TRUNC) do |f|
      f.write(data)
      # Ensure data and metadata reach disk
      f.fsync
    end
    
    # Atomic rename - single journaled operation
    File.rename(temp_path, @path)
    
    # Sync directory to persist rename
    dir_path = File.dirname(@path)
    Dir.open(dir_path) { |d| d.sync }
  end
end

The fsync method forces the file system to commit journal entries and flush data to disk. This guarantees durability at the cost of performance.

Monitor file system journal health through Ruby:

require 'open3'

def check_journal_status(device)
  stdout, stderr, status = Open3.capture3("dumpe2fs -h #{device}")
  
  return { error: stderr } unless status.success?
  
  journal_info = {}
  stdout.each_line do |line|
    case line
    when /Journal inode:\s+(\d+)/
      journal_info[:inode] = $1.to_i
    when /Journal size:\s+(\d+)M/
      journal_info[:size_mb] = $1.to_i
    when /Journal length:\s+(\d+)/
      journal_info[:length_blocks] = $1.to_i
    when /Journal sequence:\s+(0x[0-9a-f]+)/i
      journal_info[:sequence] = $1
    end
  end
  
  journal_info
end

status = check_journal_status('/dev/sda1')
# => {:inode=>8, :size_mb=>128, :length_blocks=>32768, 
#     :sequence=>"0x00004f2a"}

This information helps diagnose journal space exhaustion or corruption issues.

Implement transaction-like behavior using file system guarantees:

class TransactionalUpdate
  def initialize(base_path)
    @base_path = base_path
    @transaction_id = Time.now.to_i
  end
  
  def execute
    temp_dir = "#{@base_path}/.txn-#{@transaction_id}"
    Dir.mkdir(temp_dir)
    
    begin
      # Perform all updates in temporary location
      yield temp_dir
      
      # Force all changes to disk
      sync_directory_recursive(temp_dir)
      
      # Atomic swap - single journal transaction
      backup_dir = "#{@base_path}/.backup-#{@transaction_id}"
      File.rename(@base_path, backup_dir) if File.exist?(@base_path)
      File.rename(temp_dir, @base_path)
      
      # Sync parent to persist rename
      Dir.open(File.dirname(@base_path)) { |d| d.sync }
      
      # Cleanup old backup
      FileUtils.rm_rf(backup_dir) if backup_dir
      
    rescue => e
      # Cleanup failed transaction
      FileUtils.rm_rf(temp_dir) if File.exist?(temp_dir)
      raise
    end
  end
  
  private
  
  def sync_directory_recursive(path)
    Dir.glob("#{path}/**/*", File::FNM_DOTMATCH).each do |entry|
      next if ['.', '..'].include?(File.basename(entry))
      
      if File.file?(entry)
        File.open(entry, 'r') { |f| f.fsync }
      elsif File.directory?(entry)
        Dir.open(entry) { |d| d.sync }
      end
    end
  end
end

# Usage
updater = TransactionalUpdate.new('/var/data/config')
updater.execute do |temp_path|
  File.write("#{temp_path}/settings.json", new_settings)
  File.write("#{temp_path}/metadata.json", metadata)
end

This pattern uses file system atomicity guarantees for multi-file updates.

Design Considerations

Selecting journaling modes and configurations requires evaluating data safety requirements against performance constraints.

Data Safety vs Performance Trade-offs define the primary design decision. Journal mode provides maximum safety—no data loss except for uncommitted transactions. Applications handling financial records, medical data, or configuration files that must remain consistent should use journal mode. The performance penalty ranges from 30-50% for write-heavy workloads due to double-writing all data.

Ordered mode offers a middle ground suitable for most applications. File data is protected through write ordering, preventing exposure of uninitialized disk blocks. The performance impact is minimal (5-15%) because only metadata is journaled. Applications with moderate durability requirements—web servers, application logs, user documents—benefit from ordered mode's balance.

Writeback mode maximizes performance for applications that can reconstruct data after crashes or tolerate data loss windows. Scratch space, temporary files, and cache storage work well with writeback mode. The file system maintains structural consistency but makes no data protection guarantees.

Journal Sizing affects both performance and reliability. Small journals (32-64MB) minimize space overhead but fill quickly during write bursts. When the journal fills, the system must checkpoint—flush all pending changes to their final locations—before accepting new transactions. Frequent checkpoints create latency spikes.

Large journals (256MB-1GB) accommodate write bursts without checkpointing but waste space on systems with limited write activity. They also increase recovery time because the system must scan more journal data after crashes.

Size the journal based on write patterns:

  • Steady, low-volume writes: 64-128MB
  • Bursty write patterns: 256-512MB
  • High-throughput databases: 512MB-1GB
  • Read-mostly workloads: 32-64MB (minimal)

Internal vs External Journal Placement impacts performance characteristics. Internal journals simplify configuration—no separate partition management—but compete with data I/O for disk bandwidth. Every journal write creates seek overhead that delays data writes. Systems with spinning disks suffer significant performance degradation from internal journals.

External journals on separate devices eliminate this contention. Journal writes proceed in parallel with data writes, improving throughput by 40-60% for write-heavy workloads. SSDs make this advantage less pronounced but still measurable. The operational complexity of managing separate journal devices and ensuring they remain synchronized limits external journal adoption.

Barrier Operations force journal commits to disk, creating ordering guarantees at performance cost. Modern file systems use barriers to ensure critical operations complete before subsequent operations begin. Without barriers, disk write caches might reorder operations, violating journaling guarantees.

Applications using fsync trigger barriers explicitly. The file system inserts barriers automatically for critical metadata updates. Some systems support relaxed barrier semantics for performance—accepting small corruption risks in exchange for eliminating forced flushes. Use relaxed barriers only for non-critical data or when battery-backed write caches provide ordering guarantees.

Commit Intervals determine how long transactions remain in memory before forced journal commits. Longer intervals (default 5 seconds) batch more operations together, reducing commit overhead but increasing potential data loss. Shorter intervals (1-2 seconds) minimize data loss windows but increase commit overhead by 20-30%.

Adjust commit intervals based on:

  • Data criticality: 1-2 seconds for important data
  • Write volume: 5-10 seconds for high throughput
  • Power reliability: 15-30 seconds with UPS protection

Performance Considerations

Journaling introduces measurable overhead through additional writes, synchronization points, and recovery operations. Understanding these costs enables optimization for specific workloads.

Write Amplification occurs because journal mode writes data twice—once to journal, once to final location. Sequential write throughput drops by approximately 50% because the disk head moves between journal and data locations. Random writes suffer less (20-30% reduction) because seeking dominates latency already. Ordered mode reduces write amplification to 10-20% by journaling only metadata.

Measure write amplification in Ruby:

class IOMonitor
  def self.measure_workload
    initial = io_stats
    
    start_time = Time.now
    yield
    duration = Time.now - start_time
    
    final = io_stats
    
    {
      duration: duration,
      writes: final[:writes] - initial[:writes],
      bytes_written: final[:bytes_written] - initial[:bytes_written],
      throughput_mb_s: (final[:bytes_written] - initial[:bytes_written]) / duration / 1024 / 1024
    }
  end
  
  def self.io_stats
    stats = File.read('/proc/diskstats')
    line = stats.lines.find { |l| l.include?('sda') }
    parts = line.split
    
    {
      writes: parts[7].to_i,
      bytes_written: parts[9].to_i * 512  # 512-byte sectors
    }
  end
end

# Compare journaling modes
stats = IOMonitor.measure_workload do
  1000.times { |i| File.write("/tmp/test#{i}", "x" * 4096) }
end
# => {:duration=>1.23, :writes=>2000, :bytes_written=>8388608, 
#     :throughput_mb_s=>6.5}

Journal Commit Latency creates synchronization points that stall operations. When the journal fills or commit intervals expire, operations block until the commit completes. This latency spike ranges from 5-50ms depending on journal size and disk speed. Applications experience periodic slowdowns rather than consistent throughput.

Minimize commit latency by:

  • Batching operations between fsync calls
  • Sizing journals to avoid mid-operation fills
  • Using SSDs to reduce commit write time
  • Adjusting commit intervals to match workload patterns

Recovery Time depends on journal size and uncommitted transaction count. Small journals (64MB) replay in 1-2 seconds. Large journals (1GB) require 10-30 seconds. This delay occurs during every mount after unclean shutdown. Mission-critical systems must account for this recovery time in failover procedures.

Metadata Operation Overhead increases because journaling tracks all metadata changes. Operations that modify multiple metadata structures—like creating files—become more expensive. File creation overhead increases by 15-30% with ordered journaling compared to non-journaled file systems.

Optimize metadata-heavy workloads:

# Bad: Creates journal entries for each file
def create_files_individually(count)
  count.times do |i|
    File.write("file#{i}", "content")
  end
end

# Better: Batch creates reduce journal transactions
def create_files_batched(count, batch_size = 100)
  (count / batch_size).times do |batch|
    threads = []
    batch_size.times do |i|
      threads << Thread.new do
        idx = batch * batch_size + i
        File.write("file#{idx}", "content")
      end
    end
    threads.each(&:join)
    
    # Single sync after batch
    Dir.open('.') { |d| d.sync }
  end
end

Read Performance remains largely unaffected by journaling. Read operations bypass the journal entirely, accessing data directly from final locations. Journal overhead appears only during recovery when uncommitted transactions must be processed before reads can proceed.

Space Efficiency suffers slightly because journals consume disk space. A 256MB journal represents fixed overhead regardless of data size. For large file systems (1TB+), this overhead is negligible (0.025%). For small file systems (10GB), it becomes significant (2.5%).

CPU Overhead from journaling is minimal (1-3%) in most cases. The file system performs additional bookkeeping to track transactions, but this work is trivial compared to I/O latency. Journal checksum calculation adds slight CPU load but improves reliability by detecting corrupted transactions.

Tools & Ecosystem

Multiple tools enable journaling configuration, monitoring, and maintenance across different file systems.

tune2fs configures ext3/ext4 journaling parameters:

require 'open3'

class Ext4JournalManager
  def initialize(device)
    @device = device
  end
  
  def configure_journal(size_mb:, mode: 'ordered')
    # Set journal size (requires unmounted filesystem)
    stdout, stderr, status = Open3.capture3(
      "tune2fs -J size=#{size_mb} #{@device}"
    )
    
    raise "Journal resize failed: #{stderr}" unless status.success?
    
    # Set journaling mode
    mount_option = case mode
                   when 'journal' then 'data=journal'
                   when 'ordered' then 'data=ordered'
                   when 'writeback' then 'data=writeback'
                   else raise "Invalid mode: #{mode}"
                   end
    
    { success: true, mode: mount_option }
  end
  
  def journal_info
    stdout, = Open3.capture3("dumpe2fs -h #{@device} 2>/dev/null")
    
    info = {}
    stdout.each_line do |line|
      info[:size_mb] = $1.to_i if line =~ /Journal size:\s+(\d+)M/
      info[:inode] = $1.to_i if line =~ /Journal inode:\s+(\d+)/
      info[:backup] = $1.to_i if line =~ /Journal backup:\s+(\S+)/
    end
    
    info
  end
end

manager = Ext4JournalManager.new('/dev/sda1')
manager.configure_journal(size_mb: 256, mode: 'ordered')

debugfs provides low-level journal inspection capabilities for ext3/ext4:

def inspect_journal_blocks(device, block_count = 10)
  cmd = "debugfs -R 'logdump -c #{block_count}' #{device} 2>&1"
  output, = Open3.capture3(cmd)
  
  transactions = []
  current_transaction = nil
  
  output.each_line do |line|
    if line =~ /Journal starts at block (\d+)/
      current_transaction = { start_block: $1.to_i, entries: [] }
    elsif line =~ /FS block (\d+) logged at sequence (\d+)/
      current_transaction[:entries] << {
        fs_block: $1.to_i,
        sequence: $2.to_i
      } if current_transaction
    elsif line =~ /Journal transaction (\d+)/
      transactions << current_transaction if current_transaction
      current_transaction = { id: $1.to_i, entries: [] }
    end
  end
  
  transactions
end

xfs_admin manages XFS journaling configuration:

class XFSJournalManager
  def self.check_log_size(device)
    stdout, = Open3.capture3("xfs_admin -l #{device}")
    
    if stdout =~ /log section size = (\d+) blocks/
      blocks = $1.to_i
      { blocks: blocks, size_mb: (blocks * 4096) / 1024 / 1024 }
    else
      { error: 'Could not determine log size' }
    end
  end
  
  def self.external_log_info(device)
    stdout, = Open3.capture3("xfs_db -r -c 'sb' -c 'p' #{device}")
    
    log_device = nil
    stdout.each_line do |line|
      log_device = $1 if line =~ /logdev = (.+)/
    end
    
    { external_log: log_device }
  end
end

fsck performs journal replay during recovery:

class FilesystemRecovery
  def self.force_journal_replay(device, filesystem_type)
    case filesystem_type
    when 'ext4'
      # Journal replay happens automatically during e2fsck
      cmd = "e2fsck -y -f #{device}"
    when 'xfs'
      # XFS replays journal during mount
      cmd = "xfs_repair -L #{device}"  # -L clears log
    else
      return { error: "Unsupported filesystem: #{filesystem_type}" }
    end
    
    stdout, stderr, status = Open3.capture3(cmd)
    
    {
      success: status.success?,
      output: stdout,
      errors: stderr
    }
  end
  
  def self.check_journal_clean(device)
    stdout, = Open3.capture3("dumpe2fs -h #{device} 2>/dev/null")
    
    # Look for unclean journal indicator
    needs_recovery = stdout.include?('needs_recovery')
    
    { clean: !needs_recovery, needs_fsck: needs_recovery }
  end
end

jbd2 (journaling block device) kernel module provides the ext4 journaling implementation. Ruby can monitor jbd2 statistics:

def jbd2_statistics(device_name)
  stats_path = "/proc/fs/jbd2/#{device_name}/info"
  
  return { error: 'Statistics not available' } unless File.exist?(stats_path)
  
  stats = {}
  File.readlines(stats_path).each do |line|
    case line
    when /(\d+) transactions \((\d+) requested\)/
      stats[:transactions] = $1.to_i
      stats[:requested] = $2.to_i
    when /(\d+) blocks logged/
      stats[:blocks_logged] = $1.to_i
    when /(\d+)ms average transaction time/
      stats[:avg_transaction_ms] = $1.to_i
    end
  end
  
  stats
end

LVM snapshots interact with journaling for consistent backups:

def create_consistent_snapshot(logical_volume)
  # Freeze filesystem to ensure journal consistency
  mount_point = get_mount_point(logical_volume)
  system("fsfreeze -f #{mount_point}")
  
  begin
    # Create snapshot with journal in consistent state
    snapshot_name = "#{logical_volume}-snap-#{Time.now.to_i}"
    cmd = "lvcreate -L 10G -s -n #{snapshot_name} /dev/vg0/#{logical_volume}"
    
    stdout, stderr, status = Open3.capture3(cmd)
    raise "Snapshot failed: #{stderr}" unless status.success?
    
    { snapshot: snapshot_name, path: "/dev/vg0/#{snapshot_name}" }
  ensure
    # Unfreeze filesystem
    system("fsfreeze -u #{mount_point}")
  end
end

def get_mount_point(logical_volume)
  mounts = File.readlines('/proc/mounts')
  mount_line = mounts.find { |l| l.include?(logical_volume) }
  mount_line.split[1] if mount_line
end

Reference

Journaling Modes Comparison

Mode Journals Metadata Journals Data Safety Performance Use Case
Journal Yes Yes Highest Lowest Critical data requiring full protection
Ordered Yes No High Medium General purpose file systems
Writeback Yes No Medium Highest High-performance temporary storage

Common Mount Options

Option Description Impact
data=journal Enable full data journaling Maximum safety, 50% write penalty
data=ordered Ordered mode (default) Balanced safety and performance
data=writeback Writeback mode Maximum performance, reduced safety
barrier=0 Disable write barriers Faster commits, requires battery backup
commit=n Set commit interval to n seconds Trade latency for throughput
journal_dev=device Use external journal device Better performance, added complexity
journal_checksum Enable journal checksums Detect corruption, slight overhead

Journal Size Guidelines

System Type Write Pattern Recommended Journal Size
Desktop Light mixed workload 64-128 MB
Web server Moderate writes 128-256 MB
Database server Heavy random writes 256-512 MB
File server Large sequential writes 512 MB - 1 GB
Embedded Minimal writes 32-64 MB

Ruby File Sync Methods

Method Scope Guarantees Use Case
File#fsync File data and metadata Both flushed to disk Ensure single file durability
File#fdatasync File data only Data flushed, metadata async High-performance data writes
Dir#sync Directory entries Directory metadata flushed Persist file creation/deletion
IO.sync= All writes immediate No buffering Real-time log writing

Recovery Operations

Filesystem Command Purpose Typical Duration
ext4 e2fsck -y -f device Force journal replay and check 1-30 seconds
XFS xfs_repair device Repair with journal replay 5-60 seconds
Btrfs btrfs check --repair device Repair with transaction log 10-120 seconds
NTFS ntfsfix device Repair NTFS journal 5-30 seconds

Performance Characteristics

Operation Type Overhead vs Non-journaled Primary Cause
Sequential writes (journal mode) +50-70% Double writing data
Sequential writes (ordered) +10-20% Metadata journaling
Random writes (journal mode) +30-40% Journal seeking
Random writes (ordered) +5-15% Metadata overhead
File creation +15-30% Multiple metadata updates
File deletion +10-20% Deallocation journaling
Read operations 0-2% No overhead except recovery
Recovery time N/A 1-60 seconds depending on journal size

Diagnostic Commands

Command Purpose Example Output
dumpe2fs -h device Display ext4 journal info Journal size: 128M
debugfs -R logdump device Dump journal contents Transaction 4892 logged
xfs_db -r -c sb -c p device Display XFS superblock logstart = 32768
tune2fs -l device List filesystem features has_journal
cat /proc/fs/jbd2/device/info Show jbd2 statistics 4892 transactions

Error Messages and Solutions

Error Cause Solution
Journal transaction N failed Corrupted journal entry Run fsck, consider journal resize
JBD2: Detected IO errors Disk hardware failure Check disk health, replace if needed
Journal is not regular file Journal inode corrupted Recreate journal with tune2fs -j
Journal checksum error Data corruption in journal Clear journal with tune2fs -f -E clear_journal
Journal size too small Insufficient journal space Resize with tune2fs -J size=256