[#378] shard: Collect expired tombstones in GC every epoch

Add new epoch event handler to GC that finds all expired tombstones and
marks them and underlying objects to be removed. Shard uses callbacks
provided by the storage engine to mark underlying objects.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2021-02-17 15:27:40 +03:00 committed by Alex Vanin
parent 3d5169c4c9
commit 717f2beb47
5 changed files with 114 additions and 1 deletions

View file

@ -1,6 +1,7 @@
package engine package engine
import ( import (
"context"
"errors" "errors"
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object" objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
@ -80,3 +81,22 @@ func (e *StorageEngine) inhume(addr *objectSDK.Address, prm *shard.InhumePrm, ch
return return
} }
func (e *StorageEngine) processExpiredTombstones(ctx context.Context, addrs []*objectSDK.Address) {
tss := make(map[string]struct{}, len(addrs))
for i := range addrs {
tss[addrs[i].String()] = struct{}{}
}
e.iterateOverUnsortedShards(func(sh *shard.Shard) (stop bool) {
sh.HandleExpiredTombstones(tss)
select {
case <-ctx.Done():
return true
default:
return false
}
})
}

View file

@ -29,7 +29,10 @@ func (e *StorageEngine) AddShard(opts ...shard.Option) (*shard.ID, error) {
return nil, errors.Wrap(err, "could not generate shard ID") return nil, errors.Wrap(err, "could not generate shard ID")
} }
e.shards[id.String()] = shard.New(append(opts, shard.WithID(id))...) e.shards[id.String()] = shard.New(append(opts,
shard.WithID(id),
shard.WithExpiredObjectsCallback(e.processExpiredTombstones),
)...)
return id, nil return id, nil
} }

View file

@ -47,6 +47,7 @@ func (s *Shard) Init() error {
cancelFunc: func() {}, cancelFunc: func() {},
handlers: []eventHandler{ handlers: []eventHandler{
s.collectExpiredObjects, s.collectExpiredObjects,
s.collectExpiredTombstones,
}, },
}, },
}, },

View file

@ -236,3 +236,77 @@ func (s *Shard) collectExpiredObjects(ctx context.Context, e Event) {
return return
} }
} }
// TODO: can be unified with Shard.collectExpiredObjects.
func (s *Shard) collectExpiredTombstones(ctx context.Context, e Event) {
epoch := e.(newEpoch).epoch
var expired []*object.Address
// collect expired tombstone objects
err := s.metaBase.IterateExpired(epoch, func(expiredObject *meta.ExpiredObject) error {
select {
case <-ctx.Done():
return meta.ErrInterruptIterator
default:
}
if expiredObject.Type() == object.TypeTombstone {
expired = append(expired, expiredObject.Address())
}
return nil
})
if err != nil {
s.log.Warn("iterator over expired tombstones failed",
zap.String("error", err.Error()),
)
return
} else if len(expired) == 0 {
return
}
// check if context canceled
select {
case <-ctx.Done():
return
default:
}
s.expiredTombstonesCallback(ctx, expired)
}
// HandleExpiredTombstones mark to be removed all objects that are expired in epoch
// and protected by tombstone with string address from tss.
//
// Does not modify tss.
func (s *Shard) HandleExpiredTombstones(tss map[string]struct{}) {
inhume := make([]*object.Address, 0, len(tss))
err := s.metaBase.IterateCoveredByTombstones(tss, func(addr *object.Address) error {
inhume = append(inhume, addr)
return nil
})
if err != nil {
s.log.Warn("iterator over expired objects failed",
zap.String("error", err.Error()),
)
return
} else if len(inhume) == 0 {
return
}
_, err = s.metaBase.Inhume(new(meta.InhumePrm).
WithAddresses(inhume...).
WithGCMark(),
)
if err != nil {
s.log.Warn("could not inhume objects under the expired tombstone",
zap.String("error", err.Error()),
)
return
}
}

View file

@ -1,8 +1,10 @@
package shard package shard
import ( import (
"context"
"time" "time"
"github.com/nspcc-dev/neofs-api-go/pkg/object"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor"
meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase" meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase"
"github.com/nspcc-dev/neofs-node/pkg/util" "github.com/nspcc-dev/neofs-node/pkg/util"
@ -27,6 +29,9 @@ type Shard struct {
// Option represents Shard's constructor option. // Option represents Shard's constructor option.
type Option func(*cfg) type Option func(*cfg)
// ExpiredObjectsCallback is a callback handling list of expired objects.
type ExpiredObjectsCallback func(context.Context, []*object.Address)
type cfg struct { type cfg struct {
rmBatchSize int rmBatchSize int
@ -43,6 +48,8 @@ type cfg struct {
log *logger.Logger log *logger.Logger
gcCfg *gcCfg gcCfg *gcCfg
expiredTombstonesCallback ExpiredObjectsCallback
} }
func defaultCfg() *cfg { func defaultCfg() *cfg {
@ -157,3 +164,11 @@ func WithGCRemoverSleepInterval(dur time.Duration) Option {
c.gcCfg.removerInterval = dur c.gcCfg.removerInterval = dur
} }
} }
// WithExpiredObjectsCallback returns option to specify callback
// of the expired tombstones handler.
func WithExpiredObjectsCallback(cb ExpiredObjectsCallback) Option {
return func(c *cfg) {
c.expiredTombstonesCallback = cb
}
}