mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 05:06:28 +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:
@@ -161,33 +161,39 @@ pub async fn make_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::create_dir_all(path.as_ref()).await
|
||||
}
|
||||
|
||||
fn is_dir_error(e: &io::Error) -> bool {
|
||||
e.raw_os_error() == Some(libc::EISDIR)
|
||||
|| e.kind() == io::ErrorKind::IsADirectory
|
||||
// macOS: remove_file on a directory returns EPERM
|
||||
|| (cfg!(target_os = "macos") && e.raw_os_error() == Some(libc::EPERM))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn remove(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let meta = fs::metadata(path.as_ref()).await?;
|
||||
if meta.is_dir() {
|
||||
fs::remove_dir(path.as_ref()).await
|
||||
} else {
|
||||
fs::remove_file(path.as_ref()).await
|
||||
// Try remove_file first; fall back to remove_dir if it's a directory
|
||||
match fs::remove_file(path.as_ref()).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) if is_dir_error(&e) => fs::remove_dir(path.as_ref()).await,
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_all(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let meta = fs::metadata(path.as_ref()).await?;
|
||||
if meta.is_dir() {
|
||||
fs::remove_dir_all(path.as_ref()).await
|
||||
} else {
|
||||
fs::remove_file(path.as_ref()).await
|
||||
// Try remove_file first; fall back to remove_dir_all if it's a directory
|
||||
match fs::remove_file(path.as_ref()).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) if is_dir_error(&e) => fs::remove_dir_all(path.as_ref()).await,
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub fn remove_std(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let meta = std::fs::metadata(path)?;
|
||||
if meta.is_dir() {
|
||||
std::fs::remove_dir(path)
|
||||
} else {
|
||||
std::fs::remove_file(path)
|
||||
// Try remove_file first; fall back to remove_dir if it's a directory
|
||||
match std::fs::remove_file(path.as_ref()) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) if is_dir_error(&e) => std::fs::remove_dir(path.as_ref()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user