mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-16 18:08:21 +00:00
perf(ecstore): reduce inline PUT commit overhead (#6033)
* perf(metrics): attribute PUT stage costs Co-Authored-By: heihutu <heihutu@gmail.com> * perf(ecstore): move PUT metadata during shuffle Co-Authored-By: heihutu <heihutu@gmail.com> * perf(s3): reuse PUT object lock state Co-Authored-By: heihutu <heihutu@gmail.com> * perf(ecstore): trim PUT metadata fanout clones Build per-disk PUT metadata only for committed writer slots, move the response metadata out of the fanout vector, and preserve fresh FileInfo shuffle semantics. Co-Authored-By: heihutu <heihutu@gmail.com> * perf(metrics): make PUT stage attribution opt-in Co-Authored-By: heihutu <heihutu@gmail.com> * perf(ecstore): commit inline PUT shards directly Co-Authored-By: heihutu <heihutu@gmail.com> * perf(ecstore): streamline rename staging cleanup Use the directory-specific removal operation for rename_data staging parents. This avoids a guaranteed failed file-removal probe on Unix-like hosts and lets Windows remove the empty directory directly while preserving best-effort non-empty handling. Co-Authored-By: heihutu <heihutu@gmail.com> * test(ecstore): cover inline PUT rename failures Cache the detailed stage metrics gate once per PUT and exercise exact-quorum and quorum-minus-one failures after inline shard encoding. Co-Authored-By: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -9174,7 +9174,7 @@ impl DiskAPI for LocalDisk {
|
||||
|
||||
if let Some(src_file_path_parent) = src_file_path.parent() {
|
||||
if src_volume != super::RUSTFS_META_MULTIPART_BUCKET {
|
||||
let _ = remove_std(src_file_path_parent);
|
||||
let _ = std::fs::remove_dir(src_file_path_parent);
|
||||
} else {
|
||||
let _ = self
|
||||
.delete_file(&dst_volume_dir, &src_file_path_parent.to_path_buf(), true, false)
|
||||
@@ -9499,7 +9499,7 @@ impl DiskAPI for LocalDisk {
|
||||
if let Some(ref cleanup) = cleanup_path {
|
||||
let _ = self.delete_file(&dst_volume_dir, cleanup, true, false).await;
|
||||
} else if let Some(parent) = src_file_path.parent() {
|
||||
let _ = remove_std(parent);
|
||||
let _ = std::fs::remove_dir(parent);
|
||||
}
|
||||
|
||||
// Heal reuses a version's `data_dir` and lands the rebuilt shard on
|
||||
@@ -12439,6 +12439,10 @@ mod test {
|
||||
.join(RUSTFS_META_TMP_BUCKET)
|
||||
.join(tmp_object)
|
||||
.join(new_data_dir.to_string());
|
||||
let tmp_parent = tmp_data_dir
|
||||
.parent()
|
||||
.expect("tmp data dir should have a parent")
|
||||
.to_path_buf();
|
||||
fs::create_dir_all(&tmp_data_dir)
|
||||
.await
|
||||
.expect("new tmp data dir should be created");
|
||||
@@ -12450,6 +12454,10 @@ mod test {
|
||||
disk.rename_data(RUSTFS_META_TMP_BUCKET, tmp_object, new_fi, bucket, object)
|
||||
.await
|
||||
.expect("rename_data should commit");
|
||||
assert!(
|
||||
!tmp_parent.exists(),
|
||||
"successful non-inline commit should remove the empty staging parent"
|
||||
);
|
||||
|
||||
// The tmp xl.meta write point uses SyncMode::FileOnly: its parent dir
|
||||
// ({tmp}/{tmp_object}) must not be fsynced.
|
||||
@@ -12654,6 +12662,9 @@ mod test {
|
||||
let tmp_object = "tmp-new-inline";
|
||||
ensure_test_volume(&disk, bucket).await;
|
||||
ensure_test_volume(&disk, RUSTFS_META_TMP_BUCKET).await;
|
||||
let tmp_parent = disk
|
||||
.get_object_path(RUSTFS_META_TMP_BUCKET, tmp_object)
|
||||
.expect("tmp parent should resolve");
|
||||
|
||||
let _mode = durability_mode_override::set(DurabilityMode::Strict);
|
||||
let version_id = Uuid::parse_str("99999999-9999-9999-9999-999999999999").expect("version id should parse");
|
||||
@@ -12662,6 +12673,7 @@ mod test {
|
||||
disk.rename_data(RUSTFS_META_TMP_BUCKET, tmp_object, new_fi, bucket, object)
|
||||
.await
|
||||
.expect("inline rename_data should commit the new object");
|
||||
assert!(!tmp_parent.exists(), "successful inline commit should remove the empty staging parent");
|
||||
|
||||
let bucket_dir = disk.get_bucket_path(bucket).expect("bucket path should resolve");
|
||||
let prefix_dir = disk.get_object_path(bucket, "prefix").expect("prefix path should resolve");
|
||||
@@ -12685,6 +12697,34 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rename_data_inline_preserves_non_empty_staging_parent() {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let dir = tempdir().expect("temp dir should be created");
|
||||
let endpoint = Endpoint::try_from(dir.path().to_str().expect("temp dir should be utf8")).expect("endpoint should parse");
|
||||
let disk = LocalDisk::new(&endpoint, false).await.expect("local disk should be created");
|
||||
let bucket = "inline-staging-sentinel-bucket";
|
||||
let object = "inline-object";
|
||||
let tmp_object = "inline-stage-with-sentinel";
|
||||
ensure_test_volume(&disk, bucket).await;
|
||||
ensure_test_volume(&disk, RUSTFS_META_TMP_BUCKET).await;
|
||||
|
||||
let tmp_parent = disk
|
||||
.get_object_path(RUSTFS_META_TMP_BUCKET, tmp_object)
|
||||
.expect("tmp parent should resolve");
|
||||
fs::create_dir_all(&tmp_parent).await.expect("tmp parent should be created");
|
||||
let sentinel = tmp_parent.join("sentinel");
|
||||
fs::write(&sentinel, b"keep").await.expect("sentinel should be written");
|
||||
|
||||
let fi = test_file_info(object, Uuid::new_v4(), None, Some(Bytes::from_static(b"inline-payload")));
|
||||
disk.rename_data(RUSTFS_META_TMP_BUCKET, tmp_object, fi, bucket, object)
|
||||
.await
|
||||
.expect("non-empty staging cleanup must not negate the committed object");
|
||||
|
||||
assert_eq!(fs::read(&sentinel).await.expect("sentinel should remain"), b"keep");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[allow(clippy::await_holding_lock)]
|
||||
@@ -12859,7 +12899,10 @@ mod test {
|
||||
.expect("non-inline rename_data should commit");
|
||||
|
||||
assert!(!replacement_dir.exists(), "the destination object directory must not be replaced");
|
||||
assert!(staging_parent.exists(), "the guarded staging parent must retain its identity");
|
||||
assert!(
|
||||
!staging_parent.exists(),
|
||||
"successful commit should remove the empty staging parent after releasing its guard"
|
||||
);
|
||||
assert!(
|
||||
!replacement_staging_parent.exists(),
|
||||
"the staging parent must not be replaced between data and metadata publication"
|
||||
|
||||
Reference in New Issue
Block a user