You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
7.0 KiB
241 lines
7.0 KiB
package distribution
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
)
|
|
|
|
// ShardLocation represents where a shard is located in the topology
|
|
type ShardLocation struct {
|
|
ShardID int
|
|
NodeID string
|
|
DataCenter string
|
|
Rack string
|
|
}
|
|
|
|
// TopologyNode represents a node in the topology that can hold EC shards
|
|
type TopologyNode struct {
|
|
NodeID string
|
|
DataCenter string
|
|
Rack string
|
|
FreeSlots int // Available slots for new shards
|
|
ShardIDs []int // Shard IDs currently on this node for a specific volume
|
|
TotalShards int // Total shards on this node (for all volumes)
|
|
}
|
|
|
|
// TopologyAnalysis holds the current shard distribution analysis for a volume
|
|
type TopologyAnalysis struct {
|
|
// Shard counts at each level
|
|
ShardsByDC map[string]int
|
|
ShardsByRack map[string]int
|
|
ShardsByNode map[string]int
|
|
|
|
// Detailed shard locations
|
|
DCToShards map[string][]int // DC -> list of shard IDs
|
|
RackToShards map[string][]int // Rack -> list of shard IDs
|
|
NodeToShards map[string][]int // NodeID -> list of shard IDs
|
|
|
|
// Topology structure
|
|
DCToRacks map[string][]string // DC -> list of rack IDs
|
|
RackToNodes map[string][]*TopologyNode // Rack -> list of nodes
|
|
AllNodes map[string]*TopologyNode // NodeID -> node info
|
|
|
|
// Statistics
|
|
TotalShards int
|
|
TotalNodes int
|
|
TotalRacks int
|
|
TotalDCs int
|
|
}
|
|
|
|
// NewTopologyAnalysis creates a new empty analysis
|
|
func NewTopologyAnalysis() *TopologyAnalysis {
|
|
return &TopologyAnalysis{
|
|
ShardsByDC: make(map[string]int),
|
|
ShardsByRack: make(map[string]int),
|
|
ShardsByNode: make(map[string]int),
|
|
DCToShards: make(map[string][]int),
|
|
RackToShards: make(map[string][]int),
|
|
NodeToShards: make(map[string][]int),
|
|
DCToRacks: make(map[string][]string),
|
|
RackToNodes: make(map[string][]*TopologyNode),
|
|
AllNodes: make(map[string]*TopologyNode),
|
|
}
|
|
}
|
|
|
|
// AddShardLocation adds a shard location to the analysis
|
|
func (a *TopologyAnalysis) AddShardLocation(loc ShardLocation) {
|
|
// Update counts
|
|
a.ShardsByDC[loc.DataCenter]++
|
|
a.ShardsByRack[loc.Rack]++
|
|
a.ShardsByNode[loc.NodeID]++
|
|
|
|
// Update shard lists
|
|
a.DCToShards[loc.DataCenter] = append(a.DCToShards[loc.DataCenter], loc.ShardID)
|
|
a.RackToShards[loc.Rack] = append(a.RackToShards[loc.Rack], loc.ShardID)
|
|
a.NodeToShards[loc.NodeID] = append(a.NodeToShards[loc.NodeID], loc.ShardID)
|
|
|
|
a.TotalShards++
|
|
}
|
|
|
|
// AddNode adds a node to the topology (even if it has no shards)
|
|
func (a *TopologyAnalysis) AddNode(node *TopologyNode) {
|
|
if _, exists := a.AllNodes[node.NodeID]; exists {
|
|
return // Already added
|
|
}
|
|
|
|
a.AllNodes[node.NodeID] = node
|
|
a.TotalNodes++
|
|
|
|
// Update topology structure
|
|
if !slices.Contains(a.DCToRacks[node.DataCenter], node.Rack) {
|
|
a.DCToRacks[node.DataCenter] = append(a.DCToRacks[node.DataCenter], node.Rack)
|
|
}
|
|
a.RackToNodes[node.Rack] = append(a.RackToNodes[node.Rack], node)
|
|
|
|
// Update counts
|
|
if _, exists := a.ShardsByDC[node.DataCenter]; !exists {
|
|
a.TotalDCs++
|
|
}
|
|
if _, exists := a.ShardsByRack[node.Rack]; !exists {
|
|
a.TotalRacks++
|
|
}
|
|
}
|
|
|
|
// Finalize computes final statistics after all data is added
|
|
func (a *TopologyAnalysis) Finalize() {
|
|
// Ensure we have accurate DC and rack counts
|
|
dcSet := make(map[string]bool)
|
|
rackSet := make(map[string]bool)
|
|
for _, node := range a.AllNodes {
|
|
dcSet[node.DataCenter] = true
|
|
rackSet[node.Rack] = true
|
|
}
|
|
a.TotalDCs = len(dcSet)
|
|
a.TotalRacks = len(rackSet)
|
|
a.TotalNodes = len(a.AllNodes)
|
|
}
|
|
|
|
// String returns a summary of the analysis
|
|
func (a *TopologyAnalysis) String() string {
|
|
return fmt.Sprintf("TopologyAnalysis{shards:%d, nodes:%d, racks:%d, dcs:%d}",
|
|
a.TotalShards, a.TotalNodes, a.TotalRacks, a.TotalDCs)
|
|
}
|
|
|
|
// DetailedString returns a detailed multi-line summary
|
|
func (a *TopologyAnalysis) DetailedString() string {
|
|
s := fmt.Sprintf("Topology Analysis:\n")
|
|
s += fmt.Sprintf(" Total Shards: %d\n", a.TotalShards)
|
|
s += fmt.Sprintf(" Data Centers: %d\n", a.TotalDCs)
|
|
for dc, count := range a.ShardsByDC {
|
|
s += fmt.Sprintf(" %s: %d shards\n", dc, count)
|
|
}
|
|
s += fmt.Sprintf(" Racks: %d\n", a.TotalRacks)
|
|
for rack, count := range a.ShardsByRack {
|
|
s += fmt.Sprintf(" %s: %d shards\n", rack, count)
|
|
}
|
|
s += fmt.Sprintf(" Nodes: %d\n", a.TotalNodes)
|
|
for nodeID, count := range a.ShardsByNode {
|
|
if count > 0 {
|
|
s += fmt.Sprintf(" %s: %d shards\n", nodeID, count)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// TopologyExcess represents a topology level (DC/rack/node) with excess shards
|
|
type TopologyExcess struct {
|
|
ID string // DC/rack/node ID
|
|
Level string // "dc", "rack", or "node"
|
|
Excess int // Number of excess shards (above target)
|
|
Shards []int // Shard IDs at this level
|
|
Nodes []*TopologyNode // Nodes at this level (for finding sources)
|
|
}
|
|
|
|
// CalculateDCExcess returns DCs with more shards than the target
|
|
func CalculateDCExcess(analysis *TopologyAnalysis, dist *ECDistribution) []TopologyExcess {
|
|
var excess []TopologyExcess
|
|
|
|
for dc, count := range analysis.ShardsByDC {
|
|
if count > dist.TargetShardsPerDC {
|
|
// Collect nodes in this DC
|
|
var nodes []*TopologyNode
|
|
for _, rack := range analysis.DCToRacks[dc] {
|
|
nodes = append(nodes, analysis.RackToNodes[rack]...)
|
|
}
|
|
excess = append(excess, TopologyExcess{
|
|
ID: dc,
|
|
Level: "dc",
|
|
Excess: count - dist.TargetShardsPerDC,
|
|
Shards: analysis.DCToShards[dc],
|
|
Nodes: nodes,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Sort by excess (most excess first)
|
|
slices.SortFunc(excess, func(a, b TopologyExcess) int {
|
|
return b.Excess - a.Excess
|
|
})
|
|
|
|
return excess
|
|
}
|
|
|
|
// CalculateRackExcess returns racks with more shards than the target (within a DC)
|
|
func CalculateRackExcess(analysis *TopologyAnalysis, dc string, targetPerRack int) []TopologyExcess {
|
|
var excess []TopologyExcess
|
|
|
|
for _, rack := range analysis.DCToRacks[dc] {
|
|
count := analysis.ShardsByRack[rack]
|
|
if count > targetPerRack {
|
|
excess = append(excess, TopologyExcess{
|
|
ID: rack,
|
|
Level: "rack",
|
|
Excess: count - targetPerRack,
|
|
Shards: analysis.RackToShards[rack],
|
|
Nodes: analysis.RackToNodes[rack],
|
|
})
|
|
}
|
|
}
|
|
|
|
slices.SortFunc(excess, func(a, b TopologyExcess) int {
|
|
return b.Excess - a.Excess
|
|
})
|
|
|
|
return excess
|
|
}
|
|
|
|
// CalculateUnderservedDCs returns DCs that have fewer shards than target
|
|
func CalculateUnderservedDCs(analysis *TopologyAnalysis, dist *ECDistribution) []string {
|
|
var underserved []string
|
|
|
|
// Check existing DCs
|
|
for dc, count := range analysis.ShardsByDC {
|
|
if count < dist.TargetShardsPerDC {
|
|
underserved = append(underserved, dc)
|
|
}
|
|
}
|
|
|
|
// Check DCs with nodes but no shards
|
|
for dc := range analysis.DCToRacks {
|
|
if _, exists := analysis.ShardsByDC[dc]; !exists {
|
|
underserved = append(underserved, dc)
|
|
}
|
|
}
|
|
|
|
return underserved
|
|
}
|
|
|
|
// CalculateUnderservedRacks returns racks that have fewer shards than target
|
|
func CalculateUnderservedRacks(analysis *TopologyAnalysis, dc string, targetPerRack int) []string {
|
|
var underserved []string
|
|
|
|
for _, rack := range analysis.DCToRacks[dc] {
|
|
count := analysis.ShardsByRack[rack]
|
|
if count < targetPerRack {
|
|
underserved = append(underserved, rack)
|
|
}
|
|
}
|
|
|
|
return underserved
|
|
}
|
|
|