mm: introduce memory.min
Subsystems (6)
PATCH v3 1/2 mm: introduce memory.min
Memory controller implements the memory.low best-effort memory protection mechanism, which works perfectly in many cases and allows protecting working sets of important workloads from sudden reclaim.
But its semantics has a significant limitation: it works only as long as there is a supply of reclaimable memory. This makes it pretty useless against any sort of slow memory leaks or memory usage increases. This is especially true for swapless systems. If swap is enabled, memory soft protection effectively postpones problems, allowing a leaking application to fill all swap area, which makes no sense.
The only effective way to guarantee the memory protection in this case is to invoke the OOM killer.
It's possible to handle this case in userspace by reacting on MEMCG_LOW events; but there is still a place for a fail-safe in-kernel mechanism to provide stronger guarantees.
This patch introduces the memory.min interface for cgroup v2 memory controller. It works very similarly to memory.low (sharing the same hierarchical behavior), except that it's not disabled if there is no more reclaimable memory in the system.
--- Documentation/cgroup-v2.txt | 24 ++++++++- include/linux/memcontrol.h | 15 ++++-- include/linux/page_counter.h | 11 +++- mm/memcontrol.c | 118 ++++++++++++++++++++++++++++++++++--------- mm/page_counter.c | 63 ++++++++++++++++------- mm/vmscan.c | 18 ++++++- 6 files changed, 199 insertions(+), 50 deletions(-)
Documentation/cgroup-v2.txt +22 -2
@@ -1002,6 +1002,26 @@ PAGE_SIZE multiple when read back. The total amount of memory currently being used by the cgroup and its descendants. + memory.min + A read-write single value file which exists on non-root + cgroups. The default is "0". + + Hard memory protection. If the memory usage of a cgroup + is within its effective min boundary, the cgroup's memory + won't be reclaimed under any conditions. If there is no + unprotected reclaimable memory available, OOM killer + is invoked. + + Effective low boundary is limited by memory.min values of + all ancestor cgroups. If there is memory.min overcommitment + (child cgroup or cgroups are requiring more protected memory + than parent will allow), then each child cgroup will get + the part of parent's protection proportional to its + actual memory usage below memory.min. + + Putting more memory than generally available under this + protection is discouraged and may lead to constant OOMs. + memory.low A read-write single value file which exists on non-root cgroups. The default is "0".
@@ -1013,9 +1033,9 @@ PAGE_SIZE multiple when read back. Effective low boundary is limited by memory.low values of all ancestor cgroups. If there is memory.low overcommitment - (child cgroup or cgroups are requiring more protected memory, + (child cgroup or cgroups are requiring more protected memory than parent will allow), then each child cgroup will get - the part of parent's protection proportional to the its + the part of parent's protection proportional to its actual memory usage below memory.low. Putting more memory than generally available under this
include/linux/memcontrol.h +11 -4
@@ -59,6 +59,12 @@ enum memcg_memory_event { MEMCG_NR_MEMORY_EVENTS, }; +enum mem_cgroup_protection { +MEMCG_PROT_NONE , +MEMCG_PROT_LOW , +MEMCG_PROT_MIN , +}; +struct mem_cgroup_reclaim_cookie {pg_data_t *pgdat;int priority;
@@ -297,7 +303,8 @@ static inline bool mem_cgroup_disabled(void)return !cgroup_subsys_enabled (memory_cgrp_subsys); } -bool mem_cgroup_low (struct mem_cgroup *root,struct mem_cgroup *memcg); +enum mem_cgroup_protection mem_cgroup_protected (struct mem_cgroup *root, +struct mem_cgroup *memcg); int mem_cgroup_try_charge(struct page *page, struct mm_struct *mm, gfp_t gfp_mask, struct mem_cgroup **memcgp,
@@ -756,10 +763,10 @@ static inline void memcg_memory_event(struct mem_cgroup *memcg, { } -static inline bool mem_cgroup_low (struct mem_cgroup *root, -struct mem_cgroup *memcg) +static inline enum mem_cgroup_protection mem_cgroup_protected ( +struct mem_cgroup *root,struct mem_cgroup *memcg) { -return false; +return MEMCG_PROT_NONE ; } static inline int mem_cgroup_try_charge(struct page *page, struct mm_struct *mm,
include/linux/page_counter.h +9 -2
@@ -8,10 +8,16 @@struct page_counter {atomic_long_t usage; -unsigned long max; +unsigned long min;unsigned long low; +unsigned long max;struct page_counter *parent; +/* effective memory.min and memory.min usage tracking */ +unsigned long emin; +atomic_long_t min_usage; +atomic_long_t children_min_usage; +/* effective memory.low and memory.low usage tracking */ unsigned long elow;atomic_long_t low_usage;
@@ -47,8 +53,9 @@ bool page_counter_try_charge(struct page_counter *counter,unsigned long nr_pages, struct page_counter **fail);void page_counter_uncharge (struct page_counter *counter,unsigned long nr_pages); -int page_counter_set_max (struct page_counter *counter,unsigned long nr_pages); +void page_counter_set_min (struct page_counter *counter,unsigned long nr_pages);void page_counter_set_low (struct page_counter *counter,unsigned long nr_pages); +int page_counter_set_max (struct page_counter *counter,unsigned long nr_pages);int page_counter_memparse (const char *buf,const char *max,unsigned long *nr_pages);
mm/memcontrol.c +94 -24
188 lines not on this page · raw message
mm/page_counter.c +46 -17
113 lines not on this page · raw message
mm/vmscan.c +17 -1
@@ -2544,12 +2544,28 @@ static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)unsigned long reclaimed;unsigned long scanned; -if (mem_cgroup_low (root, memcg)) { +switch (mem_cgroup_protected (root, memcg)) { +case MEMCG_PROT_MIN : +/* +* Hard protection. +* If there is no reclaimable memory, OOM. +*/ +continue ; +case MEMCG_PROT_LOW : +/* +* Soft protection. +* Respect the protection only as long as +* there is an unprotected supply +* of reclaimable memory from other cgroups. +*/ if (!sc->memcg_low_reclaim) { sc->memcg_low_skipped =1 ;continue ; }memcg_memory_event (memcg,MEMCG_LOW ); +break ; +case MEMCG_PROT_NONE : +break ; } reclaimed = sc->nr_reclaimed;
Signature · 1 line
2.14.3
PATCH v3 2/2 mm: ignore memory.min of abandoned memory cgroups
If a cgroup has no associated tasks, invoking the OOM killer won't help release any memory, so respecting the memory.min can lead to an infinite OOM loop or system stall.
Let's ignore memory.min of unpopulated cgroups.
--- include/linux/memcontrol.h | 10 ++++++++++ mm/vmscan.c | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-)
include/linux/memcontrol.h +10 -0
@@ -374,6 +374,11 @@ static inline void mem_cgroup_put(struct mem_cgroup *memcg)css_put (&memcg->css); } +static inline bool mem_cgroup_is_populated (struct mem_cgroup *memcg) +{ +return cgroup_is_populated (memcg->css.cgroup); +} +#define mem_cgroup_from_counter (counter, member) \ container_of(counter, struct mem_cgroup, member)
@@ -835,6 +840,11 @@ static inline void mem_cgroup_put(struct mem_cgroup *memcg) { } +static inline bool mem_cgroup_is_populated (struct mem_cgroup *memcg) +{ +return false; +} +static inline struct mem_cgroup * mem_cgroup_iter(struct mem_cgroup *root, struct mem_cgroup *prev,
mm/vmscan.c +5 -1
@@ -2549,8 +2549,12 @@ static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)/* * Hard protection. * If there is no reclaimable memory, OOM. +* Abandoned cgroups are losing protection, +* because OOM killer won't release any memory. */ -continue ; +if (mem_cgroup_is_populated (memcg)) +continue ; +break ;case MEMCG_PROT_LOW :/* * Soft protection.
Signature · 1 line
2.14.3
Re: [PATCH v3 2/2] mm: ignore memory.min of abandoned memory cgroups
Quoted 11 lines · Roman Gushchin
If a cgroup has no associated tasks, invoking the OOM killer won't help release any memory, so respecting the memory.min can lead to an infinite OOM loop or system stall.
Let's ignore memory.min of unpopulated cgroups.
Signed-off-by Roman Gushchin <[email protected]>Cc Johannes Weiner <[email protected]>Cc Michal Hocko <[email protected]>Cc Vladimir Davydov <[email protected]>Cc Tejun Heo <[email protected]>
I wouldn't mind merging this into the previous patch. It's fairly small, and there is no reason to introduce an infinite OOM loop scenario into the tree, even if it's just for one commit.
Roman Gushchin
Acked-by Johannes WeinerRe: [PATCH v3 2/2] mm: ignore memory.min of abandoned memory cgroups
Quoted 13 lines · Johannes Weiner
Quoted 11 lines · Roman Gushchin
If a cgroup has no associated tasks, invoking the OOM killer won't help release any memory, so respecting the memory.min can lead to an infinite OOM loop or system stall.
Let's ignore memory.min of unpopulated cgroups.
Signed-off-by Roman Gushchin <[email protected]>Cc Johannes Weiner <[email protected]>Cc Michal Hocko <[email protected]>Cc Vladimir Davydov <[email protected]>Cc Tejun Heo <[email protected]>
Quoted 5 lines
Acked-by Johannes Weiner <[email protected]>I wouldn't mind merging this into the previous patch. It's fairly small, and there is no reason to introduce an infinite OOM loop scenario into the tree, even if it's just for one commit.
OK, makes sense.
Here is an updated version: I've merged two commits into one, added a small note about empty cgroups to docs and rebased to mm.
Andrew, can you, please, pull this one?
Thank you!
--
Andrew Morton
maintainerRe: [PATCH v3 2/2] mm: ignore memory.min of abandoned memory cgroups
Quoted 49 lines
Memory controller implements the memory.low best-effort memory protection mechanism, which works perfectly in many cases and allows protecting working sets of important workloads from sudden reclaim.
But its semantics has a significant limitation: it works only as long as there is a supply of reclaimable memory. This makes it pretty useless against any sort of slow memory leaks or memory usage increases. This is especially true for swapless systems. If swap is enabled, memory soft protection effectively postpones problems, allowing a leaking application to fill all swap area, which makes no sense.
The only effective way to guarantee the memory protection in this case is to invoke the OOM killer.It's possible to handle this case in userspace by reacting on
MEMCG_LOWevents; but there is still a place for a fail-safe in-kernel mechanism to provide stronger guarantees.This patch introduces the memory.min interface for cgroup v2 memory controller. It works very similarly to memory.low (sharing the same hierarchical behavior), except that it's not disabled if there is no more reclaimable memory in the system.
If cgroup is not populated, its memory.min is ignored, because otherwise even the OOM killer wouldn't be able to reclaim the protected memory, and the system can stall.
...
Documentation/cgroup-v2.txt +12
-0@@ -1002,6 +1002,29 @@ PAGE_SIZE multiple when read back. The total amount of memory currently being used by the cgroup and its descendants. + memory.min + A read-write single value file which exists on non-root + cgroups. The default is "0". + + Hard memory protection. If the memory usage of a cgroup + is within its effective min boundary, the cgroup's memory + won't be reclaimed under any conditions. If there is no + unprotected reclaimable memory available, OOM killer + is invoked. + + Effective low boundary is limited by memory.min values of + all ancestor cgroups. If there is memory.min overcommitment
Documentation/cgroup-v2.txt PAGE_SIZE multiple when read back. · line 1026
Quoted 10 lines
Documentation/cgroup-v2.txt +10
-0+ (child cgroup or cgroups are requiring more protected memory + than parent will allow), then each child cgroup will get + the part of parent's protection proportional to its + actual memory usage below memory.min. + + Putting more memory than generally available under this + protection is discouraged and may lead to constant OOMs. + + If a memory cgroup is not populated with processes, + its memory.min is ignored.
This is a copy-paste-edit of the memory.low description. Could we please carefully check that it all remains accurate? Should "Effective low boundary" be "Effective min boundary"? Does overcommit still apply to .min? etcetera.
Roman Gushchin
Reviewed-by Randy DunlapRe: [PATCH v3 2/2] mm: ignore memory.min of abandoned memory cgroups
Quoted 51 lines · Andrew Morton
Quoted 51 lines
Memory controller implements the memory.low best-effort memory protection mechanism, which works perfectly in many cases and allows protecting working sets of important workloads from sudden reclaim.
But its semantics has a significant limitation: it works only as long as there is a supply of reclaimable memory. This makes it pretty useless against any sort of slow memory leaks or memory usage increases. This is especially true for swapless systems. If swap is enabled, memory soft protection effectively postpones problems, allowing a leaking application to fill all swap area, which makes no sense.
The only effective way to guarantee the memory protection in this case is to invoke the OOM killer.It's possible to handle this case in userspace by reacting on
MEMCG_LOWevents; but there is still a place for a fail-safe in-kernel mechanism to provide stronger guarantees.This patch introduces the memory.min interface for cgroup v2 memory controller. It works very similarly to memory.low (sharing the same hierarchical behavior), except that it's not disabled if there is no more reclaimable memory in the system.
If cgroup is not populated, its memory.min is ignored, because otherwise even the OOM killer wouldn't be able to reclaim the protected memory, and the system can stall.
...
Documentation/cgroup-v2.txt +14
-0@@ -1002,6 +1002,29 @@ PAGE_SIZE multiple when read back. The total amount of memory currently being used by the cgroup and its descendants. + memory.min + A read-write single value file which exists on non-root + cgroups. The default is "0". + + Hard memory protection. If the memory usage of a cgroup + is within its effective min boundary, the cgroup's memory + won't be reclaimed under any conditions. If there is no + unprotected reclaimable memory available, OOM killer + is invoked. + + Effective low boundary is limited by memory.min values of + all ancestor cgroups. If there is memory.min overcommitment + (child cgroup or cgroups are requiring more protected memory + than parent will allow), then each child cgroup will get
Quoted 8 lines
Documentation/cgroup-v2.txt PAGE_SIZE multiple when read back. · line 1026
Quoted 8 lines
Documentation/cgroup-v2.txt +8
-0+ the part of parent's protection proportional to its + actual memory usage below memory.min. + + Putting more memory than generally available under this + protection is discouraged and may lead to constant OOMs. + + If a memory cgroup is not populated with processes, + its memory.min is ignored.
Hello, Andrew!
Quoted 4 lines
This is a copy-paste-edit of the memory.low description. Could we please carefully check that it all remains accurate? Should "Effective low boundary" be "Effective min boundary"? Does overcommit still apply to .min? etcetera.
Except this s/low/min replacement (good catch, thank you! diff below), the rest looks fine to me. Memory.min and memory.low are similar in their hierarchical behavior, so most of the things still apply to .min.
Also, can you, please, add
(which was accidentally lost between versions).
Thanks you!
--
Documentation/cgroup-v2.txt +1 -1
@@ -1012,7 +1012,7 @@ PAGE_SIZE multiple when read back. unprotected reclaimable memory available, OOM killer is invoked. - Effective low boundary is limited by memory.min values of + Effective min boundary is limited by memory.min values of all ancestor cgroups. If there is memory.min overcommitment (child cgroup or cgroups are requiring more protected memory than parent will allow), then each child cgroup will get
Michal Hocko
maintainerQuoted 15 lines · Roman Gushchin
Memory controller implements the memory.low best-effort memory protection mechanism, which works perfectly in many cases and allows protecting working sets of important workloads from sudden reclaim.
But its semantics has a significant limitation: it works only as long as there is a supply of reclaimable memory. This makes it pretty useless against any sort of slow memory leaks or memory usage increases. This is especially true for swapless systems. If swap is enabled, memory soft protection effectively postpones problems, allowing a leaking application to fill all swap area, which makes no sense.
The only effective way to guarantee the memory protection in this case is to invoke the OOM killer.
Quoted 8 lines
It's possible to handle this case in userspace by reacting on
MEMCG_LOWevents; but there is still a place for a fail-safe in-kernel mechanism to provide stronger guarantees.This patch introduces the memory.min interface for cgroup v2 memory controller. It works very similarly to memory.low (sharing the same hierarchical behavior), except that it's not disabled if there is no more reclaimable memory in the system.
Originally I was pushing for the hard guarantee before we landed with the best effort one. The assumption back then was that properly configured systems shouldn't see problems IIRC.
It is not entirely clear to me what is the role of the low limit wrt. leaking application from the above description TBH. I presume you have a process without any low&hard limit which leaks and basically breaks the low limit expectation because of the lack of reclaimable memory and our memcg_low_reclaim fallback.
If that is the case then the hard limit should indeed protect the respective memcg from reclaim. But what is the actuall guarantee? We can reclaim that memory by the OOM killer, because there is no protection from killing a memcg under the min limit. So what is the actual semantic?
Also how is an admin supposed to configure those limits? low limit doesn't reall protect in some cases so why should it be used at all? I see how min matches max and low matches high, so there is a nice symmetry but aren't we adding additional complexity to the API? Isn't the real problem that the other guy (leaking application) doesn't have any cap?
Please note I haven't looked at the implementation yet but I would like to make sure I understand the whole concept first.
Signature · 2 lines
Michal Hocko SUSE Labs
Quoted 20 lines · Michal Hocko
Quoted 19 lines · Roman Gushchin
Memory controller implements the memory.low best-effort memory protection mechanism, which works perfectly in many cases and allows protecting working sets of important workloads from sudden reclaim.
But its semantics has a significant limitation: it works only as long as there is a supply of reclaimable memory. This makes it pretty useless against any sort of slow memory leaks or memory usage increases. This is especially true for swapless systems. If swap is enabled, memory soft protection effectively postpones problems, allowing a leaking application to fill all swap area, which makes no sense.
The only effective way to guarantee the memory protection in this case is to invoke the OOM killer.It's possible to handle this case in userspace by reacting on
MEMCG_LOWevents; but there is still a place for a fail-safe in-kernel mechanism to provide stronger guarantees.
Quoted 8 lines
Quoted 4 lines
This patch introduces the memory.min interface for cgroup v2 memory controller. It works very similarly to memory.low (sharing the same hierarchical behavior), except that it's not disabled if there is no more reclaimable memory in the system.
Originally I was pushing for the hard guarantee before we landed with the best effort one. The assumption back then was that properly configured systems shouldn't see problems IIRC.
Personally, I'm also not a big fan of the current memory.low semantics. If you remember, my very version of memory guarantee (back to 2013) implemented a hard approach: https://lwn.net/Articles/540240/
Quoted 7 lines
It is not entirely clear to me what is the role of the low limit wrt. leaking application from the above description TBH. I presume you have a process without any low&hard limit which leaks and basically breaks the low limit expectation because of the lack of reclaimable memory and our
memcg_low_reclaimfallback.
Quoted 5 lines
If that is the case then the hard limit should indeed protect the respective memcg from reclaim. But what is the actuall guarantee? We can reclaim that memory by the OOM killer, because there is no protection from killing a memcg under the min limit. So what is the actual semantic?
If memcg memory usage is under its effective min boundary, its memory won't be reclaimed.
Making OOM killer aware of memory guarantees is a separate topic (and definitely a good idea to discuss!), but let's agree on a simple fact that there are many workloads which prefer to be killed, rather than suffer from a too high memory pressure.
Quoted 7 lines
Also how is an admin supposed to configure those limits? low limit doesn't reall protect in some cases so why should it be used at all? I see how min matches max and low matches high, so there is a nice symmetry but aren't we adding additional complexity to the API? Isn't the real problem that the other guy (leaking application) doesn't have any cap?
My main point is that memory.low requires an userspace agent to actually guarantee something. This agent supposed to track low memory events and somehow decrease memory pressure,
if memory.low watermark is reached (stop some workloads, for example). This is not always handy, and having strong guarantee makes sense, IMO.
We're experimenting with different setups, and the current approach is to set memory.min to the minimal value which guarantees normal functioning of a workload, while memory.low can be set to a much higher value, which sometimes brings some performance gains.
Thanks!