...

Linux Dirty Ratio and Dirty Background Ratio: Fine-Tuning for Optimal Write Performance

I show how linux dirty and use the dirty background ratio to control the page cache, thereby influencing write throughput, latency, and data integrity. This allows you to set specific thresholds that trigger flushes in a timely manner, prevent deadlocks, and improve the write performance of your workloads.

Key points

To start, I'll briefly summarize the key points before delving deeper into the topic.

  • Dirty Pages It buffers writes in RAM and aggregates many small accesses into more efficient I/O operations.
  • dirty_background_ratio It launches flusher threads in the background, thereby limiting the amount of junk data without the user noticing.
  • dirty_ratio slows down writing processes if the hard limit is exceeded.
  • Relation Both values determine latency spikes, throughput, and buffer size.
  • Bytes Variants (dirty_bytes) provide finer, absolute control on large servers.

Understanding "Dirty Pages"

When a process writes data, it first ends up in the Page cache and are marked as „dirty“ until the kernel can flush them to the storage device. This buffering speeds up applications because RAM responds faster than any SSD or HDD, and small writes are consolidated into large, sequential transfers. I always keep in mind how much „dirt“ I allow, because too much buffering can lengthen queues or increase the risk of unsaved data in the event of a crash. Understanding how this works helps you make better decisions regarding writeback, latency, and storage pressure. A brief background article on the Writeback Cache helps to clearly categorize this mechanism.

Data Security, fsync, and the Crash Window

These thresholds affect not only performance but also your risk window. I estimate it using a simple rule of thumb: the maximum amount of unsaved data divided by the sustained device throughput roughly gives the time it takes for the buffer to empty. Example: If I allow 4 GB of dirty data and the target medium reaches 500 MB/s, the full write operation takes about 8 seconds. During this time, the most recent writes could be lost in the event of a power outage or kernel panic.

Applications can close the window by fsync() or fdatasync() reduce, because these accesses force the file system to write data (and, depending on the journaling mode, metadata as well) to the storage medium. This is more resource-intensive, but essential for databases or journals. I make sure that my dirty limits match the sync behavior: Frequent fsync()-Views benefit from lower dirty_ratio, so that the kernel doesn't throttle further when data is being persisted regularly anyway. Conversely, for logs that are heavy on appends and rarely flush, I can allow larger buffers—always keeping in mind the accepted risk of data loss.

Barriers and write sequences are also important: Modern file systems use FUA/flush commands to properly clear controller caches. On media without power-loss protection, large buffers increase the risk; with PLP or write cache protection, larger buffers are often acceptable.

Dirty Background Ratio: The Soft Threshold

With dirty_background_ratio I specify the percentage of available memory at which flusher threads begin writing in the background. This value does not block applications but quietly starts cleanup operations to prevent the buffer from overflowing. Lower numbers result in more frequent but more consistent background writing and smooth out latency spikes. Higher numbers allow for more buffering, which increases throughput for large sequential writes but can trigger significant I/O spikes during sudden flushes. Typically, default values are around ten percent, but I adjust this limit depending on the storage medium, workload, and security requirements.

Dirty Ratio: The Hard Brake

The parameter dirty_ratio marks the threshold at which the kernel throttles writing processes until enough pages have been flushed back. This hard limit protects memory from a flood of non-persistent data and thus directly affects applications as soon as they attempt to continue producing data. For databases, I tend to set this value fairly low so that queries maintain consistent response times and avoid long flush phases. For backup jobs, on the other hand, I use more generous buffers to efficiently transfer large blocks. Typical defaults range from twenty to forty percent, but I always adjust this range to the specific load.

Interactions and Typical Relationships

Both thresholds function as Tandem and only take full effect when combined. I always set `dirty_background_ratio` lower than `dirty_ratio` so that the kernel starts background processing in a timely manner and the hard limit is rarely triggered. As a rule of thumb, I often choose a value between one-quarter and one-half of the hard limit—for example, 5–10 to 20. This way, writeback starts early enough without unnecessarily reducing throughput. If you get this ratio wrong, you’ll either experience throttling too early or background processing that starts too late, resulting in noticeable latency spikes.

Per-Device Control and Block-Layer Units

In addition to the global limits, it’s worth taking a look at the device level. Linux distributes the dirty load across so-called Backed Devices (bdi). In /sys/class/block//bdi/ I find parameters such as max_ratio, which determine how much of the globally allocated "dirty budget" a single device is allowed to use. On systems with both slow and fast drives running in parallel, I limit the slow drives so they don't become a bottleneck.

