mirror of
https://github.com/minio/minio.git
synced 2025-11-30 13:42:53 -05:00
xl: Implement posix.DeletePrefixes to enhance delete perf (#9100)
Bulk delete API was using cleanupObjectsBulk() which calls posix listing and delete API to remove objects internal files in the backend (xl.json and parts) one by one. Add DeletePrefixes in the storage API to remove the content of a directory in a single call. Also use a remove goroutine for each disk to accelerate removal.
This commit is contained in:
86
cmd/posix.go
86
cmd/posix.go
@@ -1265,16 +1265,28 @@ func (s *posix) StatFile(volume, path string) (file FileInfo, err error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// deleteFile deletes a file path if its empty. If it's successfully deleted,
|
||||
// it will recursively move up the tree, deleting empty parent directories
|
||||
// until it finds one with files in it. Returns nil for a non-empty directory.
|
||||
func deleteFile(basePath, deletePath string) error {
|
||||
if basePath == deletePath {
|
||||
// deleteFile deletes a file or a directory if its empty unless recursive
|
||||
// is set to true. If the target is successfully deleted, it will recursively
|
||||
// move up the tree, deleting empty parent directories until it finds one
|
||||
// with files in it. Returns nil for a non-empty directory even when
|
||||
// recursive is set to false.
|
||||
func deleteFile(basePath, deletePath string, recursive bool) error {
|
||||
if basePath == "" || deletePath == "" {
|
||||
return nil
|
||||
}
|
||||
basePath = filepath.Clean(basePath)
|
||||
deletePath = filepath.Clean(deletePath)
|
||||
if !strings.HasPrefix(deletePath, basePath) || deletePath == basePath {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attempt to remove path.
|
||||
if err := os.Remove((deletePath)); err != nil {
|
||||
var err error
|
||||
if recursive {
|
||||
err = os.RemoveAll(deletePath)
|
||||
} else {
|
||||
err = os.Remove(deletePath)
|
||||
}
|
||||
if err != nil {
|
||||
switch {
|
||||
case isSysErrNotEmpty(err):
|
||||
// Ignore errors if the directory is not empty. The server relies on
|
||||
@@ -1297,12 +1309,58 @@ func deleteFile(basePath, deletePath string) error {
|
||||
deletePath = strings.TrimSuffix(deletePath, SlashSeparator)
|
||||
deletePath = slashpath.Dir(deletePath)
|
||||
|
||||
// Delete parent directory. Errors for parent directories shouldn't trickle down.
|
||||
deleteFile(basePath, deletePath)
|
||||
// Delete parent directory obviously not recursively. Errors for
|
||||
// parent directories shouldn't trickle down.
|
||||
deleteFile(basePath, deletePath, false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePrefixes forcibly deletes all the contents of a set of specified paths.
|
||||
// Parent directories are automatically removed if they become empty. err can
|
||||
// bil nil while errs can contain some errors for corresponding objects. No error
|
||||
// is set if a specified prefix path does not exist.
|
||||
func (s *posix) DeletePrefixes(volume string, paths []string) (errs []error, err error) {
|
||||
atomic.AddInt32(&s.activeIOCount, 1)
|
||||
defer func() {
|
||||
atomic.AddInt32(&s.activeIOCount, -1)
|
||||
}()
|
||||
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stat a volume entry.
|
||||
_, err = os.Stat(volumeDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errVolumeNotFound
|
||||
} else if os.IsPermission(err) {
|
||||
return nil, errVolumeAccessDenied
|
||||
} else if isSysErrIO(err) {
|
||||
return nil, errFaultyDisk
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs = make([]error, len(paths))
|
||||
// Following code is needed so that we retain SlashSeparator
|
||||
// suffix if any in path argument.
|
||||
for idx, path := range paths {
|
||||
filePath := pathJoin(volumeDir, path)
|
||||
errs[idx] = checkPathLength(filePath)
|
||||
if errs[idx] != nil {
|
||||
continue
|
||||
}
|
||||
// Delete file or a directory recursively, delete parent
|
||||
// directory as well if its empty.
|
||||
errs[idx] = deleteFile(volumeDir, filePath, true)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteFile - delete a file at path.
|
||||
func (s *posix) DeleteFile(volume, path string) (err error) {
|
||||
atomic.AddInt32(&s.activeIOCount, 1)
|
||||
@@ -1316,7 +1374,7 @@ func (s *posix) DeleteFile(volume, path string) (err error) {
|
||||
}
|
||||
|
||||
// Stat a volume entry.
|
||||
_, err = os.Stat((volumeDir))
|
||||
_, err = os.Stat(volumeDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errVolumeNotFound
|
||||
@@ -1331,12 +1389,12 @@ func (s *posix) DeleteFile(volume, path string) (err error) {
|
||||
// Following code is needed so that we retain SlashSeparator suffix if any in
|
||||
// path argument.
|
||||
filePath := pathJoin(volumeDir, path)
|
||||
if err = checkPathLength((filePath)); err != nil {
|
||||
if err = checkPathLength(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete file and delete parent directory as well if its empty.
|
||||
return deleteFile(volumeDir, filePath)
|
||||
return deleteFile(volumeDir, filePath, false)
|
||||
}
|
||||
|
||||
func (s *posix) DeleteFileBulk(volume string, paths []string) (errs []error, err error) {
|
||||
@@ -1373,7 +1431,7 @@ func (s *posix) DeleteFileBulk(volume string, paths []string) (errs []error, err
|
||||
continue
|
||||
}
|
||||
// Delete file and delete parent directory as well if its empty.
|
||||
errs[idx] = deleteFile(volumeDir, filePath)
|
||||
errs[idx] = deleteFile(volumeDir, filePath, false)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1460,7 +1518,7 @@ func (s *posix) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) (err e
|
||||
|
||||
// Remove parent dir of the source file if empty
|
||||
if parentDir := slashpath.Dir(srcFilePath); isDirEmpty(parentDir) {
|
||||
deleteFile(srcVolumeDir, parentDir)
|
||||
deleteFile(srcVolumeDir, parentDir, false)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user