mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
fix(ecstore): stop logging not-found listing quorum miss at error (#5111)
A metacache listing that misses quorum purely because the volume or path is absent on a quorum of drives (every drive reports VolumeNotFound or FileNotFound) is a benign, expected outcome: list_path_raw already returns VolumeNotFound/FileNotFound and lets the caller decide how to react. The common trigger is a startup race where the system bucket (.rustfs.sys) is not yet created on every drive when an early reader such as the IAM config loader lists config/iam/. Logging that at error prints a scary message during normal boot and, in #5076, misled a user into blaming it for unrelated upload failures. Add is_benign_not_found_listing_failure() and demote the pure not-found case to debug (state = "quorum_not_found"), keeping error for listings that failed for a real reason (I/O, timeout, corruption). Return value and control flow are unchanged. Add a unit test for the classifier. Refs: #5076
This commit is contained in:
@@ -124,6 +124,24 @@ fn classify_listing_quorum_failure(errors: &[DiskError]) -> DiskError {
|
||||
DiskError::ErasureReadQuorum
|
||||
}
|
||||
|
||||
/// Returns true when a metacache listing missed quorum purely because the
|
||||
/// volume or path is absent on a quorum of drives, i.e. every recorded failure
|
||||
/// is [`DiskError::VolumeNotFound`] or [`DiskError::FileNotFound`].
|
||||
///
|
||||
/// This is a benign, expected outcome: the caller is handed
|
||||
/// `VolumeNotFound`/`FileNotFound` and decides how to react. The most common
|
||||
/// trigger is a startup race where the system bucket has not yet been created
|
||||
/// on every drive when an early reader (e.g. the IAM config loader) lists it.
|
||||
/// It must be distinguished from a listing that failed for a real reason (I/O,
|
||||
/// timeout, corruption) so the former is not surfaced at `error`. See
|
||||
/// rustfs/rustfs#5076.
|
||||
fn is_benign_not_found_listing_failure(errors: &[DiskError]) -> bool {
|
||||
!errors.is_empty()
|
||||
&& errors
|
||||
.iter()
|
||||
.all(|err| matches!(err, DiskError::VolumeNotFound | DiskError::FileNotFound))
|
||||
}
|
||||
|
||||
struct PublishedBytesWriter<W> {
|
||||
inner: W,
|
||||
published: bool,
|
||||
@@ -840,17 +858,38 @@ async fn list_path_raw_inner(
|
||||
_ => {}
|
||||
});
|
||||
|
||||
error!(
|
||||
event = EVENT_METACACHE_LISTING,
|
||||
component = LOG_COMPONENT_ECSTORE,
|
||||
subsystem = LOG_SUBSYSTEM_METACACHE,
|
||||
bucket = %opts.bucket,
|
||||
path = %opts.path,
|
||||
state = "quorum_failed",
|
||||
error = %combined_err.join(", "),
|
||||
"Metacache listing quorum failed"
|
||||
);
|
||||
let failures = errs.iter().flatten().cloned().collect::<Vec<_>>();
|
||||
// A listing that misses quorum purely because the volume/path is
|
||||
// absent on a quorum of drives is benign and expected — the caller
|
||||
// receives VolumeNotFound/FileNotFound and decides how to react.
|
||||
// The common trigger is a startup race where the system bucket is
|
||||
// not yet created on every drive when an early reader (e.g. the IAM
|
||||
// config loader) lists it, so surfacing it at `error` is misleading
|
||||
// noise (rustfs/rustfs#5076). Keep `error` for listings that failed
|
||||
// for a real reason (I/O, timeout, corruption).
|
||||
if is_benign_not_found_listing_failure(&failures) {
|
||||
debug!(
|
||||
event = EVENT_METACACHE_LISTING,
|
||||
component = LOG_COMPONENT_ECSTORE,
|
||||
subsystem = LOG_SUBSYSTEM_METACACHE,
|
||||
bucket = %opts.bucket,
|
||||
path = %opts.path,
|
||||
state = "quorum_not_found",
|
||||
error = %combined_err.join(", "),
|
||||
"Metacache listing quorum not reached (volume/path absent)"
|
||||
);
|
||||
} else {
|
||||
error!(
|
||||
event = EVENT_METACACHE_LISTING,
|
||||
component = LOG_COMPONENT_ECSTORE,
|
||||
subsystem = LOG_SUBSYSTEM_METACACHE,
|
||||
bucket = %opts.bucket,
|
||||
path = %opts.path,
|
||||
state = "quorum_failed",
|
||||
error = %combined_err.join(", "),
|
||||
"Metacache listing quorum failed"
|
||||
);
|
||||
}
|
||||
return Err(classify_listing_quorum_failure(&failures));
|
||||
}
|
||||
|
||||
@@ -1037,6 +1076,21 @@ mod tests {
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[test]
|
||||
fn benign_not_found_listing_failure_detection() {
|
||||
// Pure not-found quorum misses are benign (the volume/path simply does
|
||||
// not exist on a quorum of drives) and must not be logged at ERROR.
|
||||
assert!(is_benign_not_found_listing_failure(&[DiskError::VolumeNotFound]));
|
||||
assert!(is_benign_not_found_listing_failure(
|
||||
&[DiskError::VolumeNotFound, DiskError::FileNotFound,]
|
||||
));
|
||||
// No recorded failure is not a not-found case.
|
||||
assert!(!is_benign_not_found_listing_failure(&[]));
|
||||
// Any real error must keep the failure at ERROR severity.
|
||||
assert!(!is_benign_not_found_listing_failure(&[DiskError::VolumeNotFound, DiskError::Timeout,]));
|
||||
assert!(!is_benign_not_found_listing_failure(&[DiskError::DiskNotFound]));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_path_raw_empty_disks_returns_read_quorum() {
|
||||
let err = list_path_raw(CancellationToken::new(), ListPathRawOptions::default())
|
||||
|
||||
Reference in New Issue
Block a user