Also relevant is block-layer throttling via /sys/block//queue/wbt_lat_usec (Writeback Throttling). This allows me to set a target latency; the kernel then throttles the write load when it exceeds this target time. For SATA HDDs, I like to set conservative values to protect interactivity. On very fast NVMe drives, I disable or increase the target latency so that the controller can take full advantage of its parallelism. I choose the I/O scheduler (mq-deadline, BFQ, none) accordingly: BFQ helps with interactive systems with mixed workloads, while none or mq-deadline often performs best for pure throughput jobs on NVMe.

The interplay is crucial: If dirty_background_ratio Even though the values are low, visible bottlenecks still occur because the device is aggressively limited by WBT. I therefore calibrate both levels together—global dirty limits for buffer size and the block layer for latency protection.

Ratio vs. Bytes: Default Values and Variants

On systems with a lot of RAM Percentage values quickly add up to large absolute amounts. In that case, I prefer to set absolute upper limits using `dirty_bytes` and `dirty_background_bytes` to clearly limit the buffer size to about 2–8 GB. This decouples the control mechanism from highly fluctuating memory configurations and keeps the amount of non-persistent data predictable. The choice remains dynamic: For small servers with little RAM, percentages are often entirely sufficient. Those with high capacity often find that using byte values makes planning easier.

Parameters Meaning Typical defaults When should you change it? Note
vm.dirty_background_ratio Launch of the Background Flush as a percentage ≈ 10% In the case of latency fluctuations or very fast SSDs/NVMe drives Lower = smoother latency, higher = more buffer
vm.dirty_ratio Hard Throttle Limit as a percentage ≈ 20–40% Lower for databases, higher for backups Too high → May cause blockages during a flush
vm.dirty_background_bytes Start of the background flush in Bytes Disabled when Ratio is used Large RAM, fixed buffer targets Overrides ratio parameters
vm.dirty_bytes Hard throttling limit in Bytes Disabled when Ratio is used Large amount of RAM, configurable upper limit Overrides ratio parameters

Workload Scenarios and Recommendations

Sequential write workloads such as Backups benefit from large buffers and moderate background writing, because the kernel can write to the medium in large chunks. I often set `dirty_ratio` between 30 and 40 percent and `dirty_background_ratio` between 10 and 20 percent. Databases and small random I/O applications thrive on predictable latency, so I set hard I/O to 10–15 percent and soft I/O to 3–5 percent. For mixed web and app servers, 15–20 percent hard and 5–10 percent soft prove to be a good compromise. These ranges serve as a starting point; after that, the actual performance of your system is what matters.

File System Considerations and Mount Options

The writeback path ends in the file system—whose strategy determines latency and security. Ext4 with data=ordered (Default) writes user data before journal commits; data=writeback reduces latency but puts old data at risk after crashes. The parameter commit= (seconds) controls how often the journal is flushed. Shorter intervals reduce data loss but require more I/O. XFS uses a well-developed log design; large logbsize and proper alignment help with throughput jobs. Btrfs bundles writes using copy-on-write—which stabilizes latencies but can lead to fragmentation with small random writes and limited SSD capacity. Options such as nodatacow can help with specific paths or targeted defragmentation when latency spikes occur.

I also note that relatime/noatime (reduces metadata writes), lazytime (delayed mtime/atime is more persistent), and journaling barriers. Correct cache semantics are crucial, especially on RAID controllers or in VMs: Incorrectly configured write caches can undo all the work done through "dirty tuning.".

Direct I/O, O_SYNC, and Application Behavior

Not every request goes through the page cache. With O_DIRECT or O_SYNC/O_DSYNC Some processes bypass parts of the cache or require immediate persistence. Databases typically write a WAL/redo log synchronously and data areas asynchronously. I calibrate dirty boundaries specifically for asynchronous paths, while I guarantee low latency for synchronous paths using fast journals (NVMe, dedicated LUNs). When applications very frequently fsync() When accessing data, large buffers are of little help—latency then depends more on the controller, queue depth, and I/O scheduler than on dirty_ratio.

Practical Tuning, Step by Step

Before making any changes, I check the actual values with sysctl vm.dirty_ratio and sysctl vm.dirty_background_ratio, to document the initial state. For quick tests, I write values directly to /proc/sys/vm/for instance echo 15 > /proc/sys/vm/dirty_ratio and echo 5 > /proc/sys/vm/dirty_background_ratio. If the adjustment is permanent, I'll save it in /etc/sysctl.conf or /etc/sysctl.d/*.conf. I implement changes using sysctl -p immediately so that I can measure the effect in a timely manner. Anyone who delves deeper into the topic of system rules will benefit from practical tips on Sysctl Tuning on production servers.

Deriving Values: Sample Calculations

