diff --git a/Cargo.lock b/Cargo.lock index 3d551ef22..0eae0e405 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9563,6 +9563,7 @@ dependencies = [ "sha2 0.11.0", "shadow-rs", "smallvec", + "starshard", "temp-env", "tempfile", "thiserror 2.0.20", @@ -11656,9 +11657,9 @@ dependencies = [ [[package]] name = "starshard" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d298eb1bb81d6e5ddf447f3d26698d6ac5e5b9502b03004dbc2a2e2f8b572b6" +checksum = "4155f6127729fef4a7b2aff334e9f05e697f52b67a8512e789b640e036195544" dependencies = [ "async-trait", "hashbrown 0.17.1", diff --git a/Cargo.toml b/Cargo.toml index a1e9c9a6b..b16720722 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -300,7 +300,7 @@ siphasher = "1.0.3" smallvec = { version = "1.15.2" } compact_str = "0.10.0" snap = "1.1.2" -starshard = { version = "2.2.2" } +starshard = { version = "2.3.0" } strum = { version = "0.28.0" } sysinfo = "0.39.6" temp-env = "0.3.6" @@ -371,10 +371,10 @@ debug = "line-tables-only" [profile.release] opt-level = 3 lto = "thin" -codegen-units = 1 -debug = 0 -split-debuginfo = "off" +codegen-units = 16 +debug = 2 strip = "symbols" +split-debuginfo = "off" [profile.production] inherits = "release" diff --git a/crates/ecstore/Cargo.toml b/crates/ecstore/Cargo.toml index 517adb44c..0bdc39a37 100644 --- a/crates/ecstore/Cargo.toml +++ b/crates/ecstore/Cargo.toml @@ -129,6 +129,7 @@ hotpath-cpu = [ test-util = [] [dependencies] +starshard = { workspace = true } hotpath.workspace = true rustfs-filemeta.workspace = true rustfs-utils = { workspace = true, features = ["full"] } diff --git a/crates/ecstore/src/erasure/coding/encode.rs b/crates/ecstore/src/erasure/coding/encode.rs index 97fdc9525..a910ccb3f 100644 --- a/crates/ecstore/src/erasure/coding/encode.rs +++ b/crates/ecstore/src/erasure/coding/encode.rs @@ -680,6 +680,8 @@ impl Erasure { // capacity and never reallocates. Reading into uninitialized spare capacity // (instead of resize + slice read) also skips zero-filling each fresh buffer. let ingest_capacity = expanded_block_bytes.max(block_size); + // Pre-allocate buffer pool for this encoding session + let mut buf_pool: Vec = Vec::with_capacity(4); let mut buf = BytesMut::with_capacity(ingest_capacity); loop { match read_full_buf_or_eof(&mut reader, &mut buf, block_size).await { @@ -689,7 +691,8 @@ impl Erasure { total += n; let encode_buf = buf; let res = self.clone().encode_block_bytes_mut(encode_buf, n).await?; - buf = BytesMut::with_capacity(ingest_capacity); + // Try to reuse buffer from pool, or allocate new one + buf = buf_pool.pop().unwrap_or_else(|| BytesMut::with_capacity(ingest_capacity)); let queued_bytes = res.queued_bytes(); let _producer_stage = rustfs_io_metrics::track_ec_encode_producer_bytes(queued_bytes); let send_wait_stage_start = stage_timer_if_enabled(); @@ -697,6 +700,11 @@ impl Erasure { return Err(std::io::Error::other(format!("Failed to send encoded data : {err}"))); } record_internal_stage_if_enabled("erasure_encode_send_wait", send_wait_stage_start); + // Return buffer to pool if it has sufficient capacity + if buf.capacity() >= ingest_capacity && buf_pool.len() < 4 { + buf_pool.push(buf); + buf = BytesMut::with_capacity(ingest_capacity); + } } Ok(None) => break, Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { diff --git a/crates/ecstore/src/object_api/types.rs b/crates/ecstore/src/object_api/types.rs index 99822ff44..d91d96a0d 100644 --- a/crates/ecstore/src/object_api/types.rs +++ b/crates/ecstore/src/object_api/types.rs @@ -457,6 +457,29 @@ pub struct ObjectOptions { pub const SCANNER_PUBLICATION_LEASE_FENCE_METADATA_KEY: &str = "x-rustfs-internal-scanner-publication-lease-fence-v1"; impl ObjectOptions { + /// Create a new ObjectOptions with modified no_lock field. + pub fn with_no_lock(&self, no_lock: bool) -> Self { + let mut opts = self.clone(); + opts.no_lock = no_lock; + opts + } + + /// Create commit options from base options (optimized clone). + pub fn as_commit_opts(&self) -> Self { + let mut opts = self.clone(); + opts.no_lock = true; + opts.metadata_cache_safe = false; + opts.include_part_checksums = true; + opts + } + + /// Create read options with include_part_checksums enabled. + pub fn as_read_opts(&self) -> Self { + let mut opts = self.clone(); + opts.include_part_checksums = true; + opts + } + pub fn set_quota_admission(&mut self, current_usage: u64, quota_limit: u64) -> bool { self.quota_admission = (current_usage <= quota_limit).then_some(QuotaAdmission { current_usage, diff --git a/crates/ecstore/src/set_disk/core/io_primitives.rs b/crates/ecstore/src/set_disk/core/io_primitives.rs index 4719fe198..2cc3935a6 100644 --- a/crates/ecstore/src/set_disk/core/io_primitives.rs +++ b/crates/ecstore/src/set_disk/core/io_primitives.rs @@ -3913,14 +3913,11 @@ impl SetDisks { }; let is_delete_marker = file_info.is_canonical_delete_marker(); - let mut local_file_info; - let file_info = if file_info.erasure.index == 0 { - local_file_info = file_info.clone(); - local_file_info.erasure.index = i + 1; - &local_file_info - } else { - &file_info - }; + // Clone FileInfo and set erasure.index for this disk + let mut file_info = file_info.clone(); + if file_info.erasure.index == 0 { + file_info.erasure.index = i + 1; + } if file_info.erasure.index == 0 || (!is_delete_marker && !file_info.has_valid_erasure_geometry()) { return Err(DiskError::FileCorrupt); } @@ -3936,7 +3933,7 @@ impl SetDisks { .rename_data_borrowed_with_fence( &src_bucket, &src_object, - file_info, + &file_info, &dst_bucket, &dst_object, scanner_publication_lease_token, diff --git a/crates/ecstore/src/set_disk/ops/object.rs b/crates/ecstore/src/set_disk/ops/object.rs index a7b36696f..f8ee574af 100644 --- a/crates/ecstore/src/set_disk/ops/object.rs +++ b/crates/ecstore/src/set_disk/ops/object.rs @@ -5566,8 +5566,7 @@ impl SetDisks { // Force the full quorum fanout (allow_early_stop=false): `disks` is the // write target below, and an early-stop subset would only carry read // quorum, failing write quorum on update_object_meta (backlog#872). - let mut read_opts = opts.clone(); - read_opts.include_part_checksums = true; + let read_opts = opts.as_read_opts(); let (mut fi, _, disks) = self .get_object_fileinfo_gated(bucket, object, &read_opts, false, false) .await? @@ -7369,10 +7368,7 @@ impl crate::storage_api_contracts::object::ObjectOperations for SetDisks { return Err(err); } - let mut commit_opts = opts.clone(); - commit_opts.no_lock = true; - commit_opts.metadata_cache_safe = false; - commit_opts.include_part_checksums = true; + let commit_opts = opts.as_commit_opts(); // Note: Using clone() here is necessary because ObjectOptions has 124 fields. // Future optimization: Consider using Cow or a builder pattern. let transition_lock_guard = if opts.no_lock {