fix(scanner): bootstrap pristine usage baseline (#6471)

* fix(ecstore): fence pool metadata replica updates

* fix(ecstore): block decommission on unsafe pool metadata

* fix(ecstore): block writes after pool metadata save errors

* fix(ecstore): latch pool metadata writes before await

* fix(scanner): bootstrap pristine usage baseline
This commit is contained in:
Zhengchao An
2026-08-24 14:29:09 +08:00
committed by GitHub
parent fc98dbb654
commit 170a4c7640
7 changed files with 582 additions and 144 deletions
+3 -3
View File
@@ -33,7 +33,7 @@ use crate::bucket::{
use crate::cache_value::metacache_set::{ListPathRawOptions, list_path_raw};
use crate::config::com::{
CONFIG_PREFIX, delete_config, read_config_limited_preserve_empty, read_config_limited_preserve_empty_with_metadata,
read_config_no_lock_preserve_empty_with_metadata, read_config_preserve_empty, save_config, save_config_with_opts,
read_config_no_lock_preserve_empty_with_metadata, read_config_preserve_empty, save_config_with_opts,
save_config_with_opts_quiet,
};
use crate::data_movement;
@@ -4848,7 +4848,7 @@ impl ECStore {
let mut pool_meta = self.pool_meta.write().await;
record_decommission_unresolved_entry(&mut pool_meta, idx, generation, entry)?;
}
self.save_current_pool_meta()
self.save_current_pool_meta(&[idx])
.await
.map_err(|err| Error::other(format!("decommission unresolved entry ledger save failed: {err}")))
}
@@ -9038,7 +9038,7 @@ impl ECStore {
self.run_guarded_decommission_side_effect(rx, &operation_gate, || {
self.check_after_decommission_unfenced(idx, generation)
})
.await
.await
}
async fn check_after_decommission_unfenced(
+32 -1
View File
@@ -743,7 +743,12 @@ where
let backup_seed = load_data_usage_for_bucket_removal(store, DATA_USAGE_OBJ_NAME_PATH.as_str())
.await?
.map(|(data_usage_info, _)| data_usage_info)
.or_else(|| primary_seed.clone());
.filter(|data_usage_info| !data_usage_info.usage_snapshot_bootstrap_pending)
.or_else(|| {
primary_seed
.clone()
.filter(|data_usage_info| !data_usage_info.usage_snapshot_bootstrap_pending)
});
remove_bucket_usage_from_object_with_retries_and_publication(
store,
DATA_USAGE_OBJ_BACKUP_PATH.as_str(),
@@ -4646,6 +4651,32 @@ mod tests {
assert!(state.backup_object.is_none());
}
#[tokio::test]
async fn remove_bucket_usage_does_not_seed_backup_from_pristine_bootstrap_marker() {
let marker = DataUsageInfo {
last_update: Some(SystemTime::now()),
usage_snapshot_converged: Some(false),
usage_snapshot_bootstrap_pending: true,
..Default::default()
};
let store = Arc::new(UsageCasStore {
state: Mutex::new(UsageCasState {
object: Some((serde_json::to_vec(&marker).expect("bootstrap marker should encode"), 1)),
..Default::default()
}),
});
remove_bucket_usage_from_backend_with_store(store.as_ref(), "bucket-a")
.await
.expect("bucket removal should preserve the pending primary without creating a backup");
let state = store.state.lock().await;
assert!(state.backup_object.is_none());
let saved = serde_json::from_slice::<DataUsageInfo>(&state.object.as_ref().expect("pending primary should remain").0)
.expect("pending primary should decode");
assert!(saved.usage_snapshot_bootstrap_pending);
}
#[tokio::test]
async fn remove_bucket_usage_migrates_legacy_snapshot_without_hiding_other_buckets() {
let mut legacy = data_usage_info_for_test("bucket-a", 2, 84, SystemTime::now());