From 0e79106c2ff7d3f3c289030904c1188348c943a2 Mon Sep 17 00:00:00 2001 From: houseme Date: Sat, 22 Aug 2026 22:05:20 +0800 Subject: [PATCH] fix(storageclass): use div_ceil for inline threshold to match shard size calc (#6390) The inline_block threshold used floor division (DEFAULT_INLINE_OBJECT_BUDGET / data_shards) while shard_file_size uses ceiling division (div_ceil). For EC 12:4 with 256KiB objects, this caused a 1-byte discrepancy: - inline_block = 262144 / 12 = 21845 (floor) - shard_file_size = 262144.div_ceil(12) = 21846 (ceil) - should_inline(21846, 12, false) = false (wrong!) Fix by using div_ceil for the inline_block calculation, so both sides use the same rounding and the inline path is correctly triggered. Co-authored-by: heihutu --- crates/ecstore/src/config/storageclass.rs | 5 ++++- crates/ecstore/src/set_disk/ops/object.rs | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/ecstore/src/config/storageclass.rs b/crates/ecstore/src/config/storageclass.rs index 043bf508e..361b15702 100644 --- a/crates/ecstore/src/config/storageclass.rs +++ b/crates/ecstore/src/config/storageclass.rs @@ -248,10 +248,13 @@ impl Config { let shard_size = shard_size as usize; // Keep the historical two-data-shard object budget while preventing // wider EC layouts from multiplying the maximum inline object size. + // Use div_ceil to match the shard_file_size calculation (which also uses + // div_ceil), avoiding a 1-byte rounding discrepancy that prevents inline + // for objects right at the threshold. let inline_block = if self.initialized && self.inline_block_explicit { self.inline_block } else { - (DEFAULT_INLINE_OBJECT_BUDGET / data_shards).min(DEFAULT_INLINE_BLOCK) + DEFAULT_INLINE_OBJECT_BUDGET.div_ceil(data_shards).min(DEFAULT_INLINE_BLOCK) }; if versioned { diff --git a/crates/ecstore/src/set_disk/ops/object.rs b/crates/ecstore/src/set_disk/ops/object.rs index faacca3a8..f5a55a6b4 100644 --- a/crates/ecstore/src/set_disk/ops/object.rs +++ b/crates/ecstore/src/set_disk/ops/object.rs @@ -2123,14 +2123,27 @@ impl SetDisks { let erasure = Arc::new(erasure_from_file_info(&fi, false)?); let put_object_size = known_put_object_storage_size(data.size()); + let shard_file_size_raw = erasure.shard_file_size(put_object_size); let is_inline_buffer = - storage_class_config.should_inline(erasure.shard_file_size(put_object_size), erasure.data_shards, opts.versioned); + storage_class_config.should_inline(shard_file_size_raw, erasure.data_shards, opts.versioned); let collect_stage_timing = rustfs_io_metrics::put_stage_metrics_enabled() || issue3031_diag_enabled(); - let shard_file_size = erasure.shard_file_size(put_object_size); + let shard_file_size = shard_file_size_raw; let shard_size = erasure.shard_size(); let write_path = classify_put_write_path(is_inline_buffer, put_object_size, fi.erasure.block_size); let direct_inline_commit = matches!(write_path, SmallWritePath::Inline); + { + use std::io::Write; + let msg = format!( + "INLINE_DEBUG: bucket={} obj={} size={} shard_fs={} ds={} bs={} inline={} direct={} path={} iblock={} ver={}\n", + bucket, object, put_object_size, shard_file_size_raw, erasure.data_shards, fi.erasure.block_size, + is_inline_buffer, direct_inline_commit, write_path.metric_label(), storage_class_config.inline_block(), opts.versioned + ); + if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/rustfs_inline_debug.log") { + let _ = f.write_all(msg.as_bytes()); + } + let _ = std::io::stderr().write_all(msg.as_bytes()); + } rustfs_io_metrics::record_put_object_path(write_path.metric_label()); let writer_setup_stage_start = collect_stage_timing.then(Instant::now); let (mut writers, errors) = if direct_inline_commit {