mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-01 17:58:22 +00:00
fix(scanner): ignore missing rebalance metadata (#3282)
* fix(ecstore): harden rebalance data movement * fix(ecstore): preserve failed rebalance status * fix(ecstore): avoid rebalance walkdir total timeout * fix(ecstore): retry rebalance listing timeouts * fix(ecstore): restrict data movement resume target * refactor(ecstore): simplify multipart movement target * fix(ecstore): restore resume target checks * perf(ecstore): speed up rebalance bucket merges * fix: keep rebalance listing alive on transient failures * fix(scanner): ignore missing rebalance metadata --------- Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -3995,10 +3995,7 @@ impl HealOperations for SetDisks {
|
|||||||
let disks = disks.clone();
|
let disks = disks.clone();
|
||||||
let (_, errs) = Self::read_all_fileinfo(&disks, "", bucket, object, version_id, false, false, false).await?;
|
let (_, errs) = Self::read_all_fileinfo(&disks, "", bucket, object, version_id, false, false, false).await?;
|
||||||
if DiskError::is_all_not_found(&errs) {
|
if DiskError::is_all_not_found(&errs) {
|
||||||
warn!(
|
debug!(bucket, object, version_id, "heal_object skipped missing object");
|
||||||
"heal_object failed, all obj part not found, bucket: {}, obj: {}, version_id: {}",
|
|
||||||
bucket, object, version_id
|
|
||||||
);
|
|
||||||
let err = if !version_id.is_empty() {
|
let err = if !version_id.is_empty() {
|
||||||
Error::FileVersionNotFound
|
Error::FileVersionNotFound
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -69,10 +69,7 @@ impl SetDisks {
|
|||||||
"File info read complete"
|
"File info read complete"
|
||||||
);
|
);
|
||||||
if DiskError::is_all_not_found(&errs) {
|
if DiskError::is_all_not_found(&errs) {
|
||||||
warn!(
|
debug!(bucket, object, version_id, "heal_object skipped missing object");
|
||||||
"heal_object failed, all obj part not found, bucket: {}, obj: {}, version_id: {}",
|
|
||||||
bucket, object, version_id
|
|
||||||
);
|
|
||||||
let err = if !version_id.is_empty() {
|
let err = if !version_id.is_empty() {
|
||||||
DiskError::FileVersionNotFound
|
DiskError::FileVersionNotFound
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use crate::runtime_config::{
|
|||||||
scanner_alert_excess_folders, scanner_alert_excess_version_size, scanner_alert_excess_versions, scanner_yield_every_n_objects,
|
scanner_alert_excess_folders, scanner_alert_excess_version_size, scanner_alert_excess_versions, scanner_yield_every_n_objects,
|
||||||
};
|
};
|
||||||
use crate::scanner_budget::{ScannerCycleBudget, ScannerCycleBudgetReason};
|
use crate::scanner_budget::{ScannerCycleBudget, ScannerCycleBudgetReason};
|
||||||
use crate::scanner_io::ScannerIODisk as _;
|
use crate::scanner_io::{SCANNER_SKIP_FILE_ERROR, ScannerIODisk as _};
|
||||||
use crate::sleeper::DynamicSleeper;
|
use crate::sleeper::DynamicSleeper;
|
||||||
use metrics::{counter, describe_counter};
|
use metrics::{counter, describe_counter};
|
||||||
use rustfs_common::heal_channel::{
|
use rustfs_common::heal_channel::{
|
||||||
@@ -1286,7 +1286,7 @@ impl FolderScanner {
|
|||||||
let sz = match self.local_disk.get_size(item.clone()).await {
|
let sz = match self.local_disk.get_size(item.clone()).await {
|
||||||
Ok(sz) => sz,
|
Ok(sz) => sz,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let is_skip_file = matches!(e, StorageError::Io(ref io) if io.to_string() == "skip file");
|
let is_skip_file = matches!(e, StorageError::Io(ref io) if io.to_string() == SCANNER_SKIP_FILE_ERROR);
|
||||||
|
|
||||||
if !is_skip_file {
|
if !is_skip_file {
|
||||||
// Track failed objects to prevent infinite retry loops
|
// Track failed objects to prevent infinite retry loops
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ use rustfs_ecstore::bucket::versioning::VersioningApi as _;
|
|||||||
use rustfs_ecstore::bucket::versioning_sys::BucketVersioningSys;
|
use rustfs_ecstore::bucket::versioning_sys::BucketVersioningSys;
|
||||||
use rustfs_ecstore::config::storageclass;
|
use rustfs_ecstore::config::storageclass;
|
||||||
use rustfs_ecstore::disk::STORAGE_FORMAT_FILE;
|
use rustfs_ecstore::disk::STORAGE_FORMAT_FILE;
|
||||||
|
use rustfs_ecstore::disk::error::DiskError;
|
||||||
use rustfs_ecstore::disk::{Disk, DiskAPI};
|
use rustfs_ecstore::disk::{Disk, DiskAPI};
|
||||||
use rustfs_ecstore::error::{Error, StorageError};
|
use rustfs_ecstore::error::{Error, StorageError};
|
||||||
use rustfs_ecstore::global::GLOBAL_TierConfigMgr;
|
use rustfs_ecstore::global::GLOBAL_TierConfigMgr;
|
||||||
@@ -56,6 +57,8 @@ use tokio::time::Duration;
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
|
pub(crate) const SCANNER_SKIP_FILE_ERROR: &str = "skip file";
|
||||||
|
|
||||||
const METRIC_SCANNER_SET_SCAN_CONCURRENCY_LIMIT: &str = "rustfs_scanner_set_scan_concurrency_limit";
|
const METRIC_SCANNER_SET_SCAN_CONCURRENCY_LIMIT: &str = "rustfs_scanner_set_scan_concurrency_limit";
|
||||||
const METRIC_SCANNER_DISK_SCAN_CONCURRENCY_LIMIT: &str = "rustfs_scanner_disk_scan_concurrency_limit";
|
const METRIC_SCANNER_DISK_SCAN_CONCURRENCY_LIMIT: &str = "rustfs_scanner_disk_scan_concurrency_limit";
|
||||||
const METRIC_SCANNER_SET_SCAN_WAIT_SECONDS: &str = "rustfs_scanner_set_scan_wait_seconds";
|
const METRIC_SCANNER_SET_SCAN_WAIT_SECONDS: &str = "rustfs_scanner_set_scan_wait_seconds";
|
||||||
@@ -939,11 +942,14 @@ impl ScannerIODisk for Disk {
|
|||||||
let done_object = Metrics::time(Metric::ScanObject);
|
let done_object = Metrics::time(Metric::ScanObject);
|
||||||
|
|
||||||
if !is_xl_meta_path(&item.path) {
|
if !is_xl_meta_path(&item.path) {
|
||||||
return Err(StorageError::other("skip file".to_string()));
|
return Err(StorageError::other(SCANNER_SKIP_FILE_ERROR.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = match self.read_metadata(&item.bucket, &item.object_path()).await {
|
let data = match self.read_metadata(&item.bucket, &item.object_path()).await {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
|
Err(e) if DiskError::is_err_object_not_found(&e) || DiskError::is_err_version_not_found(&e) => {
|
||||||
|
return Err(StorageError::other(SCANNER_SKIP_FILE_ERROR.to_string()));
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(StorageError::other(format!(
|
return Err(StorageError::other(format!(
|
||||||
"failed to read metadata: {e}, bucket={}, object_path={}",
|
"failed to read metadata: {e}, bucket={}, object_path={}",
|
||||||
@@ -960,7 +966,7 @@ impl ScannerIODisk for Disk {
|
|||||||
Ok(versions) => versions,
|
Ok(versions) => versions,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to get file info versions: {}/{}, err: {e}", item.bucket, item.object_path());
|
error!("Failed to get file info versions: {}/{}, err: {e}", item.bucket, item.object_path());
|
||||||
return Err(StorageError::other("skip file".to_string()));
|
return Err(StorageError::other(SCANNER_SKIP_FILE_ERROR.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1127,8 +1133,12 @@ impl ScannerIODisk for Disk {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::scanner_folder::ScannerItem;
|
||||||
|
use rustfs_ecstore::disk::{DiskOption, STORAGE_FORMAT_FILE, endpoint::Endpoint, new_disk};
|
||||||
|
use rustfs_ecstore::pools::path2_bucket_object_with_base_path;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use temp_env::with_var;
|
use temp_env::with_var;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn record_set_scan_failure_preserves_first_error() {
|
fn record_set_scan_failure_preserves_first_error() {
|
||||||
@@ -1216,6 +1226,65 @@ mod tests {
|
|||||||
assert!(is_xl_meta_path("/data/bucket/object/xl.meta"));
|
assert!(is_xl_meta_path("/data/bucket/object/xl.meta"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_size_treats_missing_metadata_as_skip_file() {
|
||||||
|
let temp_dir = std::env::temp_dir().join(format!("rustfs-scanner-missing-meta-{}", Uuid::new_v4()));
|
||||||
|
let bucket = "bucket";
|
||||||
|
let object = "object";
|
||||||
|
let object_dir = temp_dir.join(bucket).join(object);
|
||||||
|
let metadata_path = object_dir.join(STORAGE_FORMAT_FILE);
|
||||||
|
|
||||||
|
tokio::fs::create_dir_all(&object_dir)
|
||||||
|
.await
|
||||||
|
.expect("failed to create object directory");
|
||||||
|
tokio::fs::write(&metadata_path, [])
|
||||||
|
.await
|
||||||
|
.expect("failed to create metadata placeholder");
|
||||||
|
|
||||||
|
let endpoint = Endpoint::try_from(temp_dir.to_string_lossy().as_ref()).expect("failed to create endpoint");
|
||||||
|
let disk = new_disk(
|
||||||
|
&endpoint,
|
||||||
|
&DiskOption {
|
||||||
|
cleanup: false,
|
||||||
|
health_check: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("failed to open local disk");
|
||||||
|
|
||||||
|
let relative_path = metadata_path.to_string_lossy().to_string();
|
||||||
|
let (_, scanner_path) = path2_bucket_object_with_base_path(temp_dir.to_string_lossy().as_ref(), relative_path.as_str());
|
||||||
|
let file_type = tokio::fs::metadata(&metadata_path)
|
||||||
|
.await
|
||||||
|
.expect("failed to stat metadata placeholder")
|
||||||
|
.file_type();
|
||||||
|
|
||||||
|
tokio::fs::remove_dir_all(&object_dir)
|
||||||
|
.await
|
||||||
|
.expect("failed to remove object directory");
|
||||||
|
|
||||||
|
let item = ScannerItem {
|
||||||
|
path: scanner_path,
|
||||||
|
bucket: bucket.to_string(),
|
||||||
|
prefix: object.to_string(),
|
||||||
|
object_name: STORAGE_FORMAT_FILE.to_string(),
|
||||||
|
file_type,
|
||||||
|
lifecycle: None,
|
||||||
|
replication: None,
|
||||||
|
heal_enabled: false,
|
||||||
|
heal_bitrot: false,
|
||||||
|
debug: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let err = disk
|
||||||
|
.get_size(item)
|
||||||
|
.await
|
||||||
|
.expect_err("missing metadata should be skipped instead of reported as a scanner failure");
|
||||||
|
assert!(matches!(err, StorageError::Io(ref io) if io.to_string() == SCANNER_SKIP_FILE_ERROR));
|
||||||
|
|
||||||
|
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cache_root_entry_info_flattens_bucket_children() {
|
fn cache_root_entry_info_flattens_bucket_children() {
|
||||||
let mut cache = DataUsageCache {
|
let mut cache = DataUsageCache {
|
||||||
|
|||||||
Reference in New Issue
Block a user