Re: [PATCH] mm: memcg: use ratelimited stats flush in obj_cgroup_may_zswap()

From: Joanne Koong

Date: Thu Aug 20 2026 - 18:19:02 EST


On Tue, Aug 18, 2026 at 11:35 AM Yosry Ahmed <yosry@xxxxxxxxxx> wrote:
>
> On Mon, Aug 17, 2026 at 6:53 PM Song Hu <husong@xxxxxxxxxx> wrote:
> >
> >
> >
> > 在 2026/8/18 00:04, Shakeel Butt 写道:
> > > On Mon, Aug 17, 2026 at 09:18:43PM +0800, Song Hu wrote:
> > >> obj_cgroup_may_zswap() runs on every folio swapped out through
> > >> zswap. For each ancestor with a non-max zswap.max, it flushes the
> > >> cgroup rstat hierarchy synchronously with force=true, which skips
> > >> the ratelimit inside __mem_cgroup_flush_stats(). In a swap storm
> > >> with zswap.max configured, a container takes the global rstat lock
> > >> on every swapped-out folio.
> > >
> > > Any reason you are limiting zswap through zswap.max?
> > >
> >
> > Mostly fairness on a shared pool: zswap.max_pool_percent is global
> > only, so on a multi-tenant host one cgroup's cold anonymous memory
> > can soak the pool and crowd out the others. zswap.max is the only
> > per-cgroup control over that share; memory.max bounds the total
> > footprint, not the share of the pool.
> >
> > >>
> > >> zswap_shrinker_count() had the same pattern and switched to
> > >> mem_cgroup_flush_stats_ratelimited() in commit ea80da363a1f
> > >> ("mm/zswap: use ratelimited stats flush in zswap_shrinker_count()"),
> > >> where the same flush on the shrinker side showed up at 2.88% of
> > >> kernel cycles under osq_lock on a 96-core machine.
> > >>
> > >> Measured on a KVM guest with a swap storm under a cgroup with
> > >> zswap.max set: obj_cgroup_may_zswap() was entered 198,977 times
> > >> before the patch and 198,968 times after, while
> > >> __mem_cgroup_flush_stats() was entered 281,017 times before and
> > >> 80,445 times after. The removed 200,572 flushes match the store
> > >> attempt count almost exactly; the remainder comes from other stats
> > >> readers in the swap path.
> > >
> > > This is a known issue. Using ratelimited interface also comes with a drawback
> > > that the kernel may react on stale information and the consequences might be
> > > unneeded oom-kills.
> > >
> > > There was orthogonal discussion on moving zswap limit enforcement away from
> > > rstat. Yosry, any updates on that?
>
> I am not actively looking into that, but Joanne was looking into
> AFAICT. I will respond to the thread there and CC Song as well.
>

For this zswap stat, I ran some benchmarks comparing 4 approaches
(switchable behind a runtime knob [1]):
a) rstat + forced flush (baseline aka what the tree does today)
b) rstat + ratelimited (Song's proposal)
c) hierarchical per-CPU (Yosry's idea from [2])
d) page counters (following what all the other memcg limits do)

For the setup, the benchmark creates a cgroup chain at depth X with
memory.max set to 1G on the leaf and memory.zswap.max set to 512M on
every level, and spins up 20 processes there that each allocate 100
MiB, fault it in, and touch every page four more times. With that 2000
MiB against the 1 GiB memory.max, it triggers reclaim continuously and
makes the swap traffic go through zswap. The machine I ran this on had
80 CPUs.

I also ran it with no memory.zswap.max set (ie no reads triggered,
only update path runs) - as I understand it, this is the configuration
that is more often used in practice.

These are the results I saw:

kernel cpu time (in ns) per zswap store, zswap.max set
a) b) c) d)
depth 1 567,116 35,604 35,841 34,995
depth 2 1,082,007 35,507 37,803 36,209
depth 4 2,153,329 37,591 40,117 39,893
depth 8 4,211,692 34,963 41,070 44,211
depth 32 15,728,220 53,575 94,685 65,946

kernel cpu time (in ns) per zswap store, no zswap.max (update path only):
a) b) c) d)
depth 1 34,787 34,062 34,930 35,630
depth 2 34,913 35,061 36,404 36,360
depth 4 36,422 36,809 36,922 37,483
depth 8 42,177 34,440 36,679 40,793
depth 32 55,309 55,321 57,671 59,190

c) and d) are for the most part pretty comparable to b) without
introducing the staleness problem of b). Between c) and d), I think d)
ends up outperforming c) as the # of cpus and depth gets larger.

I'm seeing that all the other memory limits (eg memory.swap.max,
memory.max, etc) are already using page counters. Is there a reason
the zswap stat can't? If not, does it make sense for the zswap stat to
switch over to using page counters?

Thanks,
Joanne

[1] https://github.com/joannekoong/linux/commit/9092e0057f038ffca43ae3f35faf58c33b1d7c9b
[2] https://lore.kernel.org/linux-fsdevel/anpsLy_pCanocgS1@xxxxxxxxxx/

> > >
> >
> > Fair point. The direction I had considered is the benign one -
> > stale low after a burst of stores overshoots admission for a cycle.
> > But after a burst of writebacks the folded value can be stale high
> > too, and premature rejection with zswap.writeback off or swap.max
> > tight ends in a memcg OOM kill that accurate stats would have
> > avoided. may_zswap() is admission control rather than a hint, so
> > the guarantee matters more here than for the shrinker side
> > of ea80da363a1f.
> >
> > If moving the limit enforcement off rstat is happening, that
> > supersedes this patch and I am happy to drop it. Otherwise it can
> > serve as a stopgap for the per-folio lock cost - your call, and
> > curious where Yosry's discussion stands.
>
> Not sure how much progress is being made there, but I would really
> like to stop the proliferation of ratelimited flushing, especially in
> this case where it can cause OOM kills.