Return NumVersions in quorum when available (#19766)

Similar to https://github.com/minio/minio/pull/17925
This commit is contained in:
Krishnan Parthasarathi 2024-05-17 13:57:37 -07:00 committed by GitHub
parent fc4561c64c
commit 1228d6bf1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 22 deletions

View File

@ -26,6 +26,20 @@ import (
"github.com/minio/pkg/v2/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
// counterMap type adds GetValueWithQuorum method to a map[T]int used to count occurrences of values of type T.
type counterMap[T comparable] map[T]int
// GetValueWithQuorum returns the first key which occurs >= quorum number of times.
func (c counterMap[T]) GetValueWithQuorum(quorum int) (T, bool) {
var zero T
for x, count := range c {
if count >= quorum {
return x, true
}
}
return zero, false
}
// figure out the most commonVersions across disk that satisfies // figure out the most commonVersions across disk that satisfies
// the 'writeQuorum' this function returns "" if quorum cannot // the 'writeQuorum' this function returns "" if quorum cannot
// be achieved and disks have too many inconsistent versions. // be achieved and disks have too many inconsistent versions.

View File

@ -344,9 +344,14 @@ func findFileInfoInQuorum(ctx context.Context, metaArr []FileInfo, modTime time.
return FileInfo{}, InsufficientReadQuorum{Err: errErasureReadQuorum, Type: RQInconsistentMeta} return FileInfo{}, InsufficientReadQuorum{Err: errErasureReadQuorum, Type: RQInconsistentMeta}
} }
// Find the successor mod time in quorum, otherwise leave the // objProps represents properties that go beyond a single version
// candidate's successor modTime as found type objProps struct {
succModTimeMap := make(map[time.Time]int) succModTime time.Time
numVersions int
}
// Find the successor mod time and numVersions in quorum, otherwise leave the
// candidate as found
otherPropsMap := make(counterMap[objProps])
var candidate FileInfo var candidate FileInfo
var found bool var found bool
for i, hash := range metaHashes { for i, hash := range metaHashes {
@ -356,24 +361,21 @@ func findFileInfoInQuorum(ctx context.Context, metaArr []FileInfo, modTime time.
candidate = metaArr[i] candidate = metaArr[i]
found = true found = true
} }
succModTimeMap[metaArr[i].SuccessorModTime]++ props := objProps{
succModTime: metaArr[i].SuccessorModTime,
numVersions: metaArr[i].NumVersions,
}
otherPropsMap[props]++
} }
} }
} }
var succModTime time.Time
var smodTimeQuorum bool
for smodTime, count := range succModTimeMap {
if count >= quorum {
smodTimeQuorum = true
succModTime = smodTime
break
}
}
if found { if found {
if smodTimeQuorum { // Update candidate FileInfo with succModTime and numVersions in quorum when available
candidate.SuccessorModTime = succModTime if props, ok := otherPropsMap.GetValueWithQuorum(quorum); ok {
candidate.IsLatest = succModTime.IsZero() candidate.SuccessorModTime = props.succModTime
candidate.IsLatest = props.succModTime.IsZero()
candidate.NumVersions = props.numVersions
} }
return candidate, nil return candidate, nil
} }

View File

@ -158,7 +158,7 @@ func TestObjectToPartOffset(t *testing.T) {
} }
func TestFindFileInfoInQuorum(t *testing.T) { func TestFindFileInfoInQuorum(t *testing.T) {
getNFInfo := func(n int, quorum int, t int64, dataDir string, succModTimes []time.Time) []FileInfo { getNFInfo := func(n int, quorum int, t int64, dataDir string, succModTimes []time.Time, numVersions []int) []FileInfo {
fi := newFileInfo("test", 8, 8) fi := newFileInfo("test", 8, 8)
fi.AddObjectPart(1, "etag", 100, 100, UTCNow(), nil, nil) fi.AddObjectPart(1, "etag", 100, 100, UTCNow(), nil, nil)
fi.ModTime = time.Unix(t, 0) fi.ModTime = time.Unix(t, 0)
@ -171,6 +171,9 @@ func TestFindFileInfoInQuorum(t *testing.T) {
fis[i].SuccessorModTime = succModTimes[i] fis[i].SuccessorModTime = succModTimes[i]
fis[i].IsLatest = succModTimes[i].IsZero() fis[i].IsLatest = succModTimes[i].IsZero()
} }
if numVersions != nil {
fis[i].NumVersions = numVersions[i]
}
quorum-- quorum--
if quorum == 0 { if quorum == 0 {
break break
@ -182,59 +185,86 @@ func TestFindFileInfoInQuorum(t *testing.T) {
commonSuccModTime := time.Date(2023, time.August, 25, 0, 0, 0, 0, time.UTC) commonSuccModTime := time.Date(2023, time.August, 25, 0, 0, 0, 0, time.UTC)
succModTimesInQuorum := make([]time.Time, 16) succModTimesInQuorum := make([]time.Time, 16)
succModTimesNoQuorum := make([]time.Time, 16) succModTimesNoQuorum := make([]time.Time, 16)
commonNumVersions := 2
numVersionsInQuorum := make([]int, 16)
numVersionsNoQuorum := make([]int, 16)
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
if i < 4 { if i < 4 {
continue continue
} }
succModTimesInQuorum[i] = commonSuccModTime succModTimesInQuorum[i] = commonSuccModTime
numVersionsInQuorum[i] = commonNumVersions
if i < 9 { if i < 9 {
continue continue
} }
succModTimesNoQuorum[i] = commonSuccModTime succModTimesNoQuorum[i] = commonSuccModTime
numVersionsNoQuorum[i] = commonNumVersions
} }
tests := []struct { tests := []struct {
fis []FileInfo fis []FileInfo
modTime time.Time modTime time.Time
succmodTimes []time.Time succmodTimes []time.Time
numVersions []int
expectedErr error expectedErr error
expectedQuorum int expectedQuorum int
expectedSuccModTime time.Time expectedSuccModTime time.Time
expectedNumVersions int
expectedIsLatest bool expectedIsLatest bool
}{ }{
{ {
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil), fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil, nil),
modTime: time.Unix(1603863445, 0), modTime: time.Unix(1603863445, 0),
expectedErr: nil, expectedErr: nil,
expectedQuorum: 8, expectedQuorum: 8,
}, },
{ {
fis: getNFInfo(16, 7, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil), fis: getNFInfo(16, 7, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil, nil),
modTime: time.Unix(1603863445, 0), modTime: time.Unix(1603863445, 0),
expectedErr: InsufficientReadQuorum{}, expectedErr: InsufficientReadQuorum{},
expectedQuorum: 8, expectedQuorum: 8,
}, },
{ {
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil), fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil, nil),
modTime: time.Unix(1603863445, 0), modTime: time.Unix(1603863445, 0),
expectedErr: InsufficientReadQuorum{}, expectedErr: InsufficientReadQuorum{},
expectedQuorum: 0, expectedQuorum: 0,
}, },
{ {
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesInQuorum), fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesInQuorum, nil),
modTime: time.Unix(1603863445, 0), modTime: time.Unix(1603863445, 0),
succmodTimes: succModTimesInQuorum,
expectedErr: nil, expectedErr: nil,
expectedQuorum: 12, expectedQuorum: 12,
expectedSuccModTime: commonSuccModTime, expectedSuccModTime: commonSuccModTime,
expectedIsLatest: false, expectedIsLatest: false,
}, },
{ {
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesNoQuorum), fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesNoQuorum, nil),
modTime: time.Unix(1603863445, 0), modTime: time.Unix(1603863445, 0),
succmodTimes: succModTimesNoQuorum,
expectedErr: nil, expectedErr: nil,
expectedQuorum: 12, expectedQuorum: 12,
expectedSuccModTime: time.Time{}, expectedSuccModTime: time.Time{},
expectedIsLatest: true, expectedIsLatest: true,
}, },
{
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil, numVersionsInQuorum),
modTime: time.Unix(1603863445, 0),
numVersions: numVersionsInQuorum,
expectedErr: nil,
expectedQuorum: 12,
expectedIsLatest: true,
expectedNumVersions: 2,
},
{
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil, numVersionsNoQuorum),
modTime: time.Unix(1603863445, 0),
numVersions: numVersionsNoQuorum,
expectedErr: nil,
expectedQuorum: 12,
expectedIsLatest: true,
expectedNumVersions: 0,
},
} }
for _, test := range tests { for _, test := range tests {
@ -254,6 +284,11 @@ func TestFindFileInfoInQuorum(t *testing.T) {
t.Errorf("Expected IsLatest to be %v but got %v", test.expectedIsLatest, fi.IsLatest) t.Errorf("Expected IsLatest to be %v but got %v", test.expectedIsLatest, fi.IsLatest)
} }
} }
if test.numVersions != nil && test.expectedNumVersions > 0 {
if test.expectedNumVersions != fi.NumVersions {
t.Errorf("Expected Numversions to be %d but got %d", test.expectedNumVersions, fi.NumVersions)
}
}
}) })
} }
} }