fix(scanner): fence legacy usage floor fallback

Co-Authored-By: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-30 00:12:18 +08:00
parent 2004001d47
commit 923f86f2aa
2 changed files with 38 additions and 2 deletions

View File

@@ -1347,6 +1347,7 @@ pub(super) async fn persisted_usage_floor_for_startup(
};
for primary_path in [DATA_USAGE_OBJ_NAME_PATH.as_str(), LEGACY_DATA_USAGE_OBJ_NAME_PATH.as_str()] {
let backup_path = format!("{primary_path}.bkp");
let is_v2_path = primary_path == DATA_USAGE_OBJ_NAME_PATH.as_str();
let primary_epoch = match read_config_with_revision(storeapi.clone(), primary_path).await {
Ok((Some(data), _)) => {
let usage = serde_json::from_slice::<DataUsageInfo>(&data).map_err(|err| {
@@ -1365,8 +1366,15 @@ pub(super) async fn persisted_usage_floor_for_startup(
None
} else {
let epoch = usage.scanner_epoch.unwrap_or_default();
update_floor(&mut floor, &usage, primary_path)?;
Some(epoch)
// A legacy snapshot may be structurally valid but older
// than an incomplete v2 snapshot left by a newer leader.
// Do not let that candidate regress the startup floor.
if !is_v2_path && invalid_baseline_epoch.is_some_and(|fenced_epoch| epoch < fenced_epoch) {
None
} else {
update_floor(&mut floor, &usage, primary_path)?;
Some(epoch)
}
}
}
Ok((None, _)) => None,

View File

@@ -2038,6 +2038,34 @@ async fn scanner_usage_floor_rejects_backup_older_than_incomplete_v2_primary() {
assert!(err.to_string().contains("no authoritative baseline"));
}
#[tokio::test]
async fn scanner_usage_floor_rejects_older_legacy_primary_after_incomplete_v2_primary() {
let store = Arc::new(MemoryConfigStore::default());
let primary = DataUsageInfo {
scanner_epoch: Some(7),
scanner_cycle: Some(100),
usage_snapshot_complete: false,
..Default::default()
};
let mut legacy = complete_usage_with_bucket_count(Some(std::time::SystemTime::UNIX_EPOCH), 0);
legacy.scanner_epoch = Some(6);
legacy.scanner_cycle = Some(103);
for (path, usage) in [
(DATA_USAGE_OBJ_NAME_PATH.as_str(), primary),
(LEGACY_DATA_USAGE_OBJ_NAME_PATH.as_str(), legacy),
] {
store.objects.lock().await.insert(
memory_config_key(RUSTFS_META_BUCKET, path),
serde_json::to_vec(&usage).expect("usage snapshot should encode"),
);
}
let err = persisted_usage_floor_for_startup(store, true)
.await
.expect_err("an older legacy baseline must not cross the incomplete v2 epoch fence");
assert!(err.to_string().contains("no authoritative baseline"));
}
#[tokio::test]
async fn scanner_leadership_fencing_recovers_incomplete_v2_primary_from_backup() {
let store = Arc::new(MemoryConfigStore::default());