From 5ea9a7ce15283c50c10e2dffee83d1c3d97df99d Mon Sep 17 00:00:00 2001 From: Erwan LE PRADO <113348625+erwanlpfr@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:30:02 +0200 Subject: [PATCH] feat: add better handler for part size (#214) * feat: add better handler for part size fix: use local file fix: try with another path fix: use bytes chore: go back go back readme goback goback goback * chore: better handling * fix: typo readme * chore: wrong comparaison * fix: typo --- README.md | 9 +++++++++ cmd/backup/config.go | 1 + cmd/backup/script.go | 1 + internal/storage/s3/s3.go | 26 +++++++++++++++++++++++--- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8416593..060c617 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,15 @@ You can populate below template according to your requirements and use it as you # AWS_STORAGE_CLASS="GLACIER" +# Setting this variable will change the S3 default part size for the copy step. +# This value is useful when you want to upload large files. +# NB : While using Scaleway as S3 provider, be aware that the parts counter is set to 1.000. +# While Minio uses a hard coded value to 10.000. As a workaround, try to set a higher value. +# Defaults to "16" (MB) if unset (from minio), you can set this value according to your needs. +# The unit is in MB and an integer. + +# AWS_PART_SIZE=16 + # You can also backup files to any WebDAV server: # The URL of the remote WebDAV server diff --git a/cmd/backup/config.go b/cmd/backup/config.go index 88ebba1..d3825e8 100644 --- a/cmd/backup/config.go +++ b/cmd/backup/config.go @@ -28,6 +28,7 @@ type Config struct { AwsSecretAccessKey string `split_words:"true"` AwsSecretAccessKeyFile string `split_words:"true"` AwsIamRoleEndpoint string `split_words:"true"` + AwsPartSize int64 `split_words:"true"` BackupSources string `split_words:"true" default:"/backup"` BackupFilename string `split_words:"true" default:"backup-%Y-%m-%dT%H-%M-%S.tar.gz"` BackupFilenameExpand bool `split_words:"true"` diff --git a/cmd/backup/script.go b/cmd/backup/script.go index 5c540dc..148e8e0 100644 --- a/cmd/backup/script.go +++ b/cmd/backup/script.go @@ -142,6 +142,7 @@ func newScript() (*script, error) { BucketName: s.c.AwsS3BucketName, StorageClass: s.c.AwsStorageClass, CACert: s.c.AwsEndpointCACert.Cert, + PartSize: s.c.AwsPartSize, } if s3Backend, err := s3.NewStorageBackend(s3Config, logFunc); err != nil { return nil, err diff --git a/internal/storage/s3/s3.go b/internal/storage/s3/s3.go index f753bd1..01514f3 100644 --- a/internal/storage/s3/s3.go +++ b/internal/storage/s3/s3.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "errors" "fmt" + "os" "path" "path/filepath" "time" @@ -22,6 +23,7 @@ type s3Storage struct { client *minio.Client bucket string storageClass string + partSize int64 } // Config contains values that define the configuration of a S3 backend. @@ -35,6 +37,7 @@ type Config struct { RemotePath string BucketName string StorageClass string + PartSize int64 CACert *x509.Certificate } @@ -89,6 +92,7 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error client: mc, bucket: opts.BucketName, storageClass: opts.StorageClass, + partSize: opts.PartSize, }, nil } @@ -100,16 +104,32 @@ func (v *s3Storage) Name() string { // Copy copies the given file to the S3/Minio storage backend. func (b *s3Storage) Copy(file string) error { _, name := path.Split(file) - - if _, err := b.client.FPutObject(context.Background(), b.bucket, filepath.Join(b.DestinationPath, name), file, minio.PutObjectOptions{ + putObjectOptions := minio.PutObjectOptions{ ContentType: "application/tar+gzip", StorageClass: b.storageClass, - }); err != nil { + } + + if b.partSize > 0 { + srcFileInfo, err := os.Stat(file) + if err != nil { + return fmt.Errorf("(*s3Storage).Copy: error reading the local file: %w", err) + } + + _, partSize, _, err := minio.OptimalPartInfo(srcFileInfo.Size(), uint64(b.partSize*1024*1024)) + if err != nil { + return fmt.Errorf("(*s3Storage).Copy: error computing the optimal s3 part size: %w", err) + } + + putObjectOptions.PartSize = uint64(partSize) + } + + if _, err := b.client.FPutObject(context.Background(), b.bucket, filepath.Join(b.DestinationPath, name), file, putObjectOptions); err != nil { if errResp := minio.ToErrorResponse(err); errResp.Message != "" { return fmt.Errorf("(*s3Storage).Copy: error uploading backup to remote storage: [Message]: '%s', [Code]: %s, [StatusCode]: %d", errResp.Message, errResp.Code, errResp.StatusCode) } return fmt.Errorf("(*s3Storage).Copy: error uploading backup to remote storage: %w", err) } + b.Log(storage.LogLevelInfo, b.Name(), "Uploaded a copy of backup `%s` to bucket `%s`.", file, b.bucket) return nil