[RFC PATCH v11 0/4] mm/swap: priority-based swap tiers with per-cgroup selection

Youngjun Park posted 4 patches 1 week, 1 day ago
Documentation/mm/index.rst     |   1 +
Documentation/mm/swap-tier.rst |  55 +++++
MAINTAINERS                    |   3 +
include/linux/swap.h           |   5 +-
mm/Makefile                    |   2 +-
mm/swap.h                      |   2 +
mm/swap_tier.c                 | 421 +++++++++++++++++++++++++++++++++
mm/swap_tier.h                 |  71 ++++++
mm/swapfile.c                  | 173 ++++++++------
9 files changed, 658 insertions(+), 75 deletions(-)
create mode 100644 Documentation/mm/swap-tier.rst
create mode 100644 mm/swap_tier.c
create mode 100644 mm/swap_tier.h
[RFC PATCH v11 0/4] mm/swap: priority-based swap tiers with per-cgroup selection
Posted by Youngjun Park 1 week, 1 day ago
This is RFC v11 of the swap tier series [1], reworked after the v10
discussion [2][3].

Motivation
==========

After the v10 discussion, I would like to first land the parts that do
not affect users, before anything touches memcg, with two goals in mind.

- Serve as a bridge that causes no trouble when the tier interface and
  memcg support are introduced later.
- Make per-cgroup swap device selection possible.

While thinking this through, I came across some insights that I'd like
to discuss with the community. Hence this RFC v11.

As a starting point, let's look at what swap priority already gives us.

- Devices with different priorities are used in priority order.
- Devices with the same priority are used round-robin.

From this point of view, a priority itself can be seen as a tier rather
than a property of a device. Each distinct priority is a tier, the
devices with that priority hang off it, and allocation walks the tiers.

This is one of the insights that led me here. In the v10 design, a tier
was a priority range, so one tier could contain multiple priorities.
That raised two questions.

- If devices in a tier are allocated in priority order, aren't they
  effectively different tiers?
- Is there a real use case for several same-priority round-robin
  groups inside one tier?

If each distinct priority is its own tier, both questions go away.
A tier is one round-robin group, and ordering exists only between
tiers. (Anyone who wants priority-based allocation inside a tier can
still get it through a separate tier interface later. 
mix allocation policy? also possible. we have interface.)

Either way, no explicit tier interface like the one in v10 is needed
yet.

Even when real tiers work are introduced, the view stays the same.

- We want an ordering between tier A and tier B.
- Devices in one tier form one service speed group. How they share
  allocations may become flexible, and will normally stay round-robin
  as it is today.
- On top of that, we will add backend transfer between tiers after
  swap virtualization.

This series changes the current swap code to follow that view, which
gives us the following.

- Per-cgroup swap can later fit into a swap tier interface without
  trouble.
- A base for the per-priority allocation queue series [4].
- It is close to a refactoring of the existing structure, so userspace
  is not tied to an implementation.

Kairui, Lian, would it be okay to use this as the groundwork for [4]?
What do you think?

Per-cgroup swap in debugfs
==========================

Patches 3 and 4 let a memory cgroup choose its tiers through debugfs.

  # swapon -p 100 /dev/nvme0n1p2
  # swapon -p 50 /dev/sdb2
  # cat /sys/kernel/debug/swap/tiers
  Idx   Prio
  0     100
  1     50
  # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers

Bit i of the mask is tier i, so /batch swaps only to sdb2. A tier keeps
its index for its lifetime, so the mask keeps selecting the same tier
across swapon and swapoff.

Masks are kept in a list keyed by cgroup ID rather than in struct
mem_cgroup, so the series does not touch memcg code and needs no Kconfig
option. A mask applies only to its own cgroup and is not inherited by
child cgroups.

This debugfs interface is a stepping stone. Once the tier model
settles, /sys/kernel/mm/swap/tiers would list and name the tiers, and a
memcg knob such as memory.swap.tiers.max would take tier names and turn
them into this mask.

Future direction
================

This is not done in this series. If the tiers introduced here settle,
which differs from giving tiers priority ranges, work can continue
along these lines depending on the discussion.

The following are common to both.
- The per-priority allocation queue series from Kairui and Lian [4].
- Add /sys/kernel/mm/swap/tiers.
- A memcg memory.swap.tiers.max interface.
- Per-tier allocation policy (an extension that needs discussion).

If tiers are assigned by priority ranges as before, the tiers here have
to be related to those ranges. When a tier is assigned at runtime, the
devices that fall into its range are merged into that tier.

If tiers are extended the way they work here, a tier stays a priority
and the interface only adds names. A tier starts with a default name,
such as its priority, and can be renamed through the tier interface.

(I think there are a lot of possible way which we can discuss.)

Change log
==========

