|
|
Log in / Subscribe / Register

Initiating writeback earlier

By Jake Edge
June 26, 2026

LSFMM+BPF

Writeback is the process of ensuring that dirty pages or folios in the page cache are flushed to the disk, so that changes to those files are made persistent. In a filesystem-track session at the 2026 Linux Storage, Filesystem, Memory Management, and BPF Summit, Jeff Layton wanted to discuss whether the writeback operation should be initiated earlier than it is today. The consensus seemed to be that it should be done earlier, but the path toward making that happen was less clear.

Background

In the last few months, Layton began, some new ioctl() commands were added to the debugfs interface for the NFS server (NFSD) that provided the ability to perform "dontcache" I/O (related to RWF_DONTCACHE, which was RWF_UNCACHED originally) and direct I/O (O_DIRECT). The controls were added in debugfs to give developers a chance to try various things before settling on a final interface. Mike Snitzer did most of the work on that, Layton said. The debugfs interface that was eventually added was somewhat different than what was proposed.

Various experiments were run with the new I/O modes to try to quantify which workloads would benefit from them. One thing became clear quickly: dontcache "was pretty awful for writes, at least under NFSD". There are a number of reasons for that, but the main one is that NFS clients send lots of writes in parallel, which end up being written by multiple threads in the server; that did not fit well with that implementation of dontcache for NFSD.

He has some patches in flight that fix the performance problem for RWF_DONTCACHE users; he thanked Jan Kara, Christoph Hellwig, and Ritesh Harjani "who helped me beat that into shape". Since the summit, those patches have been merged for 7.2. One of the characteristics of dontcache is that it immediately submits writes to the device, which was one of the problems encountered by NFSD; all of the threads woke up and submitted I/O, which caused them to queue waiting to be able to submit. The fix puts the data into the page cache until writeback completes, and moves the writeback operation to the flusher threads; that mode now performs better than direct I/O in many cases, Layton said.

[Jeff Layton]

That work started him thinking about doing writeback earlier in general. The number of dirty pages that accumulate is much lower after his fixes, which led him to a question: "are we waiting too long to kick off writes in regular buffered I/O?" Writeback is initiated for buffered I/O when the memory-management subsystem decides that the number of dirty pages is too high; it is also initiated periodically. But "modern memory sizes are huge", so the default dirty limits used are out of date; he thought they had not been updated for 20 years or more. That is really a separate problem and one that administrators "can tune around", but it may make sense to look at changing the defaults.

He wondered if relying on memory management to decide when to do writeback was really the right approach. Maybe the filesystem layer should be more proactive; some calculation based on the backing store and number of dirty pages bound for it could be used to determine when to initiate writeback. The Linux philosophy is that free memory is wasted memory, which is why the page cache, for example, exists, but it is also true that unused disk bandwidth is wasted.

He suggested having a per-superblock dirty limit that was a lot lower than what memory management would normally use. Starting writeback earlier "would help keep us out of the weeds as far as dirty pages go". Outside of the session, Kara had pointed out a potential problem: there are workloads that create lots of temporary files and then delete them. Those files are typically deleted before they get written today, so starting writeback earlier might cause them to be written unnecessarily. A lot of that kind of work is done on tmpfs these days, Layton thinks, so maybe the problem is less critical. Kara had suggested skipping writeback for inodes that have no directory entries (dentries), which might be a way to avoid much of the problem as well.

Discussion

Layton said that he was just putting those ideas out for discussion; he did not have any fixed views on where to go with them. "Any thoughts?"

Ted Ts'o said that he had some ideas, but that they might be somewhat contradictory. He wondered how important it was for the kernel to save battery power by not spinning up disks frequently; he noted that there are few laptops with those kinds of disks anymore so maybe laptop mode is a thing of the past. (It turns out that laptop mode was removed for the 7.0 kernel.)

