frostfs-node/lib/replication/implementations.go

293 lines
7.1 KiB
Go
Raw Normal View History

package replication
import (
"context"
"sync"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/localstore"
"github.com/nspcc-dev/neofs-node/lib/netmap"
"github.com/nspcc-dev/neofs-node/lib/placement"
"github.com/nspcc-dev/neofs-node/lib/rand"
"github.com/pkg/errors"
)
type (
replicationScheduler struct {
cac ContainerActualityChecker
ls localstore.Iterator
}
// SchedulerParams groups the parameters of scheduler constructor.
SchedulerParams struct {
ContainerActualityChecker
localstore.Iterator
}
objectPool struct {
mu *sync.Mutex
tasks []Address
}
multiSolver struct {
as AddressStore
pl placement.Component
}
// MultiSolverParams groups the parameters of multi solver constructor.
MultiSolverParams struct {
AddressStore
Placement placement.Component
}
)
const (
errPoolExhausted = internal.Error("object pool is exhausted")
objectPoolInstanceFailMsg = "could not create object pool"
errEmptyLister = internal.Error("empty local objects lister")
errEmptyContainerActual = internal.Error("empty container actuality checker")
multiSolverInstanceFailMsg = "could not create multi solver"
errEmptyAddressStore = internal.Error("empty address store")
errEmptyPlacement = internal.Error("empty placement")
replicationSchedulerEntity = "replication scheduler"
)
// NewObjectPool is an object pool constructor.
func NewObjectPool() ObjectPool {
return &objectPool{mu: new(sync.Mutex)}
}
// NewReplicationScheduler is a replication scheduler constructor.
func NewReplicationScheduler(p SchedulerParams) (Scheduler, error) {
switch {
case p.ContainerActualityChecker == nil:
return nil, errors.Wrap(errEmptyContainerActual, objectPoolInstanceFailMsg)
case p.Iterator == nil:
return nil, errors.Wrap(errEmptyLister, objectPoolInstanceFailMsg)
}
return &replicationScheduler{
cac: p.ContainerActualityChecker,
ls: p.Iterator,
}, nil
}
// NewMultiSolver is a multi solver constructor.
func NewMultiSolver(p MultiSolverParams) (MultiSolver, error) {
switch {
case p.Placement == nil:
return nil, errors.Wrap(errEmptyPlacement, multiSolverInstanceFailMsg)
case p.AddressStore == nil:
return nil, errors.Wrap(errEmptyAddressStore, multiSolverInstanceFailMsg)
}
return &multiSolver{
as: p.AddressStore,
pl: p.Placement,
}, nil
}
func (s *objectPool) Update(pool []Address) {
s.mu.Lock()
defer s.mu.Unlock()
s.tasks = pool
}
func (s *objectPool) Undone() int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.tasks)
}
func (s *objectPool) Pop() (Address, error) {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.tasks) == 0 {
return Address{}, errPoolExhausted
}
head := s.tasks[0]
s.tasks = s.tasks[1:]
return head, nil
}
func (s *replicationScheduler) SelectForReplication(limit int) ([]Address, error) {
// Attention! This routine might be inefficient with big number of objects
// and containers. Consider using fast traversal and filtering algorithms
// with sieve of bloom filters.
migration := make([]Address, 0, limit)
replication := make([]Address, 0)
ctx := context.Background()
if err := s.ls.Iterate(nil, func(meta *localstore.ObjectMeta) bool {
if s.cac.Actual(ctx, meta.Object.SystemHeader.CID) {
replication = append(replication, *meta.Object.Address())
} else {
migration = append(migration, *meta.Object.Address())
}
return len(migration) >= limit
}); err != nil {
return nil, err
}
lnM := len(migration)
lnR := len(replication)
edge := 0
// I considered using rand.Perm() and appending elements in `for` cycle.
// But it seems, that shuffling is efficient even when `limit-lnM`
// is 1000 times smaller than `lnR`. But it can be discussed and changed
// later anyway.
if lnM < limit {
r := rand.New()
r.Shuffle(lnR, func(i, j int) {
replication[i], replication[j] = replication[j], replication[i]
})
edge = min(limit-lnM, lnR)
}
return append(migration, replication[:edge]...), nil
}
func (s *multiSolver) Epoch() uint64 { return s.pl.NetworkState().Epoch }
func (s *multiSolver) SelfAddr() (multiaddr.Multiaddr, error) { return s.as.SelfAddr() }
func (s *multiSolver) ReservationRatio(ctx context.Context, addr Address) (int, error) {
graph, err := s.pl.Query(ctx, placement.ContainerID(addr.CID))
if err != nil {
return 0, errors.Wrap(err, "reservation ratio computation failed on placement query")
}
nodes, err := graph.Filter(func(group netmap.SFGroup, bucket *netmap.Bucket) *netmap.Bucket {
return bucket.GetSelection(group.Selectors, addr.ObjectID.Bytes())
}).NodeList()
if err != nil {
return 0, errors.Wrap(err, "reservation ratio computation failed on graph node list")
}
return len(nodes), nil
}
func (s *multiSolver) SelectRemoteStorages(ctx context.Context, addr Address, excl ...multiaddr.Multiaddr) ([]ObjectLocation, error) {
selfAddr, err := s.as.SelfAddr()
if err != nil {
return nil, errors.Wrap(err, "select remote storage nodes failed on get self address")
}
nodes, err := s.selectNodes(ctx, addr, excl...)
if err != nil {
return nil, errors.Wrap(err, "select remote storage nodes failed on get node list")
}
var (
metSelf bool
selfIndex = -1
res = make([]ObjectLocation, 0, len(nodes))
)
for i := range nodes {
if nodes[i].Equal(selfAddr) {
metSelf = true
selfIndex = i
}
res = append(res, ObjectLocation{
Node: nodes[i],
WeightGreater: !metSelf,
})
}
if selfIndex != -1 {
res = append(res[:selfIndex], res[selfIndex+1:]...)
}
return res, nil
}
func (s *multiSolver) selectNodes(ctx context.Context, addr Address, excl ...multiaddr.Multiaddr) ([]multiaddr.Multiaddr, error) {
graph, err := s.pl.Query(ctx, placement.ContainerID(addr.CID))
if err != nil {
return nil, errors.Wrap(err, "select remote storage nodes failed on placement query")
}
filter := func(group netmap.SFGroup, bucket *netmap.Bucket) *netmap.Bucket { return bucket }
if !addr.ObjectID.Empty() {
filter = func(group netmap.SFGroup, bucket *netmap.Bucket) *netmap.Bucket {
return bucket.GetSelection(group.Selectors, addr.ObjectID.Bytes())
}
}
return graph.Exclude(excl).Filter(filter).NodeList()
}
func (s *multiSolver) Actual(ctx context.Context, cid CID) bool {
graph, err := s.pl.Query(ctx, placement.ContainerID(cid))
if err != nil {
return false
}
nodes, err := graph.NodeList()
if err != nil {
return false
}
selfAddr, err := s.as.SelfAddr()
if err != nil {
return false
}
for i := range nodes {
if nodes[i].Equal(selfAddr) {
return true
}
}
return false
}
func (s *multiSolver) CompareWeight(ctx context.Context, addr Address, node multiaddr.Multiaddr) int {
selfAddr, err := s.as.SelfAddr()
if err != nil {
return -1
}
if selfAddr.Equal(node) {
return 0
}
excl := make([]multiaddr.Multiaddr, 0)
for {
nodes, err := s.selectNodes(ctx, addr, excl...)
if err != nil {
return -1
}
for j := range nodes {
if nodes[j].Equal(selfAddr) {
return -1
} else if nodes[j].Equal(node) {
return 1
}
}
excl = append(excl, nodes[0]) // TODO: when it will become relevant to append full nodes slice
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}