Skip to content

Commit d080ba0

Browse files
committed
Simplify GCS parallel upload config
1 parent f982cde commit d080ba0

6 files changed

Lines changed: 80 additions & 117 deletions

File tree

packaging/conf/pbm-conf-reference.yml

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,20 +140,16 @@
140140
# clientType: json
141141

142142
## The size of data chunks in bytes to be uploaded to the storage bucket in a single request.
143-
## Applies to standard Google SDK uploads only. It is not used when parallelUpload is enabled.
143+
## For parallel uploads, it is used as the temporary part size. When undefined,
144+
## PBM uses 10MB for standard uploads and 16MB for parallel uploads.
144145
# chunkSize: 10MB
145146

146-
## Experimental Google SDK parallel upload configuration.
147-
## This is used only with clientType: grpc. It creates temporary GCS objects and composes
148-
## them into the final object. The credentials must be allowed to delete temporary objects
149-
## for cleanup. Failed or interrupted uploads can leave temporary objects behind, so a
150-
## bucket lifecycle cleanup rule is recommended.
151-
# parallelUpload:
152-
# enabled: false
153-
## Size of each temporary part in bytes. When undefined or 0, the Google SDK default is used.
154-
# partSize: 16MB
155-
## Number of parallel upload workers. When undefined or 0, the Google SDK default is used.
156-
# maxConcurrency: 4
147+
## Experimental Google SDK parallel upload concurrency. Parallel upload is used only
148+
## with clientType: grpc and parallelUploadConcurrency greater than 1. It creates
149+
## temporary GCS objects and composes them into the final object. The credentials must
150+
## be allowed to delete temporary objects for cleanup. Failed or interrupted uploads can
151+
## leave temporary objects behind, so a bucket lifecycle cleanup rule is recommended.
152+
# parallelUploadConcurrency: 4
157153

158154
## GCS access credentials. You can use either HMAC or Service Account keys.
159155
## HMAC Example:
@@ -173,7 +169,7 @@
173169
# backoffInitial: 1s
174170
# backoffMax: 30s
175171
# backoffMultiplier: 2
176-
## Applies to standard Google SDK uploads only. It is not used when parallelUpload is enabled.
172+
## Applies to standard Google SDK uploads only. It is not used for parallel uploads.
177173
# chunkRetryDeadline: 32s
178174
#
179175
## The maximum object size that will be stored on the storage