There are also some kinds of files "where a certain amount of delay is good" because it allows the application to finish writing the file, which helps if the filesystem is doing delayed allocation. So maybe files that are open and still being written to should be deferred for writeback for a time. There are lots of policy questions that would need to be resolved for something like that, however.

Another idea, Ts'o said, might be to try to write at 50% of the backing-device bandwidth, if that can be calculated, to try to ensure that there is headroom so that fsync() latency is reasonably low. One could imagine an auto-tuning process that reserved more headroom for fsync()-heavy workloads. As he noted, some of his ideas "are in tension with one another", so he would prefer that the heuristics used are automatic. Relying on humans to change various parameters is not a good strategy: it "never happens". Layton noted that reserving too much backing-device bandwidth for writes might leave too little for reads, so that is another piece of the puzzle.

Boris Burkov said that he had encountered a problem on a huge-memory system that accumulated lots of dirty pages; "it piled up writeback until it was enormous and the resulting writeback was bad". On March 2, he tried a change that set lower fixed dirty limits, but had to back it all out the next day "because it broke everything", he said to laughter. He was not suggesting that changing the default was a bad idea, because the default is also bad, "but you're going to break something".

Kara asked what specific problem was encountered with the lower limits. Burkov said that it was a system which downloaded and installed packages; its performance regressed after the limits were changed. Kara suggested this might be the temporary-file problem he had raised with Layton earlier; maybe the installation process was using lots of temporary files that would not actually be written with higher limits.

Chuck Lever raised "a converse problem" related to NFSD. There are situations where the NFS clients are sending so many writes that NFSD simply cannot keep up "and there doesn't seem to be an effective pushback from the server to the clients". There are some ways to slow things down with NFSv4 or by adjusting the TCP window, but he thinks there needs to be a way for the virtual filesystem (VFS) layer to slow down writers. Neil Brown had told Lever that there already was a mechanism to do that, but that maybe it was not working correctly. "This room is full of experts who can tell me I'm wrong, go ahead", Lever said with a grin.

Kara said that he "was kind of right" because the feedback mechanism is geared toward local filesystems where the resources available for writes are bounded by the producer sharing the system with the writeback mechanism. For NFS, though, the number of writes that clients can send is only limited by memory. There are mechanisms to throttle writers when too much data is being written, but it is apparently broken for NFS. "Back-pressure from the server does not propagate properly" to the client. A fast-moving conversation between Kara, Layton, and Lever seemed to conclude that there are ways to provide the back-pressure.

Lever asked if there was any way to observe the back-pressure and resulting slowdown. Kara said that there wasn't but that it could be built; he had some suggestions on what kinds of information could be exported to help with that.

Ts'o said that there is decades of research on ways to predict the lifetime of files based on their name and other attributes, which might be useful for determining files that should have their writeback delayed. That would be a heuristic that either requires a decision from a system administrator, "which will never happen", or some kind of automatic determination possibly based on some of the research. It will be important to have some observability into that process as well, he said, "because debugging this is going to require us understanding which of our mysterious heuristics triggered—correctly or not so correctly".

Christian Brauner was concerned that any kind of automatic heuristic configuration in the kernel "that fits all kinds of workloads that exist, is just doomed to fail". He pointed to various control-group parameters that allowed the kernel "to make this user space's problem" and not have to come up with something that will cover everyone's needs. He thinks the mechanisms used by control groups and systemd provide a good model for how various problems like writeback limits should be handled. On the other hand, "we have fadvise(), though no one uses it", Ts'o said to knowing laughter.

Lever asked Layton for more information about why the original dontcache approach for NFSD did not perform well. The basic problem is that immediately performing the write from the thread that receives the client request blocks the thread until the write completes, Layton said. By moving that work to the flusher thread, the server thread can continue to handle requests while the writeback is happening in the flusher.

Lever also wondered how the page-cache entry was dropped once the writeback is completed, since that is what RWF_DONTCACHE is meant to do. There is a folio flag, PG_dropbehind, that tracks whether to keep the folio in the cache after its writeback completes, Layton said. The only real differences between RWF_DONTCACHE writes and buffered writes are the setting of that bit and initiating writeback as soon as possible for RWF_DONTCACHE.

