perf(heal): trim scanner and heal queue hot paths (#6307)

Cache heal queue dedup keys, avoid retry request double construction, clear task aliases after terminal completion, and age out stale scanner pending-heal ledger entries during retry sweeps.

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-20 20:28:22 +08:00
committed by GitHub
parent cd399d1e72
commit a247c79359
7 changed files with 187 additions and 28 deletions
+47 -1
View File
@@ -1018,7 +1018,7 @@ fn pending_heal(
object: object.map(ToOwned::to_owned),
version_id: version_id.map(ToOwned::to_owned),
scan_mode: HealScanMode::Deep,
first_seen: 1,
first_seen: FolderScanner::now_secs(),
last_attempt,
attempts,
last_admission_result: "full".to_string(),
@@ -1155,6 +1155,52 @@ fn test_pending_heal_retry_candidates_respect_cap_and_order() {
assert_eq!(candidates.last().and_then(|entry| entry.object.as_deref()), Some("object-127"));
}
#[tokio::test]
async fn test_pending_heal_prune_expires_stale_entries() {
let (mut scanner, temp_dir) = build_test_scanner().await;
let _guard = TestGuard::new(u64::MAX, usize::MAX, &mut scanner, temp_dir);
scanner.new_cache.info.name = "bucket".to_string();
scanner.update_cache.info.name = "bucket".to_string();
let mut stale = pending_heal(PendingScannerHealKind::Object, "bucket", Some("stale"), None, 1, 1);
stale.first_seen = FolderScanner::now_secs().saturating_sub(MAX_PENDING_SCANNER_HEAL_AGE_SECS + 1);
let fresh = pending_heal(PendingScannerHealKind::Object, "bucket", Some("fresh"), None, 1, 1);
scanner.new_cache.info.pending_heals = vec![stale, fresh];
scanner.prune_pending_scanner_heals();
assert_eq!(scanner.new_cache.info.pending_heals.len(), 1);
assert_eq!(scanner.new_cache.info.pending_heals[0].object.as_deref(), Some("fresh"));
assert_eq!(scanner.update_cache.info.pending_heals, scanner.new_cache.info.pending_heals);
assert!(scanner.pending_heals_changed);
}
#[tokio::test]
async fn test_pending_heal_update_keeps_stale_entry_until_retry_prune() {
let (mut scanner, temp_dir) = build_test_scanner().await;
let _guard = TestGuard::new(u64::MAX, usize::MAX, &mut scanner, temp_dir);
scanner.new_cache.info.name = "bucket".to_string();
scanner.update_cache.info.name = "bucket".to_string();
let mut stale = pending_heal(PendingScannerHealKind::Object, "bucket", Some("object"), None, 1, 1);
stale.first_seen = FolderScanner::now_secs().saturating_sub(MAX_PENDING_SCANNER_HEAL_AGE_SECS + 1);
scanner.new_cache.info.pending_heals = vec![stale];
scanner.update_pending_scanner_heal_after_admission(
PendingScannerHealKind::Object,
"bucket",
Some("object"),
None,
HealScanMode::Deep,
HealAdmissionResult::Dropped(HealAdmissionDropReason::QueueFull),
);
assert_eq!(scanner.new_cache.info.pending_heals.len(), 1);
assert_eq!(scanner.new_cache.info.pending_heals[0].attempts, 2);
assert_eq!(scanner.new_cache.info.pending_heals[0].object.as_deref(), Some("object"));
assert_eq!(scanner.update_cache.info.pending_heals, scanner.new_cache.info.pending_heals);
}
#[tokio::test]
async fn test_pending_heal_queue_full_deduplicates_object_entry() {
let (mut scanner, temp_dir) = build_test_scanner().await;