Skip to content

Commit 6edc4b4

Browse files
*: estimate copy to cloud speed and remaining time
Implement logic to track copy progress and calculate average speed.Estimate remaining time based on current throughput to improve observability. Signed-off-by: huanghaoyuanhhy <haoyuan.huang@zilliz.com>
1 parent 3c893c1 commit 6edc4b4

5 files changed

Lines changed: 156 additions & 84 deletions

File tree

core/meta/taskmgr/migrate.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package taskmgr
22

33
import (
44
"sync"
5+
"time"
56

67
"github.com/vbauerster/mpb/v8"
78
"github.com/vbauerster/mpb/v8/decor"
@@ -20,15 +21,15 @@ func SetMigrateJobID(jobID string) MigrateTaskOpt {
2021
}
2122
}
2223

23-
func IncMigrateCopiedSize(size int64) MigrateTaskOpt {
24+
func IncMigrateCopiedSize(size int64, cost time.Duration) MigrateTaskOpt {
2425
return func(task *MigrateTask) {
2526
task.mu.Lock()
2627
defer task.mu.Unlock()
2728

2829
task.copiedSize += size
2930

3031
if task.bar != nil {
31-
task.bar.IncrInt64(size)
32+
task.bar.EwmaIncrInt64(size, cost)
3233
}
3334
}
3435
}
@@ -38,8 +39,12 @@ func SetMigrateCopyStart() MigrateTaskOpt {
3839
opts := []mpb.BarOption{
3940
mpb.PrependDecorators(
4041
decor.Name("Uploading", decor.WC{C: decor.DindentRight | decor.DextraSpace}),
41-
decor.Counters(decor.SizeB1024(0), "% .1f / % .1f")),
42-
mpb.AppendDecorators(decor.Percentage()),
42+
decor.Counters(decor.SizeB1024(0), "% .2f / % .2f")),
43+
mpb.AppendDecorators(
44+
decor.EwmaETA(decor.ET_STYLE_GO, 30),
45+
decor.Name(" ] "),
46+
decor.EwmaSpeed(decor.SizeB1024(0), "% .2f", 30),
47+
),
4348
mpb.BarRemoveOnComplete(),
4449
}
4550

@@ -49,7 +54,7 @@ func SetMigrateCopyStart() MigrateTaskOpt {
4954
// The size in backupinfo does not include all partition L0 (partition id == -1)
5055
// and meta, so the total cannot be set directly.
5156
// Need to use dyn total.
52-
task.bar = progressbar.Progress().AddBar(0, opts...)
57+
task.bar = progressbar.Progress().New(0, mpb.BarStyle().Rbound("|"), opts...)
5358
task.bar.SetTotal(task.totalSize, false)
5459
}
5560
}

core/migrate/task.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (s *stage) IsExpired() bool {
8080
}
8181

