diff mbox series

[v5] zswap: memcontrol: implement zswap writeback disabling

Message ID 20231115172344.4155593-1-nphamcs@gmail.com (mailing list archive)
State New
Headers show
Series [v5] zswap: memcontrol: implement zswap writeback disabling | expand

Commit Message

Nhat Pham Nov. 15, 2023, 5:23 p.m. UTC
During our experiment with zswap, we sometimes observe swap IOs due to
occasional zswap store failures and writebacks-to-swap. These swapping
IOs prevent many users who cannot tolerate swapping from adopting zswap
to save memory and improve performance where possible.

This patch adds the option to disable this behavior entirely: do not
writeback to backing swapping device when a zswap store attempt fail,
and do not write pages in the zswap pool back to the backing swap
device (both when the pool is full, and when the new zswap shrinker is
called).

This new behavior can be opted-in/out on a per-cgroup basis via a new
cgroup file. By default, writebacks to swap device is enabled, which is
the previous behavior. Initially, writeback is enabled for the root
cgroup, and a newly created cgroup will inherit the current setting of
its parent.

Note that this is subtly different from setting memory.swap.max to 0, as
it still allows for pages to be stored in the zswap pool (which itself
consumes swap space in its current form).

This patch should be applied on top of the zswap shrinker series:

https://lore.kernel.org/lkml/20231106183159.3562879-1-nphamcs@gmail.com/

as it also disables the zswap shrinker, a major source of zswap
writebacks.

Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
Signed-off-by: Nhat Pham <nphamcs@gmail.com>
---
 Documentation/admin-guide/cgroup-v2.rst | 12 ++++++++
 Documentation/admin-guide/mm/zswap.rst  |  6 ++++
 include/linux/memcontrol.h              | 12 ++++++++
 include/linux/zswap.h                   |  6 ++++
 mm/memcontrol.c                         | 38 +++++++++++++++++++++++++
 mm/page_io.c                            |  6 ++++
 mm/shmem.c                              |  3 +-
 mm/zswap.c                              | 14 +++++++++
 8 files changed, 95 insertions(+), 2 deletions(-)

Comments

Chris Li Nov. 16, 2023, 8:53 p.m. UTC | #1
Hi Nhat,

As we discussed, I just want to bounce some alternative ideas related
to this write back disabled feature.

Currently, the common usage case is zswap alone or zswap + SSD. We
treat zswap and
SSD as two different tiers with different swap in performance characteristics.
If we make it more generic, we can also have more than two swap tiers.
e.g. we can have zswap, SSD, network swap or HDD swap.
The disable flag is just one bit of information, it can't describe
what is the next tier.if it is not the current swap file
implementation .

One idea is that we can have a more general swap_tier_list object to
describe the order of the swap device, The system can have more than
one such list to describe different combinations of the tier
selection.

Each memcg can have a pointer point to one of such swap_tier_list
objects, replacing the disabled write back flag in this patch.
When you swap out, it will just go through each tier in the list, try
to swap it out.
 This has some implications for the zswap shrink as well. It becomes a
more generic "move swap out data to another tier". Generally need to
load into the swap cache then write to another tier.

Open question is how we deal with the swap cache index across
different tiers. The zswap.writeback_disable will not be needed if we
have a more generic swap tier framework.

Chris


