mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-12 06:48:45 +01:00
filer.meta.backup: resume an interrupted metadata copy and add -entriesPerSecond (#12549)
* filer_pb: add a depth-first traversal that can resume after a path TraverseDfs walks a tree in listing order and reports each finished path, so a caller can save one path and continue from it later. Pages are read whole before visiting, and the entries of a page are visited by up to five workers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * filer.meta.backup: resume an interrupted metadata copy The walk saves the last finished path every few seconds, and a walk that continues an unfinished one starts after it. A fresh walk clears the saved path first, and a finished walk clears it with the walk start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * filer.meta.backup: add -entriesPerSecond to throttle the metadata copy Each entry the full copy reads waits on a rate limiter, and pages are listed only after the previous page is copied, so the flag also bounds how fast the copy lists the source filer. 0 keeps it unlimited. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * filer_pb: report each file of a depth-first walk once all before it are done A throttled walk only reported progress after a whole listing page, so a slow copy could go many minutes without a resumable path. Visit entries in runs that end at a directory and report each file as soon as every entry before it is done. 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
0ef0260844
commit
e9606c8ecc
4 files changed
+372
-5
No files matched your search
@@ -118,3 +118,52 @@ func TestCopyMetadataIgnoresAWalkStartTheStreamPassed(t *testing.T) {
|
||||
t.Fatalf("offset = %v, want a fresh walk's start after %v", offset, beforeWalk)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCopyMetadataResumesOnlyItsOwnWalk: the saved walk path belongs to the
|
||||
// walk whose start the stream replays from; a fresh walk must not skip ahead.
|
||||
func TestCopyMetadataResumesOnlyItsOwnWalk(t *testing.T) {
|
||||
backup := &FilerMetaBackupOptions{store: &kvOnlyStore{kv: map[string][]byte{}}}
|
||||
walkedTo := func() string {
|
||||
value, err := backup.store.KvGet(context.Background(), MetaBackupWalkPathKey)
|
||||
if errors.Is(err, filer.ErrKvNotFound) {
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(value)
|
||||
}
|
||||
|
||||
interrupted := errors.New("killed")
|
||||
if err := backup.copyMetadata(func() error {
|
||||
return errors.Join(backup.store.KvPut(context.Background(), MetaBackupWalkPathKey, []byte("/a/b")), interrupted)
|
||||
}); !errors.Is(err, interrupted) {
|
||||
t.Fatalf("interrupted walk: err = %v", err)
|
||||
}
|
||||
if err := backup.copyMetadata(func() error {
|
||||
if got := walkedTo(); got != "/a/b" {
|
||||
t.Fatalf("resumed walk sees %q, want /a/b", got)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := walkedTo(); got != "" {
|
||||
t.Fatalf("walk path after a finished walk = %q, want it cleared", got)
|
||||
}
|
||||
|
||||
if err := backup.setTime(MetaBackupWalkKey, time.Now().Add(-time.Hour)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := backup.store.KvPut(context.Background(), MetaBackupWalkPathKey, []byte("/x")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := backup.copyMetadata(func() error {
|
||||
if got := walkedTo(); got != "" {
|
||||
t.Fatalf("fresh walk sees %q left by a walk the stream passed", got)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user