fix: deduplicate disks in capacity calculation to prevent inflation (#1656)

This commit is contained in:
houseme
2026-01-30 00:03:21 +08:00
committed by GitHub
parent 022e3dfc21
commit 2ee81496b0
17 changed files with 631 additions and 275 deletions
-3
View File
@@ -96,9 +96,6 @@ serde_urlencoded = { workspace = true }
rustls = { workspace = true }
subtle = { workspace = true }
rustls-pemfile = { workspace = true }
# Time and Date
chrono = { workspace = true }
jiff = { workspace = true }
time = { workspace = true, features = ["parsing", "formatting", "serde"] }
+78 -34
View File
@@ -34,9 +34,7 @@ use rustfs_ecstore::bucket::metadata_sys;
use rustfs_ecstore::bucket::target::BucketTarget;
use rustfs_ecstore::bucket::utils::is_valid_object_prefix;
use rustfs_ecstore::bucket::versioning_sys::BucketVersioningSys;
use rustfs_ecstore::data_usage::{
aggregate_local_snapshots, compute_bucket_usage, load_data_usage_from_backend, store_data_usage_in_backend,
};
use rustfs_ecstore::data_usage::{compute_bucket_usage, load_data_usage_from_backend, store_data_usage_in_backend};
use rustfs_ecstore::error::StorageError;
use rustfs_ecstore::global::global_rustfs_port;
use rustfs_ecstore::metrics_realtime::{CollectMetricsOpts, MetricType, collect_local_metrics};
@@ -517,30 +515,10 @@ impl Operation for DataUsageInfoHandler {
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
};
let (disk_statuses, mut info) = match aggregate_local_snapshots(store.clone()).await {
Ok((statuses, usage)) => (statuses, usage),
Err(err) => {
warn!("aggregate_local_snapshots failed: {:?}", err);
(
Vec::new(),
load_data_usage_from_backend(store.clone()).await.map_err(|e| {
error!("load_data_usage_from_backend failed {:?}", e);
s3_error!(InternalError, "load_data_usage_from_backend failed")
})?,
)
}
};
let snapshots_available = disk_statuses.iter().any(|status| status.snapshot_exists);
if !snapshots_available {
if let Ok(fallback) = load_data_usage_from_backend(store.clone()).await {
let mut fallback_info = fallback;
fallback_info.disk_usage_status = disk_statuses.clone();
info = fallback_info;
}
} else {
info.disk_usage_status = disk_statuses.clone();
}
let mut info = load_data_usage_from_backend(store.clone()).await.map_err(|e| {
error!("load_data_usage_from_backend failed {:?}", e);
s3_error!(InternalError, "load_data_usage_from_backend failed")
})?;
let last_update_age = info.last_update.and_then(|ts| ts.elapsed().ok());
let data_missing = info.objects_total_count == 0 && info.buckets_count == 0;
@@ -576,16 +554,82 @@ impl Operation for DataUsageInfoHandler {
});
}
info.disk_usage_status = disk_statuses;
// Set capacity information
let sinfo = store.storage_info().await;
info.total_capacity = get_total_usable_capacity(&sinfo.disks, &sinfo) as u64;
info.total_free_capacity = get_total_usable_capacity_free(&sinfo.disks, &sinfo) as u64;
if info.total_capacity > info.total_free_capacity {
info.total_used_capacity = info.total_capacity - info.total_free_capacity;
// 🔧 Use the fixed capacity calculation function (built-in deduplication)
let raw_total = get_total_usable_capacity(&sinfo.disks, &sinfo);
let raw_free = get_total_usable_capacity_free(&sinfo.disks, &sinfo);
// 🔧 Add a plausibility check (extra layer of protection)
const MAX_REASONABLE_CAPACITY: u64 = 100_000 * 1024 * 1024 * 1024 * 1024; // 100 PiB
const MIN_REASONABLE_CAPACITY: u64 = 1024 * 1024 * 1024; // 1 GiB
let total_u64 = raw_total as u64;
let free_u64 = raw_free as u64;
// Detect outliers
if total_u64 > MAX_REASONABLE_CAPACITY {
error!(
"Abnormal total capacity detected: {} bytes ({:.2} TiB), capping to physical capacity",
total_u64,
total_u64 as f64 / (1024.0_f64.powi(4))
);
// Use the number of disks and the average capacity to estimate reasonable values
let disk_count = sinfo.disks.len();
if disk_count > 0 {
// Calculate the actual number of disks after deduplication (via endpoint + drive_path)
use std::collections::HashSet;
let unique_disks: HashSet<String> = sinfo
.disks
.iter()
.map(|d| format!("{}|{}", d.endpoint, d.drive_path))
.collect();
let actual_disk_count = unique_disks.len();
// Use the capacity of the first disk as a reference
if let Some(first_disk) = sinfo.disks.first() {
info.total_capacity = first_disk.total_space * actual_disk_count as u64;
info.total_free_capacity = first_disk.available_space * actual_disk_count as u64;
info!(
"Applied capacity correction: {} unique disks, capacity per disk: {} bytes",
actual_disk_count, first_disk.total_space
);
} else {
info.total_capacity = 0;
info.total_free_capacity = 0;
}
} else {
info.total_capacity = 0;
info.total_free_capacity = 0;
}
} else if total_u64 < MIN_REASONABLE_CAPACITY && total_u64 > 0 {
warn!(
"Unusually small total capacity: {} bytes ({:.2} GiB)",
total_u64,
total_u64 as f64 / (1024.0_f64.powi(3))
);
info.total_capacity = total_u64;
info.total_free_capacity = free_u64;
} else {
// Normal
info.total_capacity = total_u64;
info.total_free_capacity = free_u64;
}
// Calculate the used capacity
info.total_used_capacity = info.total_capacity.saturating_sub(info.total_free_capacity);
// Record the final statistical results
debug!(
"Capacity statistics: total={:.2} TiB, free={:.2} TiB, used={:.2} TiB",
info.total_capacity as f64 / (1024.0_f64.powi(4)),
info.total_free_capacity as f64 / (1024.0_f64.powi(4)),
info.total_used_capacity as f64 / (1024.0_f64.powi(4))
);
let data = serde_json::to_vec(&info)
.map_err(|_e| S3Error::with_message(S3ErrorCode::InternalError, "parse DataUsageInfo failed"))?;