Re: [RFC PATCH 0/2] memcg_ext: dynamic memory.low and memory.min protection

From: Hui Zhu

Date: Wed Oct 28 2026 - 06:27:55 EST


"Tejun Heo" <tj@xxxxxxxxxx mailto:tj@xxxxxxxxxx?to=%22Tejun%20Heo%22%20%3Ctj%40kernel.org%3E > :


>
> Hello, Hui.
>
> On Tue, Oct 27, 2026 at 05:11:58PM +0800, Hui Zhu wrote:
>
> >
> > Why it matters: memory.low and memory.min protect a cgroup by a fixed
> > threshold and have no idea whether it is using the memory. A
> > high-priority cgroup that holds a large working set and then goes idle
> > keeps suppressing reclaim on its siblings, and that memory is wasted.
> > A policy program sees the real activity - page faults, charges - and
> > can grant or withdraw protection as it changes.
> >
> This doesn't justify the changes. Why can't the same be achieved by
> adjusting memory.low and memory.min from userspace? The arguments made
> so far are neither convincing nor proven.
>
> Thanks.
>
> --
> tejun
>

Hi Tejun,

Thanks for taking a look.

You are right about that paragraph. It describes what dynamic
protection does rather than saying why it has to happen in the kernel,
and at the timescale I described a userspace agent writing memory.low
would keep up fine. I will drop it.

Let me try to say what I was reaching for, from the code rather than
from a benchmark - a comparison between BPF and a userspace agent would
say at least as much about how well I wrote the agent as about the
interface, so I am not sure it would settle much.

memory.low is a request, and what reclaim honors is elow, which
page_counter_calculate_protection() derives on each pass from the
parent's elow, the siblings' requests and the tree's usage. At the
first level below the reclaim root that comes out equal to what was
written, and there the knob is enough. Deeper down it is capped by the
parent's budget and shared out between siblings according to what they
claimed and what they use, so what a cgroup wrote and what it ends up
with can differ. As far as I can tell none of that is visible from
userspace: memory.low reads back the declared value, and I could not
find anything that exports elow.

Exporting elow would not help, because elow is not a property of the
cgroup alone. It depends on the reclaim root as well, which is why
page_counter_calculate_protection() warns that it is not stateless and
is only valid as part of a top-down tree iteration, and why
mem_cgroup_protection() carries the example of a concurrent global and
targeted reclaim driving a child's elow to zero. A file would show
whichever value the last reclaim happened to leave there.

So the derived value seems to be usable only where it is derived,
inside the check, and that is where low_policy() sits: it is evaluated
per pass and is handed that pass's own inputs - the memcg, the reclaim
target, the elow just computed, the usage - instead of being state that
is read at the check.

The other half is what a policy can key on. memory.low has one
dimension, bytes. A policy program is not limited to the ctx it is
handed; it can also use state that other BPF programs keep, so
protection can be a function of activity rather than of size - a
workingset refault rate, for instance. [1] reads those counters from
BPF and acts on them; it drives reclaim rather than answering a
protection hook, but the signal it reads is the one such a policy would
want. The limit is on the output rather than the input: the hook
returns a boolean, so it cannot grant anything memory.low cannot.

I would be interested in how you see this - in particular whether the
nested case is worth supporting at all, and if it is, whether this is
the right shape for it. I am happy to rework the patches, and to add a
demonstration of one concrete policy if that would make it easier to
judge.

[1] https://github.com/teawater/memcg-async-reclaim/tree/memcg_ext_plus

Thanks,
Hui