On Wed, Nov 15, 2023 at 9:24 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> During our experiment with zswap, we sometimes observe swap IOs due to
> occasional zswap store failures and writebacks-to-swap. These swapping
> IOs prevent many users who cannot tolerate swapping from adopting zswap
> to save memory and improve performance where possible.
>
> This patch adds the option to disable this behavior entirely: do not
> writeback to backing swapping device when a zswap store attempt fail,
> and do not write pages in the zswap pool back to the backing swap
> device (both when the pool is full, and when the new zswap shrinker is
> called).
>
> This new behavior can be opted-in/out on a per-cgroup basis via a new
> cgroup file. By default, writebacks to swap device is enabled, which is
> the previous behavior. Initially, writeback is enabled for the root
> cgroup, and a newly created cgroup will inherit the current setting of
> its parent.
>
> Note that this is subtly different from setting memory.swap.max to 0, as
> it still allows for pages to be stored in the zswap pool (which itself
> consumes swap space in its current form).
>
> This patch should be applied on top of the zswap shrinker series:
>
> https://lore.kernel.org/lkml/20231106183159.3562879-1-nphamcs@gmail.com/
>
> as it also disables the zswap shrinker, a major source of zswap
> writebacks.
>
> Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> Signed-off-by: Nhat Pham <nphamcs@gmail.com>
> ---
>  Documentation/admin-guide/cgroup-v2.rst | 12 ++++++++
>  Documentation/admin-guide/mm/zswap.rst  |  6 ++++
>  include/linux/memcontrol.h              | 12 ++++++++
>  include/linux/zswap.h                   |  6 ++++
>  mm/memcontrol.c                         | 38 +++++++++++++++++++++++++
>  mm/page_io.c                            |  6 ++++
>  mm/shmem.c                              |  3 +-
>  mm/zswap.c                              | 14 +++++++++
>  8 files changed, 95 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> index 3f85254f3cef..2b4ac43efdc8 100644
> --- a/Documentation/admin-guide/cgroup-v2.rst
> +++ b/Documentation/admin-guide/cgroup-v2.rst
> @@ -1679,6 +1679,18 @@ PAGE_SIZE multiple when read back.
>         limit, it will refuse to take any more stores before existing
>         entries fault back in or are written out to disk.
>
> +  memory.zswap.writeback
> +       A read-write single value file. The default value is "1". The
> +       initial value of the root cgroup is 1, and when a new cgroup is
> +       created, it inherits the current value of its parent.
> +
> +       When this is set to 0, all swapping attempts to swapping devices
> +       are disabled. This included both zswap writebacks, and swapping due
> +       to zswap store failure.
> +
> +       Note that this is subtly different from setting memory.swap.max to
> +       0, as it still allows for pages to be written to the zswap pool.
> +
>    memory.pressure
>         A read-only nested-keyed file.
>
> diff --git a/Documentation/admin-guide/mm/zswap.rst b/Documentation/admin-guide/mm/zswap.rst
> index 522ae22ccb84..b987e58edb70 100644
> --- a/Documentation/admin-guide/mm/zswap.rst
> +++ b/Documentation/admin-guide/mm/zswap.rst
> @@ -153,6 +153,12 @@ attribute, e. g.::
>
>  Setting this parameter to 100 will disable the hysteresis.
>
> +Some users cannot tolerate the swapping that comes with zswap store failures
> +and zswap writebacks. Swapping can be disabled entirely (without disabling
> +zswap itself) on a cgroup-basis as follows:
> +
> +       echo 0 > /sys/fs/cgroup/<cgroup-name>/memory.zswap.writeback
> +
>  When there is a sizable amount of cold memory residing in the zswap pool, it
>  can be advantageous to proactively write these cold pages to swap and reclaim
>  the memory for other use cases. By default, the zswap shrinker is disabled.
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index 83590fd0d6d1..3901ff4dae63 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -219,6 +219,12 @@ struct mem_cgroup {
>
>  #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
>         unsigned long zswap_max;
> +
> +       /*
> +        * Prevent pages from this memcg from being written back from zswap to
> +        * swap, and from being swapped out on zswap store failures.
> +        */
> +       bool zswap_writeback;
>  #endif
>
>         unsigned long soft_limit;
> @@ -1931,6 +1937,7 @@ static inline void count_objcg_event(struct obj_cgroup *objcg,
>  bool obj_cgroup_may_zswap(struct obj_cgroup *objcg);
>  void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size);
>  void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size);
> +bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg);
>  #else
>  static inline bool obj_cgroup_may_zswap(struct obj_cgroup *objcg)
>  {
> @@ -1944,6 +1951,11 @@ static inline void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg,
>                                              size_t size)
>  {
>  }
> +static inline bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
> +{
> +       /* if zswap is disabled, do not block pages going to the swapping device */
> +       return true;
> +}
>  #endif
>
>  #endif /* _LINUX_MEMCONTROL_H */
> diff --git a/include/linux/zswap.h b/include/linux/zswap.h
> index cbd373ba88d2..b4997e27a74b 100644
> --- a/include/linux/zswap.h
> +++ b/include/linux/zswap.h
> @@ -35,6 +35,7 @@ void zswap_swapoff(int type);
>  void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg);
>  void zswap_lruvec_state_init(struct lruvec *lruvec);
>  void zswap_lruvec_swapin(struct page *page);
> +bool is_zswap_enabled(void);
>  #else
>
>  struct zswap_lruvec_state {};
> @@ -55,6 +56,11 @@ static inline void zswap_swapoff(int type) {}
>  static inline void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg) {}
>  static inline void zswap_lruvec_init(struct lruvec *lruvec) {}
>  static inline void zswap_lruvec_swapin(struct page *page) {}
> +
> +static inline bool is_zswap_enabled(void)
> +{
> +       return false;
> +}
>  #endif
>
>  #endif /* _LINUX_ZSWAP_H */
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 786c7edf5836..5ad71ce31c74 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -5522,6 +5522,8 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)
>         WRITE_ONCE(memcg->soft_limit, PAGE_COUNTER_MAX);
>  #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
>         memcg->zswap_max = PAGE_COUNTER_MAX;
> +       WRITE_ONCE(memcg->zswap_writeback,
> +               !parent || READ_ONCE(parent->zswap_writeback));
>  #endif
>         page_counter_set_high(&memcg->swap, PAGE_COUNTER_MAX);
>         if (parent) {
> @@ -8146,6 +8148,12 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
>         rcu_read_unlock();
>  }
>
> +bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
> +{
> +       /* if zswap is disabled, do not block pages going to the swapping device */
> +       return !is_zswap_enabled() || !memcg || READ_ONCE(memcg->zswap_writeback);
> +}
> +
>  static u64 zswap_current_read(struct cgroup_subsys_state *css,
>                               struct cftype *cft)
>  {
> @@ -8176,6 +8184,31 @@ static ssize_t zswap_max_write(struct kernfs_open_file *of,
>         return nbytes;
>  }
>
> +static int zswap_writeback_show(struct seq_file *m, void *v)
> +{
> +       struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
> +
> +       seq_printf(m, "%d\n", READ_ONCE(memcg->zswap_writeback));
> +       return 0;
> +}
> +
> +static ssize_t zswap_writeback_write(struct kernfs_open_file *of,
> +                               char *buf, size_t nbytes, loff_t off)
> +{
> +       struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
> +       int zswap_writeback;
> +       ssize_t parse_ret = kstrtoint(strstrip(buf), 0, &zswap_writeback);
> +
> +       if (parse_ret)
> +               return parse_ret;
> +
> +       if (zswap_writeback != 0 && zswap_writeback != 1)
> +               return -EINVAL;
> +
> +       WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
> +       return nbytes;
> +}
> +
>  static struct cftype zswap_files[] = {
>         {
>                 .name = "zswap.current",
> @@ -8188,6 +8221,11 @@ static struct cftype zswap_files[] = {
>                 .seq_show = zswap_max_show,
>                 .write = zswap_max_write,
>         },
> +       {
> +               .name = "zswap.writeback",
> +               .seq_show = zswap_writeback_show,
> +               .write = zswap_writeback_write,
> +       },
>         { }     /* terminate */
>  };
>  #endif /* CONFIG_MEMCG_KMEM && CONFIG_ZSWAP */
> diff --git a/mm/page_io.c b/mm/page_io.c
> index cb559ae324c6..5e606f1aa2f6 100644
> --- a/mm/page_io.c
> +++ b/mm/page_io.c
> @@ -201,6 +201,12 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
>                 folio_end_writeback(folio);
>                 return 0;
>         }
> +
> +       if (!mem_cgroup_zswap_writeback_enabled(folio_memcg(folio))) {
> +               folio_mark_dirty(folio);
> +               return AOP_WRITEPAGE_ACTIVATE;
> +       }
> +
>         __swap_writepage(&folio->page, wbc);
>         return 0;
>  }
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 0d1ce70bce38..ccbaaa5f1c16 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -1514,8 +1514,7 @@ static int shmem_writepage(struct page *page, struct writeback_control *wbc)
>
>                 mutex_unlock(&shmem_swaplist_mutex);
>                 BUG_ON(folio_mapped(folio));
> -               swap_writepage(&folio->page, wbc);
> -               return 0;
> +               return swap_writepage(&folio->page, wbc);
>         }
>
>         mutex_unlock(&shmem_swaplist_mutex);
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 943090dfe793..caa467e40009 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -152,6 +152,11 @@ module_param_named(exclusive_loads, zswap_exclusive_loads_enabled, bool, 0644);
>  static bool zswap_shrinker_enabled;
>  module_param_named(shrinker_enabled, zswap_shrinker_enabled, bool, 0644);
>
> +bool is_zswap_enabled(void)
> +{
> +       return zswap_enabled;
> +}
> +
>  /*********************************
>  * data structures
>  **********************************/
> @@ -589,6 +594,9 @@ static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,
>         struct zswap_pool *pool = shrinker->private_data;
>         bool encountered_page_in_swapcache = false;
>
> +       if (!mem_cgroup_zswap_writeback_enabled(sc->memcg))
> +               return SHRINK_STOP;
> +
>         nr_protected =
>                 atomic_long_read(&lruvec->zswap_lruvec_state.nr_zswap_protected);
>         lru_size = list_lru_shrink_count(&pool->list_lru, sc);
> @@ -619,6 +627,9 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker,
>         struct lruvec *lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(sc->nid));
>         unsigned long nr_backing, nr_stored, nr_freeable, nr_protected;
>
> +       if (!mem_cgroup_zswap_writeback_enabled(memcg))
> +               return 0;
> +
>  #ifdef CONFIG_MEMCG_KMEM
>         cgroup_rstat_flush(memcg->css.cgroup);
>         nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT;
> @@ -934,6 +945,9 @@ static int shrink_memcg(struct mem_cgroup *memcg)
>         struct zswap_pool *pool;
>         int nid, shrunk = 0;
>
> +       if (!mem_cgroup_zswap_writeback_enabled(memcg))
> +               return -EINVAL;
> +
>         /*
>          * Skip zombies because their LRUs are reparented and we would be
>          * reclaiming from the parent instead of the dead memcg.
> --
> 2.34.1
Yosry Ahmed Nov. 16, 2023, 9 p.m. UTC | #2
On Thu, Nov 16, 2023 at 12:53 PM Chris Li <chrisl@kernel.org> wrote:
>
> Hi Nhat,
>
> As we discussed, I just want to bounce some alternative ideas related
> to this write back disabled feature.
>
> Currently, the common usage case is zswap alone or zswap + SSD. We
> treat zswap and
> SSD as two different tiers with different swap in performance characteristics.
> If we make it more generic, we can also have more than two swap tiers.
> e.g. we can have zswap, SSD, network swap or HDD swap.
> The disable flag is just one bit of information, it can't describe
> what is the next tier.if it is not the current swap file
> implementation .
>
> One idea is that we can have a more general swap_tier_list object to
> describe the order of the swap device, The system can have more than
> one such list to describe different combinations of the tier
> selection.
>
> Each memcg can have a pointer point to one of such swap_tier_list
> objects, replacing the disabled write back flag in this patch.
> When you swap out, it will just go through each tier in the list, try
> to swap it out.
>  This has some implications for the zswap shrink as well. It becomes a
> more generic "move swap out data to another tier". Generally need to
> load into the swap cache then write to another tier.
>
> Open question is how we deal with the swap cache index across
> different tiers. The zswap.writeback_disable will not be needed if we
> have a more generic swap tier framework.

I agree that this should be the long-term goal, and I suggested that
we make the interface more future-proof by making it more generic to
accept the types or tiers of swap allowed by the memcg:

https://lore.kernel.org/lkml/CAJD7tkY8iPBo99+1gdsSRMNDu4jkVKz8rb=W+xk9=GE0y=kSuw@mail.gmail.com/

Since we only have swap and zswap now, the implementation can be
similar to this code and basically just disable writeback if zswap is
the only allowed swapping mechanism. So we don't necessarily need to
have a full swap tiering implementation, but I agree with Chris that
at least having a future-proof interface to work with generic swap
tiering is preferrable.



>
> Chris
>
>
> On Wed, Nov 15, 2023 at 9:24 AM Nhat Pham <nphamcs@gmail.com> wrote:
> >
> > During our experiment with zswap, we sometimes observe swap IOs due to
> > occasional zswap store failures and writebacks-to-swap. These swapping
> > IOs prevent many users who cannot tolerate swapping from adopting zswap
> > to save memory and improve performance where possible.
> >
> > This patch adds the option to disable this behavior entirely: do not
> > writeback to backing swapping device when a zswap store attempt fail,
> > and do not write pages in the zswap pool back to the backing swap
> > device (both when the pool is full, and when the new zswap shrinker is
> > called).
> >
> > This new behavior can be opted-in/out on a per-cgroup basis via a new
> > cgroup file. By default, writebacks to swap device is enabled, which is
> > the previous behavior. Initially, writeback is enabled for the root
> > cgroup, and a newly created cgroup will inherit the current setting of
> > its parent.
> >
> > Note that this is subtly different from setting memory.swap.max to 0, as
> > it still allows for pages to be stored in the zswap pool (which itself
> > consumes swap space in its current form).
> >
> > This patch should be applied on top of the zswap shrinker series:
> >
> > https://lore.kernel.org/lkml/20231106183159.3562879-1-nphamcs@gmail.com/
> >
> > as it also disables the zswap shrinker, a major source of zswap
> > writebacks.
> >
> > Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> > Signed-off-by: Nhat Pham <nphamcs@gmail.com>
> > ---
> >  Documentation/admin-guide/cgroup-v2.rst | 12 ++++++++
> >  Documentation/admin-guide/mm/zswap.rst  |  6 ++++
> >  include/linux/memcontrol.h              | 12 ++++++++
> >  include/linux/zswap.h                   |  6 ++++
> >  mm/memcontrol.c                         | 38 +++++++++++++++++++++++++
> >  mm/page_io.c                            |  6 ++++
> >  mm/shmem.c                              |  3 +-
> >  mm/zswap.c                              | 14 +++++++++
> >  8 files changed, 95 insertions(+), 2 deletions(-)
> >
> > diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> > index 3f85254f3cef..2b4ac43efdc8 100644
> > --- a/Documentation/admin-guide/cgroup-v2.rst
> > +++ b/Documentation/admin-guide/cgroup-v2.rst
> > @@ -1679,6 +1679,18 @@ PAGE_SIZE multiple when read back.
> >         limit, it will refuse to take any more stores before existing
> >         entries fault back in or are written out to disk.
> >
> > +  memory.zswap.writeback
> > +       A read-write single value file. The default value is "1". The
> > +       initial value of the root cgroup is 1, and when a new cgroup is
> > +       created, it inherits the current value of its parent.
> > +
> > +       When this is set to 0, all swapping attempts to swapping devices
> > +       are disabled. This included both zswap writebacks, and swapping due
> > +       to zswap store failure.
> > +
> > +       Note that this is subtly different from setting memory.swap.max to
> > +       0, as it still allows for pages to be written to the zswap pool.
> > +
> >    memory.pressure
> >         A read-only nested-keyed file.
> >
> > diff --git a/Documentation/admin-guide/mm/zswap.rst b/Documentation/admin-guide/mm/zswap.rst
> > index 522ae22ccb84..b987e58edb70 100644
> > --- a/Documentation/admin-guide/mm/zswap.rst
> > +++ b/Documentation/admin-guide/mm/zswap.rst
> > @@ -153,6 +153,12 @@ attribute, e. g.::
> >
> >  Setting this parameter to 100 will disable the hysteresis.
> >
> > +Some users cannot tolerate the swapping that comes with zswap store failures
> > +and zswap writebacks. Swapping can be disabled entirely (without disabling
> > +zswap itself) on a cgroup-basis as follows:
> > +
> > +       echo 0 > /sys/fs/cgroup/<cgroup-name>/memory.zswap.writeback
> > +
> >  When there is a sizable amount of cold memory residing in the zswap pool, it
> >  can be advantageous to proactively write these cold pages to swap and reclaim
> >  the memory for other use cases. By default, the zswap shrinker is disabled.
> > diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> > index 83590fd0d6d1..3901ff4dae63 100644
> > --- a/include/linux/memcontrol.h
> > +++ b/include/linux/memcontrol.h
> > @@ -219,6 +219,12 @@ struct mem_cgroup {
> >
> >  #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
> >         unsigned long zswap_max;
> > +
> > +       /*
> > +        * Prevent pages from this memcg from being written back from zswap to
> > +        * swap, and from being swapped out on zswap store failures.
> > +        */
> > +       bool zswap_writeback;
> >  #endif
> >
> >         unsigned long soft_limit;
> > @@ -1931,6 +1937,7 @@ static inline void count_objcg_event(struct obj_cgroup *objcg,
> >  bool obj_cgroup_may_zswap(struct obj_cgroup *objcg);
> >  void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size);
> >  void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size);
> > +bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg);
> >  #else
> >  static inline bool obj_cgroup_may_zswap(struct obj_cgroup *objcg)
> >  {
> > @@ -1944,6 +1951,11 @@ static inline void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg,
> >                                              size_t size)
> >  {
> >  }
> > +static inline bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
> > +{
> > +       /* if zswap is disabled, do not block pages going to the swapping device */
> > +       return true;
> > +}
> >  #endif
> >
> >  #endif /* _LINUX_MEMCONTROL_H */
> > diff --git a/include/linux/zswap.h b/include/linux/zswap.h
> > index cbd373ba88d2..b4997e27a74b 100644
> > --- a/include/linux/zswap.h
> > +++ b/include/linux/zswap.h
> > @@ -35,6 +35,7 @@ void zswap_swapoff(int type);
> >  void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg);
> >  void zswap_lruvec_state_init(struct lruvec *lruvec);
> >  void zswap_lruvec_swapin(struct page *page);
> > +bool is_zswap_enabled(void);
> >  #else
> >
> >  struct zswap_lruvec_state {};
> > @@ -55,6 +56,11 @@ static inline void zswap_swapoff(int type) {}
> >  static inline void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg) {}
> >  static inline void zswap_lruvec_init(struct lruvec *lruvec) {}
> >  static inline void zswap_lruvec_swapin(struct page *page) {}
> > +
> > +static inline bool is_zswap_enabled(void)
> > +{
> > +       return false;
> > +}
> >  #endif
> >
> >  #endif /* _LINUX_ZSWAP_H */
> > diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> > index 786c7edf5836..5ad71ce31c74 100644
> > --- a/mm/memcontrol.c
> > +++ b/mm/memcontrol.c
> > @@ -5522,6 +5522,8 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)
> >         WRITE_ONCE(memcg->soft_limit, PAGE_COUNTER_MAX);
> >  #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
> >         memcg->zswap_max = PAGE_COUNTER_MAX;
> > +       WRITE_ONCE(memcg->zswap_writeback,
> > +               !parent || READ_ONCE(parent->zswap_writeback));
> >  #endif
> >         page_counter_set_high(&memcg->swap, PAGE_COUNTER_MAX);
> >         if (parent) {
> > @@ -8146,6 +8148,12 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
> >         rcu_read_unlock();
> >  }
> >
> > +bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
> > +{
> > +       /* if zswap is disabled, do not block pages going to the swapping device */
> > +       return !is_zswap_enabled() || !memcg || READ_ONCE(memcg->zswap_writeback);
> > +}
> > +
> >  static u64 zswap_current_read(struct cgroup_subsys_state *css,
> >                               struct cftype *cft)
> >  {
> > @@ -8176,6 +8184,31 @@ static ssize_t zswap_max_write(struct kernfs_open_file *of,
> >         return nbytes;
> >  }
> >
> > +static int zswap_writeback_show(struct seq_file *m, void *v)
> > +{
> > +       struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
> > +
> > +       seq_printf(m, "%d\n", READ_ONCE(memcg->zswap_writeback));
> > +       return 0;
> > +}
> > +
> > +static ssize_t zswap_writeback_write(struct kernfs_open_file *of,
> > +                               char *buf, size_t nbytes, loff_t off)
> > +{
> > +       struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
> > +       int zswap_writeback;
> > +       ssize_t parse_ret = kstrtoint(strstrip(buf), 0, &zswap_writeback);
> > +
> > +       if (parse_ret)
> > +               return parse_ret;
> > +
> > +       if (zswap_writeback != 0 && zswap_writeback != 1)
> > +               return -EINVAL;
> > +
> > +       WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
> > +       return nbytes;
> > +}
> > +
> >  static struct cftype zswap_files[] = {
> >         {
> >                 .name = "zswap.current",
> > @@ -8188,6 +8221,11 @@ static struct cftype zswap_files[] = {
> >                 .seq_show = zswap_max_show,
> >                 .write = zswap_max_write,
> >         },
> > +       {
> > +               .name = "zswap.writeback",
> > +               .seq_show = zswap_writeback_show,
> > +               .write = zswap_writeback_write,
> > +       },
> >         { }     /* terminate */
> >  };
> >  #endif /* CONFIG_MEMCG_KMEM && CONFIG_ZSWAP */
> > diff --git a/mm/page_io.c b/mm/page_io.c
> > index cb559ae324c6..5e606f1aa2f6 100644
> > --- a/mm/page_io.c
> > +++ b/mm/page_io.c
> > @@ -201,6 +201,12 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
> >                 folio_end_writeback(folio);
> >                 return 0;
> >         }
> > +
> > +       if (!mem_cgroup_zswap_writeback_enabled(folio_memcg(folio))) {
> > +               folio_mark_dirty(folio);
> > +               return AOP_WRITEPAGE_ACTIVATE;
> > +       }
> > +
> >         __swap_writepage(&folio->page, wbc);
> >         return 0;
> >  }
> > diff --git a/mm/shmem.c b/mm/shmem.c
> > index 0d1ce70bce38..ccbaaa5f1c16 100644
> > --- a/mm/shmem.c
> > +++ b/mm/shmem.c
> > @@ -1514,8 +1514,7 @@ static int shmem_writepage(struct page *page, struct writeback_control *wbc)
> >
> >                 mutex_unlock(&shmem_swaplist_mutex);
> >                 BUG_ON(folio_mapped(folio));
> > -               swap_writepage(&folio->page, wbc);
> > -               return 0;
> > +               return swap_writepage(&folio->page, wbc);
> >         }
> >
> >         mutex_unlock(&shmem_swaplist_mutex);
> > diff --git a/mm/zswap.c b/mm/zswap.c
> > index 943090dfe793..caa467e40009 100644
> > --- a/mm/zswap.c
> > +++ b/mm/zswap.c
> > @@ -152,6 +152,11 @@ module_param_named(exclusive_loads, zswap_exclusive_loads_enabled, bool, 0644);
> >  static bool zswap_shrinker_enabled;
> >  module_param_named(shrinker_enabled, zswap_shrinker_enabled, bool, 0644);
> >
> > +bool is_zswap_enabled(void)
> > +{
> > +       return zswap_enabled;
> > +}
> > +
> >  /*********************************
> >  * data structures
> >  **********************************/
> > @@ -589,6 +594,9 @@ static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,
> >         struct zswap_pool *pool = shrinker->private_data;
> >         bool encountered_page_in_swapcache = false;
> >
> > +       if (!mem_cgroup_zswap_writeback_enabled(sc->memcg))
> > +               return SHRINK_STOP;
> > +
> >         nr_protected =
> >                 atomic_long_read(&lruvec->zswap_lruvec_state.nr_zswap_protected);
> >         lru_size = list_lru_shrink_count(&pool->list_lru, sc);
> > @@ -619,6 +627,9 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker,
> >         struct lruvec *lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(sc->nid));
> >         unsigned long nr_backing, nr_stored, nr_freeable, nr_protected;
> >
> > +       if (!mem_cgroup_zswap_writeback_enabled(memcg))
> > +               return 0;
> > +
> >  #ifdef CONFIG_MEMCG_KMEM
> >         cgroup_rstat_flush(memcg->css.cgroup);
> >         nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT;
> > @@ -934,6 +945,9 @@ static int shrink_memcg(struct mem_cgroup *memcg)
> >         struct zswap_pool *pool;
> >         int nid, shrunk = 0;
> >
> > +       if (!mem_cgroup_zswap_writeback_enabled(memcg))
> > +               return -EINVAL;
> > +
> >         /*
> >          * Skip zombies because their LRUs are reparented and we would be
> >          * reclaiming from the parent instead of the dead memcg.
> > --
> > 2.34.1
Chris Li Nov. 17, 2023, 12:39 a.m. UTC | #3
Hi Yosry,

I think we are in agreement here. I am already Acked on the previous
version of this patch.
Consider this version as well because the change suggestion is from me.
I am fine with merging the zswap.writeback.

It is also good to get some discussion on the more general ABI as
well. Just like you said, we can also explore the zswap + swapfile
using the more general API. Because obsoleting user-visible ABI is
much harder.

Chris

On Thu, Nov 16, 2023 at 1:01 PM Yosry Ahmed <yosryahmed@google.com> wrote:
>
> On Thu, Nov 16, 2023 at 12:53 PM Chris Li <chrisl@kernel.org> wrote:
> >
> > Hi Nhat,
> >
> > As we discussed, I just want to bounce some alternative ideas related
> > to this write back disabled feature.
> >
> > Currently, the common usage case is zswap alone or zswap + SSD. We
> > treat zswap and
> > SSD as two different tiers with different swap in performance characteristics.
> > If we make it more generic, we can also have more than two swap tiers.
> > e.g. we can have zswap, SSD, network swap or HDD swap.
> > The disable flag is just one bit of information, it can't describe
> > what is the next tier.if it is not the current swap file
> > implementation .
> >
> > One idea is that we can have a more general swap_tier_list object to
> > describe the order of the swap device, The system can have more than
> > one such list to describe different combinations of the tier
> > selection.
> >
> > Each memcg can have a pointer point to one of such swap_tier_list
> > objects, replacing the disabled write back flag in this patch.
> > When you swap out, it will just go through each tier in the list, try
> > to swap it out.
> >  This has some implications for the zswap shrink as well. It becomes a
> > more generic "move swap out data to another tier". Generally need to
> > load into the swap cache then write to another tier.
> >
> > Open question is how we deal with the swap cache index across
> > different tiers. The zswap.writeback_disable will not be needed if we
> > have a more generic swap tier framework.
>
> I agree that this should be the long-term goal, and I suggested that
> we make the interface more future-proof by making it more generic to
> accept the types or tiers of swap allowed by the memcg:
>
> https://lore.kernel.org/lkml/CAJD7tkY8iPBo99+1gdsSRMNDu4jkVKz8rb=W+xk9=GE0y=kSuw@mail.gmail.com/
>
> Since we only have swap and zswap now, the implementation can be
> similar to this code and basically just disable writeback if zswap is
> the only allowed swapping mechanism. So we don't necessarily need to
> have a full swap tiering implementation, but I agree with Chris that
> at least having a future-proof interface to work with generic swap
> tiering is preferrable.
>
>
>
> >
> > Chris
> >
> >
> > On Wed, Nov 15, 2023 at 9:24 AM Nhat Pham <nphamcs@gmail.com> wrote:
> > >
> > > During our experiment with zswap, we sometimes observe swap IOs due to
> > > occasional zswap store failures and writebacks-to-swap. These swapping
> > > IOs prevent many users who cannot tolerate swapping from adopting zswap
> > > to save memory and improve performance where possible.
> > >
> > > This patch adds the option to disable this behavior entirely: do not
> > > writeback to backing swapping device when a zswap store attempt fail,
> > > and do not write pages in the zswap pool back to the backing swap
> > > device (both when the pool is full, and when the new zswap shrinker is
> > > called).
> > >
> > > This new behavior can be opted-in/out on a per-cgroup basis via a new
> > > cgroup file. By default, writebacks to swap device is enabled, which is
> > > the previous behavior. Initially, writeback is enabled for the root
> > > cgroup, and a newly created cgroup will inherit the current setting of
> > > its parent.
> > >
> > > Note that this is subtly different from setting memory.swap.max to 0, as
> > > it still allows for pages to be stored in the zswap pool (which itself
> > > consumes swap space in its current form).
> > >
> > > This patch should be applied on top of the zswap shrinker series:
> > >
> > > https://lore.kernel.org/lkml/20231106183159.3562879-1-nphamcs@gmail.com/
> > >
> > > as it also disables the zswap shrinker, a major source of zswap
> > > writebacks.
> > >
> > > Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> > > Signed-off-by: Nhat Pham <nphamcs@gmail.com>
> > > ---
> > >  Documentation/admin-guide/cgroup-v2.rst | 12 ++++++++
> > >  Documentation/admin-guide/mm/zswap.rst  |  6 ++++
> > >  include/linux/memcontrol.h              | 12 ++++++++
> > >  include/linux/zswap.h                   |  6 ++++
> > >  mm/memcontrol.c                         | 38 +++++++++++++++++++++++++
> > >  mm/page_io.c                            |  6 ++++
> > >  mm/shmem.c                              |  3 +-
> > >  mm/zswap.c                              | 14 +++++++++
> > >  8 files changed, 95 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> > > index 3f85254f3cef..2b4ac43efdc8 100644
> > > --- a/Documentation/admin-guide/cgroup-v2.rst
> > > +++ b/Documentation/admin-guide/cgroup-v2.rst
> > > @@ -1679,6 +1679,18 @@ PAGE_SIZE multiple when read back.
> > >         limit, it will refuse to take any more stores before existing
> > >         entries fault back in or are written out to disk.
> > >
> > > +  memory.zswap.writeback
> > > +       A read-write single value file. The default value is "1". The
> > > +       initial value of the root cgroup is 1, and when a new cgroup is
> > > +       created, it inherits the current value of its parent.
> > > +
> > > +       When this is set to 0, all swapping attempts to swapping devices
> > > +       are disabled. This included both zswap writebacks, and swapping due
> > > +       to zswap store failure.
> > > +
> > > +       Note that this is subtly different from setting memory.swap.max to
> > > +       0, as it still allows for pages to be written to the zswap pool.
> > > +
> > >    memory.pressure
> > >         A read-only nested-keyed file.
> > >
> > > diff --git a/Documentation/admin-guide/mm/zswap.rst b/Documentation/admin-guide/mm/zswap.rst
> > > index 522ae22ccb84..b987e58edb70 100644
> > > --- a/Documentation/admin-guide/mm/zswap.rst
> > > +++ b/Documentation/admin-guide/mm/zswap.rst
> > > @@ -153,6 +153,12 @@ attribute, e. g.::
> > >
> > >  Setting this parameter to 100 will disable the hysteresis.
> > >
> > > +Some users cannot tolerate the swapping that comes with zswap store failures
> > > +and zswap writebacks. Swapping can be disabled entirely (without disabling
> > > +zswap itself) on a cgroup-basis as follows:
> > > +
> > > +       echo 0 > /sys/fs/cgroup/<cgroup-name>/memory.zswap.writeback
> > > +
> > >  When there is a sizable amount of cold memory residing in the zswap pool, it
> > >  can be advantageous to proactively write these cold pages to swap and reclaim
> > >  the memory for other use cases. By default, the zswap shrinker is disabled.
> > > diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> > > index 83590fd0d6d1..3901ff4dae63 100644
> > > --- a/include/linux/memcontrol.h
> > > +++ b/include/linux/memcontrol.h
> > > @@ -219,6 +219,12 @@ struct mem_cgroup {
> > >
> > >  #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
> > >         unsigned long zswap_max;
> > > +
> > > +       /*
> > > +        * Prevent pages from this memcg from being written back from zswap to
> > > +        * swap, and from being swapped out on zswap store failures.
> > > +        */
> > > +       bool zswap_writeback;
> > >  #endif
> > >
> > >         unsigned long soft_limit;
> > > @@ -1931,6 +1937,7 @@ static inline void count_objcg_event(struct obj_cgroup *objcg,
> > >  bool obj_cgroup_may_zswap(struct obj_cgroup *objcg);
> > >  void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size);
> > >  void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size);
> > > +bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg);
> > >  #else
> > >  static inline bool obj_cgroup_may_zswap(struct obj_cgroup *objcg)
> > >  {
> > > @@ -1944,6 +1951,11 @@ static inline void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg,
> > >                                              size_t size)
> > >  {
> > >  }
> > > +static inline bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
> > > +{
> > > +       /* if zswap is disabled, do not block pages going to the swapping device */
> > > +       return true;
> > > +}
> > >  #endif
> > >
> > >  #endif /* _LINUX_MEMCONTROL_H */
> > > diff --git a/include/linux/zswap.h b/include/linux/zswap.h
> > > index cbd373ba88d2..b4997e27a74b 100644
> > > --- a/include/linux/zswap.h
> > > +++ b/include/linux/zswap.h
> > > @@ -35,6 +35,7 @@ void zswap_swapoff(int type);
> > >  void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg);
> > >  void zswap_lruvec_state_init(struct lruvec *lruvec);
> > >  void zswap_lruvec_swapin(struct page *page);
> > > +bool is_zswap_enabled(void);
> > >  #else
> > >
> > >  struct zswap_lruvec_state {};
> > > @@ -55,6 +56,11 @@ static inline void zswap_swapoff(int type) {}
> > >  static inline void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg) {}
> > >  static inline void zswap_lruvec_init(struct lruvec *lruvec) {}
> > >  static inline void zswap_lruvec_swapin(struct page *page) {}
> > > +
> > > +static inline bool is_zswap_enabled(void)
> > > +{
> > > +       return false;
> > > +}
> > >  #endif
> > >
> > >  #endif /* _LINUX_ZSWAP_H */
> > > diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> > > index 786c7edf5836..5ad71ce31c74 100644
> > > --- a/mm/memcontrol.c
> > > +++ b/mm/memcontrol.c
> > > @@ -5522,6 +5522,8 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)
> > >         WRITE_ONCE(memcg->soft_limit, PAGE_COUNTER_MAX);
> > >  #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
> > >         memcg->zswap_max = PAGE_COUNTER_MAX;
> > > +       WRITE_ONCE(memcg->zswap_writeback,
> > > +               !parent || READ_ONCE(parent->zswap_writeback));
> > >  #endif
> > >         page_counter_set_high(&memcg->swap, PAGE_COUNTER_MAX);
> > >         if (parent) {
> > > @@ -8146,6 +8148,12 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
> > >         rcu_read_unlock();
> > >  }
> > >
> > > +bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
> > > +{
> > > +       /* if zswap is disabled, do not block pages going to the swapping device */
> > > +       return !is_zswap_enabled() || !memcg || READ_ONCE(memcg->zswap_writeback);
> > > +}
> > > +
> > >  static u64 zswap_current_read(struct cgroup_subsys_state *css,
> > >                               struct cftype *cft)
> > >  {
> > > @@ -8176,6 +8184,31 @@ static ssize_t zswap_max_write(struct kernfs_open_file *of,
> > >         return nbytes;
> > >  }
> > >
> > > +static int zswap_writeback_show(struct seq_file *m, void *v)
> > > +{
> > > +       struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
> > > +
> > > +       seq_printf(m, "%d\n", READ_ONCE(memcg->zswap_writeback));
> > > +       return 0;
> > > +}
> > > +
> > > +static ssize_t zswap_writeback_write(struct kernfs_open_file *of,
> > > +                               char *buf, size_t nbytes, loff_t off)
> > > +{
> > > +       struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
> > > +       int zswap_writeback;
> > > +       ssize_t parse_ret = kstrtoint(strstrip(buf), 0, &zswap_writeback);
> > > +
> > > +       if (parse_ret)
> > > +               return parse_ret;
> > > +
> > > +       if (zswap_writeback != 0 && zswap_writeback != 1)
> > > +               return -EINVAL;
> > > +
> > > +       WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
> > > +       return nbytes;
> > > +}
> > > +
> > >  static struct cftype zswap_files[] = {
> > >         {
> > >                 .name = "zswap.current",
> > > @@ -8188,6 +8221,11 @@ static struct cftype zswap_files[] = {
> > >                 .seq_show = zswap_max_show,
> > >                 .write = zswap_max_write,
> > >         },
> > > +       {
> > > +               .name = "zswap.writeback",
> > > +               .seq_show = zswap_writeback_show,
> > > +               .write = zswap_writeback_write,
> > > +       },
> > >         { }     /* terminate */
> > >  };
> > >  #endif /* CONFIG_MEMCG_KMEM && CONFIG_ZSWAP */
> > > diff --git a/mm/page_io.c b/mm/page_io.c
> > > index cb559ae324c6..5e606f1aa2f6 100644
> > > --- a/mm/page_io.c
> > > +++ b/mm/page_io.c
> > > @@ -201,6 +201,12 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
> > >                 folio_end_writeback(folio);
> > >                 return 0;
> > >         }
> > > +
> > > +       if (!mem_cgroup_zswap_writeback_enabled(folio_memcg(folio))) {
> > > +               folio_mark_dirty(folio);
> > > +               return AOP_WRITEPAGE_ACTIVATE;
> > > +       }
> > > +
> > >         __swap_writepage(&folio->page, wbc);
> > >         return 0;
> > >  }
> > > diff --git a/mm/shmem.c b/mm/shmem.c
> > > index 0d1ce70bce38..ccbaaa5f1c16 100644
> > > --- a/mm/shmem.c
> > > +++ b/mm/shmem.c
> > > @@ -1514,8 +1514,7 @@ static int shmem_writepage(struct page *page, struct writeback_control *wbc)
> > >
> > >                 mutex_unlock(&shmem_swaplist_mutex);
> > >                 BUG_ON(folio_mapped(folio));
> > > -               swap_writepage(&folio->page, wbc);
> > > -               return 0;
> > > +               return swap_writepage(&folio->page, wbc);
> > >         }
> > >
> > >         mutex_unlock(&shmem_swaplist_mutex);
> > > diff --git a/mm/zswap.c b/mm/zswap.c
> > > index 943090dfe793..caa467e40009 100644
> > > --- a/mm/zswap.c
> > > +++ b/mm/zswap.c
> > > @@ -152,6 +152,11 @@ module_param_named(exclusive_loads, zswap_exclusive_loads_enabled, bool, 0644);
> > >  static bool zswap_shrinker_enabled;
> > >  module_param_named(shrinker_enabled, zswap_shrinker_enabled, bool, 0644);
> > >
> > > +bool is_zswap_enabled(void)
> > > +{
> > > +       return zswap_enabled;
> > > +}
> > > +
> > >  /*********************************
> > >  * data structures
> > >  **********************************/
> > > @@ -589,6 +594,9 @@ static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,
> > >         struct zswap_pool *pool = shrinker->private_data;
> > >         bool encountered_page_in_swapcache = false;
> > >
> > > +       if (!mem_cgroup_zswap_writeback_enabled(sc->memcg))
> > > +               return SHRINK_STOP;
> > > +
> > >         nr_protected =
> > >                 atomic_long_read(&lruvec->zswap_lruvec_state.nr_zswap_protected);
> > >         lru_size = list_lru_shrink_count(&pool->list_lru, sc);
> > > @@ -619,6 +627,9 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker,
> > >         struct lruvec *lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(sc->nid));
> > >         unsigned long nr_backing, nr_stored, nr_freeable, nr_protected;
> > >
> > > +       if (!mem_cgroup_zswap_writeback_enabled(memcg))
> > > +               return 0;
> > > +
> > >  #ifdef CONFIG_MEMCG_KMEM
> > >         cgroup_rstat_flush(memcg->css.cgroup);
> > >         nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT;
> > > @@ -934,6 +945,9 @@ static int shrink_memcg(struct mem_cgroup *memcg)
> > >         struct zswap_pool *pool;
> > >         int nid, shrunk = 0;
> > >
> > > +       if (!mem_cgroup_zswap_writeback_enabled(memcg))
> > > +               return -EINVAL;
> > > +
> > >         /*
> > >          * Skip zombies because their LRUs are reparented and we would be
> > >          * reclaiming from the parent instead of the dead memcg.
> > > --
> > > 2.34.1
>
Nhat Pham Nov. 18, 2023, 7:23 p.m. UTC | #4
On Thu, Nov 16, 2023 at 7:39 PM Chris Li <chrisl@kernel.org> wrote:
>
> Hi Yosry,
>
> I think we are in agreement here. I am already Acked on the previous
> version of this patch.
> Consider this version as well because the change suggestion is from me.
> I am fine with merging the zswap.writeback.
>
> It is also good to get some discussion on the more general ABI as
> well. Just like you said, we can also explore the zswap + swapfile
> using the more general API. Because obsoleting user-visible ABI is
> much harder.
>
> Chris

Hmm how about this - in the future, we support the following
options:

1. zswap.writeback == 1: no limitation to zswap writeback.
All backing swap devices (sorted by priorities?) are fair game.

2. zswap.writeback == 0: disable all forms of zswap writeback.

3. zswap.writeback == <tiers description>:  attempt to write to each
tier, one at a time.

The first two are basically what we have for this patch.
The last one will be added in a future patch.

This is from the userspace perspective. Internally, we can modify
memcg->writeback to be a pointer or a struct instead of this bool.
(as you suggested).

This way, the API remains intact and backward compatible
(and FWIW, I think there are still a lot of values in having simple
options for the users who have simple memory hierarchies).

>
> On Thu, Nov 16, 2023 at 1:01 PM Yosry Ahmed <yosryahmed@google.com> wrote:
> >
> > On Thu, Nov 16, 2023 at 12:53 PM Chris Li <chrisl@kernel.org> wrote:
> > >
> > > Hi Nhat,
> > >
> > > As we discussed, I just want to bounce some alternative ideas related
> > > to this write back disabled feature.
> > >
> > > Currently, the common usage case is zswap alone or zswap + SSD. We
> > > treat zswap and
> > > SSD as two different tiers with different swap in performance characteristics.
> > > If we make it more generic, we can also have more than two swap tiers.
> > > e.g. we can have zswap, SSD, network swap or HDD swap.
> > > The disable flag is just one bit of information, it can't describe
> > > what is the next tier.if it is not the current swap file
> > > implementation .
> > >
> > > One idea is that we can have a more general swap_tier_list object to
> > > describe the order of the swap device, The system can have more than
> > > one such list to describe different combinations of the tier
> > > selection.
> > >
> > > Each memcg can have a pointer point to one of such swap_tier_list
> > > objects, replacing the disabled write back flag in this patch.
> > > When you swap out, it will just go through each tier in the list, try
> > > to swap it out.
> > >  This has some implications for the zswap shrink as well. It becomes a
> > > more generic "move swap out data to another tier". Generally need to
> > > load into the swap cache then write to another tier.
> > >
> > > Open question is how we deal with the swap cache index across
> > > different tiers. The zswap.writeback_disable will not be needed if we
> > > have a more generic swap tier framework.
> >
> > I agree that this should be the long-term goal, and I suggested that
> > we make the interface more future-proof by making it more generic to
> > accept the types or tiers of swap allowed by the memcg:
> >
> > https://lore.kernel.org/lkml/CAJD7tkY8iPBo99+1gdsSRMNDu4jkVKz8rb=W+xk9=GE0y=kSuw@mail.gmail.com/
> >
> > Since we only have swap and zswap now, the implementation can be
> > similar to this code and basically just disable writeback if zswap is
> > the only allowed swapping mechanism. So we don't necessarily need to
> > have a full swap tiering implementation, but I agree with Chris that
> > at least having a future-proof interface to work with generic swap
> > tiering is preferrable.
> >
> >
> >
> > >
> > > Chris
> > >
> > >
> > > On Wed, Nov 15, 2023 at 9:24 AM Nhat Pham <nphamcs@gmail.com> wrote:
> > > >
> > > > During our experiment with zswap, we sometimes observe swap IOs due to
> > > > occasional zswap store failures and writebacks-to-swap. These swapping
> > > > IOs prevent many users who cannot tolerate swapping from adopting zswap
> > > > to save memory and improve performance where possible.
> > > >
> > > > This patch adds the option to disable this behavior entirely: do not
> > > > writeback to backing swapping device when a zswap store attempt fail,
> > > > and do not write pages in the zswap pool back to the backing swap
> > > > device (both when the pool is full, and when the new zswap shrinker is
> > > > called).
> > > >
> > > > This new behavior can be opted-in/out on a per-cgroup basis via a new
> > > > cgroup file. By default, writebacks to swap device is enabled, which is
> > > > the previous behavior. Initially, writeback is enabled for the root
> > > > cgroup, and a newly created cgroup will inherit the current setting of
> > > > its parent.
> > > >
> > > > Note that this is subtly different from setting memory.swap.max to 0, as
> > > > it still allows for pages to be stored in the zswap pool (which itself
> > > > consumes swap space in its current form).
> > > >
> > > > This patch should be applied on top of the zswap shrinker series:
> > > >
> > > > https://lore.kernel.org/lkml/20231106183159.3562879-1-nphamcs@gmail.com/
> > > >
> > > > as it also disables the zswap shrinker, a major source of zswap
> > > > writebacks.
> > > >
> > > > Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> > > > Signed-off-by: Nhat Pham <nphamcs@gmail.com>
> > > > ---
> > > >  Documentation/admin-guide/cgroup-v2.rst | 12 ++++++++
> > > >  Documentation/admin-guide/mm/zswap.rst  |  6 ++++
> > > >  include/linux/memcontrol.h              | 12 ++++++++
> > > >  include/linux/zswap.h                   |  6 ++++
> > > >  mm/memcontrol.c                         | 38 +++++++++++++++++++++++++
> > > >  mm/page_io.c                            |  6 ++++
> > > >  mm/shmem.c                              |  3 +-
> > > >  mm/zswap.c                              | 14 +++++++++
> > > >  8 files changed, 95 insertions(+), 2 deletions(-)
> > > >
> > > > diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> > > > index 3f85254f3cef..2b4ac43efdc8 100644
> > > > --- a/Documentation/admin-guide/cgroup-v2.rst
> > > > +++ b/Documentation/admin-guide/cgroup-v2.rst
> > > > @@ -1679,6 +1679,18 @@ PAGE_SIZE multiple when read back.
> > > >         limit, it will refuse to take any more stores before existing
> > > >         entries fault back in or are written out to disk.
> > > >
> > > > +  memory.zswap.writeback
> > > > +       A read-write single value file. The default value is "1". The
> > > > +       initial value of the root cgroup is 1, and when a new cgroup is
> > > > +       created, it inherits the current value of its parent.
> > > > +
> > > > +       When this is set to 0, all swapping attempts to swapping devices
> > > > +       are disabled. This included both zswap writebacks, and swapping due
> > > > +       to zswap store failure.
> > > > +
> > > > +       Note that this is subtly different from setting memory.swap.max to
> > > > +       0, as it still allows for pages to be written to the zswap pool.
> > > > +
> > > >    memory.pressure
> > > >         A read-only nested-keyed file.
> > > >
> > > > diff --git a/Documentation/admin-guide/mm/zswap.rst b/Documentation/admin-guide/mm/zswap.rst
> > > > index 522ae22ccb84..b987e58edb70 100644
> > > > --- a/Documentation/admin-guide/mm/zswap.rst
> > > > +++ b/Documentation/admin-guide/mm/zswap.rst
> > > > @@ -153,6 +153,12 @@ attribute, e. g.::
> > > >
> > > >  Setting this parameter to 100 will disable the hysteresis.
> > > >
> > > > +Some users cannot tolerate the swapping that comes with zswap store failures
> > > > +and zswap writebacks. Swapping can be disabled entirely (without disabling
> > > > +zswap itself) on a cgroup-basis as follows:
> > > > +
> > > > +       echo 0 > /sys/fs/cgroup/<cgroup-name>/memory.zswap.writeback
> > > > +
> > > >  When there is a sizable amount of cold memory residing in the zswap pool, it
> > > >  can be advantageous to proactively write these cold pages to swap and reclaim
> > > >  the memory for other use cases. By default, the zswap shrinker is disabled.
> > > > diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> > > > index 83590fd0d6d1..3901ff4dae63 100644
> > > > --- a/include/linux/memcontrol.h
> > > > +++ b/include/linux/memcontrol.h
> > > > @@ -219,6 +219,12 @@ struct mem_cgroup {
> > > >
> > > >  #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
> > > >         unsigned long zswap_max;
> > > > +
> > > > +       /*
> > > > +        * Prevent pages from this memcg from being written back from zswap to
> > > > +        * swap, and from being swapped out on zswap store failures.
> > > > +        */
> > > > +       bool zswap_writeback;
> > > >  #endif
> > > >
> > > >         unsigned long soft_limit;
> > > > @@ -1931,6 +1937,7 @@ static inline void count_objcg_event(struct obj_cgroup *objcg,
> > > >  bool obj_cgroup_may_zswap(struct obj_cgroup *objcg);
> > > >  void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size);
> > > >  void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size);
> > > > +bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg);
> > > >  #else
> > > >  static inline bool obj_cgroup_may_zswap(struct obj_cgroup *objcg)
> > > >  {
> > > > @@ -1944,6 +1951,11 @@ static inline void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg,
> > > >                                              size_t size)
> > > >  {
> > > >  }
> > > > +static inline bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
> > > > +{
> > > > +       /* if zswap is disabled, do not block pages going to the swapping device */
> > > > +       return true;
> > > > +}
> > > >  #endif
> > > >
> > > >  #endif /* _LINUX_MEMCONTROL_H */
> > > > diff --git a/include/linux/zswap.h b/include/linux/zswap.h
> > > > index cbd373ba88d2..b4997e27a74b 100644
> > > > --- a/include/linux/zswap.h
> > > > +++ b/include/linux/zswap.h
> > > > @@ -35,6 +35,7 @@ void zswap_swapoff(int type);
> > > >  void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg);
> > > >  void zswap_lruvec_state_init(struct lruvec *lruvec);
> > > >  void zswap_lruvec_swapin(struct page *page);
> > > > +bool is_zswap_enabled(void);
> > > >  #else
> > > >
> > > >  struct zswap_lruvec_state {};
> > > > @@ -55,6 +56,11 @@ static inline void zswap_swapoff(int type) {}
> > > >  static inline void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg) {}
> > > >  static inline void zswap_lruvec_init(struct lruvec *lruvec) {}
> > > >  static inline void zswap_lruvec_swapin(struct page *page) {}
> > > > +
> > > > +static inline bool is_zswap_enabled(void)
> > > > +{
> > > > +       return false;
> > > > +}
> > > >  #endif
> > > >
> > > >  #endif /* _LINUX_ZSWAP_H */
> > > > diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> > > > index 786c7edf5836..5ad71ce31c74 100644
> > > > --- a/mm/memcontrol.c
> > > > +++ b/mm/memcontrol.c
> > > > @@ -5522,6 +5522,8 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)
> > > >         WRITE_ONCE(memcg->soft_limit, PAGE_COUNTER_MAX);
> > > >  #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
> > > >         memcg->zswap_max = PAGE_COUNTER_MAX;
> > > > +       WRITE_ONCE(memcg->zswap_writeback,
> > > > +               !parent || READ_ONCE(parent->zswap_writeback));
> > > >  #endif
> > > >         page_counter_set_high(&memcg->swap, PAGE_COUNTER_MAX);
> > > >         if (parent) {
> > > > @@ -8146,6 +8148,12 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
> > > >         rcu_read_unlock();
> > > >  }
> > > >
> > > > +bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
> > > > +{
> > > > +       /* if zswap is disabled, do not block pages going to the swapping device */
> > > > +       return !is_zswap_enabled() || !memcg || READ_ONCE(memcg->zswap_writeback);
> > > > +}
> > > > +
> > > >  static u64 zswap_current_read(struct cgroup_subsys_state *css,
> > > >                               struct cftype *cft)
> > > >  {
> > > > @@ -8176,6 +8184,31 @@ static ssize_t zswap_max_write(struct kernfs_open_file *of,
> > > >         return nbytes;
> > > >  }
> > > >
> > > > +static int zswap_writeback_show(struct seq_file *m, void *v)
> > > > +{
> > > > +       struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
> > > > +
> > > > +       seq_printf(m, "%d\n", READ_ONCE(memcg->zswap_writeback));
> > > > +       return 0;
> > > > +}
> > > > +
> > > > +static ssize_t zswap_writeback_write(struct kernfs_open_file *of,
> > > > +                               char *buf, size_t nbytes, loff_t off)
> > > > +{
> > > > +       struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
> > > > +       int zswap_writeback;
> > > > +       ssize_t parse_ret = kstrtoint(strstrip(buf), 0, &zswap_writeback);
> > > > +
> > > > +       if (parse_ret)
> > > > +               return parse_ret;
> > > > +
> > > > +       if (zswap_writeback != 0 && zswap_writeback != 1)
> > > > +               return -EINVAL;
> > > > +
> > > > +       WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
> > > > +       return nbytes;
> > > > +}
> > > > +
> > > >  static struct cftype zswap_files[] = {
> > > >         {
> > > >                 .name = "zswap.current",
> > > > @@ -8188,6 +8221,11 @@ static struct cftype zswap_files[] = {
> > > >                 .seq_show = zswap_max_show,
> > > >                 .write = zswap_max_write,
> > > >         },
> > > > +       {
> > > > +               .name = "zswap.writeback",
> > > > +               .seq_show = zswap_writeback_show,
> > > > +               .write = zswap_writeback_write,
> > > > +       },
> > > >         { }     /* terminate */
> > > >  };
> > > >  #endif /* CONFIG_MEMCG_KMEM && CONFIG_ZSWAP */
> > > > diff --git a/mm/page_io.c b/mm/page_io.c
> > > > index cb559ae324c6..5e606f1aa2f6 100644
> > > > --- a/mm/page_io.c
> > > > +++ b/mm/page_io.c
> > > > @@ -201,6 +201,12 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
> > > >                 folio_end_writeback(folio);
> > > >                 return 0;
> > > >         }
> > > > +
> > > > +       if (!mem_cgroup_zswap_writeback_enabled(folio_memcg(folio))) {
> > > > +               folio_mark_dirty(folio);
> > > > +               return AOP_WRITEPAGE_ACTIVATE;
> > > > +       }
> > > > +
> > > >         __swap_writepage(&folio->page, wbc);
> > > >         return 0;
> > > >  }
> > > > diff --git a/mm/shmem.c b/mm/shmem.c
> > > > index 0d1ce70bce38..ccbaaa5f1c16 100644
> > > > --- a/mm/shmem.c
> > > > +++ b/mm/shmem.c
> > > > @@ -1514,8 +1514,7 @@ static int shmem_writepage(struct page *page, struct writeback_control *wbc)
> > > >
> > > >                 mutex_unlock(&shmem_swaplist_mutex);
> > > >                 BUG_ON(folio_mapped(folio));
> > > > -               swap_writepage(&folio->page, wbc);
> > > > -               return 0;
> > > > +               return swap_writepage(&folio->page, wbc);
> > > >         }
> > > >
> > > >         mutex_unlock(&shmem_swaplist_mutex);
> > > > diff --git a/mm/zswap.c b/mm/zswap.c
> > > > index 943090dfe793..caa467e40009 100644
> > > > --- a/mm/zswap.c
> > > > +++ b/mm/zswap.c
> > > > @@ -152,6 +152,11 @@ module_param_named(exclusive_loads, zswap_exclusive_loads_enabled, bool, 0644);
> > > >  static bool zswap_shrinker_enabled;
> > > >  module_param_named(shrinker_enabled, zswap_shrinker_enabled, bool, 0644);
> > > >
> > > > +bool is_zswap_enabled(void)
> > > > +{
> > > > +       return zswap_enabled;
> > > > +}
> > > > +
> > > >  /*********************************
> > > >  * data structures
> > > >  **********************************/
> > > > @@ -589,6 +594,9 @@ static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,
> > > >         struct zswap_pool *pool = shrinker->private_data;
> > > >         bool encountered_page_in_swapcache = false;
> > > >
> > > > +       if (!mem_cgroup_zswap_writeback_enabled(sc->memcg))
> > > > +               return SHRINK_STOP;
> > > > +
> > > >         nr_protected =
> > > >                 atomic_long_read(&lruvec->zswap_lruvec_state.nr_zswap_protected);
> > > >         lru_size = list_lru_shrink_count(&pool->list_lru, sc);
> > > > @@ -619,6 +627,9 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker,
> > > >         struct lruvec *lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(sc->nid));
> > > >         unsigned long nr_backing, nr_stored, nr_freeable, nr_protected;
> > > >
> > > > +       if (!mem_cgroup_zswap_writeback_enabled(memcg))
> > > > +               return 0;
> > > > +
> > > >  #ifdef CONFIG_MEMCG_KMEM
> > > >         cgroup_rstat_flush(memcg->css.cgroup);
> > > >         nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT;
> > > > @@ -934,6 +945,9 @@ static int shrink_memcg(struct mem_cgroup *memcg)
> > > >         struct zswap_pool *pool;
> > > >         int nid, shrunk = 0;
> > > >
> > > > +       if (!mem_cgroup_zswap_writeback_enabled(memcg))
> > > > +               return -EINVAL;
> > > > +
> > > >         /*
> > > >          * Skip zombies because their LRUs are reparented and we would be
> > > >          * reclaiming from the parent instead of the dead memcg.
> > > > --
> > > > 2.34.1
> >
Chris Li Nov. 19, 2023, 9:39 a.m. UTC | #5
On Sat, Nov 18, 2023 at 11:23 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> Hmm how about this - in the future, we support the following
> options:
>
> 1. zswap.writeback == 1: no limitation to zswap writeback.
> All backing swap devices (sorted by priorities?) are fair game.
>
> 2. zswap.writeback == 0: disable all forms of zswap writeback.
>
> 3. zswap.writeback == <tiers description>:  attempt to write to each
> tier, one at a time.

We can merge the zswap.writeback as it is for now to unblock you.

For the future. I think we should remove zswap.writeback completely.

Instead we have:

swap.tiers == <swap_tier_list_name>
swap.tiers == "all" all available swap tiers. "zswap + swap file".
This is the default.
swap.tiers == "zswap" zswap only, no other swap file. Internally set
zswap.writeback = 0
swap.tiers == "foo" foo is a list of swap devices it can use. You can
define your town custom swap tier list in
swap.tiers == "none" or "disabled" Not allowed to swap.

"all", "zswap", "none" are reserved keywords.
"foo", "bar" etc are custom lists of swap tiers. User define custom
tier list in sys/kernel/mm/swap/tiers:
ssd:zswap,/dev/nvme01p4
hdd:/dev/sda4,/dev/sdb4

That would define two custom tiers. "ssd" can use zswap then /dev/nvme01p4.
The exact name of the "swap.tiers" and tiers name are open to suggestions.

>
> The first two are basically what we have for this patch.
> The last one will be added in a future patch.
>
> This is from the userspace perspective. Internally, we can modify
> memcg->writeback to be a pointer or a struct instead of this bool.
> (as you suggested).

Internally I would suggest memcg->swaptiers, the write back name is
somewhat confusing. As your patch indicated. It has two situation:
1. shrinking from zpool to real swapfile. The write back is appropriate here.
 2. zswap store failed (compression ratio too low, out of memory etc).
The write back is confusing here. It is more like writing through or
skip.

>
> This way, the API remains intact and backward compatible
> (and FWIW, I think there are still a lot of values in having simple
> options for the users who have simple memory hierarchies).

swap.tiers can be simple. For example, you can modify your patch to
"swap.tires == zswap" to
set zswap.writeback bool to 0 for now. Most of your patch is still re-usable.

I think we should discuss if we want to keep zswap.writeback in the
future because that would be some code undeletable and functionally
overlap with swap.tiers

Chris
Nhat Pham Nov. 19, 2023, 7:08 p.m. UTC | #6
On Sun, Nov 19, 2023 at 1:39 AM Chris Li <chrisl@kernel.org> wrote:
>
> On Sat, Nov 18, 2023 at 11:23 AM Nhat Pham <nphamcs@gmail.com> wrote:
> >
> > Hmm how about this - in the future, we support the following
> > options:
> >
> > 1. zswap.writeback == 1: no limitation to zswap writeback.
> > All backing swap devices (sorted by priorities?) are fair game.
> >
> > 2. zswap.writeback == 0: disable all forms of zswap writeback.
> >
> > 3. zswap.writeback == <tiers description>:  attempt to write to each
> > tier, one at a time.
>
> We can merge the zswap.writeback as it is for now to unblock you.
>
> For the future. I think we should remove zswap.writeback completely.

I'm a bit weary about API changes, especially changes that affect
backward compatibility. Breaking existing userspace programs simply
with a kernel upgrade does not sound very nice to me.

(although I've heard that the eventual plan is to deprecate cgroupv1
- not sure how that is gonna proceed).

Hence my attempt at creating something that can both serve the
current use case, while still remaining (fairly) extensible for future
ideas.

>
> Instead we have:
>
> swap.tiers == <swap_tier_list_name>
> swap.tiers == "all" all available swap tiers. "zswap + swap file".
> This is the default.
> swap.tiers == "zswap" zswap only, no other swap file. Internally set
> zswap.writeback = 0
> swap.tiers == "foo" foo is a list of swap devices it can use. You can
> define your town custom swap tier list in
> swap.tiers == "none" or "disabled" Not allowed to swap.

swap.tiers == "none" or "disabled" means disallowing zswap as
well, correct?

>
> "all", "zswap", "none" are reserved keywords.
> "foo", "bar" etc are custom lists of swap tiers. User define custom
> tier list in sys/kernel/mm/swap/tiers:
> ssd:zswap,/dev/nvme01p4
> hdd:/dev/sda4,/dev/sdb4

I don't have any major argument against this. It just seems a bit
heavyweight for what we need at the moment (only disabling
swap-to-disk usage).

I'll let other people weigh in about this of course.
Johannes, how do you feel about this proposed API?

>
> That would define two custom tiers. "ssd" can use zswap then /dev/nvme01p4.
> The exact name of the "swap.tiers" and tiers name are open to suggestions.
>
> >
> > The first two are basically what we have for this patch.
> > The last one will be added in a future patch.
> >
> > This is from the userspace perspective. Internally, we can modify
> > memcg->writeback to be a pointer or a struct instead of this bool.
> > (as you suggested).
>
> Internally I would suggest memcg->swaptiers, the write back name is
> somewhat confusing. As your patch indicated. It has two situation:
> 1. shrinking from zpool to real swapfile. The write back is appropriate here.
>  2. zswap store failed (compression ratio too low, out of memory etc).
> The write back is confusing here. It is more like writing through or
> skip.
>
> >
> > This way, the API remains intact and backward compatible
> > (and FWIW, I think there are still a lot of values in having simple
> > options for the users who have simple memory hierarchies).
>
> swap.tiers can be simple. For example, you can modify your patch to
> "swap.tires == zswap" to
> set zswap.writeback bool to 0 for now. Most of your patch is still re-usable.
>

I'm less concerned about internals - that is always up to changes.
I'm a bit more concerned with the API we're exposing to the users.

> I think we should discuss if we want to keep zswap.writeback in the
> future because that would be some code undeletable and functionally
> overlap with swap.tiers

This is a fair point.

>
> Chris
Chris Li Nov. 19, 2023, 9:50 p.m. UTC | #7
On Sun, Nov 19, 2023 at 11:08 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> On Sun, Nov 19, 2023 at 1:39 AM Chris Li <chrisl@kernel.org> wrote:
> >
> > On Sat, Nov 18, 2023 at 11:23 AM Nhat Pham <nphamcs@gmail.com> wrote:
> > >
> > > Hmm how about this - in the future, we support the following
> > > options:
> > >
> > > 1. zswap.writeback == 1: no limitation to zswap writeback.
> > > All backing swap devices (sorted by priorities?) are fair game.
> > >
> > > 2. zswap.writeback == 0: disable all forms of zswap writeback.
> > >
> > > 3. zswap.writeback == <tiers description>:  attempt to write to each
> > > tier, one at a time.
> >
> > We can merge the zswap.writeback as it is for now to unblock you.
> >
> > For the future. I think we should remove zswap.writeback completely.
>
> I'm a bit weary about API changes, especially changes that affect
> backward compatibility. Breaking existing userspace programs simply
> with a kernel upgrade does not sound very nice to me.
>
> (although I've heard that the eventual plan is to deprecate cgroupv1
> - not sure how that is gonna proceed).
>
> Hence my attempt at creating something that can both serve the
> current use case, while still remaining (fairly) extensible for future
> ideas.

With that reasoning, the "swap.tiers" would serve better than "zswap.writeback",
do you think so?

>
> >
> > Instead we have:
> >
> > swap.tiers == <swap_tier_list_name>
> > swap.tiers == "all" all available swap tiers. "zswap + swap file".
> > This is the default.
> > swap.tiers == "zswap" zswap only, no other swap file. Internally set
> > zswap.writeback = 0
> > swap.tiers == "foo" foo is a list of swap devices it can use. You can
> > define your town custom swap tier list in
> > swap.tiers == "none" or "disabled" Not allowed to swap.
>
> swap.tiers == "none" or "disabled" means disallowing zswap as
> well, correct?

Correct, no swap at all.

>
> >
> > "all", "zswap", "none" are reserved keywords.
> > "foo", "bar" etc are custom lists of swap tiers. User define custom
> > tier list in sys/kernel/mm/swap/tiers:
> > ssd:zswap,/dev/nvme01p4
> > hdd:/dev/sda4,/dev/sdb4
>
> I don't have any major argument against this. It just seems a bit
> heavyweight for what we need at the moment (only disabling
> swap-to-disk usage).

The first milestone we just implement the reserved keywords without
the custom swap tier list.
That should be very similar to "zswap.writeback". Instead of writing 0
to "zswap.writeback".
You write "zswap" to "swap.tiers". Writing "none" will disable all
swap. Writing "all" will allow all swap devices.
I consider this conceptually cleaner than the "zswap.writeback" == 0
will also disable other swap types behavior. "disabled zswap writeback
== disable all swap" feels less natural.

> I'll let other people weigh in about this of course.
> Johannes, how do you feel about this proposed API?
>
> >
> > That would define two custom tiers. "ssd" can use zswap then /dev/nvme01p4.
> > The exact name of the "swap.tiers" and tiers name are open to suggestions.
> >
> > >
> > > The first two are basically what we have for this patch.
> > > The last one will be added in a future patch.
> > >
> > > This is from the userspace perspective. Internally, we can modify
> > > memcg->writeback to be a pointer or a struct instead of this bool.
> > > (as you suggested).
> >
> > Internally I would suggest memcg->swaptiers, the write back name is
> > somewhat confusing. As your patch indicated. It has two situation:
> > 1. shrinking from zpool to real swapfile. The write back is appropriate here.
> >  2. zswap store failed (compression ratio too low, out of memory etc).
> > The write back is confusing here. It is more like writing through or
> > skip.
> >
> > >
> > > This way, the API remains intact and backward compatible
> > > (and FWIW, I think there are still a lot of values in having simple
> > > options for the users who have simple memory hierarchies).
> >
> > swap.tiers can be simple. For example, you can modify your patch to
> > "swap.tires == zswap" to
> > set zswap.writeback bool to 0 for now. Most of your patch is still re-usable.
> >
>
> I'm less concerned about internals - that is always up to changes.
> I'm a bit more concerned with the API we're exposing to the users.

Me too. I think we are in agreement here. That is why I think
"swap.tiers" is more general.

>
> > I think we should discuss if we want to keep zswap.writeback in the
> > future because that would be some code undeletable and functionally
> > overlap with swap.tiers
>
> This is a fair point.

If you think we have the risk of not being able to obsolete
"zswap.writeback", then it would be much better not to introduce
"zswap.writeback" in the first place. Just have a minimal
implementation of "swap.tiers" instead. I believe from the code
complexity point of view, the complexity should be very similar.

Chris
Chris Li Nov. 20, 2023, 2:41 a.m. UTC | #8
Hi Nhat,

On Sun, Nov 19, 2023 at 01:50:17PM -0800, Chris Li wrote:
> On Sun, Nov 19, 2023 at 11:08 AM Nhat Pham <nphamcs@gmail.com> wrote:
> > I don't have any major argument against this. It just seems a bit
> > heavyweight for what we need at the moment (only disabling
> > swap-to-disk usage).
> 
> The first milestone we just implement the reserved keywords without
> the custom swap tier list.
> That should be very similar to "zswap.writeback". Instead of writing 0
> to "zswap.writeback".
> You write "zswap" to "swap.tiers". Writing "none" will disable all
> swap. Writing "all" will allow all swap devices.
> I consider this conceptually cleaner than the "zswap.writeback" == 0
> will also disable other swap types behavior. "disabled zswap writeback
> == disable all swap" feels less natural.

I implement a minimal version of the "swap.tiers" to replace the "zswap.writeback".
It only implements the ABI level. Under the hook it is using the writeback bool.

This patch builds on top of your V5 patch.

implement memory.swap.tiers on top of memory.zswap.writeback.

"memory.swap.tiers" supports two key words for now:
all: all swap swap tiers are considered. (previously zswap.writback == 1)
zswap: only zswap tier are considered. (previously zswap.writeback == 0)

Index: linux/mm/memcontrol.c
===================================================================
--- linux.orig/mm/memcontrol.c
+++ linux/mm/memcontrol.c
@@ -7992,6 +7992,32 @@ static int swap_events_show(struct seq_f
 	return 0;
 }
 
+static int swap_tiers_show(struct seq_file *m, void *v)
+{
+	struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
+
+	seq_printf(m, "%s\n", READ_ONCE(memcg->zswap_writeback) ? "all" : "zswap");
+	return 0;
+}
+
+static ssize_t swap_tiers_write(struct kernfs_open_file *of,
+				char *buf, size_t nbytes, loff_t off)
+{
+	struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
+	int zswap_writeback;
+
+	buf = strstrip(buf);
+	if (!strcmp(buf, "all"))
+		zswap_writeback = 1;
+	else if (!strcmp(buf, "zswap"))
+		zswap_writeback = 0;
+	else
+		return -EINVAL;
+
+	WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
+	return nbytes;
+}
+
 static struct cftype swap_files[] = {
 	{
 		.name = "swap.current",
@@ -8021,6 +8047,12 @@ static struct cftype swap_files[] = {
 		.file_offset = offsetof(struct mem_cgroup, swap_events_file),
 		.seq_show = swap_events_show,
 	},
+	{
+		.name = "swap.tiers",
+		.seq_show = swap_tiers_show,
+		.write = swap_tiers_write,
+	},
+
 	{ }	/* terminate */
 };
 
