Katkat

Design a Durable Concurrent Event Writer

DatabricksSoftware Engineer · Onsite

Last 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) -> WriteReceipt interface (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

  1. Concurrency & Thread Coordination: How producers hand off data without lock convoying (e.g., ring buffers, concurrent queues, multi-producer single-consumer patterns).
  2. Record Framing & Disk Layout: The on-disk binary format (headers, magic bytes, length prefixes, CRC/checksums, padding/alignment).
  3. I/O Pipeline & Batching: Group-commit mechanics, using vectorized I/O (writev), and handling fdatasync/fsync amortization.
  4. Backpressure & Disk Failure: Handling buffer saturation, slow disks, ENOSPC (disk full), and hardware I/O errors.
  5. Recovery Algorithm: Step-by-step logic to locate corruption or torn writes at log tail during restart.
  6. 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)?