From f262fcfce0c18c36ab5444a028b411505733679b Mon Sep 17 00:00:00 2001 From: houseme Date: Thu, 9 Jul 2026 01:47:14 +0800 Subject: [PATCH] perf(hotpath): add fine-grained PUT-path stage guards (HP-14) (#4541) Close the ~10ms instrumentation residual on the PUT success path that the existing coarse writer_setup/encode/rename stage metrics do not attribute. Adds `hp_guard!` measurement scopes to the previously uninstrumented sub-stages called out in backlog#935 item 2: - SetDisks::acquire_read_lock / acquire_write_lock (namespace lock acquire) - S3::put_object_prelookup (pre-write get_object_info lookup) - MultiWriter::shutdown (bitrot writer flush/close) - SetDisks::commit_rename_data_dir (old data-dir reclaim) - S3Access::put_object (S3 authorization segment) Instrumentation only: `hp_guard!` expands to nothing without the `hotpath` feature, so this is a pure-observation change with zero behavior impact and zero cost in default builds. The pre-lookup site wraps only the lookup call in a scoped block so the guard measures that slice exactly while preserving the existing match and control flow. Verified: cargo check -p rustfs-ecstore (default and --features hotpath) and cargo check -p rustfs (default and --features hotpath) all pass. Co-authored-by: heihutu --- crates/ecstore/src/erasure/coding/encode.rs | 1 + crates/ecstore/src/set_disk/core/io_primitives.rs | 1 + crates/ecstore/src/set_disk/mod.rs | 2 ++ rustfs/src/app/object_usecase.rs | 6 +++++- rustfs/src/storage/access.rs | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/ecstore/src/erasure/coding/encode.rs b/crates/ecstore/src/erasure/coding/encode.rs index 76d04dc99..260d6fd6c 100644 --- a/crates/ecstore/src/erasure/coding/encode.rs +++ b/crates/ecstore/src/erasure/coding/encode.rs @@ -279,6 +279,7 @@ impl<'a> MultiWriter<'a> { } pub async fn shutdown(&mut self) -> std::io::Result<()> { + crate::hp_guard!("MultiWriter::shutdown"); { let mut futures = FuturesUnordered::new(); for (writer_opt, err) in self.writers.iter_mut().zip(self.errs.iter_mut()) { diff --git a/crates/ecstore/src/set_disk/core/io_primitives.rs b/crates/ecstore/src/set_disk/core/io_primitives.rs index f56c5fab9..33f03b092 100644 --- a/crates/ecstore/src/set_disk/core/io_primitives.rs +++ b/crates/ecstore/src/set_disk/core/io_primitives.rs @@ -2744,6 +2744,7 @@ impl SetDisks { committed_data_dir: &str, write_quorum: usize, ) -> OldDataDirCleanup { + crate::hp_guard!("SetDisks::commit_rename_data_dir"); // Anti-misdelete guard (parity retained): MinIO sets `res.OldDataDir=""` // when old == new (`xl-storage.go:2796`) and `commitRenameDataDir` skips // the empty value (`:1837`). Rather miss a reclaim than delete the data diff --git a/crates/ecstore/src/set_disk/mod.rs b/crates/ecstore/src/set_disk/mod.rs index 1624950ef..9306c8dd3 100644 --- a/crates/ecstore/src/set_disk/mod.rs +++ b/crates/ecstore/src/set_disk/mod.rs @@ -1648,6 +1648,7 @@ impl SetDisks { } async fn acquire_read_lock_diag(&self, op: &'static str, bucket: &str, object: &str) -> Result { + crate::hp_guard!("SetDisks::acquire_read_lock"); let diag_enabled = is_object_lock_diag_enabled(); let ns_lock = self.new_ns_lock(bucket, object).await?; let acquire_start = Instant::now(); @@ -1669,6 +1670,7 @@ impl SetDisks { } async fn acquire_write_lock_diag(&self, op: &'static str, bucket: &str, object: &str) -> Result { + crate::hp_guard!("SetDisks::acquire_write_lock"); let diag_enabled = is_object_lock_diag_enabled(); let ns_lock = self.new_ns_lock(bucket, object).await?; let acquire_start = Instant::now(); diff --git a/rustfs/src/app/object_usecase.rs b/rustfs/src/app/object_usecase.rs index 95f6ce8d9..77e92548a 100644 --- a/rustfs/src/app/object_usecase.rs +++ b/rustfs/src/app/object_usecase.rs @@ -3515,7 +3515,11 @@ impl DefaultObjectUsecase { .await .map_err(ApiError::from)?, ); - let previous_current_size = match store.get_object_info(&bucket, &key, ¤t_opts).await { + let previous_current_info = { + crate::hp_guard!("S3::put_object_prelookup"); + store.get_object_info(&bucket, &key, ¤t_opts).await + }; + let previous_current_size = match previous_current_info { Ok(existing_obj_info) => { validate_existing_object_lock_for_write(&existing_obj_info, &opts)?; Some(existing_obj_info.size.max(0) as u64) diff --git a/rustfs/src/storage/access.rs b/rustfs/src/storage/access.rs index 9426112ec..b42236c52 100644 --- a/rustfs/src/storage/access.rs +++ b/rustfs/src/storage/access.rs @@ -1869,6 +1869,7 @@ impl S3Access for FS { /// /// This method returns `Ok(())` by default. async fn put_object(&self, req: &mut S3Request) -> S3Result<()> { + crate::hp_guard!("S3Access::put_object"); let req_info = ext_req_info_mut(&mut req.extensions)?; req_info.bucket = Some(req.input.bucket.clone()); req_info.object = Some(req.input.key.clone());