Linux block layer
 help / color / mirror / Atom feed
* [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately
@ 2026-09-14 10:31 Julian Sun
  2026-09-14 10:31 ` [PATCH v4 1/3] block: introduce bdev_flush_by_dev() Julian Sun
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Julian Sun @ 2026-09-14 10:31 UTC (permalink / raw)
  To: linux-block, linux-fsdevel
  Cc: axboe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	willy, jack, rostedt, tj, akpm

Bdev inodes are routinely shared by multiple memcgs. Recording their
owner wb as foreign can cause dirty throttling in one memcg to trigger
writeback of unrelated file data belonging to that wb.

On one production machine, bdev mapping snapshots showed as little
as about 10 MiB of dirty data. A queue snapshot of its owner wb showed
599 foreign-flush works, with an average requested writeback budget of
43 GiB per work.

In other words, a small amount of foreign dirtying in the bdev mapping
can trigger owner-wide writeback requests, whose budgets averaged
43 GiB in this queue snapshot.

Following Jan's suggestion, this series records foreign bdev targets
separately and flushes their mappings instead of their owner wbs. This
preserves a way for dirty throttling to initiate bdev writeback while
avoiding owner-wide writeback triggered by these records. The existing
foreign-wb mechanism remains unchanged for other inodes.

Tracking is best effort: each memcg keeps a bounded set of device
numbers without persistent device or inode references. Closed devices
and devices with a busy open_mutex are skipped. Device removal and
device-number reuse may race with lookup.

Flushes run asynchronously on a dedicated workqueue to isolate these
frequently triggered tasks from existing workqueue users. If workqueue
allocation fails, the existing foreign-wb mechanism remains in use.

Julian Sun (3):
  block: introduce bdev_flush_by_dev()
  memcg,writeback: flush foreign bdev mappings separately from owner wbs
  writeback: record bdev targets in foreign writeback tracepoints

 block/bdev.c                     |  24 +++++++
 include/linux/blkdev.h           |   1 +
 include/linux/memcontrol.h       |  15 ++++
 include/trace/events/writeback.h |  22 +++---
 mm/memcontrol.c                  | 113 ++++++++++++++++++++++++++++---
 5 files changed, 157 insertions(+), 18 deletions(-)

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v4 1/3] block: introduce bdev_flush_by_dev()
  2026-09-14 10:31 [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Julian Sun
@ 2026-09-14 10:31 ` Julian Sun
  2026-09-14 10:31 ` [PATCH v4 2/3] memcg,writeback: flush foreign bdev mappings separately from owner wbs Julian Sun
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 7+ messages in thread
From: Julian Sun @ 2026-09-14 10:31 UTC (permalink / raw)
  To: linux-block, linux-fsdevel
  Cc: axboe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	willy, jack, rostedt, tj, akpm

Add bdev_flush_by_dev() to submit page-cache writeback for a device
identified by dev_t without requiring callers to hold a persistent
device reference.

A subsequent patch will use this helper to flush foreign bdev mappings.

Signed-off-by: Julian Sun <sunjunchao@bytedance.com>
---
 block/bdev.c           | 24 ++++++++++++++++++++++++
 include/linux/blkdev.h |  1 +
 2 files changed, 25 insertions(+)

diff --git a/block/bdev.c b/block/bdev.c
index cd8323083740..da1fc56926fd 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -264,6 +264,30 @@ int sync_blockdev_nowait(struct block_device *bdev)
 }
 EXPORT_SYMBOL_GPL(sync_blockdev_nowait);
 
+/**
+ * bdev_flush_by_dev - attempt writeback of a block device's page cache
+ * @dev: target block device number
+ *
+ * Skip closed devices or devices whose open_mutex is busy.
+ * May sleep while submitting writeback; does not wait for all I/O to complete.
+ */
+void bdev_flush_by_dev(dev_t dev)
+{
+	struct block_device *bdev;
+
+	bdev = blkdev_get_no_open(dev, false);
+	if (!bdev)
+		return;
+
+	/* Prevent the device from closing between the check and writeback. */
+	if (mutex_trylock(&bdev->bd_disk->open_mutex)) {
+		if (atomic_read(&bdev->bd_openers))
+			sync_blockdev_nowait(bdev);
+		mutex_unlock(&bdev->bd_disk->open_mutex);
+	}
+	blkdev_put_no_open(bdev);
+}
+
 /*
  * Write out and wait upon all the dirty data associated with a block
  * device via its mapping.  Does not take the superblock lock.
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 4f7905c3412b..7a5f3e2f17d6 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1710,6 +1710,7 @@ void invalidate_bdev(struct block_device *bdev);
 int sync_blockdev(struct block_device *bdev);
 int sync_blockdev_range(struct block_device *bdev, loff_t lstart, loff_t lend);
 int sync_blockdev_nowait(struct block_device *bdev);
+void bdev_flush_by_dev(dev_t dev);
 void sync_bdevs(bool wait);
 void bdev_statx(const struct path *path, struct kstat *stat, u32 request_mask);
 void printk_all_partitions(void);
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v4 2/3] memcg,writeback: flush foreign bdev mappings separately from owner wbs
  2026-09-14 10:31 [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Julian Sun
  2026-09-14 10:31 ` [PATCH v4 1/3] block: introduce bdev_flush_by_dev() Julian Sun
@ 2026-09-14 10:31 ` Julian Sun
  2026-09-14 10:31 ` [PATCH v4 3/3] writeback: record bdev targets in foreign writeback tracepoints Julian Sun
  2026-09-15  3:59 ` [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Andrew Morton
  3 siblings, 0 replies; 7+ messages in thread
From: Julian Sun @ 2026-09-14 10:31 UTC (permalink / raw)
  To: linux-block, linux-fsdevel
  Cc: axboe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	willy, jack, rostedt, tj, akpm

Bdev inodes are routinely shared by multiple memcgs. Recording their
owner wb as foreign can flush unrelated file data when a source memcg
enters dirty throttling.

Record bdev device numbers separately and queue best-effort work on
memcg_bdev_frn_flusher to write only their mappings. Keep the existing
foreign-wb mechanism for non-bdev inodes.

Bdev foreign flushes can be triggered frequently. Use a dedicated
workqueue to avoid interfering with tasks on existing workqueues. If
allocation fails, retain the existing foreign-wb path.

Use fixed per-memcg slots, with a shared lock protecting device records
and in-flight state. Tracking is best effort: record only dev_t without
holding device or inode references. Skip closed devices or devices whose
open_mutex is busy. Device removal and device-number reuse may race with
lookup.

Fixes: 97b27821b485 ("writeback, memcg: Implement foreign dirty flushing")
Suggested-by: Jan Kara <jack@suse.cz>
Signed-off-by: Julian Sun <sunjunchao@bytedance.com>
---
 include/linux/memcontrol.h |  15 ++++++
 mm/memcontrol.c            | 106 ++++++++++++++++++++++++++++++++++---
 2 files changed, 113 insertions(+), 8 deletions(-)

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index 7d1c0ce189a8..d44d3ce51b50 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -176,6 +176,18 @@ struct memcg_cgwb_frn {
 	struct wb_completion done;	/* tracks in-flight foreign writebacks */
 };
 
+/*
+ * frn_lock protects dev and inflight.
+ * No extra memcg reference is taken: this work is embedded in the memcg,
+ * and mem_cgroup_css_free() waits for it to finish before freeing the memcg.
+ */
+struct bdev_frn_flush_ctx {
+	struct work_struct work;
+	dev_t dev;
+	bool inflight;
+	struct mem_cgroup *memcg;
+};
+
 /*
  * Bucket for arbitrarily byte-sized objects charged to a memory
  * cgroup. The bucket can be reparented in one piece when the cgroup
@@ -279,6 +291,9 @@ struct mem_cgroup {
 #ifdef CONFIG_CGROUP_WRITEBACK
 	struct wb_domain cgwb_domain;
 	struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT];
+	struct bdev_frn_flush_ctx bdev_frn[MEMCG_CGWB_FRN_CNT];
+	/* Nests inside mapping->i_pages. */
+	spinlock_t frn_lock;
 #endif
 
 #ifdef CONFIG_LRU_GEN_WALKS_MMU
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 1271d390b617..aac8c893d7d2 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -39,6 +39,7 @@
 #include <linux/smp.h>
 #include <linux/page-flags.h>
 #include <linux/backing-dev.h>
+#include <linux/blkdev.h>
 #include <linux/bit_spinlock.h>
 #include <linux/rcupdate.h>
 #include <linux/limits.h>
@@ -104,6 +105,7 @@ static struct kmem_cache *memcg_pn_cachep;
 
 #ifdef CONFIG_CGROUP_WRITEBACK
 static DECLARE_WAIT_QUEUE_HEAD(memcg_cgwb_frn_waitq);
+static struct workqueue_struct *memcg_bdev_frn_flusher __ro_after_init;
 #endif
 
 static inline bool task_is_dying(void)
@@ -3845,6 +3847,41 @@ void mem_cgroup_wb_stats(struct bdi_writeback *wb, unsigned long *pfilepages,
 	}
 }
 
+/*
+ * Bdev inodes are commonly shared by many memcgs. Flushing their owner wb
+ * can write unrelated file data, so record device numbers separately and
+ * flush only the bdev mappings. Tracking is best effort: record only dev_t
+ * without holding device or inode references, so it may race with device
+ * removal and re-addition.
+ */
+static void mem_cgroup_track_foreign_bdev(struct mem_cgroup *memcg, dev_t dev)
+{
+	struct bdev_frn_flush_ctx *ctx;
+	unsigned long flags;
+	int i;
+
+	/*
+	 * __folio_mark_dirty() takes mapping->i_pages with xa_lock_irqsave()
+	 * before reaching this helper. Use irqsave here as well so frn_lock's
+	 * IRQ protection does not depend on that outer locking.
+	 */
+	spin_lock_irqsave(&memcg->frn_lock, flags);
+	for (i = 0; i < MEMCG_CGWB_FRN_CNT; i++) {
+		if (memcg->bdev_frn[i].dev == dev)
+			goto out;
+	}
+
+	for (i = 0; i < MEMCG_CGWB_FRN_CNT; i++) {
+		ctx = &memcg->bdev_frn[i];
+		if (ctx->inflight)
+			continue;
+		ctx->dev = dev;
+		break;
+	}
+out:
+	spin_unlock_irqrestore(&memcg->frn_lock, flags);
+}
+
 /*
  * Foreign dirty flushing
  *
@@ -3875,17 +3912,17 @@ void mem_cgroup_wb_stats(struct bdi_writeback *wb, unsigned long *pfilepages,
  * most recent foreign dirtying events and initiating remote flushes on
  * them when local writeback isn't enough to keep the memory clean enough.
  *
- * The following two functions implement such mechanism.  When a foreign
- * page - a page whose memcg and writeback ownerships don't match - is
- * dirtied, mem_cgroup_track_foreign_dirty() records the inode owning
- * bdi_writeback on the page owning memcg.  When balance_dirty_pages()
- * decides that the memcg needs to sleep due to high dirty ratio, it calls
+ * For non-bdev inodes, when a foreign page - a page whose memcg and
+ * writeback ownerships don't match - is dirtied,
+ * mem_cgroup_track_foreign_dirty() records the inode owning bdi_writeback
+ * on the page owning memcg. When balance_dirty_pages() decides that the
+ * memcg needs to sleep due to high dirty ratio, it calls
  * mem_cgroup_flush_foreign() which queues writeback on the recorded
  * foreign bdi_writebacks which haven't expired.  Both the numbers of
  * recorded bdi_writebacks and concurrent in-flight foreign writebacks are
  * limited to MEMCG_CGWB_FRN_CNT.
  *
- * The mechanism only remembers IDs and doesn't hold any object references.
+ * These wb records only remember IDs and don't hold any object references.
  * As being wrong occasionally doesn't matter, updates and accesses to the
  * records are lockless and racy.
  */
@@ -3898,9 +3935,17 @@ void mem_cgroup_track_foreign_dirty_slowpath(struct folio *folio,
 	u64 oldest_at = now;
 	int oldest = -1;
 	int i;
+	struct address_space *mapping = folio_mapping(folio);
+	struct inode *bdev_inode = mapping ? mapping->host : NULL;
 
 	trace_track_foreign_dirty(folio, wb);
 
+	if (memcg_bdev_frn_flusher && bdev_inode &&
+	    sb_is_blkdev_sb(bdev_inode->i_sb)) {
+		mem_cgroup_track_foreign_bdev(memcg, bdev_inode->i_rdev);
+		return;
+	}
+
 	/*
 	 * Pick the slot to use.  If there is already a slot for @wb, keep
 	 * using it.  If not replace the oldest one which isn't being
@@ -3941,6 +3986,25 @@ void mem_cgroup_track_foreign_dirty_slowpath(struct folio *folio,
 	}
 }
 
+static void bdev_frn_flush_work(struct work_struct *work)
+{
+	struct bdev_frn_flush_ctx *ctx =
+		container_of(work, struct bdev_frn_flush_ctx, work);
+	unsigned long flags;
+
+	bdev_flush_by_dev(ctx->dev);
+
+	/*
+	 * The dirty tracking path takes frn_lock while holding mapping->i_pages.
+	 * Disable local IRQs here to avoid deadlocks with I/O completion
+	 * handlers that also take mapping->i_pages.
+	 */
+	spin_lock_irqsave(&ctx->memcg->frn_lock, flags);
+	ctx->inflight = false;
+	ctx->dev = 0;
+	spin_unlock_irqrestore(&ctx->memcg->frn_lock, flags);
+}
+
 /* issue foreign writeback flushes for recorded foreign dirtying events */
 void mem_cgroup_flush_foreign(struct bdi_writeback *wb)
 {
@@ -3949,6 +4013,18 @@ void mem_cgroup_flush_foreign(struct bdi_writeback *wb)
 	u64 now = jiffies_64;
 	int i;
 
+	for (i = 0; i < MEMCG_CGWB_FRN_CNT; i++) {
+		struct bdev_frn_flush_ctx *ctx = &memcg->bdev_frn[i];
+		unsigned long flags;
+
+		spin_lock_irqsave(&memcg->frn_lock, flags);
+		if (!ctx->inflight && ctx->dev) {
+			ctx->inflight = true;
+			queue_work(memcg_bdev_frn_flusher, &ctx->work);
+		}
+		spin_unlock_irqrestore(&memcg->frn_lock, flags);
+	}
+
 	for (i = 0; i < MEMCG_CGWB_FRN_CNT; i++) {
 		struct memcg_cgwb_frn *frn = &memcg->cgwb_frn[i];
 
@@ -4202,9 +4278,15 @@ static struct mem_cgroup *mem_cgroup_alloc(struct mem_cgroup *parent)
 	memcg->kmemcg_id = -1;
 #ifdef CONFIG_CGROUP_WRITEBACK
 	INIT_LIST_HEAD(&memcg->cgwb_list);
-	for (i = 0; i < MEMCG_CGWB_FRN_CNT; i++)
+	for (i = 0; i < MEMCG_CGWB_FRN_CNT; i++) {
+		struct bdev_frn_flush_ctx *ctx = &memcg->bdev_frn[i];
+
 		memcg->cgwb_frn[i].done =
 			__WB_COMPLETION_INIT(&memcg_cgwb_frn_waitq);
+		INIT_WORK(&ctx->work, bdev_frn_flush_work);
+		ctx->memcg = memcg;
+	}
+	spin_lock_init(&memcg->frn_lock);
 #endif
 	lru_gen_init_memcg(memcg);
 	return memcg;
@@ -4386,8 +4468,10 @@ static void mem_cgroup_css_free(struct cgroup_subsys_state *css)
 	int __maybe_unused i;
 
 #ifdef CONFIG_CGROUP_WRITEBACK
-	for (i = 0; i < MEMCG_CGWB_FRN_CNT; i++)
+	for (i = 0; i < MEMCG_CGWB_FRN_CNT; i++) {
 		wb_wait_for_completion(&memcg->cgwb_frn[i].done);
+		flush_work(&memcg->bdev_frn[i].work);
+	}
 #endif
 	if (cgroup_subsys_on_dfl(memory_cgrp_subsys) && !cgroup_memory_nosocket)
 		static_branch_dec(&memcg_sockets_enabled_key);
@@ -5703,6 +5787,12 @@ int __init mem_cgroup_init(void)
 	memcg_wq = alloc_workqueue("memcg", WQ_PERCPU, 0);
 	WARN_ON(!memcg_wq);
 
+#ifdef CONFIG_CGROUP_WRITEBACK
+	memcg_bdev_frn_flusher = alloc_workqueue("memcg_bdev_frn_flusher",
+					       WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
+	WARN_ON(!memcg_bdev_frn_flusher);
+#endif
+
 	for_each_possible_cpu(cpu) {
 		INIT_WORK(&per_cpu_ptr(&memcg_stock, cpu)->work,
 			  drain_local_memcg_stock);
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v4 3/3] writeback: record bdev targets in foreign writeback tracepoints
  2026-09-14 10:31 [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Julian Sun
  2026-09-14 10:31 ` [PATCH v4 1/3] block: introduce bdev_flush_by_dev() Julian Sun
  2026-09-14 10:31 ` [PATCH v4 2/3] memcg,writeback: flush foreign bdev mappings separately from owner wbs Julian Sun
@ 2026-09-14 10:31 ` Julian Sun
  2026-09-15  3:59 ` [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Andrew Morton
  3 siblings, 0 replies; 7+ messages in thread
From: Julian Sun @ 2026-09-14 10:31 UTC (permalink / raw)
  To: linux-block, linux-fsdevel
  Cc: axboe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	willy, jack, rostedt, tj, akpm

Add dev to track_foreign_dirty and flush_foreign so traces can
distinguish foreign-wb tracking from the bdev-only path. Append the
field to each event and print it as major:minor; zero denotes the
normal foreign-wb path.

Signed-off-by: Julian Sun <sunjunchao@bytedance.com>
---
 include/trace/events/writeback.h | 22 ++++++++++++++--------
 mm/memcontrol.c                  |  9 ++++++---
 2 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/include/trace/events/writeback.h b/include/trace/events/writeback.h
index 13ee076ccd16..ffd1b8df4231 100644
--- a/include/trace/events/writeback.h
+++ b/include/trace/events/writeback.h
@@ -273,9 +273,9 @@ TRACE_EVENT(inode_switch_wbs,
 
 TRACE_EVENT(track_foreign_dirty,
 
-	TP_PROTO(struct folio *folio, struct bdi_writeback *wb),
+	TP_PROTO(struct folio *folio, struct bdi_writeback *wb, dev_t dev),
 
-	TP_ARGS(folio, wb),
+	TP_ARGS(folio, wb, dev),
 
 	TP_STRUCT__entry(
 		__array(char,		name, 32)
@@ -284,6 +284,7 @@ TRACE_EVENT(track_foreign_dirty,
 		__field(u64,		cgroup_ino)
 		__field(u64,		page_cgroup_ino)
 		__field(unsigned int,	memcg_id)
+		__field(dev_t,		dev)
 	),
 
 	TP_fast_assign(
@@ -295,34 +296,37 @@ TRACE_EVENT(track_foreign_dirty,
 		__entry->ino		= inode ? inode->i_ino : 0;
 		__entry->memcg_id	= wb->memcg_css->id;
 		__entry->cgroup_ino	= __trace_wb_assign_cgroup(wb);
+		__entry->dev		= dev;
 
 		rcu_read_lock();
 		__entry->page_cgroup_ino = cgroup_ino(folio_memcg(folio)->css.cgroup);
 		rcu_read_unlock();
 	),
 
-	TP_printk("bdi %s[%llu]: ino=%llu memcg_id=%u cgroup_ino=%llu page_cgroup_ino=%llu",
+	TP_printk("bdi %s[%llu]: ino=%llu memcg_id=%u cgroup_ino=%llu page_cgroup_ino=%llu dev=%u:%u",
 		__entry->name,
 		__entry->bdi_id,
 		__entry->ino,
 		__entry->memcg_id,
 		__entry->cgroup_ino,
-		__entry->page_cgroup_ino
+		__entry->page_cgroup_ino,
+		MAJOR(__entry->dev), MINOR(__entry->dev)
 	)
 );
 
 TRACE_EVENT(flush_foreign,
 
 	TP_PROTO(struct bdi_writeback *wb, unsigned int frn_bdi_id,
-		 unsigned int frn_memcg_id),
+		 unsigned int frn_memcg_id, dev_t dev),
 
-	TP_ARGS(wb, frn_bdi_id, frn_memcg_id),
+	TP_ARGS(wb, frn_bdi_id, frn_memcg_id, dev),
 
 	TP_STRUCT__entry(
 		__array(char,		name, 32)
 		__field(u64,		cgroup_ino)
 		__field(unsigned int,	frn_bdi_id)
 		__field(unsigned int,	frn_memcg_id)
+		__field(dev_t,		dev)
 	),
 
 	TP_fast_assign(
@@ -330,13 +334,15 @@ TRACE_EVENT(flush_foreign,
 		__entry->cgroup_ino	= __trace_wb_assign_cgroup(wb);
 		__entry->frn_bdi_id	= frn_bdi_id;
 		__entry->frn_memcg_id	= frn_memcg_id;
+		__entry->dev		= dev;
 	),
 
-	TP_printk("bdi %s: cgroup_ino=%llu frn_bdi_id=%u frn_memcg_id=%u",
+	TP_printk("bdi %s: cgroup_ino=%llu frn_bdi_id=%u frn_memcg_id=%u dev=%u:%u",
 		__entry->name,
 		__entry->cgroup_ino,
 		__entry->frn_bdi_id,
-		__entry->frn_memcg_id
+		__entry->frn_memcg_id,
+		MAJOR(__entry->dev), MINOR(__entry->dev)
 	)
 );
 #endif
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index aac8c893d7d2..0784f6e40aec 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -3938,14 +3938,15 @@ void mem_cgroup_track_foreign_dirty_slowpath(struct folio *folio,
 	struct address_space *mapping = folio_mapping(folio);
 	struct inode *bdev_inode = mapping ? mapping->host : NULL;
 
-	trace_track_foreign_dirty(folio, wb);
-
 	if (memcg_bdev_frn_flusher && bdev_inode &&
 	    sb_is_blkdev_sb(bdev_inode->i_sb)) {
+		trace_track_foreign_dirty(folio, wb, bdev_inode->i_rdev);
 		mem_cgroup_track_foreign_bdev(memcg, bdev_inode->i_rdev);
 		return;
 	}
 
+	trace_track_foreign_dirty(folio, wb, 0);
+
 	/*
 	 * Pick the slot to use.  If there is already a slot for @wb, keep
 	 * using it.  If not replace the oldest one which isn't being
@@ -4020,6 +4021,7 @@ void mem_cgroup_flush_foreign(struct bdi_writeback *wb)
 		spin_lock_irqsave(&memcg->frn_lock, flags);
 		if (!ctx->inflight && ctx->dev) {
 			ctx->inflight = true;
+			trace_flush_foreign(wb, 0, 0, ctx->dev);
 			queue_work(memcg_bdev_frn_flusher, &ctx->work);
 		}
 		spin_unlock_irqrestore(&memcg->frn_lock, flags);
@@ -4037,7 +4039,8 @@ void mem_cgroup_flush_foreign(struct bdi_writeback *wb)
 		if (time_after64(frn->at, now - intv) &&
 		    atomic_read(&frn->done.cnt) == 1) {
 			frn->at = 0;
-			trace_flush_foreign(wb, frn->bdi_id, frn->memcg_id);
+			trace_flush_foreign(wb, frn->bdi_id, frn->memcg_id,
+					    0);
 			cgroup_writeback_by_id(frn->bdi_id, frn->memcg_id,
 					       WB_REASON_FOREIGN_FLUSH,
 					       &frn->done);
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately
  2026-09-14 10:31 [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Julian Sun
                   ` (2 preceding siblings ...)
  2026-09-14 10:31 ` [PATCH v4 3/3] writeback: record bdev targets in foreign writeback tracepoints Julian Sun
@ 2026-09-15  3:59 ` Andrew Morton
  2026-09-15  5:35   ` Julian Sun
  3 siblings, 1 reply; 7+ messages in thread
From: Andrew Morton @ 2026-09-15  3:59 UTC (permalink / raw)
  To: Julian Sun
  Cc: linux-block, linux-fsdevel, axboe, hannes, mhocko, roman.gushchin,
	shakeel.butt, muchun.song, willy, jack, rostedt, tj

On Mon, 14 Sep 2026 18:31:16 +0800 Julian Sun <sunjunchao@bytedance.com> wrote:

> Bdev inodes are routinely shared by multiple memcgs. Recording their
> owner wb as foreign can cause dirty throttling in one memcg to trigger
> writeback of unrelated file data belonging to that wb.
> 
> On one production machine, bdev mapping snapshots showed as little
> as about 10 MiB of dirty data. A queue snapshot of its owner wb showed
> 599 foreign-flush works, with an average requested writeback budget of
> 43 GiB per work.
> 
> In other words, a small amount of foreign dirtying in the bdev mapping
> can trigger owner-wide writeback requests, whose budgets averaged
> 43 GiB in this queue snapshot.

What is the overall runtime effect of this?  IOW, can we see some
before-and-after numbers?

> Following Jan's suggestion, this series records foreign bdev targets
> separately and flushes their mappings instead of their owner wbs. This
> preserves a way for dirty throttling to initiate bdev writeback while
> avoiding owner-wide writeback triggered by these records. The existing
> foreign-wb mechanism remains unchanged for other inodes.
> 
> Tracking is best effort: each memcg keeps a bounded set of device
> numbers without persistent device or inode references. Closed devices
> and devices with a busy open_mutex are skipped. Device removal and
> device-number reuse may race with lookup.
> 
> Flushes run asynchronously on a dedicated workqueue to isolate these
> frequently triggered tasks from existing workqueue users. If workqueue
> allocation fails, the existing foreign-wb mechanism remains in use.
> 
> Julian Sun (3):
>   block: introduce bdev_flush_by_dev()
>   memcg,writeback: flush foreign bdev mappings separately from owner wbs
>   writeback: record bdev targets in foreign writeback tracepoints
> 
>  block/bdev.c                     |  24 +++++++
>  include/linux/blkdev.h           |   1 +
>  include/linux/memcontrol.h       |  15 ++++
>  include/trace/events/writeback.h |  22 +++---
>  mm/memcontrol.c                  | 113 ++++++++++++++++++++++++++++---

I'd consider this a memcg patch, and not only due to the line counts.

Please cc linux-mm on memcg patchsets.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately
  2026-09-15  3:59 ` [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Andrew Morton
@ 2026-09-15  5:35   ` Julian Sun
  2026-09-15  6:06     ` Andrew Morton
  0 siblings, 1 reply; 7+ messages in thread
From: Julian Sun @ 2026-09-15  5:35 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-block, linux-fsdevel, axboe, hannes, mhocko, roman.gushchin,
	shakeel.butt, muchun.song, willy, jack, rostedt, tj, linux-mm

On 9/15/26 11:59 AM, Andrew Morton wrote:
> On Mon, 14 Sep 2026 18:31:16 +0800 Julian Sun <sunjunchao@bytedance.com> wrote:
> 
>> Bdev inodes are routinely shared by multiple memcgs. Recording their
>> owner wb as foreign can cause dirty throttling in one memcg to trigger
>> writeback of unrelated file data belonging to that wb.
>>
>> On one production machine, bdev mapping snapshots showed as little
>> as about 10 MiB of dirty data. A queue snapshot of its owner wb showed
>> 599 foreign-flush works, with an average requested writeback budget of
>> 43 GiB per work.
>>
>> In other words, a small amount of foreign dirtying in the bdev mapping
>> can trigger owner-wide writeback requests, whose budgets averaged
>> 43 GiB in this queue snapshot.
> 
> What is the overall runtime effect of this?  IOW, can we see some
> before-and-after numbers?

I tested this series using a QEMU/KVM VM with 4 vCPUs, 8 GiB RAM,
cgroup v2 and ext4. Virtual NVMe write bandwidth was capped at
200 MiB/s.

A fio job repeatedly overwrote a preinitialized 256 MiB file using
64 KiB buffered writes, at 64 MiB/s, for 9 GiB of logical writes.
Four memcgs, each with memory.high=32 MiB and io.max=2 MiB/s, generated
metadata and buffered writes to trigger dirty throttling and foreign
flushes. Concurrently, foreground fio performed 16 GiB of sequential
direct writes using 1 MiB requests and queue depth 32. Normal periodic
writeback remained enabled.

Three comparison pairs, with alternating execution order, produced:

Run  Kernel   File I/O (GiB)  Time (s)  BW (MiB/s)  p99 (ms)
1    base              7.19    114.74      142.79      1434
1    patched           1.57     88.62      184.87       480
      change         -78.22%   -22.76%     +29.47%   -66.52%

2    base              5.13    103.59      158.16      1434
2    patched           1.74     88.65      184.81       497
      change         -66.06%   -14.42%     +16.85%   -65.35%

3    base              6.14    109.27      149.94      1434
3    patched           1.73     88.68      184.75       480
      change         -71.77%   -18.85%     +23.22%   -66.52%

File I/O is completed block I/O matching the overwritten file's extents,
including final sync, not requested writeback budgets. The other metrics
are for the foreground workload.

Across these runs, mean file write I/O decreased by 72.7%, foreground
bandwidth increased by 23.0%, and completion time decreased by 18.8%.

The baseline varied noticeably: runs with more writes to the overwritten
file also took longer to complete the foreground workload. With this
series, foreground completion time stayed within 88.62-88.68 seconds.
This is consistent with preserving overwrite coalescing and leaving more
device bandwidth available for the foreground workload.

> 
>> Following Jan's suggestion, this series records foreign bdev targets
>> separately and flushes their mappings instead of their owner wbs. This
>> preserves a way for dirty throttling to initiate bdev writeback while
>> avoiding owner-wide writeback triggered by these records. The existing
>> foreign-wb mechanism remains unchanged for other inodes.
>>
>> Tracking is best effort: each memcg keeps a bounded set of device
>> numbers without persistent device or inode references. Closed devices
>> and devices with a busy open_mutex are skipped. Device removal and
>> device-number reuse may race with lookup.
>>
>> Flushes run asynchronously on a dedicated workqueue to isolate these
>> frequently triggered tasks from existing workqueue users. If workqueue
>> allocation fails, the existing foreign-wb mechanism remains in use.
>>
>> Julian Sun (3):
>>    block: introduce bdev_flush_by_dev()
>>    memcg,writeback: flush foreign bdev mappings separately from owner wbs
>>    writeback: record bdev targets in foreign writeback tracepoints
>>
>>   block/bdev.c                     |  24 +++++++
>>   include/linux/blkdev.h           |   1 +
>>   include/linux/memcontrol.h       |  15 ++++
>>   include/trace/events/writeback.h |  22 +++---
>>   mm/memcontrol.c                  | 113 ++++++++++++++++++++++++++++---
> 
> I'd consider this a memcg patch, and not only due to the line counts.
> 
> Please cc linux-mm on memcg patchsets.
Sure, will do.

Thanks,
-- 
Julian Sun <sunjunchao@bytedance.com>

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately
  2026-09-15  5:35   ` Julian Sun
@ 2026-09-15  6:06     ` Andrew Morton
  0 siblings, 0 replies; 7+ messages in thread
From: Andrew Morton @ 2026-09-15  6:06 UTC (permalink / raw)
  To: Julian Sun
  Cc: linux-block, linux-fsdevel, axboe, hannes, mhocko, roman.gushchin,
	shakeel.butt, muchun.song, willy, jack, rostedt, tj, linux-mm

On Tue, 15 Sep 2026 13:35:18 +0800 Julian Sun <sunjunchao@bytedance.com> wrote:

> > 
> > What is the overall runtime effect of this?  IOW, can we see some
> > before-and-after numbers?
> 
> I tested this series using a QEMU/KVM VM with 4 vCPUs, 8 GiB RAM,
> cgroup v2 and ext4. Virtual NVMe write bandwidth was capped at
> 200 MiB/s.
> 
> A fio job repeatedly overwrote a preinitialized 256 MiB file using
> 64 KiB buffered writes, at 64 MiB/s, for 9 GiB of logical writes.
> Four memcgs, each with memory.high=32 MiB and io.max=2 MiB/s, generated
> metadata and buffered writes to trigger dirty throttling and foreign
> flushes. Concurrently, foreground fio performed 16 GiB of sequential
> direct writes using 1 MiB requests and queue depth 32. Normal periodic
> writeback remained enabled.
> 
> Three comparison pairs, with alternating execution order, produced:
> 
> Run  Kernel   File I/O (GiB)  Time (s)  BW (MiB/s)  p99 (ms)
> 1    base              7.19    114.74      142.79      1434
> 1    patched           1.57     88.62      184.87       480
>       change         -78.22%   -22.76%     +29.47%   -66.52%
> 
> 2    base              5.13    103.59      158.16      1434
> 2    patched           1.74     88.65      184.81       497
>       change         -66.06%   -14.42%     +16.85%   -65.35%
> 
> 3    base              6.14    109.27      149.94      1434
> 3    patched           1.73     88.68      184.75       480
>       change         -71.77%   -18.85%     +23.22%   -66.52%
> 
> File I/O is completed block I/O matching the overwritten file's extents,
> including final sync, not requested writeback budgets. The other metrics
> are for the foreground workload.
> 
> Across these runs, mean file write I/O decreased by 72.7%, foreground
> bandwidth increased by 23.0%, and completion time decreased by 18.8%.

Thanks.  This is super-important info so please condense, summarize and
emphasize this result in the [0/N].

How real-world is this test case?  I assume people won't see gains like
this in anything they normally do?  I assume this is long-standing
behavior, so if misbehavior like this was regularly being observed then
we'd have fixed it by now.

> > 
> > I'd consider this a memcg patch, and not only due to the line counts.
> > 
> > Please cc linux-mm on memcg patchsets.
> Sure, will do.

Great.  Please update that [0/N] as indicated - help readers understand
the expected real-world impacts.  Then send us a v5 and I'll get to
work helping to deliver this to our users.


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-09-15  6:06 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-14 10:31 [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Julian Sun
2026-09-14 10:31 ` [PATCH v4 1/3] block: introduce bdev_flush_by_dev() Julian Sun
2026-09-14 10:31 ` [PATCH v4 2/3] memcg,writeback: flush foreign bdev mappings separately from owner wbs Julian Sun
2026-09-14 10:31 ` [PATCH v4 3/3] writeback: record bdev targets in foreign writeback tracepoints Julian Sun
2026-09-15  3:59 ` [PATCH v4 0/3] memcg,writeback: flush foreign bdev mappings separately Andrew Morton
2026-09-15  5:35   ` Julian Sun
2026-09-15  6:06     ` Andrew Morton

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox