Files
rustfs/crates/ecstore/src/store/heal_walk.rs
T
Zhengchao An 3531abb34a feat(heal): disk-walk UNION enumeration to heal sub-quorum versions (backlog#920) (#4527)
B5 switched heal enumeration to list_object_versions, which only reflects the
read-quorum metadata view: a version present on fewer than read-quorum disks was
never enumerated, so it was never healed. Add a per-erasure-set disk-walk UNION
enumerator (mirrors MinIO global-heal.go objQuorum=1 listPathRaw +
mergeXLV2Versions) that surfaces every (object, version) present on ANY disk and
feeds each to the existing per-version heal_object.

- filemeta: MetaCacheEntries::resolve_union (dir_quorum=1/obj_quorum=1) yields the
  cross-disk version union at one tested seam.
- ecstore: SetDisks::heal_walk_versions_page (list_path_raw fan-out, min_disks=1,
  dual object/version page bound, inclusive-forward de-overlap) + ECStore delegator
  + HealWalkVersion.
- ecstore data-safety guard: before dangling-delete, try_regenerate_recoverable_meta
  physically probes part files via check_parts; when >= data_blocks data shards
  survive (meta lost but data recoverable) it regenerates xl.meta from a surviving
  FileInfo with the correct per-disk shard index instead of dangling-deleting.
  Genuine torn writes (< data_blocks) keep the current behavior — no resurrection.
- heal: dw1: forward-marker cursor codec (reuses ResumeState.resume_cursor,
  idempotent restart on foreign tokens); list_versions_for_heal_page_disk_walk
  trait method (default falls back to the B5 read-quorum path); heal_bucket_with_resume
  selects the disk-walk enumerator when scan_mode==Deep || source==AutoHeal, else
  the unchanged B5 path; anti-loop guard aborts on (empty && truncated).

Closes rustfs/backlog#920
2026-07-09 01:07:54 +08:00

51 lines
2.0 KiB
Rust

// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Store-level entry point for the per-erasure-set disk-walk UNION heal
//! enumerator (backlog#920). Bounds-checks `(pool_idx, set_idx)` (mirroring
//! `handle_get_disks`) and delegates to the target `SetDisks`.
use super::*;
pub use crate::set_disk::HealWalkVersion;
impl ECStore {
/// Walk a specific erasure set's disks and return one page of the cross-disk
/// UNION of versions (see `SetDisks::heal_walk_versions_page`). `pool_idx` and
/// `set_idx` are bounds-checked against the live pool topology.
#[instrument(level = "debug", skip(self))]
#[allow(clippy::too_many_arguments)]
pub async fn heal_walk_versions_page(
&self,
pool_idx: usize,
set_idx: usize,
bucket: &str,
prefix: &str,
forward_to: Option<&str>,
batch_objects: usize,
version_budget: usize,
) -> Result<(Vec<HealWalkVersion>, Option<String>, bool)> {
if pool_idx >= self.pools.len() || set_idx >= self.pools[pool_idx].disk_set.len() {
return Err(Error::other(format!(
"heal disk-walk: invalid erasure set (pool index {pool_idx}, set index {set_idx}, pool count {})",
self.pools.len()
)));
}
self.pools[pool_idx].disk_set[set_idx]
.heal_walk_versions_page(bucket, prefix, forward_to, batch_objects, version_budget)
.await
.map_err(Error::from)
}
}