8282
func (s *stage) apply(ctx context.Context) error {
83-
s.logger.Info("apply stage")
83+
s.logger.Info("apply stage, it will take about 10 seconds")
8484
// use taskID as dir
8585
start := time.Now()
8686
resp, err := s.cloudCli.ApplyStage(ctx, s.clusterID, s.taskID)
@@ -192,9 +192,11 @@ func (t *Task) copyToCloud(ctx context.Context) error {
192192
SrcPrefix: t.backupDir,
193193
DestPrefix: t.stage.resp.UploadPath,
194194
Sem: t.copySem,
195-
OnSuccess: func(copyAttr storage.CopyAttr) {
196-
t.taskMgr.UpdateMigrateTask(t.taskID, taskmgr.IncMigrateCopiedSize(copyAttr.Src.Length))
195+
196+
TraceFn: func(size int64, cost time.Duration) {
197+
t.taskMgr.UpdateMigrateTask(t.taskID, taskmgr.IncMigrateCopiedSize(size, cost))
197198
},
199+
198200
CopyByServer: true,
199201
}
200202

core/storage/copier.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package storage
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"time"
8+
9+
"go.uber.org/zap"
10+
11+
"github.com/zilliztech/milvus-backup/internal/log"
12+
"github.com/zilliztech/milvus-backup/internal/util/retry"
13+
)
14+
15+
type CopyAttr struct {
16+
Src ObjectAttr
17+
DestKey string
18+
}
19+
20+
type copierOpt struct {
21+
traceFn TraceFn
22+
}
23+
24+
type TraceFn func(size int64, cost time.Duration)
25+
26+
type trackReader struct {
27+
inner io.Reader
28+
29+
lastRead time.Time
30+
traceFn TraceFn
31+
}
32+
33+
func newTrackReader(inner io.Reader, traceFn TraceFn) *trackReader {
34+
return &trackReader{inner: inner, traceFn: traceFn, lastRead: time.Now()}
35+
}
36+
37+
func (t *trackReader) Read(p []byte) (int, error) {
38+
n, err := t.inner.Read(p)
39+
if n > 0 {
40+
t.traceFn(int64(n), time.Since(t.lastRead))
41+
t.lastRead = time.Now()
42+
}
43+
return n, err
44+
}
45+
46+
type copier interface {
47+
copy(ctx context.Context, copyAttr CopyAttr) error
48+
}
49+
50+
// remoteCopier copy data from src to dest by calling dest.CopyObject
51+
type remoteCopier struct {
52+
src Client
53+
dest Client
54+
55+
logger *zap.Logger
56+
}
57+
58+
func (rp *remoteCopier) copy(ctx context.Context, copyAttr CopyAttr) error {
59+
rp.logger.Debug("copy object", zap.String("src", copyAttr.Src.Key), zap.String("dest", copyAttr.DestKey))
60+
i := CopyObjectInput{SrcCli: rp.src, SrcKey: copyAttr.Src.Key, DestKey: copyAttr.DestKey}
61+
if err := rp.dest.CopyObject(ctx, i); err != nil {
62+
return fmt.Errorf("storage: remote copier copy object %w", err)
63+
}
64+
65+
return nil
66+
}
67+
68+
// serverCopier copy data from src to dest by backup server
69+
type serverCopier struct {
70+
src Client
71+
dest Client
72+
73+
opt copierOpt
74+
75+
logger *zap.Logger
76+
}
77+
78+
func (sc *serverCopier) copy(ctx context.Context, copyAttr CopyAttr) error {
79+
sc.logger.Debug("copy object", zap.String("src_key", copyAttr.Src.Key), zap.String("dest_key", copyAttr.DestKey))
80+
81+
return retry.Do(ctx, func() error {
82+
obj, err := sc.src.GetObject(ctx, copyAttr.Src.Key)
83+
if err != nil {
84+
return fmt.Errorf("storage: server copier get object %w", err)
85+
}
86+
defer obj.Body.Close()
87+
88+
body := io.Reader(obj.Body)
89+
if sc.opt.traceFn != nil {
90+
body = newTrackReader(obj.Body, sc.opt.traceFn)
91+
}
92+
93+
i := UploadObjectInput{Body: body, Key: copyAttr.DestKey, Size: copyAttr.Src.Length}
94+
if err := sc.dest.UploadObject(ctx, i); err != nil {
95+
return fmt.Errorf("storage: copier upload object %w", err)
96+
}
97+
98+
return nil
99+
})
100+
}
101+
102+
func newCopier(src, dest Client, copyByServer bool, opt copierOpt) copier {
103+
logger := log.L().With(
104+
zap.String("src", src.Config().Bucket),
105+
zap.String("dest", dest.Config().Bucket),
106+
)
107+
if copyByServer {
108+
return &serverCopier{src: src, dest: dest, opt: opt, logger: logger.With(zap.String("copier", "server"))}
109+
}
110+
return &remoteCopier{src: src, dest: dest, logger: logger.With(zap.String("copier", "remote"))}
111+
}
Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,39 @@ import (
55
"context"
66
"io"
77
"testing"
8+
"time"
89

910
"github.com/stretchr/testify/assert"
1011
"github.com/stretchr/testify/mock"
1112
"go.uber.org/zap"
1213
)
1314

14-
func TestRemoteCopier_copy(t *testing.T) {
15+
func TestTrackReader_Read(t *testing.T) {
16+
t.Run("ReadGTZero", func(t *testing.T) {
17+
tr := newTrackReader(bytes.NewReader([]byte("hello")), func(size int64, cost time.Duration) {
18+
assert.Equal(t, 5, size)
19+
assert.True(t, cost > 0)
20+
})
21+
22+
buf := make([]byte, 10)
23+
n, err := tr.Read(buf)
24+
assert.NoError(t, err)
25+
assert.Equal(t, 5, n)
26+
})
27+
28+
t.Run("ReadZero", func(t *testing.T) {
29+
tr := newTrackReader(bytes.NewReader([]byte("hello")), func(size int64, cost time.Duration) {
30+
assert.Fail(t, "should not be called")
31+
})
32+
33+
buf := make([]byte, 0)
34+
n, err := tr.Read(buf)
35+
assert.NoError(t, err)
36+
assert.Equal(t, 0, n)
37+
})
38+
}
39+
40+
func TestRemoteCopier_Copy(t *testing.T) {
1541
dest := NewMockClient(t)
1642
src := NewMockClient(t)
1743
rp := &remoteCopier{src: src, dest: dest, logger: zap.NewNop()}

core/storage/copy_task.go

Lines changed: 3 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -10,76 +10,8 @@ import (
1010
"golang.org/x/sync/semaphore"
1111

1212
"github.com/zilliztech/milvus-backup/internal/log"
13-
"github.com/zilliztech/milvus-backup/internal/util/retry"
1413
)
1514

16-
type OnSuccessFn func(copyAttr CopyAttr)
17-
18-
type CopyAttr struct {
19-
Src ObjectAttr
20-
DestKey string
21-
}
22-
23-
type copier interface {
24-
copy(ctx context.Context, copyAttr CopyAttr) error
25-
}
26-
27-
// remoteCopier copy data from src to dest by calling dest.CopyObject
28-
type remoteCopier struct {
29-
src Client
30-
dest Client
31-
32-
logger *zap.Logger
33-
}
34-
35-
func (rp *remoteCopier) copy(ctx context.Context, copyAttr CopyAttr) error {
36-
rp.logger.Debug("copy object", zap.String("src", copyAttr.Src.Key), zap.String("dest", copyAttr.DestKey))
37-
i := CopyObjectInput{SrcCli: rp.src, SrcKey: copyAttr.Src.Key, DestKey: copyAttr.DestKey}
38-
if err := rp.dest.CopyObject(ctx, i); err != nil {
39-
return fmt.Errorf("storage: remote copier copy object %w", err)
40-
}
41-
42-
return nil
43-
}
44-
45-
// serverCopier copy data from src to dest by backup server
46-
type serverCopier struct {
47-
src Client
48-
dest Client
49-
50-
logger *zap.Logger
51-
}
52-
53-
func (sc *serverCopier) copy(ctx context.Context, copyAttr CopyAttr) error {
54-
sc.logger.Debug("copy object", zap.String("src_key", copyAttr.Src.Key), zap.String("dest_key", copyAttr.DestKey))
55-
56-
return retry.Do(ctx, func() error {
57-
obj, err := sc.src.GetObject(ctx, copyAttr.Src.Key)
58-
if err != nil {
59-
return fmt.Errorf("storage: server copier get object %w", err)
60-
}
61-
defer obj.Body.Close()
62-
63-
i := UploadObjectInput{Body: obj.Body, Key: copyAttr.DestKey, Size: copyAttr.Src.Length}
64-
if err := sc.dest.UploadObject(ctx, i); err != nil {
65-
return fmt.Errorf("storage: copier upload object %w", err)
66-
}
67-
68-
return nil
69-
})
70-
}
71-
72-
func newCopier(src, dest Client, copyByServer bool) copier {
73-
logger := log.L().With(
74-
zap.String("src", src.Config().Bucket),
75-
zap.String("dest", dest.Config().Bucket),
76-
)
77-
if copyByServer {
78-
return &serverCopier{src: src, dest: dest, logger: logger.With(zap.String("copier", "server"))}
79-
}
80-
return &remoteCopier{src: src, dest: dest, logger: logger.With(zap.String("copier", "remote"))}
81-
}
82-
8315
type CopyPrefixOpt struct {
8416
Src Client
8517
Dest Client
@@ -89,7 +21,7 @@ type CopyPrefixOpt struct {
8921

9022
Sem *semaphore.Weighted
9123

92-
OnSuccess OnSuccessFn
24+
TraceFn TraceFn
9325

9426
CopyByServer bool
9527
}
@@ -106,7 +38,7 @@ func NewCopyPrefixTask(opt CopyPrefixOpt) *CopyPrefixTask {
10638
return &CopyPrefixTask{
10739
opt: opt,
10840

109-
copier: newCopier(opt.Src, opt.Dest, opt.CopyByServer),
41+
copier: newCopier(opt.Src, opt.Dest, opt.CopyByServer, copierOpt{traceFn: opt.TraceFn}),
11042

11143
logger: log.L().With(zap.String("src", opt.SrcPrefix), zap.String("dest", opt.DestPrefix)),
11244
}
@@ -120,10 +52,6 @@ func (c *CopyPrefixTask) copy(ctx context.Context, src ObjectAttr) error {
12052
return fmt.Errorf("storage: copy prefix %w", err)
12153
}
12254

123-
if c.opt.OnSuccess != nil {
124-
c.opt.OnSuccess(attr)
125-
}
126-
12755
return nil
12856
}
12957

@@ -186,7 +114,7 @@ func NewCopyObjectsTask(opt CopyObjectsOpt) *CopyObjectsTask {
186114
return &CopyObjectsTask{
187115
opt: opt,
188116

189-
copier: newCopier(opt.Src, opt.Dest, opt.CopyByServer),
117+
copier: newCopier(opt.Src, opt.Dest, opt.CopyByServer, copierOpt{}),
190118
}
191119
}
192120

0 commit comments

Comments
 (0)