fix(erasure): do not report an existing object missing from a partial view

A node that reaches only some drives of a set, for example right after
it restarts and before it reconnects to its peers' drives, could answer
404 NoSuchKey for an object that exists. It happened when the copies it
could see were split between two revisions and neither reached read
quorum: a restore, a transition or an unversioned overwrite committed on
a write quorum but not on every drive. S3 clients and caches treat 404
as final; the honest answer from such a view is a retryable 503.

Two paths turned that view into "not found":

- getObjectFileInfo converted InsufficientReadQuorum{RQInconsistentMeta}
  to not found. Upstream added this for the case where every drive
  answered, but never checked it.
- When the version merge found no version in quorum,
  pickLatestQuorumFilesInfo marked every drive that answered as not
  found, including drives that hold the object.

Both now require every drive to have answered: metadata, or a definite
file, version or volume not found, or corrupt metadata. Otherwise the
read keeps its read-quorum error. A key absent from every drive that
answered still gets 404, and so do inconsistent copies when every drive
answered. The dangling-object purge is unchanged.

Deletes, copies, metadata updates, decommission and rebalance that read
through such a view now fail and retry instead of treating the object
as absent. The change is local to the serving node: no storage RPC,
on-disk format or configuration changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-11-06 16:02:47 +08:00
parent c45b26a75c
commit 3e954ad06e
2 changed files with 352 additions and 2 deletions
+37 -2
View File
@@ -660,9 +660,15 @@ func pickLatestQuorumFilesInfo(ctx context.Context, rawFileInfos []RawFileInfo,
meta := &xlMetaV2{versions: mergeXLV2Versions(readQuorum, false, 1, metadataShallowVersions...)}
lfi, err := meta.ToFileInfo(bucket, object, "", inclFreeVers, true)
if err != nil {
// No version in quorum proves absence only when every drive answered.
// Otherwise a drive holding a version keeps the read retryable.
partial := errors.Is(err, errFileNotFound) && !allDrivesAnswered(errs)
for i := range errs {
if errs[i] == nil {
errs[i] = err
if partial && hasVersion(metadataArray[i], inclFreeVers) {
errs[i] = errErasureReadQuorum
}
}
}
return metaFileInfos, errs
@@ -706,6 +712,33 @@ func pickLatestQuorumFilesInfo(ctx context.Context, rawFileInfos []RawFileInfo,
return metaFileInfos, errs
}
// allDrivesAnswered reports whether every drive returned its metadata or a
// definitive answer that it has none. Offline drives keep errDiskOngoingReq
// in getObjectFileInfo; unreachable ones return errDiskNotFound or a
// transport error.
func allDrivesAnswered(errs []error) bool {
for _, err := range errs {
if err != nil && !IsErr(err, errFileNotFound, errFileVersionNotFound, errFileCorrupt, errVolumeNotFound) {
return false
}
}
return true
}
// hasVersion reports whether a drive's xl.meta holds a version a read can
// return.
func hasVersion(xl *xlMetaV2, inclFreeVers bool) bool {
if xl == nil {
return false
}
for _, ver := range xl.versions {
if inclFreeVers || !ver.header.FreeVersion() {
return true
}
}
return false
}
// Checking if an object is dangling costs some IOPS; hence implementing this function
// which decides which condition it is useful to check if an object is dangling
//
@@ -1006,8 +1039,10 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
}
// when we have insufficient read quorum and inconsistent metadata return
// file not found, since we can't possibly have a way to recover this object
// anyway.
if v, ok := err.(InsufficientReadQuorum); ok && v.Type == RQInconsistentMeta {
// anyway. That holds only when every drive answered: a drive that is
// offline or not yet reconnected may hold the copies that make quorum,
// and a 404 for an existing object is final to clients.
if v, ok := err.(InsufficientReadQuorum); ok && v.Type == RQInconsistentMeta && allDrivesAnswered(errs) {
if opts.VersionID != "" {
err = errFileVersionNotFound
} else {