do delete expired entries on s3 list request (#7426)
* do delete expired entries on s3 list request https://github.com/seaweedfs/seaweedfs/issues/6837 * disable delete expires s3 entry in filer * pass opt allowDeleteObjectsByTTL to all servers * delete on get and head * add lifecycle expiration s3 tests * fix opt allowDeleteObjectsByTTL for server * fix test lifecycle expiration * fix IsExpired * fix locationPrefix for updateEntriesTTL * fix s3tests * resolv coderabbitai * GetS3ExpireTime on filer * go mod * clear TtlSeconds for volume * move s3 delete expired entry to filer * filer delete meta and data * del unusing func removeExpiredObject * test s3 put * test s3 put multipart * allowDeleteObjectsByTTL by default * fix pipline tests * rm dublicate SeaweedFSExpiresS3 * revert expiration tests * fix updateTTL * rm log * resolv comment * fix delete version object * fix S3Versioning * fix delete on FindEntry * fix delete chunks * fix sqlite not support concurrent writes/reads * move deletion out of listing transaction; delete entries and empty folders * Revert "fix sqlite not support concurrent writes/reads" This reverts commit 5d5da14e0ed91c613fe5c0ed058f58bb04fba6f0. * clearer handling on recursive empty directory deletion * handle listing errors * strut copying * reuse code to delete empty folders * use iterative approach with a queue to avoid recursive WithFilerClient calls * stop a gRPC stream from the client-side callback is to return a specific error, e.g., io.EOF * still issue UpdateEntry when the flag must be added * errors join * join path * cleaner * add context, sort directories by depth (deepest first) to avoid redundant checks * batched operation, refactoring * prevent deleting bucket * constant * reuse code * more logging * refactoring * s3 TTL time * Safety check --------- Co-authored-by: chrislu <chris.lu@gmail.com>
This commit is contained in:
committed by
GitHub
parent
cc444b1868
commit
084b377f87
@@ -308,3 +308,59 @@ func DoRemove(ctx context.Context, client SeaweedFilerClient, parentDirectoryPat
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoDeleteEmptyParentDirectories recursively deletes empty parent directories.
|
||||
// It stops at root "/" or at stopAtPath.
|
||||
// For safety, dirPath must be under stopAtPath (when stopAtPath is provided).
|
||||
// The checked map tracks already-processed directories to avoid redundant work in batch operations.
|
||||
func DoDeleteEmptyParentDirectories(ctx context.Context, client SeaweedFilerClient, dirPath util.FullPath, stopAtPath util.FullPath, checked map[string]bool) {
|
||||
if dirPath == "/" || dirPath == stopAtPath {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip if already checked (for batch delete optimization)
|
||||
dirPathStr := string(dirPath)
|
||||
if checked != nil {
|
||||
if checked[dirPathStr] {
|
||||
return
|
||||
}
|
||||
checked[dirPathStr] = true
|
||||
}
|
||||
|
||||
// Safety check: if stopAtPath is provided, dirPath must be under it (root "/" allows everything)
|
||||
stopStr := string(stopAtPath)
|
||||
if stopAtPath != "" && stopStr != "/" && !strings.HasPrefix(dirPathStr+"/", stopStr+"/") {
|
||||
glog.V(1).InfofCtx(ctx, "DoDeleteEmptyParentDirectories: %s is not under %s, skipping", dirPath, stopAtPath)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if directory is empty by listing with limit 1
|
||||
isEmpty := true
|
||||
err := SeaweedList(ctx, client, dirPathStr, "", func(entry *Entry, isLast bool) error {
|
||||
isEmpty = false
|
||||
return io.EOF // Use sentinel error to explicitly stop iteration
|
||||
}, "", false, 1)
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
glog.V(3).InfofCtx(ctx, "DoDeleteEmptyParentDirectories: error checking %s: %v", dirPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !isEmpty {
|
||||
// Directory is not empty, stop checking upward
|
||||
glog.V(3).InfofCtx(ctx, "DoDeleteEmptyParentDirectories: directory %s is not empty, stopping cleanup", dirPath)
|
||||
return
|
||||
}
|
||||
|
||||
// Directory is empty, try to delete it
|
||||
glog.V(2).InfofCtx(ctx, "DoDeleteEmptyParentDirectories: deleting empty directory %s", dirPath)
|
||||
parentDir, dirName := dirPath.DirAndName()
|
||||
|
||||
if err := DoRemove(ctx, client, parentDir, dirName, false, false, false, false, nil); err == nil {
|
||||
// Successfully deleted, continue checking upwards
|
||||
DoDeleteEmptyParentDirectories(ctx, client, util.FullPath(parentDir), stopAtPath, checked)
|
||||
} else {
|
||||
// Failed to delete, stop cleanup
|
||||
glog.V(3).InfofCtx(ctx, "DoDeleteEmptyParentDirectories: failed to delete %s: %v", dirPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
||||
"github.com/viant/ptrie"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -24,6 +25,31 @@ func (entry *Entry) IsDirectoryKeyObject() bool {
|
||||
return entry.IsDirectory && entry.Attributes != nil && entry.Attributes.Mime != ""
|
||||
}
|
||||
|
||||
func (entry *Entry) GetExpiryTime() (expiryTime int64) {
|
||||
// For S3 objects with lifecycle expiration, use Mtime (modification time)
|
||||
// For regular TTL entries, use Crtime (creation time) for backward compatibility
|
||||
if entry.Extended != nil {
|
||||
if _, hasS3Expiry := entry.Extended[s3_constants.SeaweedFSExpiresS3]; hasS3Expiry {
|
||||
// S3 lifecycle expiration: base TTL on modification time
|
||||
expiryTime = entry.Attributes.Mtime
|
||||
if expiryTime == 0 {
|
||||
expiryTime = entry.Attributes.Crtime
|
||||
}
|
||||
expiryTime += int64(entry.Attributes.TtlSec)
|
||||
return expiryTime
|
||||
}
|
||||
}
|
||||
|
||||
// Regular TTL expiration: base on creation time only
|
||||
expiryTime = entry.Attributes.Crtime + int64(entry.Attributes.TtlSec)
|
||||
return expiryTime
|
||||
}
|
||||
|
||||
func (entry *Entry) IsExpired() bool {
|
||||
return entry != nil && entry.Attributes != nil && entry.Attributes.TtlSec > 0 &&
|
||||
time.Now().Unix() >= entry.GetExpiryTime()
|
||||
}
|
||||
|
||||
func (entry *Entry) FileMode() (fileMode os.FileMode) {
|
||||
if entry != nil && entry.Attributes != nil {
|
||||
fileMode = os.FileMode(entry.Attributes.FileMode)
|
||||
|
||||
Reference in New Issue
Block a user