mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-17 03:28:32 +02:00
* feat: throughput limits for replicate, EC shard, and worker-driven moves VolumeCopy was the only rate-limitable transfer; EC shard copies, replica creation, and worker-driven moves all ran at whatever the receiving server's maintenance rate allowed, with no per-operation control. - proto: VolumeEcShardsCopyRequest and the balance / ec_balance task params and configs gain io_byte_per_second; 0 keeps today's behavior (the volume server's own maintenance rate governs). - volume server: VolumeEcShardsCopy throttles with one WriteThrottler per request, shared across the shard, .ecx, .ecj, .vif, and .ecsum copies so the limit caps the transfer as a whole - the same shape as VolumeCopy. - volume_move: ReplicateVolume accepts the limit; EcMoveOptions carries it through MoveEcShards/CopyAndMountEcShards into the copy request, with fake-client tests asserting propagation. - shell: ec.balance gains -ioBytePerSecond; volume.tier.move's replication top-up honors the command's existing -ioBytePerSecond instead of running unthrottled. - worker: balance and ec_balance configs gain io_byte_per_second (surfaced in the admin config schema), carried through detection and plugin job parameters into task params and handed to the shared mover; batch balance jobs inherit the limit from their detection results. The limit is per copy stream, so maxParallelization multiplies the aggregate ceiling. * worker plugins: expose io_byte_per_second in the plugin config and derive it The plugin-driven detection path derives its task Config from the plugin configuration values, and both balance and ec_balance left IoBytePerSecond at zero there - a configured limit silently reverted to the server maintenance rate. Both derive functions now read the field (clamped at zero), and the plugin descriptors expose it with defaults so the configuration form carries it.
255 lines
8.9 KiB
Go
255 lines
8.9 KiB
Go
package ec_balance
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/admin/config"
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/worker_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/worker/tasks/base"
|
|
)
|
|
|
|
// Config extends BaseConfig with EC balance specific settings
|
|
type Config struct {
|
|
base.BaseConfig
|
|
ImbalanceThreshold float64 `json:"imbalance_threshold"`
|
|
MinServerCount int `json:"min_server_count"`
|
|
CollectionFilter string `json:"collection_filter"`
|
|
DiskType string `json:"disk_type"`
|
|
PreferredTags []string `json:"preferred_tags"`
|
|
ReplicaPlacement string `json:"replica_placement"` // e.g. "020"; empty falls back to the master default replication (even spread only when that default is empty or zero)
|
|
IoBytePerSecond int64 `json:"io_byte_per_second"` // limit each shard copy's rate; 0 falls back to the volume server's maintenance rate
|
|
DataCenterFilter string `json:"-"` // per-detection-run, not persisted
|
|
}
|
|
|
|
// NewDefaultConfig creates a new default EC balance configuration
|
|
func NewDefaultConfig() *Config {
|
|
return &Config{
|
|
BaseConfig: base.BaseConfig{
|
|
Enabled: true,
|
|
ScanIntervalSeconds: 60 * 60, // 1 hour
|
|
MaxConcurrent: 1,
|
|
},
|
|
ImbalanceThreshold: 0.2, // 20%
|
|
MinServerCount: 3,
|
|
CollectionFilter: "",
|
|
DiskType: "",
|
|
PreferredTags: nil,
|
|
}
|
|
}
|
|
|
|
// GetConfigSpec returns the configuration schema for EC balance tasks
|
|
func GetConfigSpec() base.ConfigSpec {
|
|
return base.ConfigSpec{
|
|
Fields: []*config.Field{
|
|
{
|
|
Name: "enabled",
|
|
JSONName: "enabled",
|
|
Type: config.FieldTypeBool,
|
|
DefaultValue: true,
|
|
Required: false,
|
|
DisplayName: "Enable EC Shard Balance Tasks",
|
|
Description: "Whether EC shard balance tasks should be automatically created",
|
|
HelpText: "Toggle this to enable or disable automatic EC shard balancing",
|
|
InputType: "checkbox",
|
|
CSSClasses: "form-check-input",
|
|
},
|
|
{
|
|
Name: "scan_interval_seconds",
|
|
JSONName: "scan_interval_seconds",
|
|
Type: config.FieldTypeInterval,
|
|
DefaultValue: 60 * 60,
|
|
MinValue: 10 * 60,
|
|
MaxValue: 24 * 60 * 60,
|
|
Required: true,
|
|
DisplayName: "Scan Interval",
|
|
Description: "How often to scan for EC shard imbalances",
|
|
HelpText: "The system will check for EC shard distribution imbalances at this interval",
|
|
Placeholder: "1",
|
|
Unit: config.UnitHours,
|
|
InputType: "interval",
|
|
CSSClasses: "form-control",
|
|
},
|
|
{
|
|
Name: "max_concurrent",
|
|
JSONName: "max_concurrent",
|
|
Type: config.FieldTypeInt,
|
|
DefaultValue: 1,
|
|
MinValue: 1,
|
|
MaxValue: 5,
|
|
Required: true,
|
|
DisplayName: "Max Concurrent Tasks",
|
|
Description: "Maximum number of EC shard balance tasks that can run simultaneously",
|
|
HelpText: "Limits the number of EC shard balancing operations running at the same time",
|
|
Placeholder: "1 (default)",
|
|
Unit: config.UnitCount,
|
|
InputType: "number",
|
|
CSSClasses: "form-control",
|
|
},
|
|
{
|
|
Name: "imbalance_threshold",
|
|
JSONName: "imbalance_threshold",
|
|
Type: config.FieldTypeFloat,
|
|
DefaultValue: 0.2,
|
|
MinValue: 0.05,
|
|
MaxValue: 0.5,
|
|
Required: true,
|
|
DisplayName: "Imbalance Threshold",
|
|
Description: "Minimum shard count imbalance ratio to trigger balancing",
|
|
HelpText: "EC shard distribution imbalances above this threshold will trigger rebalancing",
|
|
Placeholder: "0.20 (20%)",
|
|
Unit: config.UnitNone,
|
|
InputType: "number",
|
|
CSSClasses: "form-control",
|
|
},
|
|
{
|
|
Name: "min_server_count",
|
|
JSONName: "min_server_count",
|
|
Type: config.FieldTypeInt,
|
|
DefaultValue: 3,
|
|
MinValue: 2,
|
|
MaxValue: 100,
|
|
Required: true,
|
|
DisplayName: "Minimum Server Count",
|
|
Description: "Minimum number of servers required for EC shard balancing",
|
|
HelpText: "EC shard balancing will only occur if there are at least this many servers",
|
|
Placeholder: "3 (default)",
|
|
Unit: config.UnitCount,
|
|
InputType: "number",
|
|
CSSClasses: "form-control",
|
|
},
|
|
{
|
|
Name: "collection_filter",
|
|
JSONName: "collection_filter",
|
|
Type: config.FieldTypeString,
|
|
DefaultValue: "",
|
|
Required: false,
|
|
DisplayName: "Collection Filter",
|
|
Description: "Only balance EC shards from specific collections",
|
|
HelpText: "Leave empty to balance all collections, or specify collection name/wildcard",
|
|
Placeholder: "my_collection",
|
|
InputType: "text",
|
|
CSSClasses: "form-control",
|
|
},
|
|
{
|
|
Name: "disk_type",
|
|
JSONName: "disk_type",
|
|
Type: config.FieldTypeString,
|
|
DefaultValue: "",
|
|
Required: false,
|
|
DisplayName: "Disk Type",
|
|
Description: "Only balance EC shards on this disk type",
|
|
HelpText: "Leave empty for all disk types, or specify hdd or ssd",
|
|
Placeholder: "hdd",
|
|
InputType: "text",
|
|
CSSClasses: "form-control",
|
|
},
|
|
{
|
|
Name: "preferred_tags",
|
|
JSONName: "preferred_tags",
|
|
Type: config.FieldTypeString,
|
|
DefaultValue: "",
|
|
Required: false,
|
|
DisplayName: "Preferred Disk Tags",
|
|
Description: "Comma-separated disk tags to prioritize for shard placement",
|
|
HelpText: "EC shards will be placed on disks with these tags first, ordered by preference",
|
|
Placeholder: "fast,ssd",
|
|
InputType: "text",
|
|
CSSClasses: "form-control",
|
|
},
|
|
{
|
|
Name: "replica_placement",
|
|
JSONName: "replica_placement",
|
|
Type: config.FieldTypeString,
|
|
DefaultValue: "",
|
|
Required: false,
|
|
DisplayName: "Replica Placement",
|
|
Description: "EC shard replica placement constraint (e.g. 020)",
|
|
HelpText: "Leave empty to use the master default replication (even spread only when that default is empty or zero). When set, limits shards per rack/node per the placement digits (dc/rack/node)",
|
|
Placeholder: "020",
|
|
InputType: "text",
|
|
CSSClasses: "form-control",
|
|
},
|
|
{
|
|
Name: "io_byte_per_second",
|
|
JSONName: "io_byte_per_second",
|
|
Type: config.FieldTypeInt,
|
|
DefaultValue: 0,
|
|
MinValue: 0,
|
|
Required: false,
|
|
DisplayName: "Shard Copy IO Limit (bytes/sec)",
|
|
Description: "Limit each EC shard copy's rate",
|
|
HelpText: "0 falls back to each volume server's own maintenance rate (-maintenanceBytePerSecond)",
|
|
Placeholder: "0 (server maintenance rate)",
|
|
Unit: config.UnitNone,
|
|
InputType: "number",
|
|
CSSClasses: "form-control",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// ToTaskPolicy converts configuration to a TaskPolicy protobuf message
|
|
func (c *Config) ToTaskPolicy() *worker_pb.TaskPolicy {
|
|
preferredTagsCopy := append([]string(nil), c.PreferredTags...)
|
|
return &worker_pb.TaskPolicy{
|
|
Enabled: c.Enabled,
|
|
MaxConcurrent: int32(c.MaxConcurrent),
|
|
RepeatIntervalSeconds: int32(c.ScanIntervalSeconds),
|
|
CheckIntervalSeconds: int32(c.ScanIntervalSeconds),
|
|
TaskConfig: &worker_pb.TaskPolicy_EcBalanceConfig{
|
|
EcBalanceConfig: &worker_pb.EcBalanceTaskConfig{
|
|
ImbalanceThreshold: c.ImbalanceThreshold,
|
|
MinServerCount: int32(c.MinServerCount),
|
|
CollectionFilter: c.CollectionFilter,
|
|
DiskType: c.DiskType,
|
|
PreferredTags: preferredTagsCopy,
|
|
ReplicaPlacement: c.ReplicaPlacement,
|
|
IoBytePerSecond: c.IoBytePerSecond,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// FromTaskPolicy loads configuration from a TaskPolicy protobuf message
|
|
func (c *Config) FromTaskPolicy(policy *worker_pb.TaskPolicy) error {
|
|
if policy == nil {
|
|
return fmt.Errorf("policy is nil")
|
|
}
|
|
|
|
c.Enabled = policy.Enabled
|
|
c.MaxConcurrent = int(policy.MaxConcurrent)
|
|
c.ScanIntervalSeconds = int(policy.RepeatIntervalSeconds)
|
|
|
|
if ecbConfig := policy.GetEcBalanceConfig(); ecbConfig != nil {
|
|
c.ImbalanceThreshold = ecbConfig.ImbalanceThreshold
|
|
c.MinServerCount = int(ecbConfig.MinServerCount)
|
|
c.CollectionFilter = ecbConfig.CollectionFilter
|
|
c.DiskType = ecbConfig.DiskType
|
|
c.PreferredTags = append([]string(nil), ecbConfig.PreferredTags...)
|
|
c.ReplicaPlacement = ecbConfig.ReplicaPlacement
|
|
c.IoBytePerSecond = ecbConfig.IoBytePerSecond
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadConfigFromPersistence loads configuration from the persistence layer if available
|
|
func LoadConfigFromPersistence(configPersistence interface{}) *Config {
|
|
cfg := NewDefaultConfig()
|
|
|
|
if persistence, ok := configPersistence.(interface {
|
|
LoadEcBalanceTaskPolicy() (*worker_pb.TaskPolicy, error)
|
|
}); ok {
|
|
if policy, err := persistence.LoadEcBalanceTaskPolicy(); err == nil && policy != nil {
|
|
if err := cfg.FromTaskPolicy(policy); err == nil {
|
|
glog.V(1).Infof("Loaded EC balance configuration from persistence")
|
|
return cfg
|
|
}
|
|
}
|
|
}
|
|
|
|
glog.V(1).Infof("Using default EC balance configuration")
|
|
return cfg
|
|
}
|