fix(heal): quiet deferred replacement recovery logs (#5954)

Treat recovery-directory lookup on a replacement endpoint already deferred as replacement_path_unavailable as an expected debug diagnostic instead of a durable generation conflict.

Keep real survivor recovery conflicts and corrupt records on the existing warning/blocking path.

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-11 17:35:06 +08:00
committed by GitHub
parent 4ac7c56c89
commit bc888931fd
2 changed files with 51 additions and 0 deletions
+50
View File
@@ -65,6 +65,14 @@ fn durable_replacement_recovery_is_due(state: &ResumeState, task_id: &str) -> bo
&& matches!(state.replacement_phase, ReplacementPhase::Verified | ReplacementPhase::CleanupPending))) && matches!(state.replacement_phase, ReplacementPhase::Verified | ReplacementPhase::CleanupPending)))
} }
fn replacement_discovery_error_is_expected_for_deferred_endpoint(
error: &Error,
endpoint: &str,
deferred_replacement_endpoints: &HashSet<String>,
) -> bool {
matches!(error, Error::Disk(DiskError::UnformattedDisk)) && deferred_replacement_endpoints.contains(endpoint)
}
fn unblock_replacement_recovery_sets_after_validation( fn unblock_replacement_recovery_sets_after_validation(
blocked_sets: &mut HashSet<String>, blocked_sets: &mut HashSet<String>,
retry_succeeded: HashSet<String>, retry_succeeded: HashSet<String>,
@@ -2500,6 +2508,7 @@ impl HealManager {
let mut endpoints = HashMap::<String, Vec<Endpoint>>::new(); let mut endpoints = HashMap::<String, Vec<Endpoint>>::new();
let mut durable_recoveries = HashMap::<String, (String, Vec<Endpoint>, Vec<String>, String)>::new(); let mut durable_recoveries = HashMap::<String, (String, Vec<Endpoint>, Vec<String>, String)>::new();
let mut conflicted_recovery_sets = HashSet::<String>::new(); let mut conflicted_recovery_sets = HashSet::<String>::new();
let mut deferred_replacement_endpoints = HashSet::<String>::new();
let local_disks = { let local_disks = {
let local_disk_map = local_disk_map_read().await; let local_disk_map = local_disk_map_read().await;
local_disk_map.values().flatten().cloned().collect::<Vec<_>>() local_disk_map.values().flatten().cloned().collect::<Vec<_>>()
@@ -2577,6 +2586,7 @@ impl HealManager {
if !super::replacement_readiness::auto_replacement_target_ready(disk, &local_disks) if !super::replacement_readiness::auto_replacement_target_ready(disk, &local_disks)
.await .await
{ {
deferred_replacement_endpoints.insert(endpoint.to_string());
skipped_invalid_count += 1; skipped_invalid_count += 1;
debug!( debug!(
target: "rustfs::heal::manager", target: "rustfs::heal::manager",
@@ -2650,6 +2660,24 @@ impl HealManager {
let replacement_task_ids = match ResumeUtils::get_replacement_intent_tasks(disk).await { let replacement_task_ids = match ResumeUtils::get_replacement_intent_tasks(disk).await {
Ok(task_ids) => task_ids, Ok(task_ids) => task_ids,
Err(error) => { Err(error) => {
let endpoint_string = endpoint.to_string();
if replacement_discovery_error_is_expected_for_deferred_endpoint(
&error,
&endpoint_string,
&deferred_replacement_endpoints,
) {
debug!(
target: "rustfs::heal::manager",
event = EVENT_HEAL_AUTO_SCAN_ENQUEUE,
component = LOG_COMPONENT_HEAL,
subsystem = LOG_SUBSYSTEM_DISK_SCANNER,
endpoint = %endpoint,
disk_state = "replacement_path_unavailable",
result = "recovery_records_unavailable",
"Replacement recovery discovery skipped for deferred replacement"
);
continue;
}
if let Some(set_disk_id) = &disk_set_disk_id { if let Some(set_disk_id) = &disk_set_disk_id {
conflicted_recovery_sets.insert(set_disk_id.clone()); conflicted_recovery_sets.insert(set_disk_id.clone());
} }
@@ -4655,6 +4683,28 @@ mod tests {
)); ));
} }
#[test]
fn replacement_recovery_discovery_unformatted_is_quiet_only_for_deferred_endpoint() {
let error = Error::Disk(DiskError::UnformattedDisk);
let deferred = HashSet::from(["endpoint-a".to_string()]);
assert!(replacement_discovery_error_is_expected_for_deferred_endpoint(
&error,
"endpoint-a",
&deferred
));
assert!(!replacement_discovery_error_is_expected_for_deferred_endpoint(
&error,
"endpoint-b",
&deferred
));
assert!(!replacement_discovery_error_is_expected_for_deferred_endpoint(
&Error::Disk(DiskError::Timeout),
"endpoint-a",
&deferred
));
}
#[test] #[test]
fn replacement_recovery_retry_barrier_requires_all_set_records_to_validate() { fn replacement_recovery_retry_barrier_requires_all_set_records_to_validate() {
let mut blocked = HashSet::from(["pool_0_set_0".to_string(), "pool_0_set_1".to_string()]); let mut blocked = HashSet::from(["pool_0_set_0".to_string(), "pool_0_set_1".to_string()]);
+1
View File
@@ -2075,6 +2075,7 @@ impl ResumeUtils {
match disk.list_dir("", RUSTFS_META_BUCKET, recovery_dir, -1).await { match disk.list_dir("", RUSTFS_META_BUCKET, recovery_dir, -1).await {
Ok(entries) => Ok(entries), Ok(entries) => Ok(entries),
Err(DiskError::FileNotFound) => Ok(Vec::new()), Err(DiskError::FileNotFound) => Ok(Vec::new()),
Err(error @ DiskError::UnformattedDisk) => Err(error.into()),
Err(error) => Err(Error::TaskExecutionFailed { Err(error) => Err(Error::TaskExecutionFailed {
message: format!("Failed to list replacement recovery records: {error}"), message: format!("Failed to list replacement recovery records: {error}"),
}), }),