From b7074129eec74da51a25cd3a2190d990edeb5c6b Mon Sep 17 00:00:00 2001 From: houseme Date: Sat, 29 Aug 2026 23:34:25 +0800 Subject: [PATCH] fix(scanner): recover usage floor from fenced backups Co-Authored-By: heihutu --- crates/scanner/src/scanner/cycle_state.rs | 1 - crates/scanner/src/scanner/tests.rs | 32 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/scanner/src/scanner/cycle_state.rs b/crates/scanner/src/scanner/cycle_state.rs index cd3d200c3..133b5f933 100644 --- a/crates/scanner/src/scanner/cycle_state.rs +++ b/crates/scanner/src/scanner/cycle_state.rs @@ -1393,7 +1393,6 @@ pub(super) async fn persisted_usage_floor_for_startup( // This is still persisted state, so it must not enable a // missing-state bootstrap. Continue to a legacy pair in // case it contains a complete, fenced snapshot. - any_found = false; } else { let backup_epoch = usage.scanner_epoch.unwrap_or_default(); // A backup write from an older leader may complete after the diff --git a/crates/scanner/src/scanner/tests.rs b/crates/scanner/src/scanner/tests.rs index 183ab65b8..0c02caec0 100644 --- a/crates/scanner/src/scanner/tests.rs +++ b/crates/scanner/src/scanner/tests.rs @@ -1923,6 +1923,38 @@ async fn scanner_startup_uses_primary_and_backup_usage_floor() { assert_eq!(epoch, 11); } +#[tokio::test] +async fn scanner_usage_floor_keeps_valid_primary_when_backup_has_no_identity() { + let store = Arc::new(MemoryConfigStore::default()); + let backup_path = format!("{}.bkp", DATA_USAGE_OBJ_NAME_PATH.as_str()); + let mut primary = complete_usage_with_bucket_count(Some(std::time::SystemTime::UNIX_EPOCH), 0); + primary.scanner_epoch = Some(8); + primary.scanner_cycle = Some(100); + let backup = DataUsageInfo { + scanner_epoch: Some(9), + scanner_cycle: Some(101), + usage_snapshot_complete: false, + ..Default::default() + }; + + for (path, usage) in [(DATA_USAGE_OBJ_NAME_PATH.as_str(), primary), (backup_path.as_str(), backup)] { + store.objects.lock().await.insert( + memory_config_key(RUSTFS_META_BUCKET, path), + serde_json::to_vec(&usage).expect("usage snapshot should encode"), + ); + } + + assert_eq!( + persisted_usage_floor(store) + .await + .expect("valid primary should remain authoritative"), + PersistedUsageFloor { + next_cycle: 101, + leader_epoch: 8, + } + ); +} + #[tokio::test] async fn scanner_usage_floor_recovers_from_incomplete_v2_primary_using_fenced_backup() { let store = Arc::new(MemoryConfigStore::default());