Files
seaweedfs/weed/storage/backend/backend.go
T
87332eb60b Cloud/remote storage & tiering: configurable multipart upload/download concurrency (#11319)
* pb: add multipart concurrency fields to RemoteConf and tier move requests

RemoteConf gains upload_concurrency/download_concurrency (0 = client
default); VolumeTierMoveDatToRemote/FromRemote requests gain a
concurrency field (0 = backend default).

* remote storage: honor RemoteConf upload/download concurrency in s3 and azure clients

s3 client: ReadFile passes conf download_concurrency to the downloader,
WriteFile uses upload_concurrency for the uploader; previously
hard-coded 1 upload / 5 download parts. 0 keeps defaults. Same for
azure client.

* storage: plumb concurrency through backend interface and tier upload/download

BackendStorage.CopyFile/DownloadFile take a concurrency hint (<=0 =
backend configured default); s3 backend reads
upload_concurrency/download_concurrency from scaffold config with
parseConcurrency fallback, rclone updated to the new signature. Tier
move gRPC handlers forward the request concurrency to the backend.

* shell: -upload_concurrency/-download_concurrency for remote.configure, -concurrent for volume.tier

remote.configure exposes upload/download concurrency persisted into
RemoteConf; volume.tier move/evict commands forward -concurrent to the
tier move requests. Documented in master-cloud.toml scaffold.

* test: cover concurrency propagation in remote tier integration test

* remote.configure: merge existing config on partial update

Load the stored RemoteConf before saving so a partial update (e.g. only
-upload_concurrency) preserves credentials, endpoints, and type instead
of replacing them with new-config defaults. Only treat a confirmed
ErrNotFound as a new configuration; propagate all other load errors so a
transient filer failure does not overwrite stored settings.

On a type transition, reset backend-specific fields to the destination
type's new-config defaults rather than inheriting the old backend's
empty values. Bound configured concurrency to a sane maximum.

* remote storage: honor configured download concurrency in S3 and Azure

ReadFileWithConcurrency now resolves a zero request override against the
client's configured download_concurrency (new downloadConcurrency()
helpers), so the remote-mount/cache read path honors
RemoteConf.DownloadConcurrency instead of the hard-coded default.

Azure also clamps the resolved value to math.MaxUint16 regardless of
whether the fallback was used, preventing uint16 wraparound when a
configured value exceeds 65535.

* shell: rename -concurrent to -concurrency and validate tier transfer bounds

Rename the -concurrent flag to -concurrency across volume.tier.upload,
volume.tier.download, and volume.tier.compact to match the proto field and
RemoteConf field names. Add validateTierConcurrency to reject values that
would wrap int32 or exceed a 1024 cap before constructing the request.

* server: clamp tier move concurrency in gRPC handlers

Add clampTierConcurrency to both VolumeTierMoveDatToRemote and
VolumeTierMoveDatFromRemote handlers so a direct gRPC caller cannot spawn
an unbounded number of network workers.

* trim verbose comments added with concurrency feature

Remove redundant doc comments on the backend interface, rclone backend,
s3_backend parseConcurrency, and test helpers that restated the obvious.

* remote.configure: apply type defaults before re-parse so explicit flags win

applyTypeDefaults ran after the second flag parse, overwriting explicit
destination flags (e.g. -s3.region=eu-west-1) with new-config defaults.
Move the type-transition default reset before the re-parse so user-supplied
flags override the destination defaults.

* remote.configure: only treat explicit -type as a type transition

The first parse defaults -type to s3, so a concurrency-only update on an
existing non-S3 config captured requestedType=s3 and wrongly triggered a
type transition, resetting the stored backend to S3. Use fs.Visit to
detect whether -type was explicitly supplied; an omitted -type keeps the
stored backend.

---------

Co-authored-by: Jack Meredith <9480542+jackusm@users.noreply.github.com>
Co-authored-by: Chris Lu <chris.lu@gmail.com>
2026-09-14 22:09:08 -07:00

144 lines
4.3 KiB
Go

