diff --git a/CHANGELOG.md b/CHANGELOG.md index f95fe9de9..3f6fdc3df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - (Improvement) Change Operator default ReplicasCount to 1 - (Maintenance) Change MD content injection method - (Maintenance) Generate README Platforms +- (Improvement) Cleanout calculation - picks members with the lowest number of shards ## [1.2.24](https://github.com/arangodb/kube-arangodb/tree/1.2.24) (2023-01-25) - (Bugfix) Fix deployment creation on ARM64 diff --git a/pkg/apis/deployment/v1/member_status.go b/pkg/apis/deployment/v1/member_status.go index 8d681d365..dbc842f15 100644 --- a/pkg/apis/deployment/v1/member_status.go +++ b/pkg/apis/deployment/v1/member_status.go @@ -53,7 +53,7 @@ type MemberStatus struct { CreatedAt meta.Time `json:"created-at"` // Conditions specific to this member Conditions ConditionList `json:"conditions,omitempty"` - // RecentTerminatons holds the times when this member was recently terminated. + // RecentTerminations holds the times when this member was recently terminated. // First entry is the oldest. (do not add omitempty, since we want to be able to switch from a list to an empty list) RecentTerminations []meta.Time `json:"recent-terminations"` // IsInitialized is set after the very first time a pod was created for this member. diff --git a/pkg/apis/deployment/v2alpha1/member_status.go b/pkg/apis/deployment/v2alpha1/member_status.go index 1a868a31f..47994db90 100644 --- a/pkg/apis/deployment/v2alpha1/member_status.go +++ b/pkg/apis/deployment/v2alpha1/member_status.go @@ -53,7 +53,7 @@ type MemberStatus struct { CreatedAt meta.Time `json:"created-at"` // Conditions specific to this member Conditions ConditionList `json:"conditions,omitempty"` - // RecentTerminatons holds the times when this member was recently terminated. + // RecentTerminations holds the times when this member was recently terminated. // First entry is the oldest. (do not add omitempty, since we want to be able to switch from a list to an empty list) RecentTerminations []meta.Time `json:"recent-terminations"` // IsInitialized is set after the very first time a pod was created for this member. diff --git a/pkg/deployment/agency/state.go b/pkg/deployment/agency/state.go index ea1ddfb98..9f356b942 100644 --- a/pkg/deployment/agency/state.go +++ b/pkg/deployment/agency/state.go @@ -132,6 +132,42 @@ func (s State) CountShards() int { return count } +// ShardsByDbServers returns a map of DBServers and the amount of shards they have +func (s State) ShardsByDbServers() map[Server]int { + result := make(map[Server]int) + + for _, collections := range s.Current.Collections { + for _, shards := range collections { + for _, shard := range shards { + for _, server := range shard.Servers { + result[server]++ + } + } + } + } + + return result +} + +// GetDBServerWithLowestShards returns the DBServer with the lowest amount of shards +func (s State) GetDBServerWithLowestShards() Server { + var resultServer Server = "" + var resultShards int + + for server, shards := range s.ShardsByDbServers() { + // init first server as result + if resultServer == "" { + resultServer = server + resultShards = shards + } else if shards < resultShards { + resultServer = server + resultShards = shards + } + } + return resultServer +} + +// PlanServers returns all servers which are part of the plan func (s State) PlanServers() Servers { q := map[Server]bool{} diff --git a/pkg/deployment/reconcile/plan_builder_scale.go b/pkg/deployment/reconcile/plan_builder_scale.go index e89f04e07..c653b4ea7 100644 --- a/pkg/deployment/reconcile/plan_builder_scale.go +++ b/pkg/deployment/reconcile/plan_builder_scale.go @@ -93,7 +93,13 @@ func (r *Reconciler) createScalePlan(status api.DeploymentStatus, members api.Me Debug("Creating scale-up plan") } else if len(members) > count { // Note, we scale down 1 member at a time - if m, err := members.SelectMemberToRemove(getCleanedServer(context), getToBeCleanedServer(context), topologyMissingMemberToRemoveSelector(status.Topology), topologyAwarenessMemberToRemoveSelector(group, status.Topology)); err != nil { + + if m, err := members.SelectMemberToRemove( + getCleanedServer(context), + getToBeCleanedServer(context), + topologyMissingMemberToRemoveSelector(status.Topology), + topologyAwarenessMemberToRemoveSelector(group, status.Topology), + getDbServerWithLowestShards(context, group)); err != nil { r.planLogger.Err(err).Str("role", group.AsRole()).Warn("Failed to select member to remove") } else { ready, message := groupReadyForRestart(context, status, m, group) @@ -194,6 +200,18 @@ func getToBeCleanedServer(ctx reconciler.ArangoAgencyGet) api.MemberToRemoveSele } } +func getDbServerWithLowestShards(ctx reconciler.ArangoAgencyGet, g api.ServerGroup) api.MemberToRemoveSelector { + return func(m api.MemberStatusList) (string, error) { + if g != api.ServerGroupDBServers { + return "", nil + } + if a, ok := ctx.GetAgencyCache(); ok { + return string(a.GetDBServerWithLowestShards()), nil + } + return "", nil + } +} + func (r *Reconciler) scaleDownCandidate(ctx context.Context, apiObject k8sutil.APIObject, spec api.DeploymentSpec, status api.DeploymentStatus, context PlanBuilderContext) api.Plan {