@@ -8183,31 +8215,6 @@ static ssize_t zswap_max_write(struct ke
 	return nbytes;
 }
 
-static int zswap_writeback_show(struct seq_file *m, void *v)
-{
-	struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
-
-	seq_printf(m, "%d\n", READ_ONCE(memcg->zswap_writeback));
-	return 0;
-}
-
-static ssize_t zswap_writeback_write(struct kernfs_open_file *of,
-				char *buf, size_t nbytes, loff_t off)
-{
-	struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
-	int zswap_writeback;
-	ssize_t parse_ret = kstrtoint(strstrip(buf), 0, &zswap_writeback);
-
-	if (parse_ret)
-		return parse_ret;
-
-	if (zswap_writeback != 0 && zswap_writeback != 1)
-		return -EINVAL;
-
-	WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
-	return nbytes;
-}
-
 static struct cftype zswap_files[] = {
 	{
 		.name = "zswap.current",
@@ -8220,11 +8227,6 @@ static struct cftype zswap_files[] = {
 		.seq_show = zswap_max_show,
 		.write = zswap_max_write,
 	},
-	{
-		.name = "zswap.writeback",
-		.seq_show = zswap_writeback_show,
-		.write = zswap_writeback_write,
-	},
 	{ }	/* terminate */
 };
 #endif /* CONFIG_MEMCG_KMEM && CONFIG_ZSWAP */
Nhat Pham Nov. 21, 2023, 6:13 p.m. UTC | #9
On Sun, Nov 19, 2023 at 6:41 PM Chris Li <chrisl@kernel.org> wrote:
>
> Hi Nhat,
>
> On Sun, Nov 19, 2023 at 01:50:17PM -0800, Chris Li wrote:
> > On Sun, Nov 19, 2023 at 11:08 AM Nhat Pham <nphamcs@gmail.com> wrote:
> > > I don't have any major argument against this. It just seems a bit
> > > heavyweight for what we need at the moment (only disabling
> > > swap-to-disk usage).
> >
> > The first milestone we just implement the reserved keywords without
> > the custom swap tier list.
> > That should be very similar to "zswap.writeback". Instead of writing 0
> > to "zswap.writeback".
> > You write "zswap" to "swap.tiers". Writing "none" will disable all
> > swap. Writing "all" will allow all swap devices.
> > I consider this conceptually cleaner than the "zswap.writeback" == 0
> > will also disable other swap types behavior. "disabled zswap writeback
> > == disable all swap" feels less natural.
>
> I implement a minimal version of the "swap.tiers" to replace the "zswap.writeback".
> It only implements the ABI level. Under the hook it is using the writeback bool.
>
> This patch builds on top of your V5 patch.
>
> implement memory.swap.tiers on top of memory.zswap.writeback.
>
> "memory.swap.tiers" supports two key words for now:
> all: all swap swap tiers are considered. (previously zswap.writback == 1)
> zswap: only zswap tier are considered. (previously zswap.writeback == 0)
>
> Index: linux/mm/memcontrol.c
> ===================================================================
> --- linux.orig/mm/memcontrol.c
> +++ linux/mm/memcontrol.c
> @@ -7992,6 +7992,32 @@ static int swap_events_show(struct seq_f
>         return 0;
>  }
>
> +static int swap_tiers_show(struct seq_file *m, void *v)
> +{
> +       struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
> +
> +       seq_printf(m, "%s\n", READ_ONCE(memcg->zswap_writeback) ? "all" : "zswap");
> +       return 0;
> +}
> +
> +static ssize_t swap_tiers_write(struct kernfs_open_file *of,
> +                               char *buf, size_t nbytes, loff_t off)
> +{
> +       struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
> +       int zswap_writeback;
> +
> +       buf = strstrip(buf);
> +       if (!strcmp(buf, "all"))
> +               zswap_writeback = 1;
> +       else if (!strcmp(buf, "zswap"))
> +               zswap_writeback = 0;
> +       else
> +               return -EINVAL;
> +
> +       WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
> +       return nbytes;
> +}
> +
>  static struct cftype swap_files[] = {
>         {
>                 .name = "swap.current",
> @@ -8021,6 +8047,12 @@ static struct cftype swap_files[] = {
>                 .file_offset = offsetof(struct mem_cgroup, swap_events_file),
>                 .seq_show = swap_events_show,
>         },
> +       {
> +               .name = "swap.tiers",
> +               .seq_show = swap_tiers_show,
> +               .write = swap_tiers_write,
> +       },
> +
>         { }     /* terminate */
>  };
>
> @@ -8183,31 +8215,6 @@ static ssize_t zswap_max_write(struct ke
>         return nbytes;
>  }
>
> -static int zswap_writeback_show(struct seq_file *m, void *v)
> -{
> -       struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
> -
> -       seq_printf(m, "%d\n", READ_ONCE(memcg->zswap_writeback));
> -       return 0;
> -}
> -
> -static ssize_t zswap_writeback_write(struct kernfs_open_file *of,
> -                               char *buf, size_t nbytes, loff_t off)
> -{
> -       struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
> -       int zswap_writeback;
> -       ssize_t parse_ret = kstrtoint(strstrip(buf), 0, &zswap_writeback);
> -
> -       if (parse_ret)
> -               return parse_ret;
> -
> -       if (zswap_writeback != 0 && zswap_writeback != 1)
> -               return -EINVAL;
> -
> -       WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
> -       return nbytes;
> -}
> -
>  static struct cftype zswap_files[] = {
>         {
>                 .name = "zswap.current",
> @@ -8220,11 +8227,6 @@ static struct cftype zswap_files[] = {
>                 .seq_show = zswap_max_show,
>                 .write = zswap_max_write,
>         },
> -       {
> -               .name = "zswap.writeback",
> -               .seq_show = zswap_writeback_show,
> -               .write = zswap_writeback_write,
> -       },
>         { }     /* terminate */
>  };
>  #endif /* CONFIG_MEMCG_KMEM && CONFIG_ZSWAP */

Hi Chris!

Thanks for the patch. Would you mind if I spend some time staring
at the suggestion again and testing it some more?

If everything is good, I'll squash this patch with the original version,
(keeping you as a co-developer of the final patch of course), and
update the documentation before re-sending everything as v6.

Anyway, have a nice Thanksgiving break everyone! Thanks for
taking the time to review my patch and discuss the API with me!
Chris Li Nov. 21, 2023, 7:08 p.m. UTC | #10
On Tue, Nov 21, 2023 at 10:13 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> Hi Chris!
>
> Thanks for the patch. Would you mind if I spend some time staring
> at the suggestion again and testing it some more?

Of course, by all means. That is just the minimal version to be
functional compatible with your zswap.writeback.

I might consider a follow up patch to add "no_zswap" and "none" to
convert the SSD only swapfile, which can't be expressed by
zswap.writeback.
That should cover all 4 combinations of zswap and swap files without
creating a custom swap tiers list.

"all": zswap + swapfile
"zswap": zswap only
"no_zswap": swapfile only.
"none": no swap.

All keyword names are open to suggestions.

>
> If everything is good, I'll squash this patch with the original version,
> (keeping you as a co-developer of the final patch of course), and
> update the documentation before re-sending everything as v6.

Great!

>
> Anyway, have a nice Thanksgiving break everyone! Thanks for
> taking the time to review my patch and discuss the API with me!

My pleasure to discuss the swap with you. We should do the online
"swap meet" and invite other developers who are interested in the swap
area as well.

Chris
Nhat Pham Nov. 22, 2023, 1:19 a.m. UTC | #11
On Tue, Nov 21, 2023 at 11:09 AM Chris Li <chrisl@kernel.org> wrote:
>
> On Tue, Nov 21, 2023 at 10:13 AM Nhat Pham <nphamcs@gmail.com> wrote:
> >
> > Hi Chris!
> >
> > Thanks for the patch. Would you mind if I spend some time staring
> > at the suggestion again and testing it some more?
>
> Of course, by all means. That is just the minimal version to be
> functional compatible with your zswap.writeback.
>
> I might consider a follow up patch to add "no_zswap" and "none" to
> convert the SSD only swapfile, which can't be expressed by
> zswap.writeback.
> That should cover all 4 combinations of zswap and swap files without
> creating a custom swap tiers list.
>
> "all": zswap + swapfile
> "zswap": zswap only
> "no_zswap": swapfile only.
> "none": no swap.
>
> All keyword names are open to suggestions.

SGTM! There might be some functionality duplication between
memory.swap.tiers = no_zswap and memory.zswap.max = 0, but
otherwise this seems reasonable to me.

no_zswap sounds a bit awkward, but I can't come up with a better
name.

>
> >
> > If everything is good, I'll squash this patch with the original version,
> > (keeping you as a co-developer of the final patch of course), and
> > update the documentation before re-sending everything as v6.
>
> Great!
>
> >
> > Anyway, have a nice Thanksgiving break everyone! Thanks for
> > taking the time to review my patch and discuss the API with me!
>
> My pleasure to discuss the swap with you. We should do the online
> "swap meet" and invite other developers who are interested in the swap
> area as well.

I look forward to this meeting! I'd love to discuss more about (z)swap
development (and more generally, multi-tier memory management).

Generic page promoter/demoter that takes into account workload
(cgroup), access recency (LRU + generations)/frequency, and tier
characteristics (latency, bandwidth, etc.) will be awesome to explore!

>
> Chris
Chris Li Nov. 22, 2023, 3:45 a.m. UTC | #12
On Tue, Nov 21, 2023 at 5:19 PM Nhat Pham <nphamcs@gmail.com> wrote:
>
> On Tue, Nov 21, 2023 at 11:09 AM Chris Li <chrisl@kernel.org> wrote:
> >
> > On Tue, Nov 21, 2023 at 10:13 AM Nhat Pham <nphamcs@gmail.com> wrote:
> > >
> > > Hi Chris!
> > >
> > > Thanks for the patch. Would you mind if I spend some time staring
> > > at the suggestion again and testing it some more?
> >
> > Of course, by all means. That is just the minimal version to be
> > functional compatible with your zswap.writeback.
> >
> > I might consider a follow up patch to add "no_zswap" and "none" to
> > convert the SSD only swapfile, which can't be expressed by
> > zswap.writeback.
> > That should cover all 4 combinations of zswap and swap files without
> > creating a custom swap tiers list.
> >
> > "all": zswap + swapfile
> > "zswap": zswap only
> > "no_zswap": swapfile only.
> > "none": no swap.
> >
> > All keyword names are open to suggestions.
>
> SGTM! There might be some functionality duplication between
> memory.swap.tiers = no_zswap and memory.zswap.max = 0, but
> otherwise this seems reasonable to me.

Yes, there is some function duplication. However, there is some small
difference that no_zswap will not enter zswap code at all. Vs
memory.zswap.max will take a short trip into zswap code to find out
Oops, not zswap for you.

>
> no_zswap sounds a bit awkward, but I can't come up with a better
> name.

Again, I am open to better suggestions.
I have also considered "!zswap", "!" has special meaning in bash, so
it will require quoting in bash.
How about "-zswap"? This does not require special quoting in bash.

>
> >
> > >
> > > If everything is good, I'll squash this patch with the original version,
> > > (keeping you as a co-developer of the final patch of course), and
> > > update the documentation before re-sending everything as v6.
> >
> > Great!
> >
> > >
> > > Anyway, have a nice Thanksgiving break everyone! Thanks for
> > > taking the time to review my patch and discuss the API with me!
> >
> > My pleasure to discuss the swap with you. We should do the online
> > "swap meet" and invite other developers who are interested in the swap
> > area as well.
>
> I look forward to this meeting! I'd love to discuss more about (z)swap
> development (and more generally, multi-tier memory management).

Let me arrange one then. I am thinking maybe every second week of the
month. That can avoid thanksgiving, christmas and new year.

Let me throw in some more ideas: writing compressed zswap data to SSD
without swap cache.

> Generic page promoter/demoter that takes into account workload
> (cgroup), access recency (LRU + generations)/frequency, and tier
> characteristics (latency, bandwidth, etc.) will be awesome to explore!

Sounds great. Looking forward to it.

Chris
Chris Li Nov. 22, 2023, 3:01 p.m. UTC | #13
On Tue, Nov 21, 2023 at 5:19 PM Nhat Pham <nphamcs@gmail.com> wrote:

> > "all": zswap + swapfile
> > "zswap": zswap only
> > "no_zswap": swapfile only.
> > "none": no swap.
> >
> > All keyword names are open to suggestions.
>
> SGTM! There might be some functionality duplication between
> memory.swap.tiers = no_zswap and memory.zswap.max = 0, but
> otherwise this seems reasonable to me.
>
> no_zswap sounds a bit awkward, but I can't come up with a better
> name.

I sleep on it a bit. I  should apply my own suggestion of using the
positive words rather than negative one to myself.
I actually define it as a non RAM base swap device. How about "disk"?
It will include SSD and HDD disk.

The current 4 combination will be:

"all": zswap + disk swap file
"zswap": zswap only
"disk": disk only (including SSD and HDD)
"none": no swap for you.

Chris
Nhat Pham Dec. 6, 2023, 6:15 p.m. UTC | #14
On Wed, Nov 22, 2023 at 7:01 AM Chris Li <chrisl@kernel.org> wrote:
>
> On Tue, Nov 21, 2023 at 5:19 PM Nhat Pham <nphamcs@gmail.com> wrote:
>
> > > "all": zswap + swapfile
> > > "zswap": zswap only
> > > "no_zswap": swapfile only.
> > > "none": no swap.
> > >
> > > All keyword names are open to suggestions.
> >
> > SGTM! There might be some functionality duplication between
> > memory.swap.tiers = no_zswap and memory.zswap.max = 0, but
> > otherwise this seems reasonable to me.
> >
> > no_zswap sounds a bit awkward, but I can't come up with a better
> > name.
>
> I sleep on it a bit. I  should apply my own suggestion of using the
> positive words rather than negative one to myself.
> I actually define it as a non RAM base swap device. How about "disk"?
> It will include SSD and HDD disk.
>
> The current 4 combination will be:
>
> "all": zswap + disk swap file
> "zswap": zswap only
> "disk": disk only (including SSD and HDD)
> "none": no swap for you.
>
> Chris

Hi Chris,

I chatted with Johannes a bit more about this design. While we still
think it's potentially useful for the future, it lacks a concrete use
case at the moment. We don't even have the infrastructure for multiple
swap tiers at the moment, so adding this interface now is just making
it more confusing for the users. I think zswap.writeback is a much
more specific interface, with concrete and immediate usability (it
stems from internal chatters and requests - so the demand is already
there).

I think we should just land the change we currently have (rebased on
top of mm-unstable to resolve merge conflicts etc.). I don't think
zswap.writeback will get in the way of any swap.tiers functionality,
correct? There might be some functionality duplication, but that's not
too bad IHMO. Then we can work on swap.tiers design and implementation
as we add the support for multiple swap tiers.
diff mbox series

Patch

diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index 3f85254f3cef..2b4ac43efdc8 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1679,6 +1679,18 @@  PAGE_SIZE multiple when read back.
 	limit, it will refuse to take any more stores before existing
 	entries fault back in or are written out to disk.
 
+  memory.zswap.writeback
+	A read-write single value file. The default value is "1". The
+	initial value of the root cgroup is 1, and when a new cgroup is
+	created, it inherits the current value of its parent.
+
+	When this is set to 0, all swapping attempts to swapping devices
+	are disabled. This included both zswap writebacks, and swapping due
+	to zswap store failure.
+
+	Note that this is subtly different from setting memory.swap.max to
+	0, as it still allows for pages to be written to the zswap pool.
+
   memory.pressure
 	A read-only nested-keyed file.
 
diff --git a/Documentation/admin-guide/mm/zswap.rst b/Documentation/admin-guide/mm/zswap.rst
index 522ae22ccb84..b987e58edb70 100644
--- a/Documentation/admin-guide/mm/zswap.rst
+++ b/Documentation/admin-guide/mm/zswap.rst
@@ -153,6 +153,12 @@  attribute, e. g.::
 
 Setting this parameter to 100 will disable the hysteresis.
 
+Some users cannot tolerate the swapping that comes with zswap store failures
+and zswap writebacks. Swapping can be disabled entirely (without disabling
+zswap itself) on a cgroup-basis as follows:
+
+	echo 0 > /sys/fs/cgroup/<cgroup-name>/memory.zswap.writeback
+
 When there is a sizable amount of cold memory residing in the zswap pool, it
 can be advantageous to proactively write these cold pages to swap and reclaim
 the memory for other use cases. By default, the zswap shrinker is disabled.
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index 83590fd0d6d1..3901ff4dae63 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -219,6 +219,12 @@  struct mem_cgroup {
 
 #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
 	unsigned long zswap_max;
+
+	/*
+	 * Prevent pages from this memcg from being written back from zswap to
+	 * swap, and from being swapped out on zswap store failures.
+	 */
+	bool zswap_writeback;
 #endif
 
 	unsigned long soft_limit;
@@ -1931,6 +1937,7 @@  static inline void count_objcg_event(struct obj_cgroup *objcg,
 bool obj_cgroup_may_zswap(struct obj_cgroup *objcg);
 void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size);
 void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size);
+bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg);
 #else
 static inline bool obj_cgroup_may_zswap(struct obj_cgroup *objcg)
 {
@@ -1944,6 +1951,11 @@  static inline void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg,
 					     size_t size)
 {
 }
+static inline bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
+{
+	/* if zswap is disabled, do not block pages going to the swapping device */
+	return true;
+}
 #endif
 
 #endif /* _LINUX_MEMCONTROL_H */
diff --git a/include/linux/zswap.h b/include/linux/zswap.h
index cbd373ba88d2..b4997e27a74b 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -35,6 +35,7 @@  void zswap_swapoff(int type);
 void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg);
 void zswap_lruvec_state_init(struct lruvec *lruvec);
 void zswap_lruvec_swapin(struct page *page);
+bool is_zswap_enabled(void);
 #else
 
 struct zswap_lruvec_state {};
@@ -55,6 +56,11 @@  static inline void zswap_swapoff(int type) {}
 static inline void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg) {}
 static inline void zswap_lruvec_init(struct lruvec *lruvec) {}
 static inline void zswap_lruvec_swapin(struct page *page) {}
