Re: Path forward for Virtualized Swap?

From: Johannes Weiner

Date: Fri Sep 25 2026 - 16:25:11 EST


On Sat, Sep 26, 2026 at 03:21:30AM +0800, Kairui Song wrote:
> On Fri, Sep 25, 2026 at 11:41:31AM +0800, Johannes Weiner wrote:
> >
> > Thanks for your thoughtful email, Kairui.
>
> Hello Johannes,
>
> > > I also want to separate two things that I think got bundled together
> > > here: not requiring a physical slot behind a compressed entry, and not
> > > charging the raw size to the swap counter. The first one is great, yeah,
> > > and it's exactly the part we want, it's what makes compression usable
> > > without provisioning disk. The second one is a policy change, maybe it's
> > > not needed for the first stage, charging a cgroup for the
> > > memories it has offloaded doesn't require any slot to exist behind them.
> > > If someone wants to run memory compression with no disk at all,
> > > memory.swap.max defaults to max, so that still works fine, right?
> >
> > I think what we found out over the course of this discussion is that
> > people have been using memory.swap.max in two ways. Regardless of what
> > we do, we will "break" one side.
> >
> > (1) The usecase you're describing. Use memory.swap.max, combined with
> > memory.max, to set a "total", predictable footprint of in-use
> > application address space. You can mmap whatever you want, but the
> > number of unique pages you can touch is limited to this sum. And you
> > can control residency vs non-residency through the invididual values
> > of those settings. If compressed entries are not included, this
> > usecase will break.
>
> Right, thanks for the reply! residency vs non-residency is one of
> the issues here. It's also about how we consider these two kind of
> resources to balance the scheduling of containers.

Ack.

> > (2) The use case we have. Use memory.swap.max to divide a finite space
> > in storage. We only have so much space on disk, and we need to manage
> > fair access. Note that this isn't about speed. We have a mix of
> > containers where some use writeback and others do not. The ones who
>
> Same for us, the usage is mixed.
>
> > write back to the swapfile need to be able to get their fair share -
> > not more, not less. Including something that doesn't actually consume
>
> Is that writeback compression rate based? I mean for zswap, you have to
> writeback uncompressable part, and then also do cold writeback through
> shrinker. The compressable part is hard to predict and control though?

It is compression rate based. And yes, the exact composition of what's
in zswap and what gets written back isn't very predictable.

We don't really care at the cgroup level, though. The goal at that
layer is isolation: a container gets a slice of memory and disk swap,
and the most important thing is that it stays within those confines
and doesn't become a noisy neighbor to the others.

We do care about workload health, too, but that sits one layer above
it. For example, we monitor memory pressure. If a workload expands
hard into (z)swap and starts thrashing, we kill it. But if it just has
a very large set of cold data that gets pushed into (z)swap without
any thrashing, we don't really care. Let it run. Maximizing
utilization in this case is more important than predictable capacity.

There is one exception, but it's narrow: when we run out of disk swap,
things can become very unstable. Especially when the workload is
mostly anon, and there is only a small share of file cache to absorb
pressure. So we kill when N% of disk swap is used.

But that's only for the cliff. If zswap didn't use disk slots, there
wouldn't be such a cliff. As long as you're within memory.max, put all
you want into zswap. We only kill if you start thrashing.

Neither of these need additional kernel interfaces. Just psi and swap
usage metrics that get polled every few seconds.

I think you could easily extend this mechanism to "predictable
capacity" by monitoring anon + shmem + a "vswap" counter and issue
kills when they go above some threshold you consider unreasonable.

It doesn't sound to me that you actually need synchronous,
cgroup-style enforcement for this? If a workload goes over, you kill
it within a few seconds.