v11
- Reworked after the v10 discussion [2][3], with no new user ABI.
- A swap tier is now a single swap priority, created and removed by
  swapon and swapoff. /sys/kernel/mm/swap/tiers and CONFIG_NR_SWAP_TIERS
  are dropped.
- The active and available swap device lists move into the tiers.
  (#1, #2 patches)
- Per-cgroup tier selection moves from memory.swap.tiers.max to debugfs.
  (#3 patch)
- Masks are kept in a cgroup ID list instead of struct mem_cgroup, and
  are no longer inherited by child cgroups. (#3 patch)
- Dropped the selftests, which tested the dropped interfaces.
- Rebased on recent mm-new.
- v10 link: https://lore.kernel.org/linux-mm/20260713025644.170839-1-youngjun.park@lge.com/

Changes up to v10 are in the v10 cover letter [1].

[1] https://lore.kernel.org/linux-mm/20260713025644.170839-1-youngjun.park@lge.com/
[2] https://lore.kernel.org/linux-mm/amDCIl51NoNPL7Op@cmpxchg.org/
[3] https://lore.kernel.org/linux-mm/amIlqHQ40baRoz3O@cmpxchg.org/
[4] https://lore.kernel.org/linux-mm/20260829-swap-pcp-priq-v2-0-68d3d925578c@gmail.com/

Youngjun Park (4):
  mm: swap: introduce swap tier infrastructure
  mm: swap: allocate swap slots from swap tiers
  mm: swap: add a debugfs interface for memcg tier selection
  mm: swap: filter swap allocation by memcg tier mask

 Documentation/mm/index.rst     |   1 +
 Documentation/mm/swap-tier.rst |  55 +++++
 MAINTAINERS                    |   3 +
 include/linux/swap.h           |   5 +-
 mm/Makefile                    |   2 +-
 mm/swap.h                      |   2 +
 mm/swap_tier.c                 | 421 +++++++++++++++++++++++++++++++++
 mm/swap_tier.h                 |  71 ++++++
 mm/swapfile.c                  | 173 ++++++++------
 9 files changed, 658 insertions(+), 75 deletions(-)
 create mode 100644 Documentation/mm/swap-tier.rst
 create mode 100644 mm/swap_tier.c
 create mode 100644 mm/swap_tier.h


base-commit: b08a65b93426d86e3f354d655d6225397b591877
-- 
2.48.1
Re: [RFC PATCH v11 0/4] mm/swap: priority-based swap tiers with per-cgroup selection
Posted by Johannes Weiner 1 week, 1 day ago
On Thu, Sep 17, 2026 at 03:34:33AM +0900, Youngjun Park wrote:
> Per-cgroup swap in debugfs
> ==========================
> 
> Patches 3 and 4 let a memory cgroup choose its tiers through debugfs.
> 
>   # swapon -p 100 /dev/nvme0n1p2
>   # swapon -p 50 /dev/sdb2
>   # cat /sys/kernel/debug/swap/tiers
>   Idx   Prio
>   0     100
>   1     50
>   # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers
> 
> Bit i of the mask is tier i, so /batch swaps only to sdb2. A tier keeps
> its index for its lifetime, so the mask keeps selecting the same tier
> across swapon and swapoff.

Can the cgroup be given a priority limit? That would have pretty
obvious inheritance semantics:

root
`- batch (memory.swap.prio.max = 20)
   `- task (memory.swap.prio.max = max)
   `- logs (memory.swap.prio.max = 10)
`- interactive (memory.swap.prio.max = max)
   `- task (memory.swap.prio.max)
Re: [RFC PATCH v11 0/4] mm/swap: priority-based swap tiers with per-cgroup selection
Posted by Youngjun Park 4 days, 11 hours ago
On 2026-09-16 16:04, Johannes Weiner wrote:
> On Thu, Sep 17, 2026 at 03:34:33AM +0900, Youngjun Park wrote:
> > Per-cgroup swap in debugfs
> > ==========================
> > 
> > Patches 3 and 4 let a memory cgroup choose its tiers through debugfs.
> > 
> >   # swapon -p 100 /dev/nvme0n1p2
> >   # swapon -p 50 /dev/sdb2
> >   # cat /sys/kernel/debug/swap/tiers
> >   Idx   Prio
> >   0     100
> >   1     50
> >   # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers
> > 
> > Bit i of the mask is tier i, so /batch swaps only to sdb2. A tier keeps
> > its index for its lifetime, so the mask keeps selecting the same tier
> > across swapon and swapoff.
>

Hello Johannes,

Sorry for the late reply on a good suggestion :)

> Can the cgroup be given a priority limit? That would have pretty
> obvious inheritance semantics:
> root
> `- batch (memory.swap.prio.max = 20)
>    `- task (memory.swap.prio.max = max)
>    `- logs (memory.swap.prio.max = 10)
> `- interactive (memory.swap.prio.max = max)
>    `- task (memory.swap.prio.max)

Right, the inheritance is clear and easy to understand, and with this I
can pre-define the limit without knowing the mask value.

But first, let me check the intent. Is the point that capping batch keeps
it from taking the faster tiers, so they are left for interactive?

If so, that matches our use case. Latency sensitive workloads get the
fast tiers, non-latency sensitive ones get the slow tiers. But...

Even then, the reverse cannot be expressed. A cap only cuts from the top,
so a latency sensitive workload given max can still fall back to the slow
tiers once the fast ones fill up. For example,

  tier0  tier1  tier2  tier3
    0     10     20     30

there is no way to say "use tier0 and tier1, but never fall back to tier2
or tier3". To cover that, the interface would also need a min value, or
some way to express a range.

And even a range is not enough. Excluding only tier2 leaves a hole in the
middle, which no min/max pair can express. That needs per-tier selection,
which is what the mask,  and what I'd carry over to the memcg
interface later (Currently memcg.swap.tiers.max).

How do you think?

Thanks!
Youngjun Park
Re: [RFC PATCH v11 0/4] mm/swap: priority-based swap tiers with per-cgroup selection
Posted by Johannes Weiner 1 day, 10 hours ago
On Mon, Sep 21, 2026 at 01:20:11AM +0900, Youngjun Park wrote:
> On 2026-09-16 16:04, Johannes Weiner wrote:
> > On Thu, Sep 17, 2026 at 03:34:33AM +0900, Youngjun Park wrote:
> > > Per-cgroup swap in debugfs
> > > ==========================
> > > 
> > > Patches 3 and 4 let a memory cgroup choose its tiers through debugfs.
> > > 
> > >   # swapon -p 100 /dev/nvme0n1p2
> > >   # swapon -p 50 /dev/sdb2
> > >   # cat /sys/kernel/debug/swap/tiers
> > >   Idx   Prio
> > >   0     100
> > >   1     50
> > >   # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers
> > > 
> > > Bit i of the mask is tier i, so /batch swaps only to sdb2. A tier keeps
> > > its index for its lifetime, so the mask keeps selecting the same tier
> > > across swapon and swapoff.
> >
> 
> Hello Johannes,
> 
> Sorry for the late reply on a good suggestion :)

No worries, and same ^_^

> > Can the cgroup be given a priority limit? That would have pretty
> > obvious inheritance semantics:
> > root
> > `- batch (memory.swap.prio.max = 20)
> >    `- task (memory.swap.prio.max = max)
> >    `- logs (memory.swap.prio.max = 10)
> > `- interactive (memory.swap.prio.max = max)
> >    `- task (memory.swap.prio.max)
> 
> Right, the inheritance is clear and easy to understand, and with this I
> can pre-define the limit without knowing the mask value.
>
> But first, let me check the intent. Is the point that capping batch keeps
> it from taking the faster tiers, so they are left for interactive?

Yes, basically, that's what I tried to express. Interactive has access
to all available capacity. Batch only has access to lower tiers.

> If so, that matches our use case. Latency sensitive workloads get the
> fast tiers, non-latency sensitive ones get the slow tiers. But...
> 
> Even then, the reverse cannot be expressed. A cap only cuts from the top,
> so a latency sensitive workload given max can still fall back to the slow
> tiers once the fast ones fill up. For example,
> 
>   tier0  tier1  tier2  tier3
>     0     10     20     30
> 
> there is no way to say "use tier0 and tier1, but never fall back to tier2
> or tier3". To cover that, the interface would also need a min value, or
> some way to express a range.

Correct, this isn't covered by the above.

> And even a range is not enough. Excluding only tier2 leaves a hole in the
> middle, which no min/max pair can express. That needs per-tier selection,
> which is what the mask,  and what I'd carry over to the memcg
> interface later (Currently memcg.swap.tiers.max).
> 
> How do you think?

I think it could help to aggregate the usecases in the cover
letter. Your cover letter describes how it works, which is great, but
it would be good to understand better what the constraints are, how it
fits in with other existing control surface and broader usage models.

With the above, yes, you can restrict who gets access to the
privileged tiers top down, but not bottom up. Is that an issue? Keep
in mind the alternative is cutting privileged groups OFF from certain
available capacity. This seems somewhat counter-intuitive to me, and
doesn't reflect a clean privilege hierarchy anymore.

If you can think of a good usecase, memory.swap.prio.min would be
certainly a natural extension. But we should get the usecase laid out.

The requirement to punch holes is the one I can relate to least. Why
would a cgroup need access to good tiers and bad tiers, but skip the
middle ones?

This would seem less like tiering/hierarchy and more like flat
per-cgroup swap pools but with obstacles.