From: Salvatore Dipietro <dipiets@amazon.com>
To: <jack@suse.cz>
Cc: <abuehaze@amazon.com>, <agruenba@redhat.com>,
<akpm@linux-foundation.org>, <alisaidi@amazon.com>,
<almaz.alexandrovich@paragon-software.com>, <apopple@nvidia.com>,
<blakgeof@amazon.com>, <brauner@kernel.org>, <byungchul@sk.com>,
<cem@kernel.org>, <david@kernel.org>,
<dipietro.salvatore@gmail.com>, <dipiets@amazon.it>,
<djwong@kernel.org>, <dlemoal@kernel.org>,
<exfat@lists.linux.dev>, <fuse-devel@lists.linux.dev>,
<gfs2@lists.linux.dev>, <gourry@gourry.net>, <hyc.lee@gmail.com>,
<joshua.hahnjy@gmail.com>, <jth@kernel.org>, <liam@infradead.org>,
<linkinjeon@kernel.org>, <linux-fsdevel@vger.kernel.org>,
<linux-kernel@vger.kernel.org>, <linux-mm@kvack.org>,
<linux-xfs@vger.kernel.org>, <ljs@kernel.org>,
<matthew.brost@intel.com>, <mhocko@suse.com>, <miklos@szeredi.hu>,
<naohiro.aota@wdc.com>, <ntfs3@lists.linux.dev>,
<ntfs@lists.linux.dev>, <pfalcato@suse.de>, <rakie.kim@sk.com>,
<ritesh.list@gmail.com>, <rppt@kernel.org>, <rvvandan@amazon.com>,
<ryan.roberts@arm.com>, <sj1557.seo@samsung.com>,
<surenb@google.com>, <vbabka@kernel.org>,
<viro@zeniv.linux.org.uk>, <willy@infradead.org>,
<ying.huang@linux.alibaba.com>, <yuezhang.mo@sony.com>,
<ziy@nvidia.com>, <kirill@shutemov.name>, <dgc@kernel.org>
Subject: Re: [PATCH 0/2] mm, iomap: account dirty at page granularity for per-block dirty folios
Date: Fri, 13 Nov 2026 15:50:15 +0000 [thread overview]
Message-ID: <20261113155015.516481-1-dipiets@amazon.com> (raw)
In-Reply-To: <mcskrz2zbav5r3xtlwqytyotgjcmmi3wapeibkqornuhb3qp5y@qk22j77kvkqx>
Hi all,
Thanks Jan, Pedro, Dave, David and Kiryl for the feedback.
Following Dave's question about how PostgreSQL gets large folios on
files it writes 8 KiB at a time, we looked deeper and found a
reproducer that needs only fio and a single disk.
What happens:
1. A buffered sequential read (VACUUM, a backup, a scan) builds large
folios through readahead. They stay large in the page cache until
they are evicted.
2. The first small write into one of these clean folios charges the
whole folio as dirty: a 4 KiB write into a 256 KiB folio charges
256 KiB, while writeback only writes the dirty 4 KiB.
3. With random writes, most writes land in a different clean folio,
so the dirty count reaches the limits with little real data.
balance_dirty_pages() then throttles the writers, and they end up
paced by how many folios writeback can clean per second rather
than by the bytes to write.
Reproducer: XFS on one EBS gp3 volume (3000 IOPS), m8g.8xlarge
(32 CPUs, 128 GiB), default read_ahead_kb=128. fio's 1 MiB reads grow
the readahead window up to the device's max_sectors_kb (256 KiB), so
the read builds 256 KiB folios.
; create.fio: 16 x 3 GiB files, direct I/O so nothing is cached
[global]
directory=/mnt/xfs/fio
filename_format=f.$jobnum
size=3072m
numjobs=16
group_reporting=1
[create]
rw=write
bs=1m
direct=1
ioengine=libaio
iodepth=16
; load.fio: buffered sequential read; builds the large folios
[global]
directory=/mnt/xfs/fio
filename_format=f.$jobnum
size=3072m
numjobs=16
group_reporting=1
[load]
rw=read
bs=1m
direct=0
ioengine=psync
; randwrite.fio: random 4 KiB buffered writes, 32 x 312 = ~10k IOPS
; invalidate=0 keeps the cached folios when fio opens the files
[randwrite]
directory=/mnt/xfs/fio
filename=f.0:f.1:f.2:f.3:f.4:f.5:f.6:f.7:f.8:f.9:f.10:f.11:f.12:f.13:f.14:f.15
rw=randwrite
bs=4k
direct=0
ioengine=psync
invalidate=0
numjobs=32
rate_iops=312
time_based=1
runtime=120
group_reporting=1
To reproduce it, run (as root):
fio create.fio
sync; echo 3 > /proc/sys/vm/drop_caches
fio load.fio # skip this step for the control run
fio randwrite.fio
fio asks for ~10k write IOPS, more than the volume's 3000, so dirty
data builds up in both runs. In the control run (no load step) each
4 KiB write creates a 4 KiB folio: after 120 s only 3.3G is dirty,
below the 12.2G background threshold, and nothing is throttled. After
the load step the same writes land in 256 KiB folios, and each write
into a clean folio is charged 256 KiB: the dirty count passes the
background threshold after 6 s and writers are throttled from 13 s.
Results on v7.4-rc2, one run per kernel, each on a fresh instance.
Writer sleep is the total balance_dirty_pages() pause time over the
32 writers during the 120 s write phase:
write p99 / max writer max folio
IOPS lat ms sleep s dirty size
baseline, control 9,984 0.01 / 0.30 0 3.3G -
baseline 4,061 60 / 160 179 23.5G 256K
v1 (this series) 9,984 0.01 / 0.67 0 3.6G 256K
max order cap 64K 7,071 50 / 280 73 22.3G 64K
max order cap 16K 9,984 0.02 / 0.05 0 11.9G 16K
split on write 9,984 0.02 / 0.17 0 3.6G 256K
evict on write 9,984 0.03 / 0.19 0 3.3G 256K
POSIX_FADV_RANDOM 9,984 0.02 / 0.05 0 3.3G 4K
The control run gave 9,984 IOPS and max latency up to 0.30 ms on every
kernel.
- max order cap: the global cap in mapping_max_folio_order() that Jan
suggested trying. At 64 KiB a 4 KiB write still charges 16x and is
throttled. At 16 KiB this run is not throttled, but dirty reached
11.9G, just below the 12.2G background threshold. The cap that
avoids throttling depends on the write size, and it removes large
folios for every file, including ones that never see small writes.
- split on write: when a buffered write (iomap opts in, not for
zeroing or unshare) finds a clean, uptodate, unmapped folio at least
4x the write size, folio_split() it down to the write's order before
dirtying it. The rest of the folio stays cached in large folios.
On the concerns Jan raised about splitting [1]: split on write only
splits clean, uptodate, unmapped folios, so it needs no new
filesystem state beyond an opt-in flag in iomap, and if
folio_split() fails the write simply proceeds as today; evict on
write avoids splitting altogether.
- evict on write: same trigger, but mapping_evict_folio() the folio
instead of splitting it (~40 lines). This discards the folio's
other cached data; nothing reads it back in this test, so the read
cost does not show here.
- POSIX_FADV_RANDOM (Dave's question): fio's fadvise_hint=random on
the load job. It avoids the problem without a kernel change, but
only where the application opts in: the hint is per open file, so
every process that reads a file must issue it, and a reader that
doesn't (a backup or copy tool, for example) still builds large
folios. Every application that reads files sequentially and then
rewrites them in place would have to adopt it, and keeps the
regression until it does. It also turns readahead off for those
files, so their sequential reads lose readahead.
Split and evict on write keep whole-folio accounting and act at write
time, so they also cover large folios already in the page cache,
whoever built them, without each application having to opt in. Both
only touch clean, uptodate, unmapped folios at least 4x the write
size, and only on iomap buffered writes. Split keeps the rest of the
folio cached at the cost of the split; evict is simpler but drops it.
I'd appreciate your opinion on these options, or any other direction
you think is more appropriate.
Thanks,
Salvatore
[1] https://lore.kernel.org/all/oocwnxqgd4klrem6hkya3sddqoxkosml4ysz23if4j7wj2qdwi@5skgc4ftu7io/
AMAZON DEVELOPMENT CENTER ITALY SRL, viale Monte Grappa 3/5, 20124 Milano, Italia, Registro delle Imprese di Milano Monza Brianza Lodi REA n. 2504859, Capitale Sociale: 10.000 EUR i.v., Cod. Fisc. e P.IVA 10100050961, Societa con Socio Unico
next prev parent reply other threads:[~2026-11-13 15:51 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 11:27 [PATCH 0/2] mm, iomap: account dirty at page granularity for per-block dirty folios Salvatore Dipietro
2026-10-27 11:27 ` [PATCH 1/2] mm: add infrastructure for per-dirty-page accounting Salvatore Dipietro
2026-10-27 11:27 ` [PATCH 2/2] mm, iomap: account dirty at page granularity for per-block dirty folios Salvatore Dipietro
2026-10-27 12:22 ` [PATCH 0/2] " David Hildenbrand (Arm)
2026-10-27 13:45 ` Kiryl Shutsemau
2026-10-27 13:59 ` Pedro Falcato
2026-11-03 11:47 ` Jan Kara
2026-11-13 15:50 ` Salvatore Dipietro [this message]
2026-10-27 23:10 ` [syzbot ci] " syzbot ci
2026-10-28 21:59 ` [PATCH 0/2] " Dave Chinner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261113155015.516481-1-dipiets@amazon.com \
--to=dipiets@amazon.com \
--cc=abuehaze@amazon.com \
--cc=agruenba@redhat.com \
--cc=akpm@linux-foundation.org \
--cc=alisaidi@amazon.com \
--cc=almaz.alexandrovich@paragon-software.com \
--cc=apopple@nvidia.com \
--cc=blakgeof@amazon.com \
--cc=brauner@kernel.org \
--cc=byungchul@sk.com \
--cc=cem@kernel.org \
--cc=david@kernel.org \
--cc=dgc@kernel.org \
--cc=dipietro.salvatore@gmail.com \
--cc=dipiets@amazon.it \
--cc=djwong@kernel.org \
--cc=dlemoal@kernel.org \
--cc=exfat@lists.linux.dev \
--cc=fuse-devel@lists.linux.dev \
--cc=gfs2@lists.linux.dev \
--cc=gourry@gourry.net \
--cc=hyc.lee@gmail.com \
--cc=jack@suse.cz \
--cc=joshua.hahnjy@gmail.com \
--cc=jth@kernel.org \
--cc=kirill@shutemov.name \
--cc=liam@infradead.org \
--cc=linkinjeon@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-xfs@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=matthew.brost@intel.com \
--cc=mhocko@suse.com \
--cc=miklos@szeredi.hu \
--cc=naohiro.aota@wdc.com \
--cc=ntfs3@lists.linux.dev \
--cc=ntfs@lists.linux.dev \
--cc=pfalcato@suse.de \
--cc=rakie.kim@sk.com \
--cc=ritesh.list@gmail.com \
--cc=rppt@kernel.org \
--cc=rvvandan@amazon.com \
--cc=ryan.roberts@arm.com \
--cc=sj1557.seo@samsung.com \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=viro@zeniv.linux.org.uk \
--cc=willy@infradead.org \
--cc=ying.huang@linux.alibaba.com \
--cc=yuezhang.mo@sony.com \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox