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");