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
+6 -1
View File
@@ -19,7 +19,10 @@ use super::storage_api::admin_usecase::admin::get_server_info;
use super::storage_api::admin_usecase::capacity::{
PoolDecommissionInfo, PoolStatus, RebalStatus, get_total_usable_capacity, get_total_usable_capacity_free,
};
use super::storage_api::admin_usecase::data_usage::{apply_bucket_usage_memory_overlay, load_data_usage_from_backend};
use super::storage_api::admin_usecase::data_usage::{
apply_bucket_usage_memory_overlay, load_data_usage_from_backend, refresh_versioned_bucket_usage_from_object_layer,
replace_bucket_usage_memory_from_info,
};
use super::storage_api::admin_usecase::{ECStore, EndpointServerPools};
use crate::app::runtime_sources::{
AppContext, current_app_context, resolve_endpoints_handle, resolve_object_store_handle_for_context,
@@ -242,6 +245,8 @@ impl DefaultAdminUsecase {
error!("load_data_usage_from_backend failed {:?}", e);
Self::app_error(S3ErrorCode::InternalError, "load_data_usage_from_backend failed")
})?;
refresh_versioned_bucket_usage_from_object_layer(store.clone(), &mut info).await;
replace_bucket_usage_memory_from_info(&info).await;
apply_bucket_usage_memory_overlay(&mut info).await;
let storage_info = StorageAdminApi::storage_info(store.as_ref()).await;
+10 -2
View File
@@ -27,7 +27,9 @@ use super::storage_api::multipart_usecase::bucket::{
versioning_sys::BucketVersioningSys,
};
use super::storage_api::multipart_usecase::compression::is_disk_compressible;
use super::storage_api::multipart_usecase::data_usage::record_bucket_object_write_memory;
use super::storage_api::multipart_usecase::data_usage::{
record_bucket_object_version_write_memory, record_bucket_object_write_memory,
};
use super::storage_api::multipart_usecase::error::{StorageError, is_err_object_not_found, is_err_version_not_found};
use super::storage_api::multipart_usecase::helper::OperationHelper;
#[cfg(test)]
@@ -489,7 +491,13 @@ impl DefaultMultipartUsecase {
));
}
// Update quota tracking after successful multipart upload
record_bucket_object_write_memory(&bucket, previous_current_size, obj_info.size.max(0) as u64).await;
let mpu_versioned = BucketVersioningSys::prefix_enabled(&bucket, &key).await;
if mpu_versioned {
record_bucket_object_version_write_memory(&bucket, previous_current_size, obj_info.size.max(0) as u64)
.await;
} else {
record_bucket_object_write_memory(&bucket, previous_current_size, obj_info.size.max(0) as u64).await;
}
}
Err(e) => {
warn!("Quota check failed for bucket {}: {}, allowing operation", bucket, e);
+38 -21
View File
@@ -50,7 +50,10 @@ use super::storage_api::object_usecase::concurrency::{
self, ConcurrencyManager, GetObjectGuard, PutObjectGuard, get_concurrency_aware_buffer_size, get_concurrency_manager,
get_put_concurrency_aware_buffer_size,
};
use super::storage_api::object_usecase::data_usage::{record_bucket_object_delete_memory, record_bucket_object_write_memory};
use super::storage_api::object_usecase::data_usage::{
record_bucket_delete_marker_memory, record_bucket_object_delete_memory, record_bucket_object_version_write_memory,
record_bucket_object_write_memory,
};
use super::storage_api::object_usecase::deadlock_detector;
use super::storage_api::object_usecase::ecfs::FS;
use super::storage_api::object_usecase::error::{
@@ -2778,8 +2781,13 @@ impl DefaultObjectUsecase {
maybe_enqueue_transition_immediate(&obj_info, LcEventSrc::S3PutObject).await;
let put_versioned = BucketVersioningSys::prefix_enabled(&bucket, &key).await;
// Fast in-memory update for immediate quota and admin usage consistency
record_bucket_object_write_memory(&bucket, previous_current_size, obj_info.size.max(0) as u64).await;
if put_versioned {
record_bucket_object_version_write_memory(&bucket, previous_current_size, obj_info.size.max(0) as u64).await;
} else {
record_bucket_object_write_memory(&bucket, previous_current_size, obj_info.size.max(0) as u64).await;
}
let raw_version = obj_info.version_id.map(|v| v.to_string());
@@ -2788,11 +2796,7 @@ impl DefaultObjectUsecase {
helper = helper.version_id(version_id.clone());
}
let put_version = if BucketVersioningSys::prefix_enabled(&bucket, &key).await {
raw_version
} else {
None
};
let put_version = if put_versioned { raw_version } else { None };
let e_tag = obj_info.etag.clone().map(|etag| to_s3s_etag(&etag));
@@ -3769,17 +3773,18 @@ impl DefaultObjectUsecase {
maybe_enqueue_transition_immediate(&oi, LcEventSrc::S3CopyObject).await;
let dest_versioned = BucketVersioningSys::prefix_enabled(&bucket, &key).await;
// Update quota tracking after successful copy
if has_bucket_metadata {
record_bucket_object_write_memory(&bucket, previous_current_size, oi.size.max(0) as u64).await;
if dest_versioned {
record_bucket_object_version_write_memory(&bucket, previous_current_size, oi.size.max(0) as u64).await;
} else {
record_bucket_object_write_memory(&bucket, previous_current_size, oi.size.max(0) as u64).await;
}
}
let raw_dest_version = oi.version_id.map(|v| v.to_string());
let dest_version = if BucketVersioningSys::prefix_enabled(&bucket, &key).await {
raw_dest_version
} else {
None
};
let dest_version = if dest_versioned { raw_dest_version } else { None };
// warn!("copy_object oi {:?}", &oi);
let object_info = oi.clone();
@@ -3994,6 +3999,7 @@ impl DefaultObjectUsecase {
&bucket,
object_to_delete.clone(),
ObjectOptions {
versioned: version_cfg.enabled(),
version_suspended: version_cfg.suspended(),
..Default::default()
},
@@ -4047,13 +4053,20 @@ impl DefaultObjectUsecase {
"failed to persist transitioned object cleanup journal"
);
}
let size = object_sizes[i].max(0) as u64;
record_bucket_object_delete_memory(
&bucket,
size,
existing_object_infos[i].is_some() && object_to_delete[i].version_id.is_none(),
)
.await;
let creates_delete_marker = object_to_delete[i].version_id.is_none()
&& version_cfg.prefix_enabled(object_to_delete[i].object_name.as_str())
&& !version_cfg.suspended();
if creates_delete_marker {
record_bucket_delete_marker_memory(&bucket).await;
} else {
let size = object_sizes[i].max(0) as u64;
record_bucket_object_delete_memory(
&bucket,
size,
existing_object_infos[i].is_some() && object_to_delete[i].version_id.is_none(),
)
.await;
}
continue;
}
@@ -4285,7 +4298,11 @@ impl DefaultObjectUsecase {
}
// Fast in-memory update for immediate quota and admin usage consistency
record_bucket_object_delete_memory(&bucket, obj_info.size.max(0) as u64, opts.version_id.is_none()).await;
if delete_creates_delete_marker(&opts) {
record_bucket_delete_marker_memory(&bucket).await;
} else {
record_bucket_object_delete_memory(&bucket, obj_info.size.max(0) as u64, opts.version_id.is_none()).await;
}
if obj_info.name.is_empty() {
if replicate_force_delete {
+29
View File
@@ -55,11 +55,35 @@ pub(crate) mod data_usage {
crate::storage::ecstore_data_usage::load_data_usage_from_backend(store).await
}
pub(crate) async fn refresh_versioned_bucket_usage_from_object_layer(
store: Arc<super::ECStore>,
data_usage_info: &mut rustfs_data_usage::DataUsageInfo,
) {
crate::storage::ecstore_data_usage::refresh_versioned_bucket_usage_from_object_layer(store, data_usage_info).await;
}
pub(crate) async fn replace_bucket_usage_memory_from_info(data_usage_info: &rustfs_data_usage::DataUsageInfo) {
crate::storage::ecstore_data_usage::replace_bucket_usage_memory_from_info(data_usage_info).await;
}
pub(crate) async fn record_bucket_object_delete_memory(bucket: &str, deleted_size: u64, removed_current_object: bool) {
crate::storage::ecstore_data_usage::record_bucket_object_delete_memory(bucket, deleted_size, removed_current_object)
.await;
}
pub(crate) async fn record_bucket_delete_marker_memory(bucket: &str) {
crate::storage::ecstore_data_usage::record_bucket_delete_marker_memory(bucket).await;
}
pub(crate) async fn record_bucket_object_version_write_memory(
bucket: &str,
previous_current_size: Option<u64>,
new_size: u64,
) {
crate::storage::ecstore_data_usage::record_bucket_object_version_write_memory(bucket, previous_current_size, new_size)
.await;
}
pub(crate) async fn record_bucket_object_write_memory(bucket: &str, previous_current_size: Option<u64>, new_size: u64) {
crate::storage::ecstore_data_usage::record_bucket_object_write_memory(bucket, previous_current_size, new_size).await;
}
@@ -214,11 +238,16 @@ pub(crate) mod bucket {
}
pub(crate) trait VersioningConfigExt {
fn enabled(&self) -> bool;
fn prefix_enabled(&self, prefix: &str) -> bool;
fn suspended(&self) -> bool;
}
impl VersioningConfigExt for s3s::dto::VersioningConfiguration {
fn enabled(&self) -> bool {
<s3s::dto::VersioningConfiguration as crate::storage::ecstore_bucket::versioning::VersioningApi>::enabled(self)
}
fn prefix_enabled(&self, prefix: &str) -> bool {
<s3s::dto::VersioningConfiguration as crate::storage::ecstore_bucket::versioning::VersioningApi>::prefix_enabled(
self, prefix,
+4 -2
View File
@@ -81,8 +81,10 @@ pub(crate) mod ecstore_config {
pub(crate) mod ecstore_data_usage {
pub(crate) use rustfs_ecstore::api::data_usage::{
apply_bucket_usage_memory_overlay, load_data_usage_from_backend, record_bucket_object_delete_memory,
record_bucket_object_write_memory, remove_bucket_usage_from_backend,
apply_bucket_usage_memory_overlay, load_data_usage_from_backend, record_bucket_delete_marker_memory,
record_bucket_object_delete_memory, record_bucket_object_version_write_memory, record_bucket_object_write_memory,
refresh_versioned_bucket_usage_from_object_layer, remove_bucket_usage_from_backend,
replace_bucket_usage_memory_from_info,
};
}