Skip to content

Commit 38dc5aa

Browse files
authored
PBM-1439 - Fix backup failure: MaxUploadParts exceeded (#1127)
* determine size of oplog in similar way with collections * update size calculation and consider compression * determine oplog window
1 parent 063dab6 commit 38dc5aa

File tree

1 file changed

+70
-1
lines changed

1 file changed

+70
-1
lines changed

pbm/backup/logical.go

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go.mongodb.org/mongo-driver/bson"
1212
"go.mongodb.org/mongo-driver/bson/primitive"
1313
"go.mongodb.org/mongo-driver/mongo"
14+
"go.mongodb.org/mongo-driver/mongo/options"
1415
"golang.org/x/sync/errgroup"
1516

1617
"github.com/percona/percona-backup-mongodb/pbm/archive"
@@ -90,7 +91,23 @@ func (b *Backup) doLogical(
9091
rsMeta.FirstWriteTS,
9192
func(ctx context.Context, w io.WriterTo, from, till primitive.Timestamp) (int64, error) {
9293
filename := rsMeta.OplogName + "/" + FormatChunkName(from, till, bcp.Compression)
93-
return storage.Upload(ctx, w, stg, bcp.Compression, bcp.CompressionLevel, filename, -1)
94+
95+
bytesPerSecond, err := getOplogBytesPerSecond(ctx, b.nodeConn)
96+
if err != nil {
97+
return 0, errors.Wrap(err, "oplog stats unavailable")
98+
}
99+
100+
// Estimate size with headroom and enforce minimum part size for cloud uploads.
101+
estimatedSize := bytesPerSecond * float64(till.T-from.T)
102+
if bcp.Compression == compress.CompressionTypeNone {
103+
estimatedSize *= 2
104+
}
105+
estimatedSize *= 2
106+
if estimatedSize < 1<<30 { // 1 GiB
107+
estimatedSize = 1 << 30
108+
}
109+
110+
return storage.Upload(ctx, w, stg, bcp.Compression, bcp.CompressionLevel, filename, int64(estimatedSize))
94111
})
95112
// ensure slicer is stopped in any case (done, error or canceled)
96113
defer stopOplogSlicer() //nolint:errcheck
@@ -458,6 +475,58 @@ func getNamespacesSize(ctx context.Context, m *mongo.Client, nss []string) (map[
458475
return rv, err
459476
}
460477

478+
// getOplogBytesPerSecond returns the average number of bytes written to the
479+
// replica‑set oplog each second. It fetches the total size of the
480+
// `local.oplog.rs` collection, reads the timestamps of the oldest and newest
481+
// entries, and divides the size by the seconds between those timestamps.
482+
func getOplogBytesPerSecond(ctx context.Context, m *mongo.Client) (float64, error) {
483+
coll := m.Database("local").Collection("oplog.rs")
484+
485+
var stat struct {
486+
Size int64 `bson:"size"`
487+
}
488+
cmd := bson.D{{Key: "collStats", Value: "oplog.rs"}}
489+
res := m.Database("local").RunCommand(ctx, cmd)
490+
if res.Err() != nil {
491+
return 0, errors.Wrap(res.Err(), "collStats local.oplog.rs")
492+
}
493+
if err := res.Decode(&stat); err != nil {
494+
return 0, errors.Wrap(err, "decode collStats")
495+
}
496+
497+
oldestTS, err := oplogTimestamp(ctx, coll, 1)
498+
if err != nil {
499+
return 0, err
500+
}
501+
newestTS, err := oplogTimestamp(ctx, coll, -1)
502+
if err != nil {
503+
return 0, err
504+
}
505+
506+
duration := int64(newestTS.T - oldestTS.T)
507+
if duration <= 0 {
508+
return 0, errors.New("invalid oplog window duration")
509+
}
510+
511+
return float64(stat.Size) / float64(duration), nil
512+
}
513+
514+
// oplogTimestamp returns the timestamp of the first (sort=1) or last (sort=-1) document in the oplog.
515+
func oplogTimestamp(ctx context.Context, coll *mongo.Collection, sort int) (primitive.Timestamp, error) {
516+
var doc bson.M
517+
err := coll.FindOne(ctx, bson.D{}, options.FindOne().SetSort(bson.D{{Key: "$natural", Value: sort}})).Decode(&doc)
518+
if err != nil {
519+
return primitive.Timestamp{}, errors.Wrap(err, "query oplog timestamp")
520+
}
521+
522+
ts, ok := doc["ts"].(primitive.Timestamp)
523+
if !ok {
524+
return primitive.Timestamp{}, errors.New("missing or invalid 'ts' field")
525+
}
526+
527+
return ts, nil
528+
}
529+
461530
func (b *Backup) checkForTimeseries(ctx context.Context, nss []string) error {
462531
if !b.brief.Version.IsShardedTimeseriesSupported() || !b.brief.Sharded {
463532
return nil

0 commit comments

Comments
 (0)