+
+static inline bool is_zswap_enabled(void)
+{
+	return false;
+}
 #endif
 
 #endif /* _LINUX_ZSWAP_H */
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 786c7edf5836..5ad71ce31c74 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5522,6 +5522,8 @@  mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)
 	WRITE_ONCE(memcg->soft_limit, PAGE_COUNTER_MAX);
 #if defined(CONFIG_MEMCG_KMEM) && defined(CONFIG_ZSWAP)
 	memcg->zswap_max = PAGE_COUNTER_MAX;
+	WRITE_ONCE(memcg->zswap_writeback,
+		!parent || READ_ONCE(parent->zswap_writeback));
 #endif
 	page_counter_set_high(&memcg->swap, PAGE_COUNTER_MAX);
 	if (parent) {
@@ -8146,6 +8148,12 @@  void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
 	rcu_read_unlock();
 }
 
+bool mem_cgroup_zswap_writeback_enabled(struct mem_cgroup *memcg)
+{
+	/* if zswap is disabled, do not block pages going to the swapping device */
+	return !is_zswap_enabled() || !memcg || READ_ONCE(memcg->zswap_writeback);
+}
+
 static u64 zswap_current_read(struct cgroup_subsys_state *css,
 			      struct cftype *cft)
 {
@@ -8176,6 +8184,31 @@  static ssize_t zswap_max_write(struct kernfs_open_file *of,
 	return nbytes;
 }
 
+static int zswap_writeback_show(struct seq_file *m, void *v)
+{
+	struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
+
+	seq_printf(m, "%d\n", READ_ONCE(memcg->zswap_writeback));
+	return 0;
+}
+
+static ssize_t zswap_writeback_write(struct kernfs_open_file *of,
+				char *buf, size_t nbytes, loff_t off)
+{
+	struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
+	int zswap_writeback;
+	ssize_t parse_ret = kstrtoint(strstrip(buf), 0, &zswap_writeback);
+
+	if (parse_ret)
+		return parse_ret;
+
+	if (zswap_writeback != 0 && zswap_writeback != 1)
+		return -EINVAL;
+
+	WRITE_ONCE(memcg->zswap_writeback, zswap_writeback);
+	return nbytes;
+}
+
 static struct cftype zswap_files[] = {
 	{
 		.name = "zswap.current",
@@ -8188,6 +8221,11 @@  static struct cftype zswap_files[] = {
 		.seq_show = zswap_max_show,
 		.write = zswap_max_write,
 	},
+	{
+		.name = "zswap.writeback",
+		.seq_show = zswap_writeback_show,
+		.write = zswap_writeback_write,
+	},
 	{ }	/* terminate */
 };
 #endif /* CONFIG_MEMCG_KMEM && CONFIG_ZSWAP */
diff --git a/mm/page_io.c b/mm/page_io.c
index cb559ae324c6..5e606f1aa2f6 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -201,6 +201,12 @@  int swap_writepage(struct page *page, struct writeback_control *wbc)
 		folio_end_writeback(folio);
 		return 0;
 	}
+
+	if (!mem_cgroup_zswap_writeback_enabled(folio_memcg(folio))) {
+		folio_mark_dirty(folio);
+		return AOP_WRITEPAGE_ACTIVATE;
+	}
+
 	__swap_writepage(&folio->page, wbc);
 	return 0;
 }