package backend
import (
"io"
"os"
"strings"
"time"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
"github.com/seaweedfs/seaweedfs/weed/util"
)
type BackendStorageFile interface {
io.ReaderAt
io.WriterAt
Truncate(off int64) error
io.Closer
GetStat() (datSize int64, modTime time.Time, err error)
Name() string
Sync() error
}
type BackendStorage interface {
ToProperties() map[string]string
NewStorageFile(key string, tierInfo *volume_server_pb.VolumeInfo) BackendStorageFile
// concurrency > 0 caps concurrent network transfers; <= 0 uses the backend default.
CopyFile(f *os.File, fn func(progressed int64, percentage float32) error, concurrency int) (key string, size int64, err error)
DownloadFile(fileName string, key string, fn func(progressed int64, percentage float32) error, concurrency int) (size int64, err error)
DeleteFile(key string) (err error)
}
type StringProperties interface {
GetString(key string) string
}
type StorageType string
type BackendStorageFactory interface {
StorageType() StorageType
BuildStorage(configuration StringProperties, configPrefix string, id string) (BackendStorage, error)
}
var (
BackendStorageFactories = make(map[StorageType]BackendStorageFactory)
BackendStorages = make(map[string]BackendStorage)
)
// used by master to load remote storage configurations
func LoadConfiguration(config *util.ViperProxy) {
StorageBackendPrefix := "storage.backend"
for backendTypeName := range config.GetStringMap(StorageBackendPrefix) {
backendStorageFactory, found := BackendStorageFactories[StorageType(backendTypeName)]
if !found {
glog.Fatalf("backend storage type %s not found", backendTypeName)
}
for backendStorageId := range config.GetStringMap(StorageBackendPrefix + "." + backendTypeName) {
if !config.GetBool(StorageBackendPrefix + "." + backendTypeName + "." + backendStorageId + ".enabled") {
continue
}
if _, found := BackendStorages[backendTypeName+"."+backendStorageId]; found {
continue
}
backendStorage, buildErr := backendStorageFactory.BuildStorage(config,
StorageBackendPrefix+"."+backendTypeName+"."+backendStorageId+".", backendStorageId)
if buildErr != nil {
glog.Fatalf("fail to create backend storage %s.%s", backendTypeName, backendStorageId)
}
BackendStorages[backendTypeName+"."+backendStorageId] = backendStorage
if backendStorageId == "default" {
BackendStorages[backendTypeName] = backendStorage
}
}
}
}
// used by volume server to receive remote storage configurations from master
func LoadFromPbStorageBackends(storageBackends []*master_pb.StorageBackend) {
for _, storageBackend := range storageBackends {
backendStorageFactory, found := BackendStorageFactories[StorageType(storageBackend.Type)]
if !found {
glog.Warningf("storage type %s not found", storageBackend.Type)
continue
}
if _, found := BackendStorages[storageBackend.Type+"."+storageBackend.Id]; found {
continue
}
backendStorage, buildErr := backendStorageFactory.BuildStorage(newProperties(storageBackend.Properties), "", storageBackend.Id)
if buildErr != nil {
glog.Fatalf("fail to create backend storage %s.%s", storageBackend.Type, storageBackend.Id)
}
BackendStorages[storageBackend.Type+"."+storageBackend.Id] = backendStorage
if storageBackend.Id == "default" {
BackendStorages[storageBackend.Type] = backendStorage
}
}
}
type Properties struct {
m map[string]string
}
func newProperties(m map[string]string) *Properties {
return &Properties{m: m}
}
func (p *Properties) GetString(key string) string {
if v, found := p.m[key]; found {
return v
}
return ""
}
func ToPbStorageBackends() (backends []*master_pb.StorageBackend) {
for sName, s := range BackendStorages {
sType, sId := BackendNameToTypeId(sName)
if sType == "" {
continue
}
backends = append(backends, &master_pb.StorageBackend{
Type: sType,
Id: sId,
Properties: s.ToProperties(),
})
}
return
}
func BackendNameToTypeId(backendName string) (backendType, backendId string) {
parts := strings.Split(backendName, ".")
if len(parts) == 1 {
return backendName, "default"
}
if len(parts) != 2 {
return
}
backendType, backendId = parts[0], parts[1]
return
}