fix(pools): preserve delete markers and recheck migration cleanup

Move solitary and null delete markers instead of dropping them, including
idempotent UUID marker retries. Check that a reader's source version still
exists before moving it, and preserve the established read winner for
conflicting timestamp ties.

Retire only the versions that were successfully moved while holding the
pooled object lock. Recheck the source and retained copies before cleanup,
so concurrent deletes and uncertain destination reads cannot revive data.
Count remaining markers when verifying decommission completion.

Add two/three-pool walker, history, purge, concurrent-delete, read-fault,
and timestamp-tie regressions.

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-10-18 22:39:12 +08:00
parent 60c7179ddb
commit 1748f62c7e
7 changed files with 290 additions and 76 deletions
+11 -7
View File
@@ -116,15 +116,18 @@ func migrationTestPools(t *testing.T) (*erasureServerPools, string) {
t.Helper()
z, bucket := consistencyPools(t)
for _, pool := range z.serverPools {
for _, set := range pool.sets {
original := set.getDisks
disks := append([]StorageAPI(nil), original()...)
for i := range disks {
disks[i] = tagTestCapacityDisk{StorageAPI: disks[i]}
pool.erasureDisksMu.Lock()
previous := pool.erasureDisks
wrapped := make([][]StorageAPI, len(previous))
for i, disks := range previous {
wrapped[i] = append([]StorageAPI(nil), disks...)
for j := range wrapped[i] {
wrapped[i][j] = tagTestCapacityDisk{StorageAPI: wrapped[i][j]}
}
set.getDisks = func() []StorageAPI { return disks }
t.Cleanup(func() { set.getDisks = original })
}
pool.erasureDisks = wrapped
pool.erasureDisksMu.Unlock()
t.Cleanup(func() { pool.erasureDisksMu.Lock(); pool.erasureDisks = previous; pool.erasureDisksMu.Unlock() })
}
return z, bucket
}
@@ -705,6 +708,7 @@ func TestPoolsMigrationKeepsCurrentCopy(t *testing.T) {
want, other := "source", "other"
if offset == 0 {
other = "source"
opts.PreserveETag = oi.ETag // interrupted copies preserve the original multipart ETag
} else if offset < 0 {
want = "other"
}