Bali, Indonesia
Insights

Tracing the Memory Cost of Slow Raft Followers: Heap Bloat, Compaction Lag, and Write Amplification

October 14, 2026
0:00
A common operational fallacy in distributed consensus architectures is the belief that quorum hides the sins of the slowest replica. In a three-node or five-node Raft group, a single node grinding through an I/O hang or NVMe controller reset should theoretically be irrelevant: as long as a strict majority Q = ⌊N/2⌋ + 1 acknowledges the append entries RPC, the leader commits and client latency remains pristine. Architectural analyses of distributed consensus—such as Yugabyte's breakdown of consensus-based replication in distributed databases—consistently highlight this fault-tolerant isolation. Yet, in production database clusters running Multi-Raft under heavy write saturation, an I/O-stalled follower invariably triggers systemic degradation across the healthy nodes. The failure mode is rarely an immediate crash; it is an insidious cascade of memory bloat, LSM-tree compaction starvation, and devastating p99 latency spikes.
To prevent slow followers from needing expensive, full-state snapshots, Raft leaders maintain an in-memory sliding window of uncommitted and recently committed entries, backed by an append-only write-ahead log (WAL). As the active follower's match index falls behind the leader's applied index, the leader is forced into an operational fork. It can either retain historical log entries across disk and RAM caches until the straggler recovers, or aggressively truncate its WAL at a fixed high-water mark and fall back to transmitting an asynchronous snapshot of the underlying state machine. Most database storage layers attempt to be accommodating. They retain historical Raft segments up to a configured retention size or time boundary. Under sustained write throughput (such as 40,000 writes per second per shard), retaining even twenty seconds of trailing logs transforms the leader's internal append entry queues into a primary source of memory pressure. In engines leveraging Go or C++ with custom memory allocators like jemalloc, these unpurged slice buffers evade garbage collection or remain trapped in dirty arenas. If the leader keeps the entries cached in memory to avoid reading from disk when serving replication RPCs to the straggler, working-set heap memory spikes linearly with the replica's lag.
YAML
# Dangerous default: retaining too many entries under sustained ingest
raft:
  max_log_retention_entries: 500000
  log_cache_size_mb: 4096
  snapshot_catchup_threshold: 450000
When the follower stalls past the log retention window, the leader gives up on streaming individual log entries and falls back to snapshot replication. This transition is where the most acute performance regressions occur. A Raft snapshot is not a mere static file copy; it is a point-in-time consistent view of the underlying key-value storage engine, typically an LSM-tree like RocksDB or Pebble. To generate this snapshot without pausing real-time writes, the storage engine acquires an internal sequence number iterator that pins the active state of the memtables and SSTables. While that snapshot iterator remains open to stream multi-gigabyte files across the wire, the LSM compaction workers are hamstrung. They cannot drop tombstones or purge obsolete record versions that have a sequence number higher than the snapshot iterator's lower bound. Under sustained writes, incoming mutations force continuous memtable flushes to Level 0 (L0). The L0 SSTable count climbs past write-stall thresholds, triggering aggressive write amplification, cascading CPU utilization, and backpressure on the client-facing consensus loop.
BASH
# Inspecting RocksDB/Pebble engine stalls during snapshot generation
$ bpftrace -e 'tracepoint:syscalls:sys_enter_fsync { @[comm] = count(); }'
# Correlating with LSM-tree L0 file accumulation
$ pebble db stats /var/data/pebble | grep -E "Level 0|Write stall"
As the leader reads gigabytes of SSTables off local disks to push them over the network socket to the catching-up follower, it causes aggressive page cache churn. Unless the snapshot streaming loop employs posix_fadvise(..., POSIX_FADV_DONTNEED) or direct I/O (O_DIRECT), the snapshot stream flushes out the database working set—frequently accessed index blocks and Bloom filters—from the Linux page cache. The database's block cache hit ratio plummets, forcing unrelated read queries to hit physical disk rather than memory. At the same time, if the storage engine is bounded by aggressive memory cgroups limits (see our deep dive on cgroups v2), the rapid combination of dirty page allocations from the snapshot stream and heap bloat from unpurged replication queues triggers the Linux kernel's synchronous memory reclamation (direct reclaim). When memory pressure reaches this threshold, allocations block in kernel space within kswapd, inducing multi-millisecond tail latency blips on what should be sub-millisecond consensus heartbeats.
Eliminating this cascade requires designing storage engines and consensus loops that treat lagging followers as untrusted I/O paths rather than peers deserving infinite patience. First, enforce an immutable memory ceiling on the Raft log cache that automatically forces eviction to disk long before system memory saturates. When a follower lags beyond memory-cached logs, the leader must read historical logs from secondary storage using dedicated, rate-limited background workers that do not compete with the primary consensus pipeline. Second, snapshot streams must be decoupled from the live LSM engine's mutable state. Modern architectures achieve this by taking native copy-on-write filesystem snapshots (e.g., ZFS, XFS reflink copies) or using hardlinks to immutable L1+ SSTables, freeing the active memtables immediately and avoiding iterator-pinned compaction stalls. Finally, the replication channel must implement explicit backpressure using bounded RPC buffers. If a follower's write acknowledgment latency drifts past three standard deviations from the cluster median, it must be proactively isolated into a non-voting catch-up state. By terminating its log retention obligations, the leader can truncate its WAL normally, unpin its compaction pipelines, and preserve predictable p99 latencies for the rest of the quorum.