Design a Write-Ahead Log Writer with Batch Writes
DatabricksSoftware Engineer · OnsiteLast updated
Design and implement a concurrent write-ahead log (WAL) engine featuring group commit, monotonic sequence assignment, torn-tail crash recovery, and explicit durability promises.
Problem Statement
In distributed storage engines and databases, synchronous disk I/O (fsync) for every incoming transaction creates severe write bottlenecks. To achieve high write throughput while maintaining ACID durability guarantees, engines rely on a Write-Ahead Log (WAL) that combines monotonic ordering, group commit (batching), and checksummed framing.
Design and implement a concurrent WALWriter that allows multiple producer threads to append byte records concurrently while a background worker flushes and syncs them to persistent storage in bounded batches.
Record Framing
Every record on disk must be framed as follows:
length: 4 bytes (unsigned big-endian int), representing the payload sizeNlsn: 8 bytes (unsigned big-endian int), monotonically increasing log sequence number (0-indexed)crc32: 4 bytes (unsigned big-endian int), CRC-32 checksum calculated over the payloadpayload:Nbytes of raw data
Component Requirements
-
Concurrent Appends:
append(payload: bytes) -> Future[int]- Assigns the next sequential 64-bit
LSNimmediately upon ingestion. - Appends to an in-memory batch buffer without blocking other writers.
- Returns a future/promise that resolves to the assigned
LSNonly after the batch containing this record is persisted to disk andos.fsync(or equivalent) has completed. - Applies backpressure: if total buffered uncommitted records exceed
max_uncommitted_records,appendmust block until space is available.
- Assigns the next sequential 64-bit
-
Group Commit Flush Criteria:
- Flush immediately if buffered batch size exceeds
max_batch_bytes. - Flush if
max_delay_mshas elapsed since the earliest buffered write in the current batch.
- Flush immediately if buffered batch size exceeds
-
Recovery:
recover(path: str) -> list[tuple[int, bytes]]- Scans the log file from the beginning, validating framing, record length, and CRC-32.
- Stops reading at the first corrupted record or incomplete tail write (torn write). Records prior to the torn tail are returned. Any corrupted tail must be cleanly truncated.
-
Graceful Shutdown:
close()- Flushes and syncs all buffered writes, resolves pending futures, and closes file handles.
Interface
from concurrent.futures import Future
from typing import List, Tuple
class WALWriter:
def __init__(self, file_path: str, max_batch_bytes: int = 64 * 1024, max_delay_ms: float = 10.0, max_uncommitted_records: int = 10000):
pass
def append(self, payload: bytes) -> Future: # Resolves to int (LSN)
pass
def close(self) -> None:
pass
@classmethod
def recover(cls, file_path: str) -> List[Tuple[int, bytes]]:
pass
Constraints
0 <= len(payload) <= 16 * 1024 * 1024(16 MiB max record size).- Must handle concurrent calls from arbitrary numbers of worker threads safely without data corruption or interleaving bytes.
- Durability acknowledgment must never be signaled early: futures cannot resolve before
fsynccompletes.
Hints
Hint 1
Decouple LSN assignment from the I/O thread: use an atomic counter to assign LSNs instantly, then push records into a thread-safe queue or double-buffered swap region.
Hint 2
For group commit, have a dedicated single background flusher thread drain the queue into a contiguous write buffer, write to disk, call `fsync()`, and only then resolve all corresponding promises.
Hint 3
During recovery, sequentially inspect headers: if remaining bytes in file are less than the expected `16 + length` or if the checksum fails, treat it as a torn write and truncate the file to the last valid offset.
Follow-up questions
- How would you implement zero-copy ring buffers or double-buffering to minimize memory allocations and thread synchronization during high contention?
- If `fsync()` returns an I/O error (e.g., EIO or ENOSPC), how should the writer fail pending futures, transition the WAL state, and notify the rest of the storage engine?
- How would you extend this design to support segment log rotation and parallel background checkpointing without stalling foreground writes?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.