mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 03:46:37 +00:00
perf: reduce spawn_blocking contention in PUT path (#3132)
* perf: reduce spawn_blocking contention in PUT path (~23% throughput gain) Flame graph profiling identified tokio blocking pool mutex contention as the #1 bottleneck (17.3% of CPU time). Each spawn_blocking call must acquire parking_lot::raw_mutex to enqueue work. With 16 concurrent PUTs × 4 disks × 3+ spawn_blocking per disk, this became a serialization point. Optimizations applied: - Merge make_dir_all + file write into single spawn_blocking - Merge read_file + parse + write + rename into single spawn_blocking for inline objects (small files) - Optimize reliable_rename to try rename first, mkdir only on ENOENT - Optimize remove/remove_std to try remove_file first, EISDIR fallback - Add encode_inline_small fast path for small objects - Parallelize bitrot writer creation with join_all Benchmark (4KiB PUT, 4-disk EC, 16 concurrent, 8 rounds, randomized A/B): Baseline: ~950 obj/s → Optimized: ~1173 obj/s (+23%) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: address review comments - Return old_data_dir for non-inline rename_data path (was incorrectly None) - Restore delete_all cleanup of PUT temp data on failure paths - Fix cargo fmt formatting Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * style: apply rustfmt from stable 1.96.0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: collapse nested if-let chains for clippy compliance Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: address Copilot review comments - fs.rs: handle macOS EPERM from remove_file on directories - os.rs: restore NotFound=Ok(()) semantics on first rename attempt - local.rs: use try-rename-then-mkdir pattern for inline rename_data Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: add unit tests for encode_inline_small fast path * test: fix comment and line length in encode_inline_small tests * fix: revert reliable_rename and write_all_internal to match original Restore the original reliable_rename logic (check parent exists, then rename in loop) and the original write_all_internal (make_dir_all outside spawn_blocking). The optimization changes caused a CI-only test failure in capacity_dirty_scope_test that could not be reproduced locally. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: update comment and fix formatting for CI - Fix macOS/BSD comment to accurately say macOS only - Fix encode_inline_small test formatting to match rustfmt 1.96.0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: propagate inline rename errors * fix: retry rename when parent missing --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -844,38 +844,45 @@ impl ObjectIO for SetDisks {
|
||||
}
|
||||
};
|
||||
|
||||
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() {
|
||||
if let Some(disk) = disk_op
|
||||
&& disk.is_online().await
|
||||
{
|
||||
let writer = match create_bitrot_writer(
|
||||
is_inline_buffer,
|
||||
Some(disk),
|
||||
RUSTFS_META_TMP_BUCKET,
|
||||
&tmp_object,
|
||||
erasure.shard_file_size(data.size()),
|
||||
erasure.shard_size(),
|
||||
HashAlgorithm::HighwayHash256S,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(writer) => writer,
|
||||
Err(err) => {
|
||||
warn!("create_bitrot_writer disk {}, err {:?}, skipping operation", disk.to_string(), err);
|
||||
errors.push(Some(err));
|
||||
writers.push(None);
|
||||
continue;
|
||||
let shard_file_size = erasure.shard_file_size(data.size());
|
||||
let shard_size = erasure.shard_size();
|
||||
let writer_futs: Vec<_> = shuffle_disks
|
||||
.iter()
|
||||
.map(|disk_op| {
|
||||
let tmp_obj = tmp_object.clone();
|
||||
async move {
|
||||
if let Some(disk) = disk_op
|
||||
&& disk.is_online().await
|
||||
{
|
||||
match create_bitrot_writer(
|
||||
is_inline_buffer,
|
||||
Some(disk),
|
||||
RUSTFS_META_TMP_BUCKET,
|
||||
&tmp_obj,
|
||||
shard_file_size,
|
||||
shard_size,
|
||||
HashAlgorithm::HighwayHash256S,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(writer) => (Some(writer), None),
|
||||
Err(err) => {
|
||||
warn!("create_bitrot_writer disk {}, err {:?}, skipping operation", disk.to_string(), err);
|
||||
(None, Some(err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(None, Some(DiskError::DiskNotFound))
|
||||
}
|
||||
};
|
||||
|
||||
writers.push(Some(writer));
|
||||
errors.push(None);
|
||||
} else {
|
||||
errors.push(Some(DiskError::DiskNotFound));
|
||||
writers.push(None);
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let writer_results = join_all(writer_futs).await;
|
||||
let mut writers = Vec::with_capacity(writer_results.len());
|
||||
let mut errors = Vec::with_capacity(writer_results.len());
|
||||
for (w, e) in writer_results {
|
||||
writers.push(w);
|
||||
errors.push(e);
|
||||
}
|
||||
|
||||
let nil_count = errors.iter().filter(|&e| e.is_none()).count();
|
||||
@@ -893,13 +900,28 @@ impl ObjectIO for SetDisks {
|
||||
HashReader::from_stream(Cursor::new(Vec::new()), 0, 0, None, None, false)?,
|
||||
);
|
||||
|
||||
let (reader, w_size) = 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 use_fast_path = is_inline_buffer && data.size() <= fi.erasure.block_size as i64;
|
||||
|
||||
let (reader, w_size) = if use_fast_path {
|
||||
match Arc::new(erasure)
|
||||
.encode_inline_small(stream, &mut writers, write_quorum)
|
||||
.await
|
||||
{
|
||||
Ok((r, w)) => (r, w),
|
||||
Err(e) => {
|
||||
error!("encode_inline_small err {:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}; // TODO: delete temporary directory on error
|
||||
} else {
|
||||
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);
|
||||
// if let Err(err) = close_bitrot_writers(&mut writers).await {
|
||||
|
||||
Reference in New Issue
Block a user