mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-20 03:22:18 +00:00
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -150,7 +150,18 @@ impl Config {
|
||||
return false;
|
||||
}
|
||||
|
||||
shard_size as usize <= self.inline_shard_limit_bytes(versioned)
|
||||
let shard_size = shard_size as usize;
|
||||
|
||||
let mut inline_block = DEFAULT_INLINE_BLOCK;
|
||||
if self.initialized {
|
||||
inline_block = self.inline_block;
|
||||
}
|
||||
|
||||
if versioned {
|
||||
shard_size <= inline_block / 8
|
||||
} else {
|
||||
shard_size <= inline_block
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline_block(&self) -> usize {
|
||||
@@ -161,15 +172,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline_shard_limit_bytes(&self, versioned: bool) -> usize {
|
||||
let inline_block = self.inline_block();
|
||||
if versioned { inline_block / 8 } else { inline_block }
|
||||
}
|
||||
|
||||
pub fn inline_object_limit_bytes(&self, data_shards: usize, versioned: bool) -> usize {
|
||||
self.inline_shard_limit_bytes(versioned).saturating_mul(data_shards.max(1))
|
||||
}
|
||||
|
||||
pub fn capacity_optimized(&self) -> bool {
|
||||
if !self.initialized {
|
||||
false
|
||||
@@ -334,32 +336,3 @@ pub fn validate_parity_inner(ss_parity: usize, rrs_parity: usize, set_drive_coun
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn inline_object_limit_matches_default_non_versioned_budget() {
|
||||
let cfg = Config {
|
||||
initialized: true,
|
||||
inline_block: DEFAULT_INLINE_BLOCK,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(cfg.inline_shard_limit_bytes(false), DEFAULT_INLINE_BLOCK);
|
||||
assert_eq!(cfg.inline_object_limit_bytes(8, false), DEFAULT_INLINE_BLOCK * 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_object_limit_scales_down_for_versioned_objects() {
|
||||
let cfg = Config {
|
||||
initialized: true,
|
||||
inline_block: DEFAULT_INLINE_BLOCK,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(cfg.inline_shard_limit_bytes(true), DEFAULT_INLINE_BLOCK / 8);
|
||||
assert_eq!(cfg.inline_object_limit_bytes(8, true), DEFAULT_INLINE_BLOCK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ pub fn decode_part_index(index: Option<&Bytes>) -> Option<Index> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_data_from_chunk(chunk: Vec<u8>, size: i64, actual_size: i64, index: Option<Index>) -> Result<PutObjReader> {
|
||||
pub fn put_obj_reader_from_chunk(chunk: Vec<u8>, size: i64, actual_size: i64, index: Option<Index>) -> Result<PutObjReader> {
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
let sha256hex = if !chunk.is_empty() {
|
||||
@@ -74,7 +74,7 @@ pub fn put_data_from_chunk(chunk: Vec<u8>, size: i64, actual_size: i64, index: O
|
||||
};
|
||||
|
||||
let reader = IndexedDataMovementReader::new(Cursor::new(chunk), index);
|
||||
let hash_reader = HashReader::from_reader(reader, size, actual_size, None, sha256hex, false)?;
|
||||
let hash_reader = HashReader::from_stream(reader, size, actual_size, None, sha256hex, false)?;
|
||||
Ok(PutObjReader::new(hash_reader))
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ pub(crate) async fn migrate_object(
|
||||
let part_size = i64::try_from(part.size).map_err(|_| Error::other("part size overflow"))?;
|
||||
let part_actual_size = if part.actual_size > 0 { part.actual_size } else { part_size };
|
||||
let index = decode_part_index(part.index.as_ref());
|
||||
let mut data = put_data_from_chunk(chunk, part_size, part_actual_size, index)?;
|
||||
let mut data = put_obj_reader_from_chunk(chunk, part_size, part_actual_size, index)?;
|
||||
|
||||
let pi = match store
|
||||
.put_object_part(
|
||||
@@ -254,7 +254,7 @@ pub(crate) async fn migrate_object(
|
||||
.first()
|
||||
.and_then(|part| decode_part_index(part.index.as_ref()));
|
||||
let reader = IndexedDataMovementReader::new(BufReader::new(rd.stream), index);
|
||||
let hrd = HashReader::from_reader(reader, object_info.size, actual_size, object_info.etag.clone(), None, false)?;
|
||||
let hrd = HashReader::from_stream(reader, object_info.size, actual_size, object_info.etag.clone(), None, false)?;
|
||||
let mut data = PutObjReader::new(hrd);
|
||||
|
||||
if let Err(err) = store
|
||||
|
||||
+16
-167
@@ -119,19 +119,18 @@ use tokio::{
|
||||
time::{interval, timeout},
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::error;
|
||||
use tracing::{debug, info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
const ENV_RUSTFS_PUT_INLINE_OBJECT_MAX_BYTES: &str = "RUSTFS_PUT_INLINE_OBJECT_MAX_BYTES";
|
||||
const ENV_RUSTFS_PUT_FORCE_DISABLE_INLINE: &str = "RUSTFS_PUT_FORCE_DISABLE_INLINE";
|
||||
const SLOW_PUT_STORAGE_PHASE_DEBUG_THRESHOLD_MS: u64 = 100;
|
||||
const SLOW_PUT_STORAGE_PHASE_WARN_THRESHOLD_MS: u64 = 1_000;
|
||||
const SLOW_PUT_STORAGE_PHASE_ERROR_THRESHOLD_MS: u64 = 5_000;
|
||||
pub const DEFAULT_READ_BUFFER_SIZE: usize = MI_B; // 1 MiB = 1024 * 1024;
|
||||
pub const MAX_PARTS_COUNT: usize = 10000;
|
||||
pub(crate) const RUSTFS_MULTIPART_BUCKET_KEY: &str = "x-rustfs-internal-multipart-bucket";
|
||||
pub(crate) const RUSTFS_MULTIPART_OBJECT_KEY: &str = "x-rustfs-internal-multipart-object";
|
||||
|
||||
fn env_flag_enabled(name: &str) -> bool {
|
||||
rustfs_utils::get_env_bool(name, false)
|
||||
}
|
||||
|
||||
fn env_non_negative_usize(name: &str) -> Option<usize> {
|
||||
rustfs_utils::get_env_opt_usize(name)
|
||||
pub(crate) fn strip_internal_multipart_metadata(metadata: &mut HashMap<String, String>) {
|
||||
metadata.remove(RUSTFS_MULTIPART_BUCKET_KEY);
|
||||
metadata.remove(RUSTFS_MULTIPART_OBJECT_KEY);
|
||||
}
|
||||
|
||||
fn capacity_scope_from_disks(disks: &[Option<DiskStore>]) -> CapacityScope {
|
||||
@@ -164,65 +163,6 @@ fn record_capacity_scope_if_needed(scope_token: Option<Uuid>, disks: &[Option<Di
|
||||
}
|
||||
}
|
||||
|
||||
fn resolved_put_inline_buffer_enabled(object_size: i64, inline_by_topology: bool) -> bool {
|
||||
if !inline_by_topology || object_size < 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if env_flag_enabled(ENV_RUSTFS_PUT_FORCE_DISABLE_INLINE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
env_non_negative_usize(ENV_RUSTFS_PUT_INLINE_OBJECT_MAX_BYTES)
|
||||
.map(|value| usize::try_from(object_size).is_ok_and(|size| size <= value))
|
||||
.unwrap_or(inline_by_topology)
|
||||
}
|
||||
|
||||
fn log_put_storage_phase(
|
||||
bucket: &str,
|
||||
object: &str,
|
||||
phase: &str,
|
||||
elapsed: Duration,
|
||||
object_size: i64,
|
||||
inline_selected: bool,
|
||||
write_quorum: usize,
|
||||
) {
|
||||
let duration_ms = elapsed.as_millis() as u64;
|
||||
if duration_ms < SLOW_PUT_STORAGE_PHASE_DEBUG_THRESHOLD_MS {
|
||||
return;
|
||||
}
|
||||
|
||||
if duration_ms >= SLOW_PUT_STORAGE_PHASE_ERROR_THRESHOLD_MS {
|
||||
error!(
|
||||
phase,
|
||||
duration_ms, object_size, inline_selected, write_quorum, bucket, object, "PUT storage phase is critically slow"
|
||||
);
|
||||
} else if duration_ms >= SLOW_PUT_STORAGE_PHASE_WARN_THRESHOLD_MS {
|
||||
warn!(
|
||||
phase,
|
||||
duration_ms, object_size, inline_selected, write_quorum, bucket, object, "PUT storage phase is slow"
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
phase,
|
||||
duration_ms, object_size, inline_selected, write_quorum, bucket, object, "PUT storage phase exceeded debug threshold"
|
||||
);
|
||||
}
|
||||
}
|
||||
use tracing::error;
|
||||
use tracing::{debug, info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const DEFAULT_READ_BUFFER_SIZE: usize = MI_B; // 1 MiB = 1024 * 1024;
|
||||
pub const MAX_PARTS_COUNT: usize = 10000;
|
||||
pub(crate) const RUSTFS_MULTIPART_BUCKET_KEY: &str = "x-rustfs-internal-multipart-bucket";
|
||||
pub(crate) const RUSTFS_MULTIPART_OBJECT_KEY: &str = "x-rustfs-internal-multipart-object";
|
||||
|
||||
pub(crate) fn strip_internal_multipart_metadata(metadata: &mut HashMap<String, String>) {
|
||||
metadata.remove(RUSTFS_MULTIPART_BUCKET_KEY);
|
||||
metadata.remove(RUSTFS_MULTIPART_OBJECT_KEY);
|
||||
}
|
||||
|
||||
/// Get the duplex buffer size from environment variable or use default.
|
||||
///
|
||||
/// This function reads `RUSTFS_DUPLEX_BUFFER_SIZE` environment variable
|
||||
@@ -872,18 +812,13 @@ impl ObjectIO for SetDisks {
|
||||
let erasure = erasure_coding::Erasure::new(fi.erasure.data_blocks, fi.erasure.parity_blocks, fi.erasure.block_size);
|
||||
|
||||
let is_inline_buffer = {
|
||||
let inline_by_topology = if let Some(sc) = GLOBAL_STORAGE_CLASS.get() {
|
||||
if let Some(sc) = GLOBAL_STORAGE_CLASS.get() {
|
||||
sc.should_inline(erasure.shard_file_size(data.size()), opts.versioned)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
resolved_put_inline_buffer_enabled(data.size(), inline_by_topology)
|
||||
}
|
||||
};
|
||||
if is_inline_buffer {
|
||||
rustfs_io_metrics::record_put_inline_selected(data.size(), opts.versioned);
|
||||
}
|
||||
|
||||
let writer_setup_start = Instant::now();
|
||||
let mut writers = Vec::with_capacity(shuffle_disks.len());
|
||||
let mut errors = Vec::with_capacity(shuffle_disks.len());
|
||||
for disk_op in shuffle_disks.iter() {
|
||||
@@ -917,15 +852,6 @@ impl ObjectIO for SetDisks {
|
||||
writers.push(None);
|
||||
}
|
||||
}
|
||||
log_put_storage_phase(
|
||||
bucket,
|
||||
object,
|
||||
"writer_setup",
|
||||
writer_setup_start.elapsed(),
|
||||
data.size(),
|
||||
is_inline_buffer,
|
||||
write_quorum,
|
||||
);
|
||||
|
||||
let nil_count = errors.iter().filter(|&e| e.is_none()).count();
|
||||
if nil_count < write_quorum {
|
||||
@@ -937,9 +863,6 @@ impl ObjectIO for SetDisks {
|
||||
return Err(Error::other(format!("not enough disks to write: {errors:?}")));
|
||||
}
|
||||
|
||||
let object_size = data.size();
|
||||
let encode_write_start = Instant::now();
|
||||
|
||||
let stream = mem::replace(
|
||||
&mut data.stream,
|
||||
HashReader::from_stream(Cursor::new(Vec::new()), 0, 0, None, None, false)?,
|
||||
@@ -948,30 +871,15 @@ impl ObjectIO for SetDisks {
|
||||
let (reader, w_size) = match Arc::new(erasure).encode(stream, &mut writers, write_quorum).await {
|
||||
Ok((r, w)) => (r, w),
|
||||
Err(e) => {
|
||||
log_put_storage_phase(
|
||||
bucket,
|
||||
object,
|
||||
"encode_write",
|
||||
encode_write_start.elapsed(),
|
||||
object_size,
|
||||
is_inline_buffer,
|
||||
write_quorum,
|
||||
);
|
||||
error!("encode err {:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}; // TODO: delete temporary directory on error
|
||||
|
||||
let _ = mem::replace(&mut data.stream, reader);
|
||||
log_put_storage_phase(
|
||||
bucket,
|
||||
object,
|
||||
"encode_write",
|
||||
encode_write_start.elapsed(),
|
||||
object_size,
|
||||
is_inline_buffer,
|
||||
write_quorum,
|
||||
);
|
||||
// if let Err(err) = close_bitrot_writers(&mut writers).await {
|
||||
// error!("close_bitrot_writers err {:?}", err);
|
||||
// }
|
||||
|
||||
if (w_size as i64) < data.size() {
|
||||
warn!("put_object write size < data.size(), w_size={}, data.size={}", w_size, data.size());
|
||||
@@ -1047,7 +955,6 @@ impl ObjectIO for SetDisks {
|
||||
|
||||
drop(writers); // drop writers to close all files, this is to prevent FileAccessDenied errors when renaming data
|
||||
|
||||
let post_write_lock_start = Instant::now();
|
||||
if !opts.no_lock && object_lock_guard.is_none() {
|
||||
let ns_lock = self.new_ns_lock(bucket, object).await?;
|
||||
object_lock_guard = Some(ns_lock.get_write_lock(get_lock_acquire_timeout()).await.map_err(|e| {
|
||||
@@ -1057,17 +964,7 @@ impl ObjectIO for SetDisks {
|
||||
))
|
||||
})?);
|
||||
}
|
||||
log_put_storage_phase(
|
||||
bucket,
|
||||
object,
|
||||
"post_write_lock",
|
||||
post_write_lock_start.elapsed(),
|
||||
data.size(),
|
||||
is_inline_buffer,
|
||||
write_quorum,
|
||||
);
|
||||
|
||||
let finalize_start = Instant::now();
|
||||
let (online_disks, _, op_old_dir) = Self::rename_data(
|
||||
&shuffle_disks,
|
||||
RUSTFS_META_TMP_BUCKET,
|
||||
@@ -1077,18 +974,7 @@ impl ObjectIO for SetDisks {
|
||||
object,
|
||||
write_quorum,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|_| {
|
||||
log_put_storage_phase(
|
||||
bucket,
|
||||
object,
|
||||
"finalize",
|
||||
finalize_start.elapsed(),
|
||||
data.size(),
|
||||
is_inline_buffer,
|
||||
write_quorum,
|
||||
);
|
||||
})?;
|
||||
.await?;
|
||||
|
||||
if let Some(old_dir) = op_old_dir {
|
||||
self.commit_rename_data_dir(&online_disks, bucket, object, &old_dir.to_string(), write_quorum)
|
||||
@@ -1098,15 +984,6 @@ impl ObjectIO for SetDisks {
|
||||
drop(object_lock_guard); // drop object lock guard to release the lock
|
||||
|
||||
self.delete_all(RUSTFS_META_TMP_BUCKET, &tmp_dir).await?;
|
||||
log_put_storage_phase(
|
||||
bucket,
|
||||
object,
|
||||
"finalize",
|
||||
finalize_start.elapsed(),
|
||||
data.size(),
|
||||
is_inline_buffer,
|
||||
write_quorum,
|
||||
);
|
||||
|
||||
for (i, op_disk) in online_disks.iter().enumerate() {
|
||||
if let Some(disk) = op_disk
|
||||
@@ -2037,9 +1914,6 @@ impl ObjectOperations for SetDisks {
|
||||
if let Some(ref version_id) = opts.version_id {
|
||||
fi.version_id = Uuid::parse_str(version_id).ok();
|
||||
}
|
||||
if let Some(checksum) = &opts.resolved_checksum {
|
||||
fi.checksum = Some(checksum.clone());
|
||||
}
|
||||
|
||||
self.update_object_meta(bucket, object, fi.clone(), &online_disks)
|
||||
.await
|
||||
@@ -4403,31 +4277,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn resolved_put_inline_buffer_enabled_honors_disable_env() {
|
||||
temp_env::with_var(ENV_RUSTFS_PUT_FORCE_DISABLE_INLINE, Some("true"), || {
|
||||
assert!(!resolved_put_inline_buffer_enabled(4096, true));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn resolved_put_inline_buffer_enabled_honors_max_bytes_override() {
|
||||
temp_env::with_var(ENV_RUSTFS_PUT_INLINE_OBJECT_MAX_BYTES, Some("4096"), || {
|
||||
assert!(resolved_put_inline_buffer_enabled(4096, true));
|
||||
assert!(!resolved_put_inline_buffer_enabled(4097, true));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn resolved_put_inline_buffer_enabled_ignores_invalid_override() {
|
||||
temp_env::with_var(ENV_RUSTFS_PUT_INLINE_OBJECT_MAX_BYTES, Some("invalid"), || {
|
||||
assert!(resolved_put_inline_buffer_enabled(4096, true));
|
||||
});
|
||||
}
|
||||
|
||||
async fn current_setup_type() -> SetupType {
|
||||
if is_dist_erasure().await {
|
||||
SetupType::DistErasure
|
||||
|
||||
@@ -70,7 +70,6 @@ pub struct ObjectOptions {
|
||||
|
||||
pub eval_metadata: Option<HashMap<String, String>>,
|
||||
|
||||
pub resolved_checksum: Option<Bytes>,
|
||||
pub want_checksum: Option<Checksum>,
|
||||
pub skip_verify_bitrot: bool,
|
||||
pub capacity_scope_token: Option<Uuid>,
|
||||
@@ -511,18 +510,6 @@ impl ObjectInfo {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let actual_size = fi
|
||||
.parts
|
||||
.iter()
|
||||
.map(|part| {
|
||||
if part.actual_size > 0 {
|
||||
part.actual_size
|
||||
} else {
|
||||
i64::try_from(part.size).unwrap_or_default()
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
|
||||
// TODO: part checksums
|
||||
|
||||
ObjectInfo {
|
||||
@@ -535,7 +522,6 @@ impl ObjectInfo {
|
||||
delete_marker: fi.deleted,
|
||||
mod_time: fi.mod_time,
|
||||
size: fi.size,
|
||||
actual_size,
|
||||
parts,
|
||||
is_latest: fi.is_latest,
|
||||
user_tags,
|
||||
|
||||
Reference in New Issue
Block a user