[PATCH] ceph: make nearfull sync writes opt-in

Alex Markuze <[email protected]> Mon, 6 Jul 2026 14:03:09 +0000
Newsgroups org.kernel.vger.ceph-devel
Message-ID <[email protected]>
The kernel CephFS client has historically treated a cluster or pool
NEARFULL condition as a request to force successful writes through
generic_write_sync().  That effectively turns otherwise buffered writes
into synchronous writes and can cause a severe throughput drop as soon
as a single OSD or the file data pool crosses the nearfull threshold.

On modern large clusters, NEARFULL is primarily an operator health
signal rather than an immediate client-side capacity failure.  Operators
can still have substantial usable capacity while a cluster is
rebalancing, splitting PGs, or expanding onto new devices.  RBD, RGW and
the userspace CephFS client do not impose this extra client-side
sync-write throttle, so the kernel client behavior is surprising and
operationally painful.

Change the default behavior so NEARFULL no longer changes normal
write-sync semantics.  FULL and pool FULL still fail with -ENOSPC, and
explicitly synchronous writes continue to be synced by
generic_write_sync().

Add a nearfull_sync mount option for deployments that want the legacy
backpressure behavior.  When this option is set, successful writes are
promoted to IOCB_DSYNC if the cluster or file data pool is marked
NEARFULL, preserving the old behavior for conservative deployments.

Link: https://tracker.ceph.com/issues/74849
Signed-off-by: Alex Markuze <[email protected]>
---
 Documentation/filesystems/ceph.rst |  6 ++++++
 fs/ceph/file.c                     |  8 +++++---
 fs/ceph/super.c                    | 10 ++++++++++
 fs/ceph/super.h                    |  1 +
 4 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/Documentation/filesystems/ceph.rst b/Documentation/filesystems/ceph.rst
index 6d2276a87a5a..ee2ca0c0c654 100644
--- a/Documentation/filesystems/ceph.rst
+++ b/Documentation/filesystems/ceph.rst
@@ -194,6 +194,12 @@ Mount Options
         copies.  Currently, it's only used in copy_file_range, which will revert
         to the default VFS implementation if this option is used.
 
+  nearfull_sync
+	Force written data to stable storage when the cluster or file data pool is
+	marked NEARFULL. This restores the legacy client-side backpressure
+	behavior. By default, CephFS writes are not forced synchronous solely
+	because of NEARFULL.
+
   recover_session=<no|clean>
 	Set auto reconnect mode in the case where the client is blocklisted. The
 	available modes are "no" and "clean". The default is "no".
diff --git a/fs/ceph/file.c b/fs/ceph/file.c
index 3823671ab95a..0ae9f09aa233 100644
--- a/fs/ceph/file.c
+++ b/fs/ceph/file.c
@@ -2400,7 +2400,8 @@ static ssize_t ceph_splice_read(struct file *in, loff_t *ppos,
  * dropping our cap refs and allowing the pending snap to logically
  * complete _before_ this write occurs.
  *
- * If we are near ENOSPC, write synchronously.
+ * If requested, nearfull writes are synced to preserve the legacy
+ * client-side backpressure behavior.
  */
 static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from)
 {
@@ -2568,8 +2569,9 @@ static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	}
 
 	if (written >= 0) {
-		if ((map_flags & CEPH_OSDMAP_NEARFULL) ||
-		    (pool_flags & CEPH_POOL_FLAG_NEARFULL))
+		if (ceph_test_mount_opt(fsc, NEARFULL_SYNC) &&
+		    ((map_flags & CEPH_OSDMAP_NEARFULL) ||
+		     (pool_flags & CEPH_POOL_FLAG_NEARFULL)))
 			iocb->ki_flags |= IOCB_DSYNC;
 		written = generic_write_sync(iocb, written);
 	}
diff --git a/fs/ceph/super.c b/fs/ceph/super.c
index 0bbd38933f0e..4d129483e345 100644
--- a/fs/ceph/super.c
+++ b/fs/ceph/super.c
@@ -178,6 +178,7 @@ enum {
 	Opt_pagecache,
 	Opt_sparseread,
 	Opt_lazyio,
+	Opt_nearfull_sync,
 };
 
 enum ceph_recover_session_mode {
@@ -207,6 +208,7 @@ static const struct fs_parameter_spec ceph_mount_parameters[] = {
 	fsparam_flag_no	("lazyio",			Opt_lazyio),
 	fsparam_string	("mds_namespace",		Opt_mds_namespace),
 	fsparam_string	("mon_addr",			Opt_mon_addr),
+	fsparam_flag_no	("nearfull_sync",		Opt_nearfull_sync),
 	fsparam_flag_no ("poolperm",			Opt_poolperm),
 	fsparam_flag_no ("quotadf",			Opt_quotadf),
 	fsparam_u32	("rasize",			Opt_rasize),
@@ -601,6 +603,12 @@ static int ceph_parse_mount_param(struct fs_context *fc,
 		else
 			fsopt->flags |= CEPH_MOUNT_OPT_LAZYIO;
 		break;
+	case Opt_nearfull_sync:
+		if (result.negated)
+			fsopt->flags &= ~CEPH_MOUNT_OPT_NEARFULL_SYNC;
+		else
+			fsopt->flags |= CEPH_MOUNT_OPT_NEARFULL_SYNC;
+		break;
 	case Opt_test_dummy_encryption:
 #ifdef CONFIG_FS_ENCRYPTION
 		fscrypt_free_dummy_policy(&fsopt->dummy_enc_policy);
@@ -759,6 +767,8 @@ static int ceph_show_options(struct seq_file *m, struct dentry *root)
 		seq_puts(m, ",sparseread");
 	if (fsopt->flags & CEPH_MOUNT_OPT_LAZYIO)
 		seq_puts(m, ",lazyio");
+	if (fsopt->flags & CEPH_MOUNT_OPT_NEARFULL_SYNC)
+		seq_puts(m, ",nearfull_sync");
 
 	fscrypt_show_test_dummy_encryption(m, ',', root->d_sb);
 
diff --git a/fs/ceph/super.h b/fs/ceph/super.h
index 3737ea7ed88b..632ffb1b401e 100644
--- a/fs/ceph/super.h
+++ b/fs/ceph/super.h
@@ -46,6 +46,7 @@
 #define CEPH_MOUNT_OPT_NOPAGECACHE     (1<<16) /* bypass pagecache altogether */
 #define CEPH_MOUNT_OPT_SPARSEREAD      (1<<17) /* always do sparse reads */
 #define CEPH_MOUNT_OPT_LAZYIO          (1<<18) /* force lazyio for all file opens */
+#define CEPH_MOUNT_OPT_NEARFULL_SYNC   (1<<19) /* sync writes when nearfull */
 
 #define CEPH_MOUNT_OPT_DEFAULT			\
 	(CEPH_MOUNT_OPT_DCACHE |		\
-- 
2.34.1