mirror of
https://github.com/pgsty/minio.git
synced 2026-11-11 01:55:30 +03:00
fix(heal): heal a version whose expiry drive healing enqueues
Drive healing evaluates lifecycle on the versions of one pool. For a version it found due it enqueued the expiry and skipped the heal. The expiry is decided again under the object lock, with the versions in all pools and the Object Lock state it reads then, and can be refused: a newer version in another pool, an unreadable history, a changed rule, or a delete-all the exact-key purge refuses. Such a version was never healed onto the replaced drive and kept reduced redundancy for as long as the refusal lasted. The scanner heals only versions without a due action, so it did not repair it either. Keep the enqueue and heal the version. A version that the expiry then removes costs one heal. Heal and expiry exclude each other through the object lock in the writers' lock domain, which the single-pool lock domain change provides; a version removed first reads as not found and is skipped, and heal rebuilds only from read-quorum metadata, so it does not bring back a version deleted on a write quorum. Tests: a version refused across pools is healed onto the replaced drive and survives the expiry; an unversioned object due is healed and then removed from every drive; a versioned latest version due is healed and hidden by a marker; a stale minority is not recreated. The first three fail without the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
+11
-4
@@ -371,8 +371,14 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
applyExpiryOnTransitionedObject(ctx, newObjectLayerFn(), objInfo, evt, lcEventSrc_Heal)
|
||||
return false
|
||||
case evt.Action.Delete():
|
||||
// Enqueue the expiry, but heal the version: the expiry is
|
||||
// decided again under the object lock, with the versions in
|
||||
// all pools and the protection it reads then, and can be
|
||||
// refused. A skipped version would stay without this drive
|
||||
// for as long as it is refused. One that is deleted later
|
||||
// costs one heal.
|
||||
globalExpiryState.enqueueByDays(objInfo, evt, lcEventSrc_Heal)
|
||||
return true
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -450,9 +456,10 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
|
||||
var versionNotFound int
|
||||
for _, version := range fivs.Versions {
|
||||
// Ignore healing a version if:
|
||||
// - It is uploaded after the drive healing is started
|
||||
// - An object that is already expired by ILM rule.
|
||||
// Ignore healing a version that is uploaded after the drive
|
||||
// healing started. filterLifecycle only schedules the ILM
|
||||
// action due for the version; it reports false for every
|
||||
// version, also one it expires.
|
||||
if !started.IsZero() && version.ModTime.After(started) || filterLifecycle(bucket, version.Name, version) {
|
||||
versionNotFound++
|
||||
if !send(healEntrySkipped(uint64(version.Size))) {
|
||||
|
||||
Reference in New Issue
Block a user