mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-15 16:28:30 +01:00
s3 lifecycle: count filer entries a walk reads but skips as progress (#12658)
* dailyrun: count entries read from the filer, skipped ones included A versioned bucket's pre-pass and a resumed walk read entries without emitting them, so a walk can read for hours with no visited entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * s3 lifecycle: count listed entries as progress work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
53377bcb5d
commit
fd4603d657
5 files changed
+75
-7
No files matched your search
@@ -1,10 +1,14 @@
|
||||
package dailyrun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/stats"
|
||||
)
|
||||
|
||||
@@ -46,7 +50,10 @@ type RunSummary struct {
|
||||
EntriesVisited int64
|
||||
// Distinct object keys among the listed entries.
|
||||
ObjectsVisited int64
|
||||
EventsScanned int64
|
||||
// Every entry read from the filer, including the ones a walk skips
|
||||
// without visiting (versioned pre-pass, resume start, other shards).
|
||||
EntriesListed int64
|
||||
EventsScanned int64
|
||||
// LifecycleDelete outcomes keyed by the dispatch_total outcome label.
|
||||
// They count RPC results, not reclaimed space.
|
||||
Outcomes map[string]int64
|
||||
@@ -63,6 +70,7 @@ type RunSummary struct {
|
||||
type RunStats struct {
|
||||
entries atomic.Int64
|
||||
objects atomic.Int64
|
||||
listed atomic.Int64
|
||||
events atomic.Int64
|
||||
|
||||
mu sync.Mutex
|
||||
@@ -123,6 +131,41 @@ func (s *RunStats) visitEntry(newObject bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// CountListed returns client with its ListEntries results counted as
|
||||
// EntriesListed.
|
||||
func (s *RunStats) CountListed(client filer_pb.SeaweedFilerClient) filer_pb.SeaweedFilerClient {
|
||||
if s == nil || client == nil {
|
||||
return client
|
||||
}
|
||||
return &listCountingClient{SeaweedFilerClient: client, stats: s}
|
||||
}
|
||||
|
||||
type listCountingClient struct {
|
||||
filer_pb.SeaweedFilerClient
|
||||
stats *RunStats
|
||||
}
|
||||
|
||||
func (c *listCountingClient) ListEntries(ctx context.Context, in *filer_pb.ListEntriesRequest, opts ...grpc.CallOption) (filer_pb.SeaweedFiler_ListEntriesClient, error) {
|
||||
stream, err := c.SeaweedFilerClient.ListEntries(ctx, in, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &listCountingStream{SeaweedFiler_ListEntriesClient: stream, stats: c.stats}, nil
|
||||
}
|
||||
|
||||
type listCountingStream struct {
|
||||
filer_pb.SeaweedFiler_ListEntriesClient
|
||||
stats *RunStats
|
||||
}
|
||||
|
||||
func (s *listCountingStream) Recv() (*filer_pb.ListEntriesResponse, error) {
|
||||
resp, err := s.SeaweedFiler_ListEntriesClient.Recv()
|
||||
if err == nil && resp.GetEntry() != nil {
|
||||
s.stats.listed.Add(1)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *RunStats) scanEvent() {
|
||||
if s == nil {
|
||||
return
|
||||
@@ -176,6 +219,7 @@ func (s *RunStats) Summary() RunSummary {
|
||||
out := RunSummary{
|
||||
EntriesVisited: s.entries.Load(),
|
||||
ObjectsVisited: s.objects.Load(),
|
||||
EntriesListed: s.listed.Load(),
|
||||
EventsScanned: s.events.Load(),
|
||||
}
|
||||
s.mu.Lock()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/s3_lifecycle_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle/bootstrap"
|
||||
@@ -167,3 +168,24 @@ func TestRunStats_NilIsANoOp(t *testing.T) {
|
||||
runStats.setShardErrors(1)
|
||||
assert.Equal(t, RunSummary{}, runStats.Summary())
|
||||
}
|
||||
|
||||
// A walk can read a bucket without visiting anything: the versioned
|
||||
// pre-pass reads plain files without emitting them, and a resumed walk
|
||||
// skips every key up to its start.
|
||||
func TestRunStats_CountListedCountsSkippedEntries(t *testing.T) {
|
||||
mtime := time.Now().Add(-7 * 24 * time.Hour)
|
||||
client := &fakeFiler{tree: map[string][]*filer_pb.Entry{
|
||||
"/buckets/bkt": {file("a.txt", mtime, 1), file("b.txt", mtime, 1), file("c.txt", mtime, 1)},
|
||||
}}
|
||||
stats := &RunStats{}
|
||||
emitted := 0
|
||||
require.NoError(t, FilerListFunc(stats.CountListed(client), "/buckets", nil)(context.Background(), "bkt", "c.txt", func(*bootstrap.Entry) error {
|
||||
emitted++
|
||||
return nil
|
||||
}))
|
||||
|
||||
assert.Zero(t, emitted)
|
||||
assert.Equal(t, int64(6), stats.Summary().EntriesListed, "both passes over the versioned bucket")
|
||||
var nilStats *RunStats
|
||||
assert.Same(t, client, nilStats.CountListed(client))
|
||||
}
|
||||
Reference in new issue
Block a user