[Proposal|Performance] Eliminating Group Commit: A Leaderless Write Path for RocksDB
> # TL;DR
>
> **We eliminate RocksDB's group commit by letting every writer perform its own WAL write and its own memtable insertion in parallel — no leader, no follower, no cross-writer handoff.**
>
> **On a 32-core machine, this delivers +30% CPU utilization and +50% insert throughput on a pure-write workload.**
---
# Eliminating Group Commit: A Leaderless Write Path for RocksDB
## Abstract
RocksDB's write path funnels every concurrent writer through a single leader via the `WriteThread` group-commit protocol. The leader holds the WAL write mutex for the duration of the disk write and fsync, serializing all followers behind both a CPU handoff and a filesystem I/O latency. On modern many-core hardware this caps write-heavy workloads at a small fraction of available CPU.
We present a two-layer redesign of the WAL write path that preserves RocksDB's durability and ordering semantics while eliminating the leader/follower bottleneck. Layer 1 introduces a ping-pong double-buffered WAL writer that hoists filesystem I/O out of the mutex-protected hot path: `SyncWAL()` swaps buffers under the mutex (O(1), in-memory) and performs the disk write outside of it, so new writers append into a fresh buffer concurrently with I/O. Layer 2 replaces the group-commit protocol entirely with a fully leaderless path: each writer independently claims an LSN, a byte range on the WAL file, and a reservation on the active ping-pong buffer, then serializes its record into its reserved slice with a plain memcpy and performs its own memtable insertion concurrently with other writers. No thread ever acts as a leader for any other thread — neither on the WAL side nor on the memtable side — and there is no follower handoff. Visibility is published via a lock-free watermark structure modeled on InnoDB's `Link_buf`, which tracks the largest LSN such that every byte up to it is resident in the buffer and the corresponding memtable insertion is complete. Structural events (WAL rotation, memtable switch, write stall) are handled by a coalesced slow path that drains the active buffer and runs the legacy switch sequence under the existing DB mutex.
On a 32-core pure-write workload, the legacy group-commit path saturates at approximately 20 cores busy; the leaderless path reaches approximately 26 cores busy — a 30% improvement in CPU utilization and a 50% improvement in insert throughput. The remaining idle capacity is attributable to memtable insert contention and the slow-path structural-switch serialization, not the WAL write path.
## 1. Introduction
### 1.1 Motivation
The core bottleneck of RocksDB's existing write path is the `WriteThread` group-commit protocol. Concurrent writers arrive at `DB::Write`, enqueue themselves on the `WriteThread` queue, and are then classified into one of three roles: the leader of the current group, a follower within the group, or a writer blocked behind the current leader. The leader executes the group's WAL write and memtable insertion (or coordinates parallel memtable insertion by followers) and then releases the group.
Two costs are inherent to this protocol:
1. **I/O inside the critical section.** When the group's write is `sync=true` or when the leader is the only writer in its group, the leader holds `log_write_mutex_` (in recent revisions `wal_write_mutex_`) across `WriteBuffered` and the subsequent `Sync`. The duration of this hold is dominated by filesystem latency. Every follower, every next-group writer, and every concurrent `SyncWAL` caller waits for the full I/O latency before it can progress.
2. **Leader/follower handoff.** Even when I/O is elided (`manual_wal_flush=true`, `disableWAL=true`, or group sizes of one), the queue-based leader election, parallel-insert coordination, and group exit each impose CPU overhead on every write. On many-core machines this overhead becomes the visible ceiling once I/O is otherwise hidden.
Applications that drive WAL durability out-of-band — for example, a dedicated 100ms-period group-committer thread that calls `DB::SyncWAL()` on behalf of all pending writers — pay both costs without benefit. They have already collapsed durability syncs themselves; `WriteThread` only serializes additional memcpys behind the leader.
On a 32-core machine, a pure-write benchmark against the legacy path saturates at approximately 20 cores busy regardless of writer thread count — the WriteThread funnel is the binding constraint. We target this class of workload.
### 1.2 Contributions
We contribute:
- A WAL writer (`NonBlockingIOWritableFileWriter`) that suppresses all implicit I/O during `Append()` and double-buffers `SyncWAL()`, moving filesystem I/O out of the mutex-protected critical section (§3).
- A leaderless write path (gated by `DBOptions::use_group_commit=false`) in which concurrent writers independently claim LSN, WAL byte range, and buffer reservation under a single adaptive mutex held only for O(1) work, then serialize their WAL records via lockless memcpy and perform their own memtable insertion concurrently — with no leader, no follower, and no cross-writer handoff on either the WAL or memtable side (§4).
- A paired-advance protocol for `(next_lsn, next_wal_offset)` that preserves the invariant `lsn_i < lsn_j ⇔ offset_i < offset_j` within any single buffer, which is the property RocksDB recovery relies on (§4.2).
- A `Link_buf`-based `min_non_hole_lsn` watermark for publishing out-of-order writer completion to readers without regressing `last_sequence_` (§4.4).
- A coalesced slow path for WAL rotation, memtable switch, and write stall that preserves the legacy switch sequence without reintroducing leader/follower serialization on the fast path (§4.5).
- A correctness argument for recovery, snapshot read visibility, and error propagation in the leaderless model (§5, §6).
## 2. Background
RocksDB's WAL is a sequence of length-prefixed records grouped into 32 KiB physical blocks. Block boundaries affect record framing: `log::Writer::AddRecord` computes padding based on `block_offset_ = file_offset % kBlockSize`, so two writers that append concurrently cannot simply interleave their record bytes unless each knows its absolute file offset at serialization time.
The existing `log_write_mutex_` serializes `AddRecord` calls so that `block_offset_` is unambiguous. In `manual_wal_flush=true`, the mutex also covers the deferred `WriteBuffer` that drains the accumulated buffer to disk. In `manual_wal_flush=false`, every `AddRecord` implicitly drains; under `sync=true`, it also fsyncs before returning. In both cases the mutex hold time is a linear function of the bytes in flight and the disk latency.
The `WriteThread` sits on top of this: it reduces the per-write mutex acquisitions by collapsing a burst of concurrent writers into a single `AddRecord` call by the leader for the full group batch. But it does not reduce the mutex hold time per batch — it just amortizes it. Once the leader's window is the binding constraint (saturating I/O or saturating a core), adding followers no longer scales.
Read visibility is driven by `VersionSet::last_sequence_`, advanced by the leader at group commit to the highest LSN in the group. Followers rely on this monotonic advance for read-your-writes.
## 3. Architecture Layer 1: Nonblocking WAL Writer
### 3.1 Overview
`NonBlockingIOWritableFileWriter` is a subclass of `WritableFileWriter` that replaces the implicit-flush-on-full policy of the base class with two complementary behaviors:
- `Append()` never performs I/O. The underlying buffer `buf_` grows unbounded as writers add records.
- `SyncWAL()` is the sole I/O driver. It executes a ping-pong swap under the WAL write mutex, then performs the actual disk write and fsync outside the mutex.
### 3.2 Ping-Pong Protocol
The writer owns two aligned buffers, `buf_` and `flush_buf_`. The invariant maintained across the lifetime of the writer is:
> Outside of `SyncWAL()`, `flush_buf_.size() == 0`; `buf_` accumulates appends.
`SyncWAL()` operates in three phases:
```
Phase 1 (under wal_write_mutex_, O(1))
wait until !IsSyncing() // at most one ping-pong in flight
std::swap(buf_, flush_buf_) // pre-allocated, no allocation here
mark IsSyncing = true
release wal_write_mutex_
Phase 2 (no mutex)
WriteToFile(flush_buf_) // disk write + rate limiting
FlushOSCacheAndMaybeRangeSync // fsync + optional range_sync
Phase 3 (under wal_write_mutex_, O(1))
clear flush_buf_
MarkLogsSynced()
IsSyncing = false, signal wal_io_cv_
```
During Phase 2, concurrent `AddRecord` calls append into `buf_` under the mutex; they observe `flush_buf_` non-empty and do not interfere with it. The disk write and the append stream therefore proceed in parallel, bounded only by the in-memory buffer capacity.
### 3.3 Ownership and Invariants
Ownership of the two buffers is partitioned by phase:
- `buf_` is owned by the mutex: readers and writers of `buf_` must hold `wal_write_mutex_`.
- `flush_buf_` is owned by the `IsSyncing` flag: the thread that flips `IsSyncing = true` in Phase 1 owns `flush_buf_` through Phase 2 and yields ownership in Phase 3.
Debug asserts verify that `flush_buf_` is empty whenever a writer holds the mutex for `AddRecord`, that `SwitchMemtable` and `LockWAL` wait for `!IsSyncing()` and then re-assert `flush_buf_` empty, and that no base-class `WriteBuffered` path is reached with `flush_buf_` non-empty.
### 3.4 Error Policy
I/O failure in Phase 2 is fatal: the writer aborts the process. The rationale is that Phase 1 has already published the fresh `buf_` to new writers, and those writers have appended records whose LSNs were allocated before the failure. Returning an error to the `SyncWAL()` caller would falsely ascribe those records to the failed sync; retrying Phase 2 is likewise impossible because the buffer being retried is no longer the tail of the WAL. Aborting is consistent with the behavior RocksDB already exhibits on irrecoverable WAL write failures in the `manual_wal_flush` path.
### 3.5 Compatibility
Layer 1 is activated by a new `DBOptions::nonblocking_wal_io` flag. When true, validation at `DB::Open` rejects `manual_wal_flush=true`, `two_write_queues=true`, WAL direct I/O, and WAL checksum handoff. Per-write validation rejects `WriteOptions::sync=true` — callers that need synchronous durability use `DB::SyncWAL()` explicitly after `DB::Write()`. All other configurations are unchanged.
When `nonblocking_wal_io=false` (default), the writer is not instantiated and the legacy `WritableFileWriter` path is used bit-identically.
## 4. Architecture Layer 2: Leaderless Write Path
### 4.1 Overview
Layer 2 replaces `WriteThread`'s leader/follower protocol with a per-writer independent claim protocol. A new `DBOptions::use_group_commit` flag (default `true`) gates the selection; `DBImpl::WriteImpl` dispatches to `WriteImplNoGroup` when it is `false`.
The leaderless path requires Layer 1: it reuses `NonBlockingIOWritableFileWriter`'s two buffers as two persistent *slots*, alternating between them on WAL rotation and on slow-path drains. A writer's steps are:
1. A short-held claim under `wal_write_mutex_` that advances the paired (LSN, WAL offset) counters and bumps the active slot's in-flight count. O(1), no I/O, no allocation.
2. A lockless memcpy of the serialized WAL record into the writer's reserved slice of the active slot's buffer.
3. A lockless memtable insertion into the active memtable, proceeding concurrently with other writers' memtable insertions — the same concurrent-insert-safe skiplist path today's `allow_concurrent_memtable_write` uses, but without the WriteThread-coordinated parallel-insert mechanism.
4. A lockless release that decrements the slot's in-flight count and publishes the writer's LSN range to the visibility watermark (§4.5).
No step is executed on behalf of another writer. The WAL record for a writer is serialized directly into its reserved byte range; there is no leader that concatenates records. The memtable insertion for a writer runs on that writer's own thread; there is no leader that batches or redistributes inserts. The WAL write mutex is held only during step 1 and is released for the entire duration of steps 2–4, so per-writer work happens in parallel with all other writers' steps 2–4.
### 4.2 Paired LSN/Offset Allocation
Each writer must claim two resources atomically from the allocator's perspective: the next LSN to be assigned to its batch, and the next WAL byte offset at which its serialized record will begin. The invariant RocksDB recovery relies on is
> Within any single buffer, `lsn_i < lsn_j ⇒ offset_i < offset_j`.
This is the property that lets `log::Reader` scan the file in physical order and recover records in LSN order.
We preserve the invariant by advancing both counters under the same mutex. `wal_write_mutex_` is an adaptive `InstrumentedMutex` that spins briefly before parking; the critical section contains two plain loads and four plain stores and does not branch. We have collapsed what was initially a separate spinlock-protected allocator onto this mutex after measuring that the adaptive mutex's overhead is within noise of the spinlock's and that a single locking primitive simplifies reasoning about `ClaimSlot`/`SwitchActive` interaction (§4.5).
The mutex also serializes slot claims and slot switches, so all three operations — LSN+offset advance, in-flight-count increment on the active slot, and active-slot swap — happen under one lock. The critical section is O(1) and does not involve I/O.
### 4.3 Slot Reservation and Release
The active slot's `in_flight_count` tracks writers between claim and memcpy completion. Claims under the mutex use a plain `fetch_add(1, relaxed)`; releases after the memcpy use `fetch_sub(1, release)` without the mutex. The slot switcher (§4.5) spins on `load(acquire)` on the old slot's count until it drains to zero, using the release/acquire pair to establish happens-before with every completing writer's memcpy.
Releases are lockless because the mutex is not needed to publish release visibility — the switcher's spin is the only consumer, and release/acquire is sufficient. Claims require the mutex because the choice of "which slot is active" may change concurrently, and the mutex is the synchronizing event for that change.
### 4.4 Per-Writer Record Serialization
Each writer serializes its WAL record into its reserved byte range via `SerializeRecord` (`db/log_record_serializer.cc`), a free function that produces byte-for-byte identical output to `log::Writer::AddRecord` for the same payload at the same absolute file offset. It does not touch `log::Writer::block_offset_` because the absolute offset is already known at claim time (§4.2); `block_offset_` is reconstructible from the offset modulo the block size.
The writer's critical steps are therefore:
```
size = SerializedRecordSize(batch) [1]
lock wal_write_mutex_
lsn = next_lsn_; next_lsn_ += batch_size
offset = A->next_wal_offset; A->next_wal_offset += size
A->in_flight_count.fetch_add(1, relaxed)
unlock wal_write_mutex_
SerializeRecord(A->bytes + offset, batch, [2]
A->file_offset_start + offset)
Link_buf::Store(lsn, lsn + batch_size) [3]
MemTable::Insert(batch, lsn) // lockless, concurrent [4]
A->in_flight_count.fetch_sub(1, release) [5]
Link_buf::AdvanceWatermark() // and SetLastSeqAtLeast [6]
```
Step [1] is computed outside the mutex. Steps [2] through [6] all execute without any mutex held, in parallel with every other writer's [2]–[6] and with Layer 1's Phase 2 (§3.2) I/O. The watermark advance in step [6] is the single point at which the writer's LSN becomes visible to readers, and it is ordered after both the WAL memcpy and the memtable insertion, so a reader that reads at or below `min_non_hole_lsn` is guaranteed to see the writer's memtable entries.
### 4.5 Visibility Watermark: `Link_buf`
Writers complete out of LSN order. To publish completion to readers without regressing `last_sequence_`, we maintain `min_non_hole_lsn`: the largest LSN such that every LSN ≤ it has been both memcpy'd into its buffer slice and inserted into the active memtable (§4.4 step [4]). Readers take snapshots at or below this watermark.
The data structure is a fixed-size circular ring of 64K slots, modeled on InnoDB's `ut0link_buf.h`. Each writer, on completion, stores the end-LSN of its range at `(start_lsn % kCapacity)`. An opportunistic scanner (any writer, or a dedicated thread) reads forward from the current watermark: if `ring[watermark % kCapacity]` contains a value `v > watermark`, the watermark advances to `v`; otherwise it stops. A single CAS updates the watermark.
Crucially, we do **not** clear slots on advance. An earlier design that cleared slots had a race where a concurrent wrap-around `Store()` which legally passed the ring-capacity check could be clobbered by the scanner's post-CAS clear. Instead, stale values from prior wrap-arounds are filtered by the check `next <= pos`, which is always false for a live entry. This matches InnoDB's design and eliminates the clobber window.
Oversize batches (larger than `kCapacity - max_in_flight`) are rejected at prewrite via `CheckBatchSize`; ring-full conditions (no advance possible for kCapacity LSNs) abort the process, since they indicate that one of the in-flight writers has stalled beyond the ring's horizon. The horizon is large enough that the app-level contract "call `SyncWAL` before `kCapacity` LSNs accumulate" is easy to satisfy.
Finally, `VersionSet::SetLastSequenceAtLeast(new_watermark)` uses a CAS-max so that concurrent advance by multiple writers cannot regress `last_sequence_`.
### 4.6 SyncWAL in the Leaderless Path
`SyncWAL()` in the leaderless path (§3.2's Phase 1 extended) must wait for the slot's in-flight count to drain before writing the bytes to disk. Let A be the currently active slot and B the standby. The protocol is:
```
lock wal_write_mutex_
if another SyncWAL is in progress for A:
wait on log_sync_cv_, return
set sync_wal_in_progress_ = true
SwitchActive(A -> B) // publish B as active
unlock wal_write_mutex_
WaitDrain(A) // spin on A->in_flight_count
FlushSlotBytes(A, 0..A->next_wal_offset) // write + fsync
lock wal_write_mutex_
MarkLogsSynced()
ResetDrainedSlot(A) // A becomes the new standby
sync_wal_in_progress_ = false
broadcast log_sync_cv_
unlock wal_write_mutex_
```
Concurrent `SyncWAL()` callers collapse: the second caller waits on the first's completion via `log_sync_cv_` and returns without additional I/O, mirroring the legacy `getting_synced` pattern.
Durability of records in A is provided by the fsync in the I/O phase; the fsync failure policy is identical to Layer 1 (§3.4): abort. Writers whose records were in A and whose `DB::Write()` returned before the `SyncWAL` was called have already received "success"; on crash those records may be lost. This is the same durability contract as `manual_wal_flush=true` and must be documented as the mode's contract.
### 4.7 Structural Switch Slow Path
Memtable switch, WAL rotation, and write stall all require a synchronization point where the active buffer is drained so that SwitchMemtable can run its legacy sequence under `mutex_`. We coalesce these triggers with a DB-level `structural_switch_in_progress_` flag and condition variable.
When a writer's prewrite check fires a structural trigger:
1. If `structural_switch_in_progress_` is true, the writer waits on the cv and retries the prewrite check on wake.
2. Otherwise, it sets the flag under `mutex_`, drains the active slot inline (`WaitDrain(A)` outside the mutex, then `FlushSlotBytes(A)` inside `wal_write_mutex_`), and runs the legacy `SwitchMemtable` sequence under `mutex_`.
3. On completion, it clears the flag and broadcasts.
WAL rotation does not fsync on the switch path; durability of the pre-rotation bytes is deferred to the next `SyncWAL()`, which walks `logs_` and fsyncs each inactive entry alongside the back log, matching the legacy pattern. The memtable-swap barrier (`WaitDrain + FlushSlotBytes + emplace_back`) always runs on every switch, independent of `log_empty_` state — we force `creating_new_log = true` in the leaderless branch to bypass the non-atomic `log_empty_` read that would otherwise be racy without the `WriteThread` quiescence.
The concurrent-insert safety of memtable switch in the leaderless path depends on the memtable's range-del table being empty, because `ConstructFragmentedRangeTombstones` is not safe against concurrent inserts. We enforce this by rejecting `WriteBatch`es that contain any `DeleteRange` operation at the entry to the leaderless path. This is a v1 restriction and is scheduled for relaxation once we have a concurrent-safe fragmentation strategy.
### 4.8 Error Policy
All post-claim I/O failures in the leaderless path (`SyncWAL` I/O, structural-switch `FlushSlotBytes`) abort the process, for the same reason as Layer 1 (§3.4): the fresh slot has already been published to new writers at the moment the failed operation began, so there is no consistent rollback state. Pre-claim errors (prewrite checks, batch-size validation, ring-capacity check) are returned to the caller in the normal way.
## 5. Recovery
RocksDB recovery scans each WAL file with `log::Reader`, applies the resulting `WriteBatch`es to the memtable in file order, and advances `last_sequence_` to the batch's sequence number. Two properties must hold for the leaderless path to be recovery-safe:
1. **Within a single WAL file, file order = LSN order.** This is guaranteed by the paired-advance protocol (§4.2) and by the fact that every record in a given WAL file was memcpy'd into a single slot-aliased buffer during its active window.
2. **Partial buffer on crash is benign.** A crash mid-`SyncWAL` may leave a partial disk image of the slot's byte range. Since `log::Reader` stops at the first malformed record, any tail of the file that was not fully written is discarded on recovery. LSNs allocated for records in the lost tail are not reused — this is identical to the legacy behavior.
3. **WAL file boundaries preserve LSN order.** A new WAL is created only during a structural switch, which drains the prior slot inline before rotating. Therefore all LSNs in file N+1 are strictly greater than all LSNs in file N.
No change to `log::Reader`, `DBImpl::RecoverLogFiles`, or `WalManager` is required.
## 6. Read Visibility and Snapshots
In the legacy path, `GetSnapshot()` returns `last_sequence_`, which is advanced monotonically by the leader. In the leaderless path, `last_sequence_` is advanced by `SetLastSequenceAtLeast` (§4.5) as `min_non_hole_lsn` progresses. The guarantee is weaker in one way and stronger in another:
- **Weaker:** a writer's own `Put` is not immediately visible to that writer's subsequent `Get` without an intervening snapshot, because the writer may return from `Put` before `min_non_hole_lsn` catches up to its LSN. This is the same behavior RocksDB already exhibits under `unordered_write=true` and is acceptable for the target workload, in which read-after-write within a single thread is rare.
- **Stronger:** snapshots are never stale with respect to their own consistency contract; `Get` without an explicit snapshot reads at `min_non_hole_lsn`, which excludes in-flight writes with LSNs above it but never includes writes that are not yet in the buffer.
The user-visible contract change is explicit and must be documented: in the leaderless mode, "read your own write" within a single thread requires taking a snapshot after `Put` returns.
## 7. Compatibility
| Option | Layer 1 compatible | Layer 2 compatible |
|--------------------------------|--------------------|--------------------|
| `manual_wal_flush=true` | No | No |
| `two_write_queues=true` | No | No |
| `unordered_write=true` | Yes | No |
| `enable_pipelined_write=true` | Yes | No |
| WAL direct I/O | No | No |
| WAL checksum handoff | No | No |
| `WriteOptions::sync=true` | No | No |
| `WriteOptions::disableWAL` | Yes | No |
| `DeleteRange` in batch | Yes | No (v1) |
| 2PC / `WritePrepared` | Yes | No (v1) |
The legacy write path is bit-identical when both flags are at defaults. Layer 2 requires Layer 1.
## 8. Evaluation
On a 32-core machine running a pure-write workload (batch size 64, value size 1 KiB, default compaction), the legacy group-commit path saturates at approximately 20 cores busy. Additional writer threads beyond that point do not increase throughput; `perf` shows the threads blocked on `WriteThread` queue waits.
With Layer 1 + Layer 2 enabled (`nonblocking_wal_io=true`, `use_group_commit=false`, durability driven by a 100ms-period out-of-band `SyncWAL()` thread), the same workload reaches approximately 26 cores busy — a 30% improvement in CPU utilization and a 50% improvement in insert throughput. `perf` on the leaderless path shows the remaining idle time attributable to:
- **Memtable insert contention** — the skiplist's concurrent-insert path has its own hot cache line. This is orthogonal to the WAL work and is a natural next target.
- **Structural-switch serialization** — the slow path of §4.7 drains the active buffer before running `SwitchMemtable`. At sustained write throughputs this fires often enough to be visible; further reducing the drain latency is future work.
Neither of the remaining sources of idle time lives in the WAL write path itself; the WAL bottleneck has been removed.
## 9. Related Work
The paired LSN/offset allocator and the `Link_buf`-based watermark are both directly inspired by MySQL InnoDB's redo log (Oracle's `ut0link_buf.h` in particular), which solved the same problem — a serializing log writer becoming the bottleneck on many-core hardware — by publishing out-of-order completion through a lockless watermark. InnoDB's redo log is single-file and handles neither WAL file rotation nor memtable integration; our contribution is the integration of this pattern into RocksDB's structural-switch and recovery model.
Prior RocksDB work on `unordered_write=true` and the two-queue write path attacks the leader cost in a narrower way, by allowing the memtable insertion to be reordered against the WAL write. Those modes leave the `WriteThread` funnel in place on the WAL side; the leaderless path removes it entirely.
## 10. Discussion and Open Questions
We would welcome upstream feedback on the following design choices:
1. **API shape.** The current proposal uses two `DBOptions` flags (`nonblocking_wal_io` and `use_group_commit`). An alternative is a single mode enum. We prefer the two-flag shape because Layer 1 is independently useful for `manual_wal_flush`-style workloads without the compatibility restrictions of Layer 2, but we have no strong view.
2. **Durability contract.** The abort-on-I/O-error policy for Layer 1 and Layer 2 is stricter than some RocksDB users expect. We believe it is the only sound option once the ping-pong swap has happened, but would welcome discussion of alternatives (e.g., a background retry loop with a bounded retry budget).
3. **Restriction list for v1.** The v1 leaderless mode rejects `DeleteRange`, 2PC transactions, `sync=true`, and `disableWAL`. All are reachable extensions but each requires non-trivial additional design work. The question is whether upstream prefers v1 to land with the current restrictions (and v2 to relax them incrementally) or feature-parity at first merge.
4. **`Link_buf` sizing.** 64K slots is a heuristic. A configurable ceiling (or an app-level contract to `SyncWAL()` within `kCapacity` LSNs) is plausible; we have not found a workload that requires more, but are open to making it tunable.
5. **Read visibility semantics change.** The "read your own write requires an intervening snapshot" change is material. It matches `unordered_write=true`'s contract, but making it the default behavior of a new mode deserves discussion.
## 11. Conclusion
We have described a two-layer redesign of the RocksDB WAL write path that removes the `WriteThread` group-commit bottleneck without regressing recovery, durability within the declared contract, or the legacy write path's performance under default options. The ping-pong WAL writer hoists I/O out of the WAL write mutex; the leaderless claim protocol eliminates the leader/follower handoff. On a 32-core pure-write workload, the redesign improves CPU utilization by 30% and insert throughput by 50%, with the remaining idle capacity now residing in the memtable and structural-switch paths rather than the WAL write path.
1 条评论