diff --git a/mm/shmem.c b/mm/shmem.c
index 0d1ce70bce38..ccbaaa5f1c16 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1514,8 +1514,7 @@  static int shmem_writepage(struct page *page, struct writeback_control *wbc)
 
 		mutex_unlock(&shmem_swaplist_mutex);
 		BUG_ON(folio_mapped(folio));
-		swap_writepage(&folio->page, wbc);
-		return 0;
+		return swap_writepage(&folio->page, wbc);
 	}
 
 	mutex_unlock(&shmem_swaplist_mutex);
diff --git a/mm/zswap.c b/mm/zswap.c
index 943090dfe793..caa467e40009 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -152,6 +152,11 @@  module_param_named(exclusive_loads, zswap_exclusive_loads_enabled, bool, 0644);
 static bool zswap_shrinker_enabled;
 module_param_named(shrinker_enabled, zswap_shrinker_enabled, bool, 0644);
 
+bool is_zswap_enabled(void)
+{
+	return zswap_enabled;
+}
+
 /*********************************
 * data structures
 **********************************/
@@ -589,6 +594,9 @@  static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,
 	struct zswap_pool *pool = shrinker->private_data;
 	bool encountered_page_in_swapcache = false;
 
+	if (!mem_cgroup_zswap_writeback_enabled(sc->memcg))
+		return SHRINK_STOP;
+
 	nr_protected =
 		atomic_long_read(&lruvec->zswap_lruvec_state.nr_zswap_protected);
 	lru_size = list_lru_shrink_count(&pool->list_lru, sc);
@@ -619,6 +627,9 @@  static unsigned long zswap_shrinker_count(struct shrinker *shrinker,
 	struct lruvec *lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(sc->nid));
 	unsigned long nr_backing, nr_stored, nr_freeable, nr_protected;
 
+	if (!mem_cgroup_zswap_writeback_enabled(memcg))
+		return 0;
+
 #ifdef CONFIG_MEMCG_KMEM
 	cgroup_rstat_flush(memcg->css.cgroup);
 	nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT;
@@ -934,6 +945,9 @@  static int shrink_memcg(struct mem_cgroup *memcg)
 	struct zswap_pool *pool;
 	int nid, shrunk = 0;
 
+	if (!mem_cgroup_zswap_writeback_enabled(memcg))
+		return -EINVAL;
+
 	/*
 	 * Skip zombies because their LRUs are reparented and we would be
 	 * reclaiming from the parent instead of the dead memcg.