mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 09:18:28 +00:00
fix(ecstore): group inline dst dir fsync (#6228)
Route strict inline rename_data dst-parent fsync through the default-off group-commit helper when enabled while preserving the namespace file-sync limited path by default. Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -9745,7 +9745,8 @@ impl DiskAPI for LocalDisk {
|
|||||||
{
|
{
|
||||||
let fsync_started = rustfs_io_metrics::put_stage_timer();
|
let fsync_started = rustfs_io_metrics::put_stage_timer();
|
||||||
if let Err(err) =
|
if let Err(err) =
|
||||||
os::fsync_dir_with_namespace_file_sync_limit(dst_parent, mutation_lease.clone(), admission).await
|
os::fsync_dst_dir_group_commit_or_namespace_file_sync_limit(dst_parent, mutation_lease.clone(), admission)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
rustfs_io_metrics::record_put_object_stage_duration_from(
|
rustfs_io_metrics::record_put_object_stage_duration_from(
|
||||||
rustfs_io_metrics::PUT_STAGE_SET_DISK_RENAME_DST_DIR_FSYNC,
|
rustfs_io_metrics::PUT_STAGE_SET_DISK_RENAME_DST_DIR_FSYNC,
|
||||||
@@ -13092,6 +13093,80 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial(dst_dir_fsync_group_commit)]
|
||||||
|
async fn rename_data_inline_uses_dst_dir_fsync_group_commit_when_enabled() {
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
let _group_commit = os::set_dst_dir_fsync_group_commit_for_test(true);
|
||||||
|
let _mode = durability_mode_override::set(DurabilityMode::Strict);
|
||||||
|
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 = "grouped-inline-dst-fsync-bucket";
|
||||||
|
let object = "dir/inline-object";
|
||||||
|
let tmp_object = "tmp-grouped-inline-dst-fsync";
|
||||||
|
ensure_test_volume(&disk, bucket).await;
|
||||||
|
ensure_test_volume(&disk, RUSTFS_META_TMP_BUCKET).await;
|
||||||
|
|
||||||
|
let version_id = Uuid::parse_str("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb").expect("version id should parse");
|
||||||
|
let new_fi = test_file_info(object, version_id, None, Some(Bytes::from_static(b"inline-payload")));
|
||||||
|
disk.rename_data(RUSTFS_META_TMP_BUCKET, tmp_object, new_fi, bucket, object)
|
||||||
|
.await
|
||||||
|
.expect("inline rename_data should commit");
|
||||||
|
|
||||||
|
let dst_meta_parent = disk
|
||||||
|
.get_object_path(bucket, &format!("{object}/{STORAGE_FORMAT_FILE}"))
|
||||||
|
.expect("dst meta path should resolve")
|
||||||
|
.parent()
|
||||||
|
.expect("dst meta should have a parent")
|
||||||
|
.to_path_buf();
|
||||||
|
assert_eq!(
|
||||||
|
os::fsync_dir_recorder::grouped_batch_sizes(&dst_meta_parent),
|
||||||
|
vec![1],
|
||||||
|
"enabled inline rename_data must route the dst parent fsync through the group commit coordinator"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!os::fsync_dir_recorder::was_limited(&dst_meta_parent),
|
||||||
|
"enabled grouped dst parent fsync must not also run the direct file-sync limited path"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial(dst_dir_fsync_group_commit)]
|
||||||
|
async fn rename_data_inline_dst_dir_fsync_group_commit_failure_rolls_back_fresh_put() {
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
let _group_commit = os::set_dst_dir_fsync_group_commit_for_test(true);
|
||||||
|
let _mode = durability_mode_override::set(DurabilityMode::Strict);
|
||||||
|
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 = "grouped-inline-dst-fsync-failure-bucket";
|
||||||
|
let object = "dir/inline-object";
|
||||||
|
let tmp_object = "tmp-grouped-inline-dst-fsync-failure";
|
||||||
|
ensure_test_volume(&disk, bucket).await;
|
||||||
|
ensure_test_volume(&disk, RUSTFS_META_TMP_BUCKET).await;
|
||||||
|
|
||||||
|
let dst_meta_parent = dir.path().join(bucket).join(object);
|
||||||
|
os::fsync_dir_recorder::set_grouped_failure(&dst_meta_parent, io::ErrorKind::PermissionDenied);
|
||||||
|
let version_id = Uuid::parse_str("cccccccc-cccc-cccc-cccc-cccccccccccc").expect("version id should parse");
|
||||||
|
let new_fi = test_file_info(object, version_id, None, Some(Bytes::from_static(b"inline-payload")));
|
||||||
|
let err = disk
|
||||||
|
.rename_data(RUSTFS_META_TMP_BUCKET, tmp_object, new_fi, bucket, object)
|
||||||
|
.await
|
||||||
|
.expect_err("grouped dst dir fsync failure must fail the fresh inline PUT");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(err, DiskError::Io(ref io_err) if io_err.kind() == io::ErrorKind::PermissionDenied),
|
||||||
|
"grouped dst dir fsync failure must propagate the injected permission error"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!dst_meta_parent.join(STORAGE_FORMAT_FILE).exists(),
|
||||||
|
"fresh inline PUT rollback must remove the committed xl.meta after grouped dst dir fsync failure"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Seed a first PUT of `object` (no prior version) through the non-inline
|
// Seed a first PUT of `object` (no prior version) through the non-inline
|
||||||
// rename_data path and return (disk, tempdir). The object dir and any prefix
|
// rename_data path and return (disk, tempdir). The object dir and any prefix
|
||||||
// dirs are created during the commit.
|
// dirs are created during the commit.
|
||||||
|
|||||||
@@ -676,6 +676,18 @@ pub(crate) async fn fsync_dst_dir_group_commit(dir: impl AsRef<Path>) -> io::Res
|
|||||||
fsync_dst_dir_group_commit_with_enabled(dir, dst_dir_fsync_group_commit_enabled()).await
|
fsync_dst_dir_group_commit_with_enabled(dir, dst_dir_fsync_group_commit_enabled()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn fsync_dst_dir_group_commit_or_namespace_file_sync_limit(
|
||||||
|
dir: impl AsRef<Path>,
|
||||||
|
lease: Arc<NamespaceMutationLease>,
|
||||||
|
admission: &FileSyncAdmission,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
if dst_dir_fsync_group_commit_enabled() {
|
||||||
|
fsync_dst_dir_group_commit_with_enabled(dir, true).await
|
||||||
|
} else {
|
||||||
|
fsync_dir_with_namespace_file_sync_limit(dir, lease, admission).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) async fn fsync_dst_dir_group_commit_for_test(dir: impl AsRef<Path>, enabled: bool) -> io::Result<()> {
|
pub(crate) async fn fsync_dst_dir_group_commit_for_test(dir: impl AsRef<Path>, enabled: bool) -> io::Result<()> {
|
||||||
fsync_dst_dir_group_commit_with_enabled(dir, enabled).await
|
fsync_dst_dir_group_commit_with_enabled(dir, enabled).await
|
||||||
|
|||||||
Reference in New Issue
Block a user