weighing durability against latency
Every form of persistence in Redis costs latency, whether through fsync calls on every write or through the fork process behind a snapshot. Anyone who does not understand this tradeoff ends up choosing either a configuration that causes latency spikes in production, or one that loses more data than planned when something actually breaks.
Table of Contents
- 1. Why persistence and performance are in conflict
- 2. AOF fsync strategies: always, everysec, no
- 3. The cost of fsync in detail
- 4. RDB snapshots and the fork cost during BGSAVE
- 5. Copy-on-write and memory doubling during the fork
- 6. Detecting latency spikes: latency monitor and INFO
- 7. Mitigation strategies for latency spikes
- 8. Benchmarking approach: with and without persistence
- 9. Choosing persistence configuration by criticality
- 10. Summary
- 11. FAQ
1. Why persistence and performance are in conflict
Redis achieves its extreme speed because all data is held in memory and reads and writes are processed inside the event loop without touching disk. As soon as persistence enters the picture, that is, durably saving data to disk, tension with this fundamental principle is inevitable. Every mechanism that makes data durable, whether an AOF log or an RDB snapshot, eventually has to write bytes to a block-based storage medium, and that step alone is orders of magnitude slower than a pure in-memory operation.
The core persistence performance tradeoff can be reduced to a simple question: how much data are you willing to lose in a crash, and how much latency are you willing to accept in normal operation to minimize that risk? There is no configuration that optimizes both sides at once. Every decision toward more durability costs measurable latency, and every decision toward more performance increases the risk of data loss on a crash or power outage.
Redis offers two fundamentally different persistence mechanisms with clearly different tradeoff profiles: AOF (Append Only File) logs every write command continuously and offers fine-grained control over durability via the fsync strategy. RDB (Redis Database) creates periodic snapshots of the entire dataset, offering lower steady-state overhead but coarser control over the maximum acceptable data loss. The following sections analyze both mechanisms in detail.
Important context: the persistence performance tradeoff is not a Redis-specific problem, but a fundamental property of any system that wants to combine in-memory speed with disk durability. PostgreSQL, MySQL and other databases know the same tension under different names, such as write-ahead logging with synchronous or asynchronous commit. Understanding the tradeoff in Redis transfers this understanding directly to other systems with comparable architecture.
2. AOF fsync strategies: always, everysec, no
AOF persistence writes every write command to an append-only log, but merely writing into the filesystem buffer is not enough to guarantee durability. Only an fsync syscall forces the operating system to physically transfer the data to the storage medium. Redis offers three values for appendfsync: always runs fsync after every single write command and guarantees maximum durability, but costs the highest latency because every command waits for a synchronous disk write.
everysec is the default and a deliberate compromise: fsync runs at most once per second in a separate background thread, while the main process keeps processing commands without waiting. In the worst case, the write commands of the last second are lost on a crash, which is an acceptable risk for most applications relative to the performance gained. no leaves the timing of fsync entirely to the operating system, offering the highest performance, but potentially costing minutes of data in the event of a failure, depending on the kernel's dirty page settings.
# redis.conf: configure AOF persistence and fsync strategy
appendonly yes
appendfsync everysec
# Alternatives:
# appendfsync always: maximum durability, highest latency
# appendfsync no: minimal latency, kernel decides fsync timing
# AOF file format and directory
appenddirname "appendonlydir"
aof-use-rdb-preamble yes
3. The cost of fsync in detail
A single fsync call blocks until the operating system confirms that the data physically resides on the storage medium. On classic spinning disks this can take several milliseconds, on SSDs typically well under one millisecond, but on network storage such as NFS or EBS without local caching it can again be several milliseconds or more, depending on network latency and storage backend. Under appendfsync always, this latency multiplies with every single write command, which quickly becomes the limiting factor for overall throughput at high write rates.
Important for understanding the persistence performance relationship: the fsync call itself runs in the AOF background thread and does not directly block the main Redis thread. Even so, noticeable latency spikes can occur when the background thread falls behind on fsync and the main thread has to wait for the previous fsync to finish before the next write command, because otherwise the AOF buffer would grow uncontrolled. This behavior is visible as aof-fsync-always-lag in Redis internal metrics and is a commonly overlooked reason for sporadic latency spikes despite an everysec configuration.
Another often underestimated factor is the storage backend itself: virtualized environments with a shared I/O budget, for example cloud instances with burst credits for IOPS, can suddenly show much higher fsync latency once the credit balance is exhausted, without anything in the Redis configuration having changed. A latency problem that seems to appear out of nowhere therefore often has its cause outside Redis, in the storage metrics of the underlying infrastructure.
4. RDB snapshots and the fork cost during BGSAVE
RDB persistence uses a completely different mechanism: instead of logging every command individually, Redis periodically calls fork() to create a child process that writes a consistent snapshot of the entire dataset to disk while the parent process keeps serving commands. The advantage: the actual disk write does not block the main process. The disadvantage: the fork() call itself is not free, especially with large datasets.
A key point for understanding this: the fork does not copy the entire memory content, only the page table, the bookkeeping structure that maps virtual to physical addresses. The actual memory pages are only duplicated via copy-on-write once a change actually happens, which makes the fork itself much faster than a naive full copy would be.
The duration of a fork() scales roughly linearly with the size of the process page table, which in turn depends on the amount of memory in use. For a Redis instance with 20 GB of resident memory, a fork can easily take 20 to 200 milliseconds, depending on hypervisor, kernel version and huge page configuration. During this fork, the Redis main process is completely blocked, because fork() is a synchronous operating system call that copies, or marks for copy-on-write, the entire process memory state. This short but complete blockage is one of the best-known causes of latency spikes related to persistence in Redis.
# redis.conf: configure RDB snapshot intervals
save 900 1
save 300 10
save 60 10000
# Meaning: snapshot after 900s if >=1 change,
# after 300s if >=10 changes, after 60s if >=10000 changes
# Read fork duration and snapshot statistics
redis-cli INFO persistence | grep -E "rdb_|latest_fork"
# rdb_bgsave_in_progress:0
# rdb_last_bgsave_status:ok
# rdb_last_bgsave_time_sec:4
# latest_fork_usec:87213
5. Copy-on-write and memory doubling during the fork
After fork(), the parent and child processes initially share the same physical memory through copy-on-write pages. As long as neither process modifies a memory page, no additional memory is consumed. However, as soon as the Redis main process performs a write during an ongoing BGSAVE on a page that the child process still needs for the snapshot, the kernel duplicates that page before applying the change. With a heavily write-bound workload during a long BGSAVE, this effect can cause actual memory consumption to temporarily rise well above the normal level.
This memory doubling is an often underestimated aspect of the persistence performance relationship: a server that normally runs at 60 percent memory utilization can easily climb toward 90 percent or more during a long BGSAVE under heavy write load, as many pages get duplicated through copy-on-write. If too little memory headroom remains, this can trigger an out-of-memory kill by the Linux OOM killer, with far more severe consequences than a single latency spike. Sufficient memory headroom, typically at least 20 to 30 percent above the normal used_memory, is therefore a hard requirement for safe RDB operation under write-heavy workloads.
| appendfsync | Max. data loss | Latency impact | Recommendation |
|---|---|---|---|
| always | practically none | high, per write command | only for extremely critical data |
| everysec | up to 1 second | low, in the background | default for most setups |
| no | up to several minutes | minimal | only with non-critical data |
6. Detecting latency spikes: latency monitor and INFO
To even detect latency spikes caused by persistence mechanisms, Redis offers a built-in latency monitor. Running CONFIG SET latency-monitor-threshold 100 enables recording of all events that take longer than 100 milliseconds, including the categories fork, fsync, command and others. LATENCY HISTORY fork then shows concrete timestamps and durations of all recorded fork events, enabling a direct correlation with BGSAVE timing.
In addition, INFO persistence provides important aggregate metrics: rdb_changes_since_last_save shows how many changes have accumulated since the last snapshot, aof_pending_rewrite shows whether an AOF rewrite is pending, and latest_fork_usec gives the duration of the last fork in microseconds. These metrics should be monitored continuously and exported into a monitoring system like Prometheus, because one-off manual checks easily miss latency spikes that only occur under specific load patterns.
# Enable and read the latency monitor
redis-cli CONFIG SET latency-monitor-threshold 100
redis-cli LATENCY HISTORY fork
# 1) 1) (integer) 1721739600
# 2) (integer) 142
# 2) 1) (integer) 1721739900
# 2) (integer) 156
redis-cli LATENCY LATEST
# fork 1721739900 156 156
# Aggregate persistence metrics
redis-cli INFO persistence | grep -E "rdb_changes|aof_pending|latest_fork"
# Export these fields into Prometheus via redis_exporter for continuous tracking
7. Mitigation strategies for latency spikes
The most effective mitigation against fork-related latency spikes is to schedule BGSAVE deliberately during low-load periods, instead of triggering it solely through change-based save directives. An external scheduler that manually triggers BGSAVE at night or during known off-peak windows, combined with disabled automatic save points, gives significantly more control than the default configuration. In addition, disabling Transparent Huge Pages on Linux (echo never > /sys/kernel/mm/transparent_hugepage/enabled) measurably reduces fork duration, because THP increases the size of the page table entries that need to be copied.
For AOF-related spikes, separating AOF rewrite from regular fsync helps: auto-aof-rewrite-percentage and auto-aof-rewrite-min-size control when an AOF rewrite is triggered automatically, and an overly aggressive default can cause rewrites to happen during load spikes. A deliberately less frequent threshold combined with manual control via an external cron job reduces the likelihood that persistence operations overlap with production load spikes.
# redis.conf: mitigation measures against latency spikes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 128mb
save ""
# Automatic save points disabled, BGSAVE scheduled externally instead
# Disable Transparent Huge Pages at the operating system level
# echo never > /sys/kernel/mm/transparent_hugepage/enabled
8. Benchmarking approach: with and without persistence
Quantifying the actual persistence performance tradeoff for your own environment requires more than documentation, it requires a controlled benchmark with a realistic dataset. redis-benchmark allows a direct comparison of the same workload under different persistence configurations. It is important to run the benchmark with a realistic dataset size, because both fork cost and fsync latency depend heavily on the amount of data and the write pattern, and small test datasets drastically underestimate the effects.
A sensible test setup compares at least three scenarios: persistence fully disabled as the baseline, appendfsync everysec as the practical default, and appendfsync always as the worst case for maximum durability. Results should include not just average latency but p99 and p999 percentiles, because it is exactly in the upper percentiles that the spikes caused by fsync and fork become visible, while the average often masks them.
# Benchmark without persistence as a baseline
redis-cli CONFIG SET appendonly no
redis-cli CONFIG SET save ""
redis-benchmark -q -n 100000 -c 50 -t set,get --latency
# Benchmark with appendfsync everysec
redis-cli CONFIG SET appendonly yes
redis-cli CONFIG SET appendfsync everysec
redis-benchmark -q -n 100000 -c 50 -t set,get --latency
# Compare percentiles instead of average
redis-benchmark -q -n 100000 -c 50 -t set --latency -P 1
# Additionally measure appendfsync always as a worst-case reference
redis-cli CONFIG SET appendfsync always
redis-benchmark -q -n 100000 -c 50 -t set,get --latency
Mironsoft
Redis persistence tuning and latency diagnostics
Are latency spikes from fsync or BGSAVE under control?
We analyze your persistence configuration, measure fork duration and fsync latency under production load, and find the balance between durability and performance that fits your risk profile.
Latency audit
Evaluate latency monitor and fork statistics to identify causes
Configuration
Set appendfsync, save intervals and rewrite thresholds for production
Benchmarking
Run realistic load tests with and without persistence variants
9. Choosing persistence configuration by criticality
An often neglected factor in this decision is recovery time after a failure: RDB snapshots load significantly faster on restart than a long AOF log, because the log has to be replayed sequentially command by command, while an RDB snapshot is a direct binary image. For very large datasets, this difference can be the deciding factor for enabling RDB in addition to AOF, even when AOF alone would be sufficient for ongoing durability.
The right persistence configuration follows from the criticality of the stored data, not from a blanket best-practice recommendation. For pure cache instances, where the dataset can be reconstructed at any time from an origin source, persistence is often entirely unnecessary, meaning maximum performance without any tradeoff at all. For short-lived session storage, appendfsync everysec combined with periodic RDB snapshots is usually sufficient, because losing one second of writes is rarely business-critical.
For business-critical primary store data, such as financial transactions or inventory data, appendfsync always can be the right choice despite its latency cost, especially when replication exists as a second layer of protection. This combination of local durability and remote redundancy addresses both the crash-before-fsync risk and the loss of an entire node. In that case, investing in NVMe SSDs to bring fsync latency down to a practical level is worthwhile, rather than choosing slower storage options with high fsync overhead for cost reasons. The combination of hardware choice and persistence configuration ultimately determines how well the tradeoff is resolved for the specific use case.
10. Summary
The persistence performance tradeoff in Redis cannot be resolved, only deliberately shaped. AOF with appendfsync everysec offers the best ratio between durability and latency for most workloads, while always and no mark the two extremes. RDB snapshots cause short but complete blocking through the fork call, whose duration scales with memory size, and copy-on-write can lead to significant temporary memory doubling during a BGSAVE.
Latency spikes caused by persistence can be reliably detected with the built-in latency monitor and INFO persistence, and deliberately scheduling BGSAVE outside load spikes significantly reduces their frequency. The right configuration ultimately depends on the criticality of the data: pure caches can forgo persistence entirely, critical primary data justifies the latency premium of appendfsync always combined with fast hardware.
Persistence-Performance Tradeoffs: The Essentials at a Glance
AOF fsync
everysec as the default, always only for maximum durability requirements despite latency cost.
Fork cost
Scales with resident memory. Schedule BGSAVE during off-peak times, disable THP.
Copy-on-write
Plan at least 20 to 30 percent memory headroom against temporary doubling during BGSAVE.
Monitoring
Continuously export LATENCY HISTORY fork and INFO persistence into Prometheus.