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]
Message-ID: <20190301174907.GA2375@cmpxchg.org>
Date:   Fri, 1 Mar 2019 12:49:07 -0500
From:   Johannes Weiner <hannes@...xchg.org>
To:     Andrey Ryabinin <aryabinin@...tuozzo.com>
Cc:     Andrew Morton <akpm@...ux-foundation.org>, linux-mm@...ck.org,
        linux-kernel@...r.kernel.org, Michal Hocko <mhocko@...nel.org>,
        Vlastimil Babka <vbabka@...e.cz>,
        Rik van Riel <riel@...riel.com>,
        Mel Gorman <mgorman@...hsingularity.net>,
        Roman Gushchin <guro@...com>,
        Shakeel Butt <shakeelb@...gle.com>
Subject: Re: [PATCH RFC] mm/vmscan: try to protect active working set of
 cgroup from reclaim.

Hello Andrey,

On Fri, Mar 01, 2019 at 01:38:26PM +0300, Andrey Ryabinin wrote:
> On 2/26/19 3:50 PM, Andrey Ryabinin wrote:
> > On 2/22/19 10:15 PM, Johannes Weiner wrote:
> >> On Fri, Feb 22, 2019 at 08:58:25PM +0300, Andrey Ryabinin wrote:
> >>> In a presence of more than 1 memory cgroup in the system our reclaim
> >>> logic is just suck. When we hit memory limit (global or a limit on
> >>> cgroup with subgroups) we reclaim some memory from all cgroups.
> >>> This is sucks because, the cgroup that allocates more often always wins.
> >>> E.g. job that allocates a lot of clean rarely used page cache will push
> >>> out of memory other jobs with active relatively small all in memory
> >>> working set.
> >>>
> >>> To prevent such situations we have memcg controls like low/max, etc which
> >>> are supposed to protect jobs or limit them so they to not hurt others.
> >>> But memory cgroups are very hard to configure right because it requires
> >>> precise knowledge of the workload which may vary during the execution.
> >>> E.g. setting memory limit means that job won't be able to use all memory
> >>> in the system for page cache even if the rest the system is idle.
> >>> Basically our current scheme requires to configure every single cgroup
> >>> in the system.
> >>>
> >>> I think we can do better. The idea proposed by this patch is to reclaim
> >>> only inactive pages and only from cgroups that have big
> >>> (!inactive_is_low()) inactive list. And go back to shrinking active lists
> >>> only if all inactive lists are low.
> >>
> >> Yes, you are absolutely right.
> >>
> >> We shouldn't go after active pages as long as there are plenty of
> >> inactive pages around. That's the global reclaim policy, and we
> >> currently fail to translate that well to cgrouped systems.
> >>
> >> Setting group protections or limits would work around this problem,
> >> but they're kind of a red herring. We shouldn't ever allow use-once
> >> streams to push out hot workingsets, that's a bug.
> >>
> >>> @@ -2489,6 +2491,10 @@ static void get_scan_count(struct lruvec *lruvec, struct mem_cgroup *memcg,
> >>>  
> >>>  		scan >>= sc->priority;
> >>>  
> >>> +		if (!sc->may_shrink_active && inactive_list_is_low(lruvec,
> >>> +						file, memcg, sc, false))
> >>> +			scan = 0;
> >>> +
> >>>  		/*
> >>>  		 * If the cgroup's already been deleted, make sure to
> >>>  		 * scrape out the remaining cache.
> >>> @@ -2733,6 +2739,7 @@ static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)
> >>>  	struct reclaim_state *reclaim_state = current->reclaim_state;
> >>>  	unsigned long nr_reclaimed, nr_scanned;
> >>>  	bool reclaimable = false;
> >>> +	bool retry;
> >>>  
> >>>  	do {
> >>>  		struct mem_cgroup *root = sc->target_mem_cgroup;
> >>> @@ -2742,6 +2749,8 @@ static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)
> >>>  		};
> >>>  		struct mem_cgroup *memcg;
> >>>  
> >>> +		retry = false;
> >>> +
> >>>  		memset(&sc->nr, 0, sizeof(sc->nr));
> >>>  
> >>>  		nr_reclaimed = sc->nr_reclaimed;
> >>> @@ -2813,6 +2822,13 @@ static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)
> >>>  			}
> >>>  		} while ((memcg = mem_cgroup_iter(root, memcg, &reclaim)));
> >>>  
> >>> +		if ((sc->nr_scanned - nr_scanned) == 0 &&
> >>> +		     !sc->may_shrink_active) {
> >>> +			sc->may_shrink_active = 1;
> >>> +			retry = true;
> >>> +			continue;
> >>> +		}
> >>
> >> Using !scanned as the gate could be a problem. There might be a cgroup
> >> that has inactive pages on the local level, but when viewed from the
> >> system level the total inactive pages in the system might still be low
> >> compared to active ones. In that case we should go after active pages.
> >>
> >> Basically, during global reclaim, the answer for whether active pages
> >> should be scanned or not should be the same regardless of whether the
> >> memory is all global or whether it's spread out between cgroups.
> >>
> >> The reason this isn't the case is because we're checking the ratio at
> >> the lruvec level - which is the highest level (and identical to the
> >> node counters) when memory is global, but it's at the lowest level
> >> when memory is cgrouped.
> >>
> >> So IMO what we should do is:
> >>
> >> - At the beginning of global reclaim, use node_page_state() to compare
> >>   the INACTIVE_FILE:ACTIVE_FILE ratio and then decide whether reclaim
> >>   can go after active pages or not. Regardless of what the ratio is in
> >>   individual lruvecs.
> >>
> >> - And likewise at the beginning of cgroup limit reclaim, walk the
> >>   subtree starting at sc->target_mem_cgroup, sum up the INACTIVE_FILE
> >>   and ACTIVE_FILE counters, and make inactive_is_low() decision on
> >>   those sums.
> >>
> > 
> > Sounds reasonable.
> > 
> 
> On the second thought it seems to be better to keep the decision on lru level.
> There are couple reasons for this:
> 
> 1) Using bare node_page_state() (or sc->targe_mem_cgroup's total_[in]active counters) would be wrong.
>  Because some cgroups might have protection set (memory.low) and we must take it into account. Also different
> cgroups have different available swap space/memory.swappiness and it must be taken into account as well to.
>
> So it has to be yet another full memcg-tree iteration.

It should be possible to take that into account on the first iteration
and adjust the inactive/active counters in proportion to how much of
the cgroup's total memory is exempt by memory.low or min, right?

> 2) Let's consider simple case. Two cgroups, one with big 'active' set of pages the other allocates one-time used pages.
> So the total inactive is low, thus checking inactive ratio on higher level will result in reclaiming pages.
> While with check on lru-level only inactive will be reclaimed.

It's the other way around. Let's say you have two cgroups, A and B:

        A:  500M inactive   10G active -> inactive is low
        B:   10G inactive  500M active -> inactive is NOT low
   ----------------------------------------------------------
   global: 10.5G inactive 10.5G active -> inactive is NOT low

Checking locally will scan active pages from A. Checking globally will
not, because there is plenty of use-once pages from B.

So if you check globally, without any protection, A and B compete
evenly during global reclaim. Under the same reclaim pressure, A has
managed to activate most of its pages whereas B has not. That means A
is hotter and B provides the better reclaim candidates.

If you apply this decision locally, on the other hand, you are no
longer aging the groups at the same rate. And then the LRU orders
between groups will no longer be comparable, and you won't be
reclaiming the coldest memory in the system anymore.

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