Design a Durable Concurrent Event Writer
DatabricksSoftware Engineer · OnsiteLast updated
Design a high-throughput, thread-safe embedded log writer that safely batches and persists records from thousands of concurrent producer threads into an append-only file.
Scenario
You are building a high-performance, in-process Write-Ahead Log (WAL) writer for an embedded storage engine. Thousands of application worker threads concurrently produce opaque byte records that must be appended to a single logical file on local NVMe storage with strong durability, ordering, and consistency guarantees.
Requirements
Functional Requirements
- Client API: Expose an
append(record: bytes) -> WriteReceiptinterface (synchronous or asynchronous via futures/callbacks) returning the record's persistent offset or Log Sequence Number (LSN). - Ordering & Atomicity: Preserved total order of acknowledged writes; records must never be interleaved or partially readable. An individual record write must be all-or-nothing.
- Crash Recovery: If the process terminates abruptly or power fails, a recovery routine must scan the file, detect the exact valid tail, discard any torn writes, and make the log ready for subsequent appends.
- Lifecycle & Teardown: Support graceful shutdown by draining queued records, performing an atomic disk sync, and closing descriptors cleanly.
Non-Functional Requirements & Scale
- Concurrency: Up to 5,000 concurrent producer threads invoking
append. - Throughput: Sustain at least 50,000 appends/sec and 200 MB/sec on modern local SSDs.
- Durability SLA: Support configurable durability, including a group-commit mode guaranteeing persistence within at most 5 ms or when a batch reaches 1 MB.
- Resource Boundaries: Strict memory limit for buffered in-flight writes (e.g., max 64 MB buffered); apply deterministic backpressure when disk write throughput drops.
What Your Design Should Cover
- Concurrency & Thread Coordination: How producers hand off data without lock convoying (e.g., ring buffers, concurrent queues, multi-producer single-consumer patterns).
- Record Framing & Disk Layout: The on-disk binary format (headers, magic bytes, length prefixes, CRC/checksums, padding/alignment).
- I/O Pipeline & Batching: Group-commit mechanics, using vectorized I/O (
writev), and handlingfdatasync/fsyncamortization. - Backpressure & Disk Failure: Handling buffer saturation, slow disks,
ENOSPC(disk full), and hardware I/O errors. - Recovery Algorithm: Step-by-step logic to locate corruption or torn writes at log tail during restart.
- Operational Metrics: Critical health signals to expose (e.g., queue lag, sync latency, batch fill ratio).
Hints
Hint 1
Decouple concurrent producer threads from the physical file descriptor using a bounded Multi-Producer Single-Consumer (MPSC) queue or ring buffer to avoid lock contention on I/O operations.
Hint 2
Implement a group-commit mechanism that aggregates multiple pending records into a single vectorized write (`writev`) followed by an amortized `fdatasync`, fulfilling promises to all waiting callers at once.
Hint 3
Prevent torn writes from corrupting the log by framing records with magic bytes, total payload length, and a CRC32C checksum, allowing recovery routines to find the last valid frame and truncate cleanly.
Follow-up questions
- How would you support multi-tiered durability where some critical records require an immediate synchronous `fsync` while others can tolerate group-commit delay?
- How does your design change if you migrate from standard buffered I/O with `fsync` to direct I/O (`O_DIRECT`) or memory-mapped files (`mmap`)?
- How would you implement seamless log file segment rolling without stalling active producer writes when a file exceeds a target size (e.g., 1 GB)?
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.