fix(scanner): account versioned delete markers in usage (#3904)

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
This commit is contained in:
Henry Guo
2026-06-26 18:34:21 +08:00
committed by GitHub
parent 0321e4c6ca
commit a8327f8901
11 changed files with 503 additions and 82 deletions
+18 -1
View File
@@ -172,6 +172,7 @@ impl SizeSummary {
pub fn actions_accounting(&mut self, oi: &ObjectInfo, size: i64, actual_size: i64) {
if oi.delete_marker {
self.delete_markers += 1;
return;
}
if oi.version_id.is_some_and(|v| !v.is_nil()) && size == actual_size {
@@ -180,7 +181,7 @@ impl SizeSummary {
self.total_size += if size > 0 { size as usize } else { 0 };
if oi.delete_marker || oi.transitioned_object.free_version {
if oi.transitioned_object.free_version {
return;
}
@@ -1065,6 +1066,22 @@ mod tests {
assert_eq!(summary1.versions, 15);
}
#[test]
fn size_summary_counts_delete_markers_separately_from_versions() {
let mut summary = SizeSummary::new();
let marker = ObjectInfo {
delete_marker: true,
version_id: Some(uuid::Uuid::new_v4()),
..Default::default()
};
summary.actions_accounting(&marker, 0, 0);
assert_eq!(summary.delete_markers, 1);
assert_eq!(summary.versions, 0);
assert_eq!(summary.total_size, 0);
}
#[test]
fn test_data_usage_entry_merge_sums_failed_objects() {
let mut left = DataUsageEntry {
+3 -3
View File
@@ -697,10 +697,10 @@ impl ScannerItem {
component = LOG_COMPONENT_SCANNER,
subsystem = LOG_SUBSYSTEM_LIFECYCLE,
bucket = %self.bucket,
state = "versioning_lookup_failed",
"Scanner lifecycle action skipped"
state = "versioning_lookup_failed_defaulting",
"Scanner lifecycle action falling back to default bucket versioning"
);
return;
Default::default()
}
};
+72
View File
@@ -1543,8 +1543,10 @@ mod tests {
use super::*;
use crate::scanner_folder::ScannerItem;
use crate::{DiskOption, Endpoint, new_disk, path2_bucket_object_with_base_path};
use rustfs_filemeta::FileInfo;
use serial_test::serial;
use temp_env::with_var;
use time::OffsetDateTime;
use uuid::Uuid;
fn bucket_info(name: &str) -> BucketInfo {
@@ -1770,6 +1772,76 @@ mod tests {
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
}
#[tokio::test]
async fn get_size_counts_delete_markers_separately_from_versions() {
let temp_dir = std::env::temp_dir().join(format!("rustfs-scanner-versioned-usage-{}", Uuid::new_v4()));
let bucket = "bucket";
let object = "object";
let object_dir = temp_dir.join(bucket).join(object);
let metadata_path = object_dir.join(STORAGE_FORMAT_FILE);
tokio::fs::create_dir_all(&object_dir)
.await
.expect("failed to create object directory");
let mut meta = FileMeta::new();
for (size, timestamp) in [(10, 10), (20, 20)] {
let mut fi = FileInfo::new(object, 1, 1);
fi.version_id = Some(Uuid::new_v4());
fi.mod_time = Some(OffsetDateTime::from_unix_timestamp(timestamp).expect("timestamp should be valid"));
fi.size = size;
meta.add_version(fi).expect("object version should be added");
}
let mut delete_marker = FileInfo::new(object, 1, 1);
delete_marker.version_id = Some(Uuid::new_v4());
delete_marker.mod_time = Some(OffsetDateTime::from_unix_timestamp(30).expect("timestamp should be valid"));
delete_marker.deleted = true;
meta.add_version(delete_marker).expect("delete marker should be added");
tokio::fs::write(&metadata_path, meta.marshal_msg().expect("metadata should marshal"))
.await
.expect("failed to write metadata");
let endpoint = Endpoint::try_from(temp_dir.to_string_lossy().as_ref()).expect("failed to create endpoint");
let disk = new_disk(
&endpoint,
&DiskOption {
cleanup: false,
health_check: false,
},
)
.await
.expect("failed to open local disk");
let relative_path = metadata_path.to_string_lossy().to_string();
let (_, scanner_path) = path2_bucket_object_with_base_path(temp_dir.to_string_lossy().as_ref(), relative_path.as_str());
let file_type = tokio::fs::metadata(&metadata_path)
.await
.expect("failed to stat metadata")
.file_type();
let item = ScannerItem {
path: scanner_path,
bucket: bucket.to_string(),
prefix: object.to_string(),
object_name: STORAGE_FORMAT_FILE.to_string(),
file_type,
lifecycle: None,
replication: None,
heal_enabled: false,
heal_bitrot: false,
debug: false,
};
let summary = disk.get_size(item).await.expect("scanner should read versioned metadata");
assert_eq!(summary.versions, 2);
assert_eq!(summary.delete_markers, 1);
assert_eq!(summary.total_size, 30);
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
}
#[test]
fn cache_root_entry_info_flattens_bucket_children() {
let mut cache = DataUsageCache {