Write-Ahead Logging (WAL) is a technique that ensures data durability and enables crash recovery. All changes are first written to a log file before being applied to the main database, guaranteeing that committed transactions survive system failures.


1. Overview

What is Write-Ahead Logging?

WAL follows a simple principle: write to the log first, then to the database. If a crash occurs, the database can recover by replaying the log.

[[Svg Src="./witdatabase-wal-write-operation.svg" Alt="witdatabase-wal-write-operation"]]

Why WAL?

Benefit Description
Durability Committed transactions survive crashes
Fast commits Sequential WAL writes are faster than random data file writes
Crash recovery Replay log to restore consistent state
Concurrent reads Readers can access data file while writers use WAL

Journal Modes

WitDatabase supports two journaling modes:

Mode Key Description Best For
WAL wal Write changes to log, replay forward Default, concurrent reads
Rollback rollback Save original values, undo on crash Simple, less disk space

How WAL Differs from Rollback Journal

[[Svg Src="./witdatabase-wal-vs-rollback.svg" Alt="witdatabase-wal-vs-rollback"]]


2. WAL Operation

WAL File Structure

The WAL file contains a sequence of log entries:

[[Svg Src="./witdatabase-wal-file-structure.svg" Alt="witdatabase-wal-file-structure"]]

Entry Types

Type Code Description
Put 1 Insert or update a key-value pair
Delete 2 Delete a key
BeginTransaction 3 Start of transaction
CommitTransaction 4 Transaction committed (durable point)
RollbackTransaction 5 Transaction rolled back

Write Path

When you write data with WAL enabled:

[[Svg Src="./witdatabase-put-commit-flow.svg" Alt="witdatabase-put-commit-flow"]]

Read Path

Reads don't involve the WAL — they read from the cache or data file:

[[Svg Src="./witdatabase-get-flow.svg" Alt="witdatabase-get-flow"]]

Concurrent Access

WAL enables concurrent readers and writers:

[[Svg Src="./witdatabase-wal-concurrency.svg" Alt="witdatabase-wal-concurrency"]]

WAL File Location

The WAL file is created next to the data file:

Database: /path/to/app.witdb
WAL file: /path/to/app.witdb-wal

With encryption enabled:
Database: /path/to/secure.witdb (encrypted)
WAL file: /path/to/secure.witdb-wal (also encrypted)

3. Checkpoints

What is a Checkpoint?

A checkpoint transfers changes from the WAL to the main data file. After a checkpoint, the WAL can be truncated because those changes are now in the data file.

[[Svg Src="./witdatabase-wal-checkpoint.svg" Alt="witdatabase-wal-checkpoint"]]

Automatic Checkpoints

WitDatabase performs automatic checkpoints based on WAL size:

Threshold Default Description
Size threshold 1 MB Checkpoint when WAL exceeds this size
// Automatic checkpoint is enabled by default
// Triggers when WAL grows beyond threshold

Manual Checkpoints

You can trigger a checkpoint explicitly:

// Core API
db.Checkpoint();

// SQL Engine
engine.Execute("PRAGMA wal_checkpoint");

// Forces all WAL changes to data file

Checkpoint Process

[[Svg Src="./witdatabase-checkpoint-steps.svg" Alt="witdatabase-checkpoint-steps"]]

When to Checkpoint

Automatic (recommended):

  • Let WitDatabase manage checkpoints automatically
  • Occurs when WAL grows beyond threshold
  • Balances performance and disk space

Manual (specific scenarios):

// Before backup
db.Checkpoint();  // Ensure all data is in main file
File.Copy("app.witdb", "backup.witdb");

// Before shutdown
db.Checkpoint();  // Minimize recovery time on next open
db.Dispose();

// Periodically in long-running applications
var timer = new PeriodicTimer(TimeSpan.FromMinutes(30));
while (await timer.WaitForNextTickAsync())
{
    db.Checkpoint();
}

Checkpoint Impact on Performance