After another minute or two of discussion on some other quick topics, the session ran out of time and concluded.


Index entries for this article
KernelBlock layer/Writeback
ConferenceStorage, Filesystem, Memory-Management and BPF Summit/2026


to post comments

BPF?

Posted Jun 27, 2026 6:19 UTC (Sat) by alonz (subscriber, #815) [Link]

I wonder how come nobody suggested to use BPF for the tunable policies?

This is an area where better block layer scheduling would really help

Posted Jun 27, 2026 11:51 UTC (Sat) by koverstreet (subscriber, #4296) [Link]

When you've got background processes competing with foreground processes, the correct answer is priority scheduling. Trying to solve these problems above the block layer is probably doomed to failure.

Unfortunately, block layer scheduling is a mess - there's a cgroup interface, which doesn't work for in-kernel users like background writeback, and there's a bio interface which has never as far as I can tell actually worked. And there's two (!) competing block IO schedulers - the real one, and blk-writeback-throttling, which is really a scheduler in disguise. The writeback throttling scheduler has the right design for something that wants to limit load to the device to keep latencies low (so fsync is fast), but it has no priority interface (and the interface it does have is one of the more insane things in the block layer).

On top of that, if the device is doing writeback caching, the signal you get for doing wbt-throttle style latency limiting is crap. I've already moved all internal writes in bcachefs to REQ_FUA to avoid causing queuing delays for foreground writes, but writeback probably needs to be FUA too.

Buffer Bloat - NFS style

Posted Jun 27, 2026 12:06 UTC (Sat) by neilbrown (subscriber, #359) [Link] (2 responses)

> .... but he thinks there needs to be a way for the virtual filesystem (VFS) layer to slow down writers. Neil Brown had told Lever that there already was a mechanism to do that.....

The VFS (or maybe the MM) estimates the throughput of each "backing device" and throttles writes when they risk using more than a fair share of the dirty threshold for that one backing device (slower devices get a smaller share). This, as Jan mentioned, works nicely for local applications.

It works for nfsd threads too in that they will be throttled, but they aren't the source of the problem, they are the messenger. If the NFS client keeps sending WRITE requests, more and more nfsd threads will be throttled trying to write, until we run out of threads. With the latest kernels we set a high limit and dynamically grow the thread pool to this limit, so this can be lots of threads each with (e.g.) 1MB of data being written. Once we run out of threads the receive-queue on the socket fills up, and if the client used "nconnect" there might be 16 sockets each of which will have a full receive queue. Then the xmit queues on the client will fill up. Then the applications on the client will start being throttled....

Or maybe the client-side applications will be throttled earlier. Data that hasn't been acknowledged as "safe" the server is accounted as dirty data on the client and should throttle the client application based on measured throughput.

So if the server has a larger dirty-threshold than the sum of the dirty thresholds for all the active clients, then the client applications should get throttled before the server hits the threshold and starts throttling nfsd threads. But if the clients have a larger total dirty threshold, then they will swamp the server's dirty pool and I guess that creates the situation that Chuck sees as problematic.

This feels a lot like "buffer bloat" which affected wifi and similar network links until it was fixed. As I understand it, the various buffers were much bigger than the link could handle in a "reasonable" time, much like the situation where the clients have more total buffer space than the server and generate more load than it can handle. The solution for buffer bloat was (I think) to better estimate end-to-end throughput and limit the total outstanding queue size. Possibly a similar solution would help NFS.

The current throttling is based on a number of bytes or fraction of available memory size. I have occasionally wondered if it would be better to limit it based on time-to-flush. As a throughput estimate is already available, we could limit the number of dirty pages for each "bdi" to the amount of memory which can be flushed in N seconds. When I first looked into the dirty threshold many years ago it was because NFS clients would take a very long to time to fsync a file, and it needs to do this to respond to "stat" - because to get an accurate mtime, it needs to flush pending writes, then ask the server what the timestamp is. So "ls -l" would take a very long time. The work-around was to reduce the dirty-threshold substantially. I really wanted to set a "maximum time that stat() is allowed to take" but I had to set a "maximum number of dirty bytes". If throttling was time-to-flush based, I would have been able to have more meaningful control...

Buffer Bloat - NFS style

Posted Jun 28, 2026 1:20 UTC (Sun) by ebiederm (subscriber, #35028) [Link] (1 responses)

Just in case you are having trouble finding it here is a link to the algorithm that was used to solve buffer bloat.

https://en.wikipedia.org/wiki/CoDel#Derived_algorithms

Your time to flush is almost exactly the maximum time to transit the queue that CoDel uses.

Packet drops can be replaced by throttling user space when userspace is writing.

What is very nice about that approach is that it is auto-tuning. Working on both fast and slow devices with the same parameters.

I have been wondering for years when I see these conversations about block layer throttling come up if a CoDel style solution might fix things.

Given that CoDel works hop by hop it might even be possible to apply to each layer of the I/O stack independently. Given fs vs block vs network could be a real advantage.

Buffer Bloat - NFS style

Posted Jun 28, 2026 2:21 UTC (Sun) by koverstreet (subscriber, #4296) [Link]

CoDeL is what writeback throttling uses.

Unfortunately, it's entirely separate from normal block layer scheduling.

Some clarifications...

Posted Jun 27, 2026 12:45 UTC (Sat) by jlayton (subscriber, #31672) [Link]

As always, Jake did a great writeup, but this is a complex topic. A couple of small clarifications/corrections:

> In the last few months, Layton began, some new ioctl() commands were added...

You don't use ioctl() to set the io_cache_read/write modes. You just write() to them. Unfortunately, they take an integer, and not a string, so you have to peek at the source code to figure out the right values.

> The basic problem is that immediately performing the write from the thread that receives the client request blocks the thread until the write completes,

Not technically true. With the old DONTCACHE code, writes are written to the pagecache, and then the kernel immediately did a filemap_fdatawrite_range() on the range written. That submits them to the device, but doesn't wait for the I/O to complete. With multiple writing threads, they all compete to submit to the device queues, and that makes them perform terribly in aggregate.

With the new model, we just kick the flusher thread and have it do a proportional amount of writeback to what was written. The writers can get back to doing other stuff more quickly, and the writeback side of things is handled by the dedicated thread (or eventually, a set of threads), so there is less contention.

Or you could just do away with NIH...

Posted Jun 27, 2026 18:56 UTC (Sat) by intelfx (subscriber, #130118) [Link] (1 responses)

> Another idea, Ts'o said, might be to try to write at 50% of the backing-device bandwidth, if that can be calculated, to try to ensure that there is headroom so that fsync() latency is reasonably low. One could imagine an auto-tuning process that reserved more headroom for fsync()-heavy workloads. As he noted, some of his ideas ""are in tension with one another"", so he would prefer that the heuristics used are automatic. Relying on humans to change various parameters is not a good strategy: it ""never happens"". Layton noted that reserving too much backing-device bandwidth for writes might leave too little for reads, so that is another piece of the puzzle.

Ground-breaking idea: stop the NIH and just do what zfs does. It does linear write throttling depending on the amount of dirty data, with configurable start/end points (i.e. percentage points of dirty cache size to max dirty size where throttling starts, and where writes are 100% throttled). This works swimmingly in practice, and certainly better than the current behavior of "do nothing and let the occasional slow USB stick get swamped with gigabytes of dirty data pending writeback").

Of course, there's room for improvement. If the thing could be expressed in seconds rather than bytes, it'd be even better (did anyone say "bufferbloat"?).

Or you could just do away with NIH...

Posted Jun 27, 2026 21:32 UTC (Sat) by Cyberax (✭ supporter ✭, #52523) [Link]

The problem with ZFS is that it owns the cache completely. Its ARC is not used for mmap, so it can be the single writeback authority.

That's the issue in question: there's no nice single control point in real Linux filesystems for writeback throttling.

Seems like a step backwards.....

Posted Jun 30, 2026 5:28 UTC (Tue) by dgc (subscriber, #6611) [Link]

> He has some patches in flight that fix the performance problem for RWF_DONTCACHE users;
> he thanked Jan Kara, Christoph > Hellwig, and Ritesh Harjani ""who helped me beat that into
> shape"". Since the summit, those patches have been merged for 7.2. One of the characteristics
> of dontcache is that it immediately submits writes to the device, which was one of the problems
> encountered by NFSD; all of the threads woke up and submitted I/O, which caused them to
> queue waiting to be able to submit. The fix puts the data into the page cache until writeback
> completes, and moves the writeback operation to the flusher threads; that mode now performs
> better than direct I/O in many cases, Layton said.

Huh. I bet that neuters concurrent/non-blocking RWF_DONTCACHE performance on high IOPS storage subsystems (i.e. millions of IOPS).

i.e. this is effectively changing RWF_DONTCACHE write IO from no-wait write-through to short-period write-behind aggregation. As such, it will re-introduce all the "write IOPS rate limited by flusher thread" issues that the original write-through based RWF_DONTCACHE implementation broke us away from.

That is, the flusher thread is a single thread, and it becomes CPU bound on IO submission at somewhere between 100-400k IOPS (depending on whether delayed allocation is needed or not). It does not matter how many files are being written to concurrently nor the level of buffered write concurrency - once it is CPU bound buffered writes on that entire filesystem cannot be serviced any faster.

In comparison, when IO submission is done in write() context, writes across multiple files are submitted to the storage concurrently. For applications that are optimised for write-through IO, buffered write IO submission scales out to 100-400k IOPS *per file* rather than *per-filesystem*.

As I just implied, one can't simply change application write() IO from write-back to write through and expect it to magically perform better. That is one of the reasons this change was behind a new IO flag. Write-through IO has issues with head-of-line blocking in the IO path, and if this isn't considered in the application IO engine design then worker thread starvation and service latency issues will abound. The problems with the NFSD imply that all they did was add RWF_DONTCACHE to the existing threaded synchronous IO engine and, as expected, head-of-line blocking in the IO path occurred...

Neutering RWF_DONTCACHE IO concurrency because a single application wasn't designed for write-through IO is, IMO, a poor architectural choice.

FWIW, the "flusher thread limits write-back IO performance" behaviour is right there in the commit message:

> Competing writers (dontcache vs buffered, separate files):
> Before After
> buffered writer 868 433 MB/s
> dontcache writer 415 433 MB/s
> Aggregate 1,284 866 MB/s

This degradation in aggregate IO isn't because of "fair writeback bandwidth sharing" as the commit message states - it's because the flusher thread cannot submit IO any faster than 866MB/s. The storage sure can handle more IO, but the flusher thread can't submit IO fast enough to make it go any faster.

If throughput was limited by incoming write() CPU overhead, the adding a second file and writer would increase overall write throughput. But it didn't, so write throughput is most likely limited by writeback IO submission. The fact that writeback bandwidth is evenly shared isn't a surprise - that's working exactly as design: fairly share the available writeback bandwidth evenly between all incoming writers.

So, yeah, this seems like a big step backwards to me....

-Dave.

Tuning

Posted Jul 3, 2026 9:46 UTC (Fri) by claudex (subscriber, #92510) [Link]

> That is really a separate problem and one that administrators ""can tune around"", but it may make sense to look at changing the defaults.

For an administrator to change the default, you have to provide extensive documentation on what make sense to change and what are the drawbacks. Because, the default value are thought to be set for a reason (and the example for trying to change them to revert them the next day is a good example).


Copyright © 2026, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds