remove double reads delete versions (#13544)

deleting collection of versions belonging
to same object, we can avoid re-reading
the xl.meta from the disk instead purge
all the requested versions in-memory,

the tradeoff is to allocate a map to de-dup
the versions, allow disks to be read only
once per object.

additionally reduce the data transfer between
nodes by shortening msgp data values.
This commit is contained in:
Harshavardhana 2021-11-01 10:50:07 -07:00 committed by GitHub
parent 15dcacc1fc
commit bb639d9f29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 310 additions and 259 deletions

View File

@ -1088,37 +1088,64 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
writeQuorums[i] = getWriteQuorum(len(storageDisks)) writeQuorums[i] = getWriteQuorum(len(storageDisks))
} }
versions := make([]FileInfo, len(objects)) versionsMap := make(map[string]FileInfoVersions, len(objects))
for i := range objects { for i := range objects {
if objects[i].VersionID == "" { // Construct the FileInfo data that needs to be preserved on the disk.
modTime := opts.MTime vr := FileInfo{
if opts.MTime.IsZero() {
modTime = UTCNow()
}
uuid := opts.VersionID
if uuid == "" {
uuid = mustGetUUID()
}
if opts.Versioned || opts.VersionSuspended {
versions[i] = FileInfo{
Name: objects[i].ObjectName,
ModTime: modTime,
Deleted: true, // delete marker
ReplicationState: objects[i].ReplicationState(),
}
versions[i].SetTierFreeVersionID(mustGetUUID())
if opts.Versioned {
versions[i].VersionID = uuid
}
continue
}
}
versions[i] = FileInfo{
Name: objects[i].ObjectName, Name: objects[i].ObjectName,
VersionID: objects[i].VersionID, VersionID: objects[i].VersionID,
ReplicationState: objects[i].ReplicationState(), ReplicationState: objects[i].ReplicationState(),
// save the index to set correct error at this index.
Idx: i,
} }
versions[i].SetTierFreeVersionID(mustGetUUID()) vr.SetTierFreeVersionID(mustGetUUID())
// VersionID is not set means delete is not specific about
// any version, look for if the bucket is versioned or not.
if objects[i].VersionID == "" {
if opts.Versioned || opts.VersionSuspended {
// Bucket is versioned and no version was explicitly
// mentioned for deletes, create a delete marker instead.
vr.ModTime = UTCNow()
vr.Deleted = true
// Versioning suspended means that we add a `null` version
// delete marker, if not add a new version for this delete
// marker.
if opts.Versioned {
vr.VersionID = mustGetUUID()
}
}
}
// De-dup same object name to collect multiple versions for same object.
v, ok := versionsMap[objects[i].ObjectName]
if ok {
v.Versions = append(v.Versions, vr)
} else {
v = FileInfoVersions{
Name: vr.Name,
Versions: []FileInfo{vr},
}
}
if vr.Deleted {
dobjects[i] = DeletedObject{
DeleteMarker: vr.Deleted,
DeleteMarkerVersionID: vr.VersionID,
DeleteMarkerMTime: DeleteMarkerMTime{vr.ModTime},
ObjectName: vr.Name,
ReplicationState: vr.ReplicationState,
}
} else {
dobjects[i] = DeletedObject{
ObjectName: vr.Name,
VersionID: vr.VersionID,
ReplicationState: vr.ReplicationState,
}
}
versionsMap[objects[i].ObjectName] = v
}
dedupVersions := make([]FileInfoVersions, 0, len(versionsMap))
for _, version := range versionsMap {
dedupVersions = append(dedupVersions, version)
} }
// Initialize list of errors. // Initialize list of errors.
@ -1130,17 +1157,24 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
wg.Add(1) wg.Add(1)
go func(index int, disk StorageAPI) { go func(index int, disk StorageAPI) {
defer wg.Done() defer wg.Done()
delObjErrs[index] = make([]error, len(objects))
if disk == nil { if disk == nil {
delObjErrs[index] = make([]error, len(versions)) for i := range objects {
for i := range versions {
delObjErrs[index][i] = errDiskNotFound delObjErrs[index][i] = errDiskNotFound
} }
return return
} }
delObjErrs[index] = disk.DeleteVersions(ctx, bucket, versions) errs := disk.DeleteVersions(ctx, bucket, dedupVersions)
for i, err := range errs {
if err == nil {
continue
}
for _, v := range dedupVersions[i].Versions {
delObjErrs[index][v.Idx] = err
}
}
}(index, disk) }(index, disk)
} }
wg.Wait() wg.Wait()
// Reduce errors for each object // Reduce errors for each object
@ -1162,28 +1196,17 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
} }
if errs[objIndex] == nil { if errs[objIndex] == nil {
NSUpdated(bucket, objects[objIndex].ObjectName) defer NSUpdated(bucket, objects[objIndex].ObjectName)
}
if versions[objIndex].Deleted {
dobjects[objIndex] = DeletedObject{
DeleteMarker: versions[objIndex].Deleted,
DeleteMarkerVersionID: versions[objIndex].VersionID,
DeleteMarkerMTime: DeleteMarkerMTime{versions[objIndex].ModTime},
ObjectName: versions[objIndex].Name,
ReplicationState: versions[objIndex].ReplicationState,
}
} else {
dobjects[objIndex] = DeletedObject{
ObjectName: versions[objIndex].Name,
VersionID: versions[objIndex].VersionID,
ReplicationState: versions[objIndex].ReplicationState,
}
} }
} }
// Check failed deletes across multiple objects // Check failed deletes across multiple objects
for _, version := range versions { for i, dobj := range dobjects {
// This object errored, no need to attempt a heal.
if errs[i] != nil {
continue
}
// Check if there is any offline disk and add it to the MRF list // Check if there is any offline disk and add it to the MRF list
for _, disk := range storageDisks { for _, disk := range storageDisks {
if disk != nil && disk.IsOnline() { if disk != nil && disk.IsOnline() {
@ -1193,7 +1216,7 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
// all other direct versionId references we should // all other direct versionId references we should
// ensure no dangling file is left over. // ensure no dangling file is left over.
er.addPartial(bucket, version.Name, version.VersionID, -1) er.addPartial(bucket, dobj.ObjectName, dobj.VersionID, -1)
break break
} }
} }

View File

@ -225,7 +225,7 @@ func (d *naughtyDisk) Delete(ctx context.Context, volume string, path string, re
return d.disk.Delete(ctx, volume, path, recursive) return d.disk.Delete(ctx, volume, path, recursive)
} }
func (d *naughtyDisk) DeleteVersions(ctx context.Context, volume string, versions []FileInfo) []error { func (d *naughtyDisk) DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions) []error {
if err := d.calcError(); err != nil { if err := d.calcError(); err != nil {
errs := make([]error, len(versions)) errs := make([]error, len(versions))
for i := range errs { for i := range errs {

View File

@ -80,20 +80,20 @@ type FilesInfoVersions struct {
} }
// FileInfoVersions represent a list of versions for a given file. // FileInfoVersions represent a list of versions for a given file.
//msgp:tuple FileInfoVersions
// The above means that any added/deleted fields are incompatible.
type FileInfoVersions struct { type FileInfoVersions struct {
// Name of the volume. // Name of the volume.
Volume string Volume string `msg:"v,omitempty"`
// Name of the file. // Name of the file.
Name string Name string `msg:"n,omitempty"`
IsEmptyDir bool
// Represents the latest mod time of the // Represents the latest mod time of the
// latest version. // latest version.
LatestModTime time.Time LatestModTime time.Time `msg:"lm"`
Versions []FileInfo Versions []FileInfo `msg:"vs"`
} }
// findVersionIndex will return the version index where the version // findVersionIndex will return the version index where the version
@ -115,69 +115,74 @@ func (f *FileInfoVersions) findVersionIndex(v string) int {
// The above means that any added/deleted fields are incompatible. // The above means that any added/deleted fields are incompatible.
type FileInfo struct { type FileInfo struct {
// Name of the volume. // Name of the volume.
Volume string Volume string `msg:"v,omitempty"`
// Name of the file. // Name of the file.
Name string Name string `msg:"n,omitempty"`
// Version of the file. // Version of the file.
VersionID string VersionID string `msg:"vid,omitempty"`
// Indicates if the version is the latest // Indicates if the version is the latest
IsLatest bool IsLatest bool `msg:"is"`
// Deleted is set when this FileInfo represents // Deleted is set when this FileInfo represents
// a deleted marker for a versioned bucket. // a deleted marker for a versioned bucket.
Deleted bool Deleted bool `msg:"del"`
// TransitionStatus is set to Pending/Complete for transitioned // TransitionStatus is set to Pending/Complete for transitioned
// entries based on state of transition // entries based on state of transition
TransitionStatus string TransitionStatus string `msg:"ts"`
// TransitionedObjName is the object name on the remote tier corresponding // TransitionedObjName is the object name on the remote tier corresponding
// to object (version) on the source tier. // to object (version) on the source tier.
TransitionedObjName string TransitionedObjName string `msg:"to"`
// TransitionTier is the storage class label assigned to remote tier. // TransitionTier is the storage class label assigned to remote tier.
TransitionTier string TransitionTier string `msg:"tt"`
// TransitionVersionID stores a version ID of the object associate // TransitionVersionID stores a version ID of the object associate
// with the remote tier. // with the remote tier.
TransitionVersionID string TransitionVersionID string `msg:"tv"`
// ExpireRestored indicates that the restored object is to be expired. // ExpireRestored indicates that the restored object is to be expired.
ExpireRestored bool ExpireRestored bool `msg:"exp"`
// DataDir of the file // DataDir of the file
DataDir string DataDir string `msg:"dd"`
// Indicates if this object is still in V1 format. // Indicates if this object is still in V1 format.
XLV1 bool XLV1 bool `msg:"v1"`
// Date and time when the file was last modified, if Deleted // Date and time when the file was last modified, if Deleted
// is 'true' this value represents when while was deleted. // is 'true' this value represents when while was deleted.
ModTime time.Time ModTime time.Time `msg:"mt"`
// Total file size. // Total file size.
Size int64 Size int64 `msg:"sz"`
// File mode bits. // File mode bits.
Mode uint32 Mode uint32 `msg:"m"`
// File metadata // File metadata
Metadata map[string]string Metadata map[string]string `msg:"meta"`
// All the parts per object. // All the parts per object.
Parts []ObjectPartInfo Parts []ObjectPartInfo `msg:"parts"`
// Erasure info for all objects. // Erasure info for all objects.
Erasure ErasureInfo Erasure ErasureInfo `msg:"ei"`
MarkDeleted bool // mark this version as deleted MarkDeleted bool `msg:"md"` // mark this version as deleted
ReplicationState ReplicationState // Internal replication state to be passed back in ObjectInfo ReplicationState ReplicationState `msg:"rs"` // Internal replication state to be passed back in ObjectInfo
Data []byte // optionally carries object data Data []byte `msg:"d,allownil"` // optionally carries object data
NumVersions int NumVersions int `msg:"nv"`
SuccessorModTime time.Time SuccessorModTime time.Time `msg:"smt"`
Fresh bool // indicates this is a first time call to write FileInfo. Fresh bool `msg:"fr"` // indicates this is a first time call to write FileInfo.
// Position of this version or object in a multi-object delete call,
// no other caller must set this value other than multi-object delete call.
// usage in other calls in undefined please avoid.
Idx int `msg:"i"`
} }
// InlineData returns true if object contents are inlined alongside its metadata. // InlineData returns true if object contents are inlined alongside its metadata.

View File

@ -550,8 +550,8 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
if zb0001 != 24 { if zb0001 != 25 {
err = msgp.ArrayError{Wanted: 24, Got: zb0001} err = msgp.ArrayError{Wanted: 25, Got: zb0001}
return return
} }
z.Volume, err = dc.ReadString() z.Volume, err = dc.ReadString()
@ -711,13 +711,18 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Fresh") err = msgp.WrapError(err, "Fresh")
return return
} }
z.Idx, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "Idx")
return
}
return return
} }
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *FileInfo) EncodeMsg(en *msgp.Writer) (err error) { func (z *FileInfo) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 24 // array header, size 25
err = en.Append(0xdc, 0x0, 0x18) err = en.Append(0xdc, 0x0, 0x19)
if err != nil { if err != nil {
return return
} }
@ -860,14 +865,19 @@ func (z *FileInfo) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "Fresh") err = msgp.WrapError(err, "Fresh")
return return
} }
err = en.WriteInt(z.Idx)
if err != nil {
err = msgp.WrapError(err, "Idx")
return
}
return return
} }
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *FileInfo) MarshalMsg(b []byte) (o []byte, err error) { func (z *FileInfo) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// array header, size 24 // array header, size 25
o = append(o, 0xdc, 0x0, 0x18) o = append(o, 0xdc, 0x0, 0x19)
o = msgp.AppendString(o, z.Volume) o = msgp.AppendString(o, z.Volume)
o = msgp.AppendString(o, z.Name) o = msgp.AppendString(o, z.Name)
o = msgp.AppendString(o, z.VersionID) o = msgp.AppendString(o, z.VersionID)
@ -911,6 +921,7 @@ func (z *FileInfo) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.AppendInt(o, z.NumVersions) o = msgp.AppendInt(o, z.NumVersions)
o = msgp.AppendTime(o, z.SuccessorModTime) o = msgp.AppendTime(o, z.SuccessorModTime)
o = msgp.AppendBool(o, z.Fresh) o = msgp.AppendBool(o, z.Fresh)
o = msgp.AppendInt(o, z.Idx)
return return
} }
@ -922,8 +933,8 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
if zb0001 != 24 { if zb0001 != 25 {
err = msgp.ArrayError{Wanted: 24, Got: zb0001} err = msgp.ArrayError{Wanted: 25, Got: zb0001}
return return
} }
z.Volume, bts, err = msgp.ReadStringBytes(bts) z.Volume, bts, err = msgp.ReadStringBytes(bts)
@ -1083,6 +1094,11 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Fresh") err = msgp.WrapError(err, "Fresh")
return return
} }
z.Idx, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Idx")
return
}
o = bts o = bts
return return
} }
@ -1100,87 +1116,62 @@ func (z *FileInfo) Msgsize() (s int) {
for za0003 := range z.Parts { for za0003 := range z.Parts {
s += z.Parts[za0003].Msgsize() s += z.Parts[za0003].Msgsize()
} }
s += z.Erasure.Msgsize() + msgp.BoolSize + z.ReplicationState.Msgsize() + msgp.BytesPrefixSize + len(z.Data) + msgp.IntSize + msgp.TimeSize + msgp.BoolSize s += z.Erasure.Msgsize() + msgp.BoolSize + z.ReplicationState.Msgsize() + msgp.BytesPrefixSize + len(z.Data) + msgp.IntSize + msgp.TimeSize + msgp.BoolSize + msgp.IntSize
return return
} }
// DecodeMsg implements msgp.Decodable // DecodeMsg implements msgp.Decodable
func (z *FileInfoVersions) DecodeMsg(dc *msgp.Reader) (err error) { func (z *FileInfoVersions) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32 var zb0001 uint32
zb0001, err = dc.ReadMapHeader() zb0001, err = dc.ReadArrayHeader()
if err != nil { if err != nil {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
for zb0001 > 0 { if zb0001 != 4 {
zb0001-- err = msgp.ArrayError{Wanted: 4, Got: zb0001}
field, err = dc.ReadMapKeyPtr() return
}
z.Volume, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Volume")
return
}
z.Name, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Name")
return
}
z.LatestModTime, err = dc.ReadTime()
if err != nil {
err = msgp.WrapError(err, "LatestModTime")
return
}
var zb0002 uint32
zb0002, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err, "Versions")
return
}
if cap(z.Versions) >= int(zb0002) {
z.Versions = (z.Versions)[:zb0002]
} else {
z.Versions = make([]FileInfo, zb0002)
}
for za0001 := range z.Versions {
err = z.Versions[za0001].DecodeMsg(dc)
if err != nil { if err != nil {
err = msgp.WrapError(err) err = msgp.WrapError(err, "Versions", za0001)
return return
} }
switch msgp.UnsafeString(field) {
case "Volume":
z.Volume, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Volume")
return
}
case "Name":
z.Name, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Name")
return
}
case "IsEmptyDir":
z.IsEmptyDir, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "IsEmptyDir")
return
}
case "LatestModTime":
z.LatestModTime, err = dc.ReadTime()
if err != nil {
err = msgp.WrapError(err, "LatestModTime")
return
}
case "Versions":
var zb0002 uint32
zb0002, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err, "Versions")
return
}
if cap(z.Versions) >= int(zb0002) {
z.Versions = (z.Versions)[:zb0002]
} else {
z.Versions = make([]FileInfo, zb0002)
}
for za0001 := range z.Versions {
err = z.Versions[za0001].DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "Versions", za0001)
return
}
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
} }
return return
} }
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *FileInfoVersions) EncodeMsg(en *msgp.Writer) (err error) { func (z *FileInfoVersions) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 5 // array header, size 4
// write "Volume" err = en.Append(0x94)
err = en.Append(0x85, 0xa6, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65)
if err != nil { if err != nil {
return return
} }
@ -1189,41 +1180,16 @@ func (z *FileInfoVersions) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "Volume") err = msgp.WrapError(err, "Volume")
return return
} }
// write "Name"
err = en.Append(0xa4, 0x4e, 0x61, 0x6d, 0x65)
if err != nil {
return
}
err = en.WriteString(z.Name) err = en.WriteString(z.Name)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Name") err = msgp.WrapError(err, "Name")
return return
} }
// write "IsEmptyDir"
err = en.Append(0xaa, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x44, 0x69, 0x72)
if err != nil {
return
}
err = en.WriteBool(z.IsEmptyDir)
if err != nil {
err = msgp.WrapError(err, "IsEmptyDir")
return
}
// write "LatestModTime"
err = en.Append(0xad, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65)
if err != nil {
return
}
err = en.WriteTime(z.LatestModTime) err = en.WriteTime(z.LatestModTime)
if err != nil { if err != nil {
err = msgp.WrapError(err, "LatestModTime") err = msgp.WrapError(err, "LatestModTime")
return return
} }
// write "Versions"
err = en.Append(0xa8, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73)
if err != nil {
return
}
err = en.WriteArrayHeader(uint32(len(z.Versions))) err = en.WriteArrayHeader(uint32(len(z.Versions)))
if err != nil { if err != nil {
err = msgp.WrapError(err, "Versions") err = msgp.WrapError(err, "Versions")
@ -1242,21 +1208,11 @@ func (z *FileInfoVersions) EncodeMsg(en *msgp.Writer) (err error) {
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *FileInfoVersions) MarshalMsg(b []byte) (o []byte, err error) { func (z *FileInfoVersions) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// map header, size 5 // array header, size 4
// string "Volume" o = append(o, 0x94)
o = append(o, 0x85, 0xa6, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65)
o = msgp.AppendString(o, z.Volume) o = msgp.AppendString(o, z.Volume)
// string "Name"
o = append(o, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
o = msgp.AppendString(o, z.Name) o = msgp.AppendString(o, z.Name)
// string "IsEmptyDir"
o = append(o, 0xaa, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x44, 0x69, 0x72)
o = msgp.AppendBool(o, z.IsEmptyDir)
// string "LatestModTime"
o = append(o, 0xad, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65)
o = msgp.AppendTime(o, z.LatestModTime) o = msgp.AppendTime(o, z.LatestModTime)
// string "Versions"
o = append(o, 0xa8, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73)
o = msgp.AppendArrayHeader(o, uint32(len(z.Versions))) o = msgp.AppendArrayHeader(o, uint32(len(z.Versions)))
for za0001 := range z.Versions { for za0001 := range z.Versions {
o, err = z.Versions[za0001].MarshalMsg(o) o, err = z.Versions[za0001].MarshalMsg(o)
@ -1270,72 +1226,48 @@ func (z *FileInfoVersions) MarshalMsg(b []byte) (o []byte, err error) {
// UnmarshalMsg implements msgp.Unmarshaler // UnmarshalMsg implements msgp.Unmarshaler
func (z *FileInfoVersions) UnmarshalMsg(bts []byte) (o []byte, err error) { func (z *FileInfoVersions) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32 var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil { if err != nil {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
for zb0001 > 0 { if zb0001 != 4 {
zb0001-- err = msgp.ArrayError{Wanted: 4, Got: zb0001}
field, bts, err = msgp.ReadMapKeyZC(bts) return
}
z.Volume, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Volume")
return
}
z.Name, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Name")
return
}
z.LatestModTime, bts, err = msgp.ReadTimeBytes(bts)
if err != nil {
err = msgp.WrapError(err, "LatestModTime")
return
}
var zb0002 uint32
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Versions")
return
}
if cap(z.Versions) >= int(zb0002) {
z.Versions = (z.Versions)[:zb0002]
} else {
z.Versions = make([]FileInfo, zb0002)
}
for za0001 := range z.Versions {
bts, err = z.Versions[za0001].UnmarshalMsg(bts)
if err != nil { if err != nil {
err = msgp.WrapError(err) err = msgp.WrapError(err, "Versions", za0001)
return return
} }
switch msgp.UnsafeString(field) {
case "Volume":
z.Volume, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Volume")
return
}
case "Name":
z.Name, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Name")
return
}
case "IsEmptyDir":
z.IsEmptyDir, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "IsEmptyDir")
return
}
case "LatestModTime":
z.LatestModTime, bts, err = msgp.ReadTimeBytes(bts)
if err != nil {
err = msgp.WrapError(err, "LatestModTime")
return
}
case "Versions":
var zb0002 uint32
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Versions")
return
}
if cap(z.Versions) >= int(zb0002) {
z.Versions = (z.Versions)[:zb0002]
} else {
z.Versions = make([]FileInfo, zb0002)
}
for za0001 := range z.Versions {
bts, err = z.Versions[za0001].UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "Versions", za0001)
return
}
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
} }
o = bts o = bts
return return
@ -1343,7 +1275,7 @@ func (z *FileInfoVersions) UnmarshalMsg(bts []byte) (o []byte, err error) {
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *FileInfoVersions) Msgsize() (s int) { func (z *FileInfoVersions) Msgsize() (s int) {
s = 1 + 7 + msgp.StringPrefixSize + len(z.Volume) + 5 + msgp.StringPrefixSize + len(z.Name) + 11 + msgp.BoolSize + 14 + msgp.TimeSize + 9 + msgp.ArrayHeaderSize s = 1 + msgp.StringPrefixSize + len(z.Volume) + msgp.StringPrefixSize + len(z.Name) + msgp.TimeSize + msgp.ArrayHeaderSize
for za0001 := range z.Versions { for za0001 := range z.Versions {
s += z.Versions[za0001].Msgsize() s += z.Versions[za0001].Msgsize()
} }

View File

@ -57,7 +57,7 @@ type StorageAPI interface {
// Metadata operations // Metadata operations
DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool) error DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool) error
DeleteVersions(ctx context.Context, volume string, versions []FileInfo) []error DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions) []error
WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) error WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) error
UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo) error UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo) error
ReadVersion(ctx context.Context, volume, path, versionID string, readData bool) (FileInfo, error) ReadVersion(ctx context.Context, volume, path, versionID string, readData bool) (FileInfo, error)

View File

@ -590,7 +590,7 @@ func (client *storageRESTClient) Delete(ctx context.Context, volume string, path
} }
// DeleteVersions - deletes list of specified versions if present // DeleteVersions - deletes list of specified versions if present
func (client *storageRESTClient) DeleteVersions(ctx context.Context, volume string, versions []FileInfo) (errs []error) { func (client *storageRESTClient) DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions) (errs []error) {
if len(versions) == 0 { if len(versions) == 0 {
return errs return errs
} }

View File

@ -18,7 +18,7 @@
package cmd package cmd
const ( const (
storageRESTVersion = "v40" // Add ReplicationState field storageRESTVersion = "v41" // Optimized DeleteVersions API
storageRESTVersionPrefix = SlashSeparator + storageRESTVersion storageRESTVersionPrefix = SlashSeparator + storageRESTVersion
storageRESTPrefix = minioReservedBucketPath + "/storage" storageRESTPrefix = minioReservedBucketPath + "/storage"
) )

View File

@ -643,7 +643,7 @@ func (s *storageRESTServer) DeleteVersionsHandler(w http.ResponseWriter, r *http
return return
} }
versions := make([]FileInfo, totalVersions) versions := make([]FileInfoVersions, totalVersions)
decoder := msgp.NewReader(r.Body) decoder := msgp.NewReader(r.Body)
for i := 0; i < totalVersions; i++ { for i := 0; i < totalVersions; i++ {
dst := &versions[i] dst := &versions[i]

View File

@ -421,8 +421,8 @@ func (p *xlStorageDiskIDCheck) Delete(ctx context.Context, volume string, path s
// DeleteVersions deletes slice of versions, it can be same object // DeleteVersions deletes slice of versions, it can be same object
// or multiple objects. // or multiple objects.
func (p *xlStorageDiskIDCheck) DeleteVersions(ctx context.Context, volume string, versions []FileInfo) (errs []error) { func (p *xlStorageDiskIDCheck) DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions) (errs []error) {
// Mererly for tracing storage // Merely for tracing storage
path := "" path := ""
if len(versions) > 0 { if len(versions) > 0 {
path = versions[0].Name path = versions[0].Name

View File

@ -821,13 +821,102 @@ func (s *xlStorage) ListDir(ctx context.Context, volume, dirPath string, count i
return entries, nil return entries, nil
} }
func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis ...FileInfo) error {
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
if err != nil {
if err != errFileNotFound {
return err
}
metaDataPoolPut(buf) // Never used, return it
buf, err = s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFileV1))
if err != nil {
return err
}
}
if len(buf) == 0 {
return errFileNotFound
}
volumeDir, err := s.getVolDir(volume)
if err != nil {
return err
}
if !isXL2V1Format(buf) {
// Delete the meta file, if there are no more versions the
// top level parent is automatically removed.
return s.deleteFile(volumeDir, pathJoin(volumeDir, path), true)
}
var xlMeta xlMetaV2
if err = xlMeta.Load(buf); err != nil {
return err
}
var (
dataDir string
lastVersion bool
)
for _, fi := range fis {
dataDir, lastVersion, err = xlMeta.DeleteVersion(fi)
if err != nil {
return err
}
if dataDir != "" {
versionID := fi.VersionID
if versionID == "" {
versionID = nullVersionID
}
// PR #11758 used DataDir, preserve it
// for users who might have used master
// branch
if !xlMeta.data.remove(versionID, dataDir) {
filePath := pathJoin(volumeDir, path, dataDir)
if err = checkPathLength(filePath); err != nil {
return err
}
if err = s.moveToTrash(filePath, true); err != nil {
if err != errFileNotFound {
return err
}
}
}
}
}
if !lastVersion {
buf, err = xlMeta.AppendTo(metaDataPoolGet())
defer metaDataPoolPut(buf)
if err != nil {
return err
}
return s.WriteAll(ctx, volume, pathJoin(path, xlStorageFormatFile), buf)
}
// Move xl.meta to trash
filePath := pathJoin(volumeDir, path, xlStorageFormatFile)
if err = checkPathLength(filePath); err != nil {
return err
}
err = s.moveToTrash(filePath, false)
if err == nil || err == errFileNotFound {
s.deleteFile(volumeDir, pathJoin(volumeDir, path), false)
}
return err
}
// DeleteVersions deletes slice of versions, it can be same object // DeleteVersions deletes slice of versions, it can be same object
// or multiple objects. // or multiple objects.
func (s *xlStorage) DeleteVersions(ctx context.Context, volume string, versions []FileInfo) []error { func (s *xlStorage) DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions) []error {
errs := make([]error, len(versions)) errs := make([]error, len(versions))
for i, version := range versions { for i, fiv := range versions {
if err := s.DeleteVersion(ctx, volume, version.Name, version, false); err != nil { if err := s.deleteVersions(ctx, volume, fiv.Name, fiv.Versions...); err != nil {
errs[i] = err errs[i] = err
} }
} }

4
go.mod
View File

@ -63,7 +63,7 @@ require (
github.com/nats-io/stan.go v0.8.3 github.com/nats-io/stan.go v0.8.3
github.com/ncw/directio v1.0.5 github.com/ncw/directio v1.0.5
github.com/nsqio/go-nsq v1.0.8 github.com/nsqio/go-nsq v1.0.8
github.com/philhofer/fwd v1.1.1 github.com/philhofer/fwd v1.1.2-0.20210722190033-5c56ac6d0bb9
github.com/pierrec/lz4 v2.6.0+incompatible github.com/pierrec/lz4 v2.6.0+incompatible
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_golang v1.11.0
@ -74,7 +74,7 @@ require (
github.com/secure-io/sio-go v0.3.1 github.com/secure-io/sio-go v0.3.1
github.com/shirou/gopsutil/v3 v3.21.9 github.com/shirou/gopsutil/v3 v3.21.9
github.com/streadway/amqp v1.0.0 github.com/streadway/amqp v1.0.0
github.com/tinylib/msgp v1.1.6 github.com/tinylib/msgp v1.1.7-0.20211026165309-e818a1881b0e
github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/bytebufferpool v1.0.0
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
github.com/yargevad/filepathx v1.0.0 github.com/yargevad/filepathx v1.0.0

6
go.sum
View File

@ -1218,8 +1218,9 @@ github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bA
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/philhofer/fwd v1.1.2-0.20210722190033-5c56ac6d0bb9 h1:6ob53CVz+ja2i7easAStApZJlh7sxyq3Cm7g1Di6iqA=
github.com/philhofer/fwd v1.1.2-0.20210722190033-5c56ac6d0bb9/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@ -1430,8 +1431,9 @@ github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiff
github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tinylib/msgp v1.1.7-0.20211026165309-e818a1881b0e h1:P5tyWbssToKowBPTA1/EzqPXwrZNc8ZeNPdjgpcDEoI=
github.com/tinylib/msgp v1.1.7-0.20211026165309-e818a1881b0e/go.mod h1:g7jEyb18KPe65d9RRhGw+ThaJr5duyBH8eaFgBUor7Y=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=