Scenario Impact
During checkpoint Brief pause for new writes
Small WAL Quick checkpoint, minimal impact
Large WAL Longer checkpoint, more I/O
Frequent checkpoints Less recovery time, more I/O
Rare checkpoints Larger WAL, longer recovery

Best practice: Let automatic checkpoints handle most cases. Use manual checkpoints only for specific needs like pre-backup.


4. Crash Recovery

How Recovery Works

When WitDatabase opens after a crash, it automatically recovers by replaying the WAL:

[[Svg Src="./witdatabase-crash-recovery.svg" Alt="witdatabase-crash-recovery"]]

Recovery Scenarios

Scenario 1: Crash after COMMIT written to WAL

[[Svg Src="./witdatabase-wal-recovery-example.svg" Alt="witdatabase-wal-recovery-example"]]

Scenario 2: Crash before COMMIT written

[[Svg Src="./witdatabase-wal-recovery-rollback.svg" Alt="witdatabase-wal-recovery-rollback"]]

Scenario 3: Crash during checkpoint

[[Svg Src="./witdatabase-wal-recovery-success.svg" Alt="witdatabase-wal-recovery-success"]]

Integrity Verification

WAL entries include CRC32 checksums:

[[Svg Src="./witdatabase-wal-entry-format.svg" Alt="witdatabase-wal-entry-format"]]

During replay:

  • Each entry's CRC is verified
  • Corrupted entries are skipped
  • Recovery stops at first corruption (partial entry from crash)

Recovery with Encryption

If the database is encrypted, the WAL is also encrypted:

// Encrypted database
var db = WitDatabase.Open("secure.witdb", "password");

// WAL is automatically encrypted with same key
// Recovery works the same way — decrypt then replay

Recovery requires the correct password — without it, WAL entries cannot be decrypted.

Monitoring Recovery

// Recovery happens automatically on open
var db = WitDatabase.Open("app.witdb");

// Check if recovery occurred (if logging is enabled)
// Recovery count is logged to diagnostics

Recovery Time

Recovery time depends on WAL size:

WAL Size Approximate Recovery Time
< 1 MB < 100 ms
1-10 MB 100 ms - 1 s
10-100 MB 1-10 s
> 100 MB 10+ s

To minimize recovery time:

  • Use automatic checkpoints (keeps WAL small)
  • Checkpoint before planned shutdowns
  • Set appropriate checkpoint threshold

5. Rollback Journal

How Rollback Journal Works

The rollback journal is an alternative to WAL. Instead of writing new values to a log, it saves original values before modifying them.

[[Svg Src="./witdatabase-rollback-journal-operation.svg" Alt="witdatabase-rollback-journal-operation"]]

Configuration

// Connection string
"Data Source=app.witdb;Journal=rollback"

// Builder pattern
var db = new WitDatabaseBuilder()
    .WithFilePath("app.witdb")
    .WithBTree()
    .WithRollbackJournal()
    .Build();

Journal File Location

Each active transaction has its own journal file:

Database: /path/to/app.witdb
Journal:  /path/to/app.witdb_12345.rollback  (12345 = transaction ID)

Multiple concurrent transactions:
  app.witdb_12345.rollback  (Tx 12345)
  app.witdb_12346.rollback  (Tx 12346)

Recovery with Rollback Journal

On startup, WitDatabase checks for orphaned journal files:

[[Svg Src="./witdatabase-rollback-recovery.svg" Alt="witdatabase-rollback-recovery"]]

WAL vs Rollback Journal

Aspect WAL Rollback Journal
Write pattern Sequential (append) Random (save originals)
Commit speed Fast (sync WAL only) Medium (sync data file)
Concurrent reads ✅ Yes ⚠️ Limited
Disk space WAL file grows Journal per transaction
Recovery direction Forward (replay) Backward (undo)
Complexity Higher Lower
Best for Read-heavy, concurrent Simple, single-user

When to Use Rollback Journal

Use rollback journal when:

  • Single-user applications
  • Simple deployment requirements
  • Disk space is limited
  • WAL complexity is not needed