I like to start with tangible specifications. Example 1: Web/app server with 64 GB of RAM and NVMe. The goal is low latency. I set dirty_background_bytes=1073741824 (1 GB) and dirty_bytes=3221225472 (3 GB). With a sustained NVMe throughput of 2 GB/s, that means it takes about 0.5–1.5 seconds to empty—good for interactive workloads. Example 2: Backup node with 128 GB of RAM, fast SATA RAID at 800 MB/s. I choose dirty_background_ratio=10, dirty_ratio=35. In absolute terms, that's about 12.8 GB and 44.8 GB; the RAID takes 16–56 seconds to empty. That's okay, because the job isn't interactive.

Example 3: Database server with 256 GB of RAM, separate journal on NVMe, data on an SSD array. I set an absolute limit to avoid outliers: dirty_background_bytes=2147483648 (2 GB), dirty_bytes=8589934592 (8 GB). This keeps the crash window size predictable and reduces sudden slowdowns during checkpoints.

Writeback Timing and Related Parameters

In addition to the limit values, the following factors influence Timer the writeback behavior and, consequently, the user experience of the applications. With vm.dirty_writeback_centisecs I control the interval at which the kernel flusher is woken up, while vm.dirty_expire_centisecs defines the maximum age dirty pages are allowed to reach. Shorter intervals result in more frequent but smaller flushes; longer intervals save on I/O calls but risk larger batches. I only adjust these values if measurements reveal genuine drawbacks, such as flushes occurring too infrequently on fast NVMe drives. Taking a methodical approach here helps avoid swings between overly aggressive and overly sluggish writeback activity.

Monitoring and Fine-Tuning

After making adjustments, I observe continuous the key metrics used to highlight successes and side effects. In /proc/meminfo I check „Dirty“ and „Writeback“ to see buffer levels and active flushes. Tools like iostat, sar, or atop show me throughput, queues, and latency trends. This post on Analyze I/O Wait. Only after analyzing this data do I adjust the limits—either lowering or raising them—in small increments to avoid any unexpected side effects.

Containers, cgroups, and Fair Resource Allocation

In container environments, workloads share the same kernel mechanisms. Cgroup writeback ensures that dirty pages are attributed to the source. I use the cgroups’ I/O controllers (blkcg) to limit bandwidth or IOPS per container when individual tenants are buffering too aggressively. Absolute byte limits at the host level (dirty_bytes) prevent a single guest from using up the entire dirty budget. In addition, I limit memory via memory.max, so that Writeback does not wait for a global flush to react. The goal remains: No guest load should trigger host-wide throttling of the dirty_ratio enforce.

Hosting Environments and VMs

In multi-tenant setups and VMs, I pay attention to Overbooking for RAM and I/O, because percentage-based limits have different effects in those areas. Absolute byte limits can prevent individual guests from building up too much buffer and slowing down their neighbors. I take storage deduplication, ballooning, and controller caches into account because they override buffering effects. For managed servers, it pays off when the provider sets sensible defaults so that customers experience consistent response times. Those who operate their own nodes benefit from clearly defined profile settings for each workload class.

Common Misconceptions and Pitfalls

  • „More buffer = ever-increasing throughput.“ This is incorrect for workloads that are heavily random or for devices with a small queue depth. Buffers that are too large cause flush bursts and queues.
  • „dirty_ratio does not affect reads.“ Indirectly, yes: Aggressive writeback phases displace cache pages and increase read latencies.
  • „Bytes and reason go hand in hand.“ No. If you set the "Bytes" variants, they override the "Ratio" counterparts. Keep it clear.
  • „fsync() makes dirty limits irrelevant.“ No. While frequent syncs reduce the risk window, the remainder of the load is still subject to the limit values.
  • „A fast storage device solves everything.“ Not if the block layer throttles (WBT) or the file system is mounted suboptimally.
  • „Drop_caches is a tuning tool.“ Clearing the cache skews measurements and exacerbates latency spikes. I avoid doing this in production.

Troubleshooting: Common Symptoms and Solutions

Pile up Latency peaks, I first lower the background threshold so that flushes start earlier and large write bursts occur less frequently. If applications experience intermittent bottlenecks, the hard limit is usually set too high, or the storage device cannot handle the resulting flush bursts. In such cases, I lower the `dirty_ratio`, check the read-ahead settings, and review the file system journaling options. With very fast NVMe hardware, I gradually increase the background threshold so as not to artificially cap throughput. After each change, I rely on the measurement data, not on gut feelings.

Brief summary for practical application

With few Adjusting screws I can control how Linux buffers write operations, when flushers start, and when the kernel throttles performance. The Dirty Background Ratio ensures gentle cleanup, while the Dirty Ratio places stricter limits on RAM usage. The relationship between these two values determines whether your system prioritizes consistent latencies or maximum throughput. I document the defaults, make changes in small increments, and consistently analyze the measurements. The result is a configuration that sensibly balances workload, medium, and risk—and delivers noticeably faster performance in practice.

Current articles