pbm/config/config_test.go

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,10 @@ func TestConfig(t *testing.T) {
406406
ClientEmail: "ce1",
407407
PrivateKey: "pk1",
408408
},
409-
ClientType: gcs.ClientTypeJSON,
410-
ChunkSize: 100,
411-
MaxObjSizeGB: floatPtr(1.1),
409+
ClientType: gcs.ClientTypeJSON,
410+
ChunkSize: 100,
411+
ParallelUploadConcurrency: 4,
412+
MaxObjSizeGB: floatPtr(1.1),
412413
Retryer: &gcs.Retryer{
413414
BackoffInitial: 11 * time.Minute,
414415
BackoffMax: 111 * time.Minute,
@@ -450,6 +451,11 @@ func TestConfig(t *testing.T) {
450451
param: "storage.gcs.chunkSize",
451452
val: fmt.Sprintf("%d", wantCfg.Storage.GCS.ChunkSize),
452453
},
454+
{
455+
desc: "parallelUploadConcurrency",
456+
param: "storage.gcs.parallelUploadConcurrency",
457+
val: fmt.Sprintf("%d", wantCfg.Storage.GCS.ParallelUploadConcurrency),
458+
},
453459
{
454460
desc: "maxObjSizeGB",
455461
param: "storage.gcs.maxObjSizeGB",
@@ -533,18 +539,8 @@ func TestConfig(t *testing.T) {
533539
val: string(gcs.ClientTypeGRPC),
534540
},
535541
{
536-
desc: "parallelUpload.enabled",
537-
param: "storage.gcs.parallelUpload.enabled",
538-
val: "true",
539-
},
540-
{
541-
desc: "parallelUpload.partSize",
542-
param: "storage.gcs.parallelUpload.partSize",
543-
val: "123",
544-
},
545-
{
546-
desc: "parallelUpload.maxConcurrency",
547-
param: "storage.gcs.parallelUpload.maxConcurrency",
542+
desc: "parallelUploadConcurrency",
543+
param: "storage.gcs.parallelUploadConcurrency",
548544
val: "4",
549545
},
550546
}
@@ -568,17 +564,8 @@ func TestConfig(t *testing.T) {
568564
if gotGCS.ClientType != gcs.ClientTypeGRPC {
569565
t.Fatalf("clientType: got=%q, want=%q", gotGCS.ClientType, gcs.ClientTypeGRPC)
570566
}
571-
if gotGCS.ParallelUpload == nil {
572-
t.Fatal("expected parallelUpload config")
573-
}
574-
if !gotGCS.ParallelUpload.Enabled {
575-
t.Fatal("expected parallelUpload.enabled=true")
576-
}
577-
if gotGCS.ParallelUpload.PartSize != 123 {
578-
t.Fatalf("parallelUpload.partSize: got=%d, want=123", gotGCS.ParallelUpload.PartSize)
579-
}
580-
if gotGCS.ParallelUpload.MaxConcurrency != 4 {
581-
t.Fatalf("parallelUpload.maxConcurrency: got=%d, want=4", gotGCS.ParallelUpload.MaxConcurrency)
567+
if gotGCS.ParallelUploadConcurrency != 4 {
568+
t.Fatalf("parallelUploadConcurrency: got=%d, want=4", gotGCS.ParallelUploadConcurrency)
582569
}
583570
})
584571

pbm/storage/gcs/config.go

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,13 @@ type Config struct {
2525

2626
// The maximum number of bytes that the Writer will attempt to send in a single request.
2727
// https://pkg.go.dev/cloud.google.com/go/storage#Writer
28-
ChunkSize int `bson:"chunkSize,omitempty" json:"chunkSize,omitempty" yaml:"chunkSize,omitempty"`
29-
MaxObjSizeGB *float64 `bson:"maxObjSizeGB,omitempty" json:"maxObjSizeGB,omitempty" yaml:"maxObjSizeGB,omitempty"`
30-
31-
ParallelUpload *ParallelUpload `bson:"parallelUpload,omitempty" json:"parallelUpload,omitempty" yaml:"parallelUpload,omitempty"`
28+
ChunkSize int `bson:"chunkSize,omitempty" json:"chunkSize,omitempty" yaml:"chunkSize,omitempty"`
29+
ParallelUploadConcurrency int `bson:"parallelUploadConcurrency,omitempty" json:"parallelUploadConcurrency,omitempty" yaml:"parallelUploadConcurrency,omitempty"`
30+
MaxObjSizeGB *float64 `bson:"maxObjSizeGB,omitempty" json:"maxObjSizeGB,omitempty" yaml:"maxObjSizeGB,omitempty"`
3231

3332
Retryer *Retryer `bson:"retryer,omitempty" json:"retryer,omitempty" yaml:"retryer,omitempty"`
3433
}
3534

36-
// ParallelUpload configures the experimental Google Cloud Storage parallel upload feature.
37-
//
38-
// Parallel uploads are supported only by the Google SDK gRPC client.
39-
// HMAC credentials continue to use the MinIO client and are not affected by this setting.
40-
type ParallelUpload struct {
41-
// Enabled enables Writer parallel uploads when clientType is grpc.
42-
Enabled bool `bson:"enabled,omitempty" json:"enabled,omitempty" yaml:"enabled,omitempty"`
43-
44-
// PartSize is the size of each temporary part in bytes.
45-
// If unset, PBM uses the computed ChunkSize for the uploaded object.
46-
PartSize int `bson:"partSize,omitempty" json:"partSize,omitempty" yaml:"partSize,omitempty"`
47-
48-
// MaxConcurrency is the number of goroutines used to upload parts in parallel.
49-
// If unset, the Google SDK chooses a value based on the number of CPUs.
50-
MaxConcurrency int `bson:"maxConcurrency,omitempty" json:"maxConcurrency,omitempty" yaml:"maxConcurrency,omitempty"`
51-
}
52-
5335
//nolint:lll
5436
type Credentials struct {
5537
// Workload Identity Federation (allows missing JSON/HMAC credentials)
@@ -100,10 +82,6 @@ func (cfg *Config) Clone() *Config {
10082
v := *cfg.MaxObjSizeGB
10183
rv.MaxObjSizeGB = &v
10284
}
103-
if cfg.ParallelUpload != nil {
104-
v := *cfg.ParallelUpload
105-
rv.ParallelUpload = &v
106-
}
10785
if cfg.Retryer != nil {
10886
v := *cfg.Retryer
10987
rv.Retryer = &v
@@ -129,10 +107,10 @@ func (cfg *Config) Equal(other *Config) bool {
129107
if cfg.ChunkSize != other.ChunkSize {
130108
return false
131109
}
132-
if !reflect.DeepEqual(cfg.MaxObjSizeGB, other.MaxObjSizeGB) {
110+
if cfg.ParallelUploadConcurrency != other.ParallelUploadConcurrency {
133111
return false
134112
}
135-
if !reflect.DeepEqual(cfg.ParallelUpload, other.ParallelUpload) {
113+
if !reflect.DeepEqual(cfg.MaxObjSizeGB, other.MaxObjSizeGB) {
136114
return false
137115
}
138116
if !reflect.DeepEqual(cfg.Credentials, other.Credentials) {
@@ -173,17 +151,11 @@ func (cfg *Config) Cast() error {
173151
return errors.Errorf("invalid clientType %q", cfg.ClientType)
174152
}
175153

176-
if cfg.ChunkSize == 0 {
177-
cfg.ChunkSize = defaultChunkSize
154+
if cfg.ChunkSize == 0 && cfg.parallelUploadEnabled() {
155+
cfg.ChunkSize = defaultParallelUploadChunkSize
178156
}
179-
180-
if cfg.ParallelUpload != nil {
181-
if cfg.ParallelUpload.PartSize < 0 {
182-
return errors.New("parallelUpload.partSize cannot be negative")
183-
}
184-
if cfg.ParallelUpload.MaxConcurrency < 0 {
185-
return errors.New("parallelUpload.maxConcurrency cannot be negative")
186-
}
157+
if cfg.ChunkSize == 0 && !cfg.parallelUploadEnabled() {
158+
cfg.ChunkSize = defaultChunkSize
187159
}
188160

189161
if cfg.Retryer == nil {
@@ -223,5 +195,5 @@ func (cfg *Config) GetMaxObjSizeGB() float64 {
223195
}
224196

225197
func (cfg *Config) parallelUploadEnabled() bool {
226-
return cfg.ClientType == ClientTypeGRPC && cfg.ParallelUpload != nil && cfg.ParallelUpload.Enabled
198+
return cfg.ClientType == ClientTypeGRPC && cfg.ParallelUploadConcurrency > 1
227199
}

pbm/storage/gcs/config_test.go

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,12 @@ func TestCast(t *testing.T) {
3737
t.Fatalf("wrong config after Cast, diff=%s", cmp.Diff(*c, *want))
3838
}
3939

40-
t.Run("parallel upload validation", func(t *testing.T) {
40+
t.Run("validation", func(t *testing.T) {
4141
for _, tt := range []struct {
4242
name string
4343
cfg *Config
4444
err string
4545
}{
46-
{
47-
name: "negative part size",
48-
cfg: &Config{ParallelUpload: &ParallelUpload{PartSize: -1}},
49-
err: "parallelUpload.partSize cannot be negative",
50-
},
51-
{
52-
name: "negative max concurrency",
53-
cfg: &Config{ParallelUpload: &ParallelUpload{MaxConcurrency: -1}},
54-
err: "parallelUpload.maxConcurrency cannot be negative",
55-
},
5646
{
5747
name: "invalid client type",
5848
cfg: &Config{ClientType: ClientType("xml")},
@@ -67,6 +57,35 @@ func TestCast(t *testing.T) {
6757
})
6858
}
6959
})
60+
61+
t.Run("negative parallel upload concurrency is disabled", func(t *testing.T) {
62+
c := &Config{
63+
ClientType: ClientTypeGRPC,
64+
ParallelUploadConcurrency: -1,
65+
}
66+
err := c.Cast()
67+
if err != nil {
68+
t.Fatalf("got error during Cast: %v", err)
69+
}
70+
if c.ChunkSize != defaultChunkSize {
71+
t.Fatalf("wrong chunk size: got %d, want %d", c.ChunkSize, defaultChunkSize)
72+
}
73+
})
74+
75+
t.Run("parallel upload default chunk size", func(t *testing.T) {
76+
c := &Config{
77+
ClientType: ClientTypeGRPC,
78+
ParallelUploadConcurrency: 2,
79+
}
80+
err := c.Cast()
81+
if err != nil {
82+
t.Fatalf("got error during Cast: %v", err)
83+
}
84+
if c.ChunkSize != defaultParallelUploadChunkSize {
85+
t.Fatalf("wrong parallel upload chunk size: got %d, want %d",
86+
c.ChunkSize, defaultParallelUploadChunkSize)
87+
}
88+
})
7089
}
7190

7291
func TestConfig(t *testing.T) {
@@ -77,12 +96,8 @@ func TestConfig(t *testing.T) {
7796
ClientEmail: "email@example.com",
7897
PrivateKey: "-----BEGIN PRIVATE KEY-----\nKey\n-----END PRIVATE KEY-----\n",
7998
},
80-
ClientType: ClientTypeGRPC,
81-
ParallelUpload: &ParallelUpload{
82-
Enabled: true,
83-
PartSize: 16 * 1024 * 1024,
84-
MaxConcurrency: 4,
85-
},
99+
ClientType: ClientTypeGRPC,
100+
ParallelUploadConcurrency: 4,
86101
}
87102

88103
t.Run("Clone", func(t *testing.T) {
@@ -95,11 +110,11 @@ func TestConfig(t *testing.T) {
95110
t.Error("expected clone to be equal")
96111
}
97112

98-
opts.ParallelUpload.MaxConcurrency = 8
113+
opts.ParallelUploadConcurrency = 8
99114
if opts.Equal(clone) {
100115
t.Error("expected clone to be unchanged when updating original")
101116
}
102-
opts.ParallelUpload.MaxConcurrency = 4
117+
opts.ParallelUploadConcurrency = 4
103118

104119
opts.Bucket = "updatedName"
105120
if opts.Equal(clone) {
@@ -132,9 +147,9 @@ func TestConfig(t *testing.T) {
132147
}
133148

134149
clone = opts.Clone()
135-
clone.ParallelUpload.Enabled = false
150+
clone.ParallelUploadConcurrency = 8
136151
if opts.Equal(clone) {
137-
t.Error("expected not to be equal when updating parallel upload")
152+
t.Error("expected not to be equal when updating parallel upload concurrency")
138153
}
139154
})
140155

pbm/storage/gcs/gcs.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import (
1414
const (
1515
gcsEndpointURL = "storage.googleapis.com"
1616

17-
defaultChunkSize = 10 * 1024 * 1024 // 10MiB
18-
defaultMaxObjSizeGB = 5018 // 4.9 TB
17+
defaultChunkSize = 10 * 1024 * 1024 // 10MiB
18+
defaultParallelUploadChunkSize = 16 * 1024 * 1024 // 16MiB, matches Google SDK PCU default
19+
defaultMaxObjSizeGB = 5018 // 4.9 TB
1920

2021
defaultMaxAttempts = 5
2122
defaultBackoffInitial = time.Second

pbm/storage/gcs/google_client.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ func newGoogleClient(cfg *Config, l log.LogEvent) (*googleClient, error) {
5555
return nil, errors.Wrap(merr, "marshal GCS credentials")
5656
}
5757
if cfg.ClientType == ClientTypeGRPC {
58-
cli, err = storagegcs.NewGRPCClient(ctx, option.WithCredentialsJSON(creds))
58+
cli, err = storagegcs.NewGRPCClient(ctx,
59+
option.WithCredentialsJSON(creds),
60+
storagegcs.WithDisabledClientMetrics())
5961
} else {
6062
cli, err = storagegcs.NewClient(ctx, option.WithCredentialsJSON(creds))
6163
}
@@ -71,7 +73,7 @@ func newGoogleClient(cfg *Config, l log.LogEvent) (*googleClient, error) {
7173
return nil, fmt.Errorf("validate default credential type: %w", adcErr)
7274
}
7375
if cfg.ClientType == ClientTypeGRPC {
74-
cli, err = storagegcs.NewGRPCClient(ctx)
76+
cli, err = storagegcs.NewGRPCClient(ctx, storagegcs.WithDisabledClientMetrics())
7577
} else {
7678
cli, err = storagegcs.NewClient(ctx)
7779
}
@@ -159,28 +161,18 @@ func (g googleClient) save(name string, data io.Reader, options ...storage.Optio
159161
ctx := context.Background()
160162
w := g.bucketHandle.Object(path.Join(g.cfg.Prefix, name)).NewWriter(ctx)
161163
if g.cfg.parallelUploadEnabled() {
162-
pu := g.cfg.ParallelUpload
163164
if g.log != nil && opts.UseLogger {
164-
partSize := "SDK default"
165-
if pu.PartSize > 0 {
166-
partSize = fmt.Sprintf("%v (%v)", pu.PartSize, storage.PrettySize(int64(pu.PartSize)))
167-
}
168-
169-
concurrency := "SDK default"
170-
if pu.MaxConcurrency > 0 {
171-
concurrency = fmt.Sprintf("%d", pu.MaxConcurrency)
172-
}
173-
174-
g.log.Debug(`uploading %q [size hint: %v (%v); parallel upload part size: %s; concurrency: %s]`,
165+
g.log.Debug(`uploading %q [size hint: %v (%v); parallel upload part size: %v (%v); concurrency: %d]`,
175166
name,
176167
opts.Size, storage.PrettySize(opts.Size),
177-
partSize, concurrency)
168+
g.cfg.ChunkSize, storage.PrettySize(int64(g.cfg.ChunkSize)),
169+
g.cfg.ParallelUploadConcurrency)
178170
}
179171

180172
w.EnableParallelUpload = true
181173
w.ParallelUploadConfig = storagegcs.ParallelUploadConfig{
182-
PartSize: pu.PartSize,
183-
MaxConcurrency: pu.MaxConcurrency,
174+
PartSize: g.cfg.ChunkSize,
175+
MaxConcurrency: g.cfg.ParallelUploadConcurrency,
184176
}
185177
} else {
186178
const align int64 = 256 << 10 // 256 KiB (both min size and alignment)

0 commit comments

Comments
 (0)