Use WAL (default) when:

  • Multiple readers expected
  • Write performance is important
  • Concurrent access patterns
  • Standard database workloads
// Rollback journal for simple desktop app
"Data Source=app.witdb;Journal=rollback"

// WAL for web server (default)
"Data Source=app.witdb;Journal=wal"
// or simply: "Data Source=app.witdb"

6. Configuration

Connection String Properties

Property Type Default Values Description
Journal enum wal wal, rollback Journal mode
Sync Writes bool true true, false Sync on commit

Sync Mode

The Sync Writes option controls whether commits wait for disk sync:

// Full durability (default) — commit waits for fsync
"Data Source=app.witdb;Sync Writes=true"

// Faster commits — risk of data loss on OS crash
"Data Source=app.witdb;Sync Writes=false"
Mode Durability Performance Use Case
Sync Writes=true Full Normal Production (default)
Sync Writes=false Reduced Faster Development, caching

Warning: Sync Writes=false can lose committed transactions if the OS crashes or power fails. The transaction is durable once the OS writes to disk, but that timing is unpredictable.

Builder Pattern Configuration

// WAL with defaults
var db = new WitDatabaseBuilder()
    .WithFilePath("app.witdb")
    .WithBTree()
    .WithWal()
    .Build();

// Rollback journal
var db = new WitDatabaseBuilder()
    .WithFilePath("app.witdb")
    .WithBTree()
    .WithRollbackJournal()
    .Build();

// WAL with encryption
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithBTree()
    .WithEncryption("password")
    .WithWal()  // WAL is also encrypted
    .Build();

WAL File Management

// Get WAL size
var walPath = "app.witdb-wal";
if (File.Exists(walPath))
{
    var size = new FileInfo(walPath).Length;
    Console.WriteLine(
Loading...
quot;WAL size: {size / 1024} KB"); } // Manual checkpoint to control WAL size db.Checkpoint(); // WAL is deleted on clean shutdown db.Dispose(); // WAL truncated, may be deleted

7. Quick Reference

Journal Modes

Mode Key Recovery Concurrent Reads Write Speed
WAL wal Replay forward ✅ Yes Fast
Rollback rollback Undo backward ⚠️ Limited Medium

Connection String Examples

// Default (WAL, sync enabled)
"Data Source=app.witdb"

// Explicit WAL
"Data Source=app.witdb;Journal=wal"

// Rollback journal
"Data Source=app.witdb;Journal=rollback"

// Fast commits (reduced durability)
"Data Source=app.witdb;Sync Writes=false"

// Full configuration
"Data Source=app.witdb;Journal=wal;Sync Writes=true;Transactions=true"

Key Operations

Operation Core API Description
Checkpoint db.Checkpoint() Flush WAL to data file
Flush db.Flush() Flush cache to disk
Sync db.Sync() Sync WAL to disk

File Locations

File Extension Description
Data file .witdb Main database file
WAL .witdb-wal Write-ahead log
Rollback .witdb_*.rollback Rollback journal (per transaction)

Recovery Behavior

Scenario WAL Recovery Rollback Recovery
Committed transaction ✅ Replayed ✅ Already in data file
Uncommitted transaction ❌ Discarded ✅ Undone from journal
Partial write ✅ CRC check fails, skipped ✅ Original restored

Best Practices

General:

  • Use WAL (default) for most applications
  • Let automatic checkpoints manage WAL size
  • Checkpoint before backups

High availability:

  • Keep Sync Writes=true (default)
  • Monitor WAL file size
  • Plan for recovery time based on WAL size

Development/testing:

  • Sync Writes=false acceptable for faster tests
  • Use :memory: for unit tests (no journaling needed)

Backups:

// Safe backup procedure
db.Checkpoint();  // Ensure WAL is flushed
db.Flush();       // Ensure cache is flushed
// Now copy app.witdb (WAL is empty)
File.Copy("app.witdb", "backup.witdb");