mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-13 08:36:54 +00:00
perf(ecstore): improve erasure write diagnostics and single-block performance (#3280)
* docs(object-capacity): add localized crate docs * fix(ecstore): improve quorum and transport diagnostics * perf(ecstore): add safe single-block write fast path * refactor(ecstore): collapse layered small write paths * chore(docs): keep issue 662 design note tracked * fix(docs): restore issue 662 design note * chore(docs): keep issue 662 design local only * feat(obs): add internode reliability metrics and dashboard * feat(obs): extend internode diagnostics and service logging * fix(docs): use AGENTS guide filename * perf(ecstore): reuse owned buffer in small encode * fix(ecstore): tighten small write diagnostics --------- Co-authored-by: cxymds <Cxymds@qq.com>
This commit is contained in:
@@ -773,6 +773,37 @@ fn delete_file_info_version_id(version_id: Option<Uuid>) -> Option<Uuid> {
|
||||
}
|
||||
}
|
||||
|
||||
fn object_fits_single_block(object_size: i64, block_size: usize) -> bool {
|
||||
match usize::try_from(object_size) {
|
||||
Ok(size) => size > 0 && size <= block_size,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn should_use_inline_small_fast_path(is_inline_buffer: bool, object_size: i64, block_size: usize) -> bool {
|
||||
is_inline_buffer && object_fits_single_block(object_size, block_size)
|
||||
}
|
||||
|
||||
fn should_use_single_block_non_inline_fast_path(is_inline_buffer: bool, object_size: i64, block_size: usize) -> bool {
|
||||
!is_inline_buffer && object_fits_single_block(object_size, block_size)
|
||||
}
|
||||
|
||||
enum SmallWritePath {
|
||||
Inline,
|
||||
SingleBlockNonInline,
|
||||
Pipeline,
|
||||
}
|
||||
|
||||
fn classify_small_write_path(is_inline_buffer: bool, object_size: i64, block_size: usize) -> SmallWritePath {
|
||||
if should_use_inline_small_fast_path(is_inline_buffer, object_size, block_size) {
|
||||
SmallWritePath::Inline
|
||||
} else if should_use_single_block_non_inline_fast_path(is_inline_buffer, object_size, block_size) {
|
||||
SmallWritePath::SingleBlockNonInline
|
||||
} else {
|
||||
SmallWritePath::Pipeline
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ObjectIO for SetDisks {
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
@@ -1065,10 +1096,10 @@ impl ObjectIO for SetDisks {
|
||||
HashReader::from_stream(Cursor::new(Vec::new()), 0, 0, None, None, false)?,
|
||||
);
|
||||
|
||||
let use_fast_path = is_inline_buffer && data.size() <= fi.erasure.block_size as i64;
|
||||
let write_path = classify_small_write_path(is_inline_buffer, data.size(), fi.erasure.block_size);
|
||||
|
||||
let (reader, w_size) = if use_fast_path {
|
||||
match Arc::new(erasure)
|
||||
let (reader, w_size) = match write_path {
|
||||
SmallWritePath::Inline => match Arc::new(erasure)
|
||||
.encode_inline_small(stream, &mut writers, write_quorum)
|
||||
.await
|
||||
{
|
||||
@@ -1077,15 +1108,24 @@ impl ObjectIO for SetDisks {
|
||||
error!("encode_inline_small err {:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match Arc::new(erasure).encode(stream, &mut writers, write_quorum).await {
|
||||
},
|
||||
SmallWritePath::SingleBlockNonInline => match Arc::new(erasure)
|
||||
.encode_single_block_non_inline(stream, &mut writers, write_quorum)
|
||||
.await
|
||||
{
|
||||
Ok((r, w)) => (r, w),
|
||||
Err(e) => {
|
||||
error!("encode_single_block_non_inline err {:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
},
|
||||
SmallWritePath::Pipeline => match Arc::new(erasure).encode(stream, &mut writers, write_quorum).await {
|
||||
Ok((r, w)) => (r, w),
|
||||
Err(e) => {
|
||||
error!("encode err {:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let _ = mem::replace(&mut data.stream, reader);
|
||||
@@ -2971,7 +3011,18 @@ impl MultipartOperations for SetDisks {
|
||||
HashReader::from_stream(Cursor::new(Vec::new()), 0, 0, None, None, false)?,
|
||||
);
|
||||
|
||||
let (reader, w_size) = Arc::new(erasure).encode(stream, &mut writers, write_quorum).await?; // TODO: delete temporary directory on error
|
||||
let write_path = classify_small_write_path(false, data.size(), fi.erasure.block_size);
|
||||
|
||||
let (reader, w_size) = match write_path {
|
||||
SmallWritePath::SingleBlockNonInline => {
|
||||
Arc::new(erasure)
|
||||
.encode_single_block_non_inline(stream, &mut writers, write_quorum)
|
||||
.await?
|
||||
}
|
||||
SmallWritePath::Inline | SmallWritePath::Pipeline => {
|
||||
Arc::new(erasure).encode(stream, &mut writers, write_quorum).await?
|
||||
}
|
||||
}; // TODO: delete temporary directory on error
|
||||
|
||||
let _ = mem::replace(&mut data.stream, reader);
|
||||
|
||||
@@ -6716,6 +6767,47 @@ mod tests {
|
||||
assert_eq!(delete_file_info_version_id(None), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_object_fast_path_selection_prefers_inline_only_when_inline_buffer_and_single_block() {
|
||||
assert!(should_use_inline_small_fast_path(true, 1024, 4096));
|
||||
assert!(!should_use_single_block_non_inline_fast_path(true, 1024, 4096));
|
||||
assert!(matches!(classify_small_write_path(true, 1024, 4096), SmallWritePath::Inline));
|
||||
|
||||
assert!(!should_use_inline_small_fast_path(false, 1024, 4096));
|
||||
assert!(should_use_single_block_non_inline_fast_path(false, 1024, 4096));
|
||||
assert!(matches!(
|
||||
classify_small_write_path(false, 1024, 4096),
|
||||
SmallWritePath::SingleBlockNonInline
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_object_fast_path_selection_rejects_zero_and_multi_block_payloads() {
|
||||
assert!(!should_use_inline_small_fast_path(true, 0, 4096));
|
||||
assert!(!should_use_single_block_non_inline_fast_path(false, 0, 4096));
|
||||
assert!(matches!(classify_small_write_path(true, 0, 4096), SmallWritePath::Pipeline));
|
||||
|
||||
assert!(!should_use_inline_small_fast_path(true, -1, 4096));
|
||||
assert!(!should_use_single_block_non_inline_fast_path(false, -1, 4096));
|
||||
assert!(matches!(classify_small_write_path(false, -1, 4096), SmallWritePath::Pipeline));
|
||||
|
||||
assert!(!should_use_inline_small_fast_path(true, 8192, 4096));
|
||||
assert!(!should_use_single_block_non_inline_fast_path(false, 8192, 4096));
|
||||
assert!(matches!(classify_small_write_path(false, 8192, 4096), SmallWritePath::Pipeline));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_object_part_fast_path_selection_matches_single_block_non_inline_rules() {
|
||||
assert!(should_use_single_block_non_inline_fast_path(false, 4096, 4096));
|
||||
assert!(should_use_single_block_non_inline_fast_path(false, 2048, 4096));
|
||||
assert!(!should_use_single_block_non_inline_fast_path(false, 4097, 4096));
|
||||
assert!(!should_use_single_block_non_inline_fast_path(false, 0, 4096));
|
||||
assert!(matches!(
|
||||
classify_small_write_path(false, 4096, 4096),
|
||||
SmallWritePath::SingleBlockNonInline
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_cold_storage_class() {
|
||||
// Test cold storage classes
|
||||
|
||||
Reference in New Issue
Block a user