mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
fix(ecstore): reduce rename data version signatures (#4383)
This commit is contained in:
@@ -3103,6 +3103,18 @@ fn local_disk_scan_lock_key(bucket: &str, base_dir: &str, filter_prefix: Option<
|
||||
(bucket.to_owned(), prefix)
|
||||
}
|
||||
|
||||
fn rename_data_versions_signature(meta: &FileMeta) -> Option<Vec<u8>> {
|
||||
if meta.versions.len() > 10 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut signature = Vec::with_capacity(meta.versions.len() * 16);
|
||||
for version in meta.versions.iter() {
|
||||
signature.extend_from_slice(version.header.version_id.unwrap_or_default().as_bytes());
|
||||
}
|
||||
Some(signature)
|
||||
}
|
||||
|
||||
fn is_root_path(path: impl AsRef<Path>) -> bool {
|
||||
path.as_ref().components().count() == 1 && path.as_ref().has_root()
|
||||
}
|
||||
@@ -4121,6 +4133,7 @@ impl DiskAPI for LocalDisk {
|
||||
let _ = xlmeta.data.remove_two(version_id, *old_data_dir);
|
||||
}
|
||||
xlmeta.add_version(fi)?;
|
||||
let version_signature = rename_data_versions_signature(&xlmeta);
|
||||
let new_dst_buf = xlmeta.marshal_msg()?;
|
||||
|
||||
let src_file_parent = src_file_path.parent().unwrap_or(src_volume_dir.as_path());
|
||||
@@ -4249,7 +4262,7 @@ impl DiskAPI for LocalDisk {
|
||||
|
||||
Ok(RenameDataResp {
|
||||
old_data_dir: has_old_data_dir,
|
||||
sign: None,
|
||||
sign: version_signature,
|
||||
})
|
||||
} else {
|
||||
// Inline: merge read + parse + write + rename into single spawn_blocking
|
||||
@@ -4261,7 +4274,7 @@ impl DiskAPI for LocalDisk {
|
||||
None
|
||||
};
|
||||
|
||||
let (old_data_dir, _dst_buf) = tokio::task::spawn_blocking(move || {
|
||||
let (old_data_dir, version_signature) = tokio::task::spawn_blocking(move || {
|
||||
// Read existing xl.meta
|
||||
let has_dst_buf = match std::fs::read(&dst) {
|
||||
Ok(buf) => Some(Bytes::from(buf)),
|
||||
@@ -4283,6 +4296,7 @@ impl DiskAPI for LocalDisk {
|
||||
let _ = xlmeta.data.remove_two(version_id, *d);
|
||||
}
|
||||
xlmeta.add_version(fi)?;
|
||||
let version_signature = rename_data_versions_signature(&xlmeta);
|
||||
let new_buf = xlmeta.marshal_msg()?;
|
||||
|
||||
// Write new xl.meta + rename
|
||||
@@ -4347,7 +4361,7 @@ impl DiskAPI for LocalDisk {
|
||||
os::fsync_dir_std(dst_parent)?;
|
||||
}
|
||||
|
||||
Ok::<(Option<uuid::Uuid>, Option<Bytes>), std::io::Error>((old_data_dir, has_dst_buf))
|
||||
Ok::<(Option<uuid::Uuid>, Option<Vec<u8>>), std::io::Error>((old_data_dir, version_signature))
|
||||
})
|
||||
.await
|
||||
.map_err(DiskError::from)??;
|
||||
@@ -4361,7 +4375,7 @@ impl DiskAPI for LocalDisk {
|
||||
|
||||
Ok(RenameDataResp {
|
||||
old_data_dir,
|
||||
sign: None,
|
||||
sign: version_signature,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5079,6 +5093,7 @@ mod test {
|
||||
.expect("rename_data should commit");
|
||||
|
||||
assert_eq!(resp.old_data_dir, Some(old_data_dir));
|
||||
assert_eq!(resp.sign, Some(version_id.as_bytes().to_vec()));
|
||||
assert!(
|
||||
dst_object_dir
|
||||
.join(old_data_dir.to_string())
|
||||
@@ -5286,6 +5301,7 @@ mod test {
|
||||
.expect("inline rename_data should commit");
|
||||
|
||||
assert_eq!(resp.old_data_dir, Some(old_data_dir));
|
||||
assert_eq!(resp.sign, Some(version_id.as_bytes().to_vec()));
|
||||
let backup_path = dst_object_dir.join(old_data_dir.to_string()).join(STORAGE_FORMAT_FILE_BACKUP);
|
||||
assert!(backup_path.exists());
|
||||
// The rollback backup must contain the previous metadata bytes verbatim so
|
||||
|
||||
@@ -2406,6 +2406,13 @@ enum OrphanDirScan {
|
||||
Missing,
|
||||
}
|
||||
|
||||
fn rename_data_versions_key(versions: &[u8]) -> Option<[u8; 8]> {
|
||||
let prefix = versions.get(..8)?;
|
||||
let mut key = [0; 8];
|
||||
key.copy_from_slice(prefix);
|
||||
Some(key)
|
||||
}
|
||||
|
||||
impl SetDisks {
|
||||
pub(in crate::set_disk) fn default_read_quorum(&self) -> usize {
|
||||
self.set_drive_count - self.default_parity_count
|
||||
@@ -2577,10 +2584,8 @@ impl SetDisks {
|
||||
return Err(ret_err);
|
||||
}
|
||||
|
||||
let versions = None;
|
||||
// TODO: reduceCommonVersions
|
||||
|
||||
let data_dir = Self::reduce_common_data_dir(&data_dirs, write_quorum);
|
||||
let versions = Self::select_rename_data_versions(&disk_versions, &errs, write_quorum);
|
||||
let online_disks = Self::eval_disks(disks, &errs);
|
||||
let cleanup_disks = if let Some(data_dir) = data_dir {
|
||||
disks
|
||||
@@ -2602,6 +2607,63 @@ impl SetDisks {
|
||||
Ok((online_disks, versions, data_dir, cleanup_disks))
|
||||
}
|
||||
|
||||
pub(in crate::set_disk) fn reduce_common_versions(disk_versions: &[Option<Vec<u8>>], write_quorum: usize) -> Option<Vec<u8>> {
|
||||
let mut versions_count = HashMap::new();
|
||||
|
||||
for versions in disk_versions.iter().flatten() {
|
||||
if let Some(key) = rename_data_versions_key(versions) {
|
||||
*versions_count.entry(key).or_insert(0usize) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let (common_versions, max_count) = versions_count
|
||||
.into_iter()
|
||||
.max_by_key(|(_, count)| *count)
|
||||
.unwrap_or(([0; 8], 0));
|
||||
|
||||
if max_count < write_quorum {
|
||||
return None;
|
||||
}
|
||||
|
||||
disk_versions
|
||||
.iter()
|
||||
.flatten()
|
||||
.find(|versions| rename_data_versions_key(versions).is_some_and(|key| key == common_versions))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(in crate::set_disk) fn select_rename_data_versions(
|
||||
disk_versions: &[Option<Vec<u8>>],
|
||||
errs: &[Option<DiskError>],
|
||||
write_quorum: usize,
|
||||
) -> Option<Vec<u8>> {
|
||||
let mut versions = Self::reduce_common_versions(disk_versions, write_quorum);
|
||||
for (dversions, err) in disk_versions.iter().zip(errs.iter()) {
|
||||
if err.is_some() {
|
||||
continue;
|
||||
}
|
||||
let Some(dversions) = dversions.as_ref().filter(|versions| !versions.is_empty()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match versions.as_ref() {
|
||||
Some(current_versions) if dversions != current_versions => {
|
||||
if dversions.len() > current_versions.len() {
|
||||
versions = Some(dversions.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
versions = Some(dversions.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
versions
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[tracing::instrument(level = "debug", skip(self, disks))]
|
||||
pub(in crate::set_disk) async fn commit_rename_data_dir(
|
||||
|
||||
@@ -5958,6 +5958,41 @@ mod tests {
|
||||
assert_eq!(result, Some(uuid1)); // Ignore None votes; uuid1 should still meet quorum
|
||||
}
|
||||
|
||||
fn rename_versions_signature(first_key: u8, version_count: usize) -> Vec<u8> {
|
||||
let mut signature = vec![0; version_count * 16];
|
||||
signature[7] = first_key;
|
||||
signature
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_common_versions_requires_write_quorum() {
|
||||
let common = rename_versions_signature(1, 1);
|
||||
let other = rename_versions_signature(2, 1);
|
||||
|
||||
let disk_versions = vec![Some(common.clone()), Some(common.clone()), Some(other)];
|
||||
let result = SetDisks::reduce_common_versions(&disk_versions, 2);
|
||||
assert_eq!(result, Some(common));
|
||||
|
||||
let split_versions = vec![
|
||||
Some(rename_versions_signature(1, 1)),
|
||||
Some(rename_versions_signature(2, 1)),
|
||||
None,
|
||||
];
|
||||
let result = SetDisks::reduce_common_versions(&split_versions, 2);
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_rename_data_versions_keeps_longer_success_disparity() {
|
||||
let common = rename_versions_signature(1, 1);
|
||||
let longer = rename_versions_signature(2, 2);
|
||||
let disk_versions = vec![Some(common.clone()), Some(common), Some(longer.clone())];
|
||||
let errs = vec![None, None, None];
|
||||
|
||||
let result = SetDisks::select_rename_data_versions(&disk_versions, &errs, 2);
|
||||
assert_eq!(result, Some(longer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_object_quorum_from_meta_returns_not_found_when_all_metadata_is_missing() {
|
||||
let errs = vec![
|
||||
|
||||
Reference in New Issue
Block a user