How Turso's Write-Ahead Log (WAL) Works: Architecture and Implementation
Turso's Write-Ahead Log (WAL) is implemented in core/storage/wal.rs as a SQLite-compatible durability layer that uses atomic locks, vectored I/O, and coordinated checkpointing to ensure crash recovery while allowing concurrent reads.
Turso extends SQLite's WAL mode with a custom Rust implementation that lives in the tursodatabase/turso repository. The WAL persists in a separate "-wal" file alongside the database, managed by the WalFile struct which implements the Wal trait. This architecture provides atomic durability, crash recovery, and high-performance concurrent access through a sophisticated coordination layer.
Core Data Structures and Coordination
The WAL implementation centers on several key structures that manage state, coordination, and I/O.
WalFileShared (core/storage/wal.rs:2712) holds the in-process state including the maximum frame number, backfill counters, checksums, and lock metadata. This structure is shared across all connections within a single process.
WalCoordination (core/storage/wal.rs:664) is a trait that abstracts the coordination backend, providing atomic operations for snapshot publishing, lock acquisition, and frame caching. The default implementation, InProcessWalCoordination (core/storage/wal.rs:889), uses the TursoRwLock primitive—a 64-bit atomic that packs a 32-bit value, reader count, and writer bit into one cache line for fast synchronization (core/storage/wal.rs:742-774).
The public API is defined by the Wal trait (core/storage/wal.rs:997), which exposes methods like begin_write_tx, commit_prepared_frames, checkpoint, and truncate_wal.
Starting a Write Transaction
Write transactions begin with begin_write_tx, which accepts WalAutoActions bitflags to enable automatic checkpointing or log restarts.
wal.begin_write_tx(WalAutoActions::all_enabled())?;
The method first attempts to acquire the writer lock via the coordination backend. If the log has been fully back-filled (all frames checkpointed to the database file), the WAL header is restarted before writing. This restart is performed by WalCoordination::try_restart_log_for_write, which upgrades the read-mark-0 lock, rewrites the header, and clears the initialized flag (core/storage/wal.rs:1442-1455).
Preparing and Appending Frames
During an active transaction, the engine serializes modified pages into WAL frames through a multi-phase process:
-
prepare_framesserializes each page into a frame, computes the rolling checksum usingchecksum_wal, and records metadata (page reference, frame ID, cumulative checksum) in aPreparedFramesstruct (core/storage/wal.rs:334-354). -
append_frames_vectoredperforms the actual I/O using vectoredpwritevsystem calls to write all buffers efficiently, followed by an optionalfsyncviaprepare_wal_finish. -
commit_prepared_framesupdates the in-memory metadata (max frame, checksum, transaction count) and publishes the new commit viaWalCoordination::publish_commit(core/storage/wal.rs:892-904). -
finalize_committed_pagesclears dirty-page flags on the database cache and optionally syncs the DB file.
Checkpointing and Back-filling
Checkpointing copies committed WAL frames into the main database file and advances the backfill counter. The checkpoint method (core/storage/wal.rs:1103-1130) accepts a CheckpointMode:
CheckpointMode::Fullcopies frames to the DB file but retains the WAL.CheckpointMode::Truncatecopies frames and then truncates the WAL to zero length.
The checkpoint process acquires the checkpoint lock (acquire_checkpoint_guard) and read-mark-0 lock for exclusive access. It determines the safe frame limit via determine_max_safe_checkpoint_frame (based on active read-marks), reads frames in batches using read_frames_batch, writes them to the database file, and advances the shared nbackfills counter via publish_backfill.
When truncating, truncate_wal zeroes the file and reinitializes the header via prepare_wal_header, which generates a new random salt and checksum (core/storage/wal.rs:141-148).
Concurrency Control and Locking
Turso's WAL supports concurrent readers through a multi-version concurrency control scheme:
-
Read transactions acquire a read-mark (one of five slots) that records the frame number visible to that reader. The lock-free value is stored inside
TursoRwLock. The coordination layer providestry_begin_read_txto select the best available read-mark. -
Write transactions acquire the exclusive writer lock via
try_begin_write_tx. The writer holds read-mark-0 to ensure exclusive access during header restarts or checkpoints. -
Checkpoint operations obtain an additional checkpoint lock via
try_checkpoint_lockto prevent concurrent checkpoints.
All lock acquisition logic resides in InProcessWalCoordination (e.g., try_write_lock, try_read_mark_shared, try_checkpoint_lock).
Practical Code Examples
Example 1: Simple Write Transaction
use turso::storage::wal::{Wal, WalAutoActions, CheckpointMode, FileSyncType};
fn write_page(wal: &impl Wal, page_no: u64, page_data: &[u8], db_size: u64) -> anyhow::Result<()> {
// 1) Start transaction with auto-actions enabled
wal.begin_write_tx(WalAutoActions::all_enabled())?;
// 2) Prepare frames with checksum calculation
let page_size = PageSize::from_bytes(page_data.len() as u32);
let prepared = wal.prepare_frames(
&[PageRef::new(page_no)],
page_size,
Some(db_size as u32),
None,
)?;
// 3) Append frames via vectored I/O
wal.append_frames_vectored(vec![PageRef::new(page_no)], page_size)?;
// 4) Commit to make visible to readers
wal.commit_prepared_frames(&[prepared]);
// 5) Sync WAL to disk
wal.sync(FileSyncType::Normal)?;
// 6) End transaction
wal.end_write_tx();
// 7) Auto-checkpoint if needed
if wal.should_checkpoint() {
wal.checkpoint(&pager, CheckpointMode::Full)?;
}
Ok(())
}
Example 2: Manual Checkpoint with Truncation
fn truncate_wal(wal: &impl Wal, pager: &Pager) -> anyhow::Result<()> {
// Run TRUNCATE checkpoint - copies all frames then zeros the WAL
let result = wal.checkpoint(
pager,
CheckpointMode::Truncate { upper_bound_inclusive: None }
)?;
// Ensure truncate I/O completes
wal.truncate_wal(&mut result.clone(), FileSyncType::Normal)?;
Ok(())
}
Summary
- Turso's WAL is implemented in
core/storage/wal.rsas theWalFilestruct implementing theWaltrait, providing SQLite-compatible durability. - Write transactions use
begin_write_tx,prepare_frames,append_frames_vectored, andcommit_prepared_framesto ensure atomic, durable writes with rolling checksums. - Checkpointing copies frames to the database file via
checkpointwith modesFullorTruncate, advancing thenbackfillscounter and potentially zeroing the WAL file. - Concurrency control uses
TursoRwLockand read-marks to allow concurrent readers while maintaining exclusive writer access and checkpoint coordination. - Automatic actions (
WalAutoActions) can trigger checkpoints or log restarts automatically after commits or when the log is fully back-filled.
Frequently Asked Questions
What is the difference between WalFileShared and WalCoordination?
WalFileShared (core/storage/wal.rs:2712) is a concrete struct holding the actual in-process state like frame counters and checksums, while WalCoordination (core/storage/wal.rs:664) is a trait abstracting how this state is synchronized across connections. The default InProcessWalCoordination implements this trait for single-process scenarios using atomic operations on WalFileShared, but the trait allows for alternative backends that could support cross-process or distributed coordination.
How does Turso's WAL handle crash recovery?
Crash recovery relies on the WAL file containing a complete history of committed transactions since the last checkpoint. When reopening a database, Turso reads the WAL header to determine the maximum valid frame and uses the checksums stored in each frame header to verify integrity. The begin_write_tx method checks if the log needs restarting (when wal_max_frame == wal_total_backfilled), and the checkpoint mechanism ensures the database file is only updated after WAL frames are fully synced to disk.
When does the WAL file get truncated?
The WAL file is truncated to zero length when a CheckpointMode::Truncate checkpoint completes successfully (core/storage/wal.rs:141-148). This occurs after all frames have been back-filled to the database file and the nbackfills counter equals the maximum frame number. The truncation is guarded by the checkpoint lock and read-mark-0 to ensure no readers are accessing old frames.
How does Turso handle concurrent reads during writes?
Turso uses read-marks (up to five slots) stored in TursoRwLock to allow multiple concurrent readers. When a write transaction commits via commit_prepared_frames, it publishes the new max frame number without blocking existing readers, who continue to see the snapshot captured when they acquired their read-mark. Readers are only blocked if they attempt to read a page that the writer is currently modifying, and the writer holds read-mark-0 during checkpoints to prevent new readers from starting while the database file is being updated.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →