lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Mon, 17 Jun 2024 17:31:21 +0200
From: Jesper Dangaard Brouer <hawk@...nel.org>
To: Yosry Ahmed <yosryahmed@...gle.com>, Shakeel Butt <shakeel.butt@...ux.dev>
Cc: Andrew Morton <akpm@...ux-foundation.org>,
 Johannes Weiner <hannes@...xchg.org>, Michal Hocko <mhocko@...e.com>,
 Roman Gushchin <roman.gushchin@...ux.dev>, Yu Zhao <yuzhao@...gle.com>,
 Muchun Song <songmuchun@...edance.com>,
 Facebook Kernel Team <kernel-team@...a.com>, linux-mm@...ck.org,
 linux-kernel@...r.kernel.org, kernel-team <kernel-team@...udflare.com>
Subject: Re: [PATCH] memcg: use ratelimited stats flush in the reclaim



On 16/06/2024 02.28, Yosry Ahmed wrote:
> On Sat, Jun 15, 2024 at 1:13 AM Shakeel Butt <shakeel.butt@...ux.dev> wrote:
>>
>> The Meta prod is seeing large amount of stalls in memcg stats flush
>> from the memcg reclaim code path. At the moment, this specific callsite
>> is doing a synchronous memcg stats flush. The rstat flush is an
>> expensive and time consuming operation, so concurrent relaimers will
>> busywait on the lock potentially for a long time. Actually this issue is
>> not unique to Meta and has been observed by Cloudflare [1] as well. For
>> the Cloudflare case, the stalls were due to contention between kswapd
>> threads running on their 8 numa node machines which does not make sense
>> as rstat flush is global and flush from one kswapd thread should be
>> sufficient for all. Simply replace the synchronous flush with the
>> ratelimited one.
>>

Like Yosry, I don't agree that simply using ratelimited flush here is
the right solution, at-least other options need to be investigated first.

I can confirm that at Cloudflare, we are seeing massive issues due
kswapd and to this specific mem_cgroup_flush_stats() call inlined in
shrink_node, taking the rstat lock. I looked at our 12 numa node
machines, given kswapd have a kthread per NUMA node, and realized the
issue is much worse on these servers, as lock contention on rstat lock
happens more frequently, where 12 CPUs waste cycles on spinning every
time kswapd runs.  We added fleet wide stats (/proc/N/schedstat) for
kthreads, and realized we are on fleet wide average burning 20.000 CPU
cores on kswapd, that primarily spins on rstat lock.

Try to run this command to see kswapd CPU usage spikes:
   $ pidstat -u -C kswapd 1 60

Code wise: when the Per-CPU-Pages (PCP) freelist is empty,
__alloc_pages_slowpath calls wake_all_kswapds(), this cause wakeup calls
to all the kswapdN threads simultaneously. The kswapd thread will invoke
shrink_node, which as explained trigger the cgroup rstat flush operation
as part of its work. Thus, effectively the kernel's own memory
management processes triggers self-inflicted rstat lock contention, by
waking up all kswapd threads simultaneously.

So, we definitely need a solution to this issue!
(... more ideas below inlined as comments to Yosry)

>> One may raise a concern on potentially using 2 sec stale (at worst)
>> stats for heuristics like desirable inactive:active ratio and preferring
>> inactive file pages over anon pages but these specific heuristics do not
>> require very precise stats and also are ignored under severe memory
>> pressure. This patch has been running on Meta fleet for more than a
>> month and we have not observed any issues. Please note that MGLRU is not
>> impacted by this issue at all as it avoids rstat flushing completely.
>>
>> Link: https://lore.kernel.org/all/6ee2518b-81dd-4082-bdf5-322883895ffc@kernel.org [1]
>> Signed-off-by: Shakeel Butt <shakeel.butt@...ux.dev>
>> ---
>>   mm/vmscan.c | 2 +-
>>   1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/mm/vmscan.c b/mm/vmscan.c
>> index c0429fd6c573..bda4f92eba71 100644
>> --- a/mm/vmscan.c
>> +++ b/mm/vmscan.c
>> @@ -2263,7 +2263,7 @@ static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc)
>>           * Flush the memory cgroup stats, so that we read accurate per-memcg
>>           * lruvec stats for heuristics.
>>           */
>> -       mem_cgroup_flush_stats(sc->target_mem_cgroup);
>> +       mem_cgroup_flush_stats_ratelimited(sc->target_mem_cgroup);
> 
> I think you already know my opinion about this one :) I don't like it
> at all, and I will explain why below. I know it may be a necessary
> evil, but I would like us to make sure there is no other option before
> going forward with this.
> 
I'm signing up to solving this somehow, as this is a real prod issue.

An easy way to solve the kswapd issue, would be to reintroduce
"stats_flush_ongoing" concept, that was reverted in 7d7ef0a4686a ("mm:
memcg: restore subtree stats flushing") (Author: Yosry Ahmed), and
introduced in 3cd9992b9302 ("memcg: replace stats_flush_lock with an
atomic") (Author: Yosry Ahmed).

The concept is: If there is an ongoing rstat flush, this time limited to
the root cgroup, then don't perform the flush.  We can only do this for
the root cgroup tree, as flushing can be done for subtrees, but kswapd
is always for root tree, so it is good enough to solve the kswapd
thundering herd problem.  We might want to generalize this beyond memcg.


> Unfortunately, I am travelling this week, so I probably won't be able
> to follow up on this for a week or so, but I will try to lay down my
> thoughts as much as I can.
> 
> Why don't I like this?
> 
> - From a high level, I don't like the approach of replacing
> problematic flushing calls with the ratelimited version. It strikes me
> as a whac-a-mole approach that is mitigating symptoms of a larger
> problem.
> 

This also looks like a whac-a-mole approach to me.

I'm not directly against rate-limited flushing, but I do think the 2 sec
interval is too long. IMHO we should be able to have a smaller bound in
the ms area.

Like I proposed 50 ms flush rate limiting in [2]
  [2] 
https://lore.kernel.org/all/171328990014.3930751.10674097155895405137.stgit@firesoul/


> - With the added thresholding code, a flush is only done if there is a
> significant number of pending updates in the relevant subtree.
> Choosing the ratelimited approach is intentionally ignoring a
> significant change in stats (although arguably it could be irrelevant
> stats).
> 

My production observations are that the thresholding code isn't limiting
the flushing in practice.


> - Reclaim code is an iterative process, so not updating the stats on
> every retry is very counterintuitive. We are retrying reclaim using
> the same stats and heuristics used by a previous iteration,
> essentially dismissing the effects of those previous iterations.
> 
> - Indeterministic behavior like this one is very difficult to debug if
> it causes problems. The missing updates in the last 2s (or whatever
> period) could be of any magnitude. We may be ignoring GBs of
> free/allocated memory. What's worse is, if it causes any problems,
> tracing it back to this flush will be extremely difficult.
> 

The 2 sec seems like a long period for me.

> What can we do?
> 
> - Try to make more fundamental improvements to the flushing code (for
> memcgs or cgroups in general). The per-memcg flushing thresholding is
> an example of this. For example, if flushing is taking too long
> because we are flushing all subsystems, it may make sense to have
> separate rstat trees for separate subsystems.
> 
> One other thing we can try is add a mutex in the memcg flushing path.
> I had initially had this in my subtree flushing series [1], but I
> dropped it as we thought it's not very useful. 

I'm running an experimental kernel with rstat lock converted to mutex on
a number of production servers, and we have not observed any regressions.
The kswapd thundering herd problem also happen on these machines, but as
these are sleep-able background threads, it is fine to sleep on the mutex.


> Currently in
> mem_cgroup_flush_stats(), we check if there are enough pending updates
> to flush, then we call cgroup_flush_stats() and spin on the lock. It
> is possible that while we spin, those pending updates we observed have
> been flushed. If we add back the mutex like in [1], then once we
> acquire the mutex we check again to make sure there are still enough
> stats to flush.
> 

It is a good point to re-check after obtaining the lock, as things could
have changed in the intermediate period when waiting for the lock. This
is also done (for time) in my 50 ms flush rate limit patch [1] ;-)


> [1]https://lore.kernel.org/all/20231010032117.1577496-6-yosryahmed@google.com/
> 
> - Try to avoid the need for flushing in this path. I am not sure what
> approach MGLRU uses to avoid the flush, but if we can do something
> similar for classic LRUs that would be preferable. I am guessing MGLRU
> may be maintaining its own stats outside of the rstat framework.
> 
> - Try to figure out if one (or a few) update paths are regressing all
> flushers. If one specific stat or stats update path is causing most of
> the updates, we can try to fix that instead. Especially if it's a
> counter that is continuously being increased and decreases (so the net
> change is not as high as we think).
> 
> At the end of the day, all of the above may not work, and we may have
> to live with just using the ratelimited approach. But I *really* hope
> we could actually go the other way. Fix things on a more fundamental
> level and eventually drop the ratelimited variants completely.
>

My pipe dream is that kernel can avoiding the cost of maintain the
cgroup threshold stats for flushing, and instead rely on a dynamic time
based threshold (in ms area) that have no fast-path overhead :-P


> Just my 2c. Sorry for the long email :)

Appreciate your input a lot! :-)

--Jesper

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