mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-24 15:00:43 +02:00
s3: bound ListMultipartUploads memory to the page size (#11971)
* s3: bound ListMultipartUploads memory to the page size listMultipartUploads loaded every open upload in the bucket into a slice and selectMultipartUploads filtered and sorted a second full copy, so one request cost O(total open uploads) heap regardless of prefix, key-marker, or max-uploads; concurrent calls multiplied it and could OOM the gateway. The response is ordered by the object key kept in extended attributes, not the upload id the filer sorts by, so every entry still has to be seen. Streaming the listing into a bounded top-K collector keeps at most maxUploads (capped at maxUploadsList) candidates, bounding heap by the page instead of the folder. A caller-supplied max-uploads above the server cap now also paginates rather than accumulating unboundedly. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: cover bounded ListMultipartUploads accumulation Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: report the effective max-uploads cap in ListMultipartUploads * s3: cover ListMultipartUploads retry through the collector --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
7154732b84
commit
186bc5cf7f
3 files changed
+199
-40
No files matched your search
@@ -93,17 +93,17 @@ func isRetryableListError(err error) bool {
|
||||
return err != nil && !isFilerNotFound(err) && util.IsTransientError(err)
|
||||
}
|
||||
|
||||
// listWithRetry replays doList while the filer answers with a transient error.
|
||||
// listRetry replays doList while the filer answers with a transient error.
|
||||
// Both failure points, the ListEntries call itself and the stream.Recv that
|
||||
// follows it, surface as a plain error out of filer_pb.List, so a single retry
|
||||
// point above it covers both.
|
||||
func listWithRetry(parentDirectoryPath string, doList func() (entries []*filer_pb.Entry, isLast bool, err error)) (entries []*filer_pb.Entry, isLast bool, err error) {
|
||||
// point above it covers both. A replayed attempt may re-deliver entries the
|
||||
// last attempt already saw, so a doList that accumulates must reset first.
|
||||
func listRetry(parentDirectoryPath string, doList func() error) (err error) {
|
||||
|
||||
backoff := listRetryInitialBackoff
|
||||
for attempt := 1; ; attempt++ {
|
||||
entries, isLast, err = doList()
|
||||
if err == nil || attempt >= listRetryAttempts || !isRetryableListError(err) {
|
||||
return entries, isLast, err
|
||||
if err = doList(); err == nil || attempt >= listRetryAttempts || !isRetryableListError(err) {
|
||||
return err
|
||||
}
|
||||
glog.V(1).Infof("list %s attempt %d/%d hit a transient error, retrying in %v: %v", parentDirectoryPath, attempt, listRetryAttempts, backoff, err)
|
||||
time.Sleep(backoff)
|
||||
@@ -112,6 +112,16 @@ func listWithRetry(parentDirectoryPath string, doList func() (entries []*filer_p
|
||||
|
||||
}
|
||||
|
||||
func listWithRetry(parentDirectoryPath string, doList func() (entries []*filer_pb.Entry, isLast bool, err error)) (entries []*filer_pb.Entry, isLast bool, err error) {
|
||||
|
||||
err = listRetry(parentDirectoryPath, func() error {
|
||||
entries, isLast, err = doList()
|
||||
return err
|
||||
})
|
||||
return entries, isLast, err
|
||||
|
||||
}
|
||||
|
||||
// A delete is idempotent at the filer, which answers an entry that is already
|
||||
// gone with an empty resp.Error, so a reply the transport dropped can be
|
||||
// reissued instead of surfaced: as a 500 on the bucket delete, or as a per-key
|
||||
|
||||
Reference in new issue
Block a user