From 19a6ca789685057ea2191041ce25d69249fe3774 Mon Sep 17 00:00:00 2001 From: Pavel Karpy <carpawell@nspcc.ru> Date: Sat, 12 Nov 2022 16:48:44 +0300 Subject: [PATCH] [#1502] node: Store lock object on every container node Includes extending listing methods in the Storage Engine with object types. It allows tuning replication/policer algorithms: container nodes do not remove `LOCK` objects as redundant and try to fulfill `LOCK` placement on the ohter container nodes. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru> --- CHANGELOG.md | 2 ++ pkg/local_object_storage/engine/evacuate.go | 12 ++++--- pkg/local_object_storage/engine/list.go | 8 ++--- pkg/local_object_storage/engine/list_test.go | 16 ++++----- pkg/local_object_storage/metabase/list.go | 34 ++++++++++++------- .../metabase/list_test.go | 33 +++++++++--------- pkg/local_object_storage/shard/list.go | 6 ++-- pkg/services/policer/check.go | 28 ++++++++++----- pkg/services/policer/process.go | 8 ++--- pkg/services/policer/queue.go | 4 +-- 10 files changed, 87 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e83031c61..0cc9a40b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Changelog for NeoFS Node ### Changed - `object lock` command reads CID and OID the same way other commands do (#1971) +- `LOCK` object are stored on every container node (#1502) ### Fixed - Open FSTree in sync mode by default (#1992) @@ -28,6 +29,7 @@ Changelog for NeoFS Node - Session token's IAT and NBF checks in ACL service (#2028) - Losing meta information on request forwarding (#2040) - Assembly process triggered by a request with a bearer token (#2040) +- Losing locking context after metabase resync (#1502) ### Removed ### Updated diff --git a/pkg/local_object_storage/engine/evacuate.go b/pkg/local_object_storage/engine/evacuate.go index 0a19e537f..ad989fd3e 100644 --- a/pkg/local_object_storage/engine/evacuate.go +++ b/pkg/local_object_storage/engine/evacuate.go @@ -136,8 +136,10 @@ mainLoop: loop: for i := range lst { + addr := lst[i].Address + var getPrm shard.GetPrm - getPrm.SetAddress(lst[i]) + getPrm.SetAddress(addr) getRes, err := sh.Get(getPrm) if err != nil { @@ -147,18 +149,18 @@ mainLoop: return res, err } - hrw.SortSliceByWeightValue(shards, weights, hrw.Hash([]byte(lst[i].EncodeToString()))) + hrw.SortSliceByWeightValue(shards, weights, hrw.Hash([]byte(addr.EncodeToString()))) for j := range shards { if _, ok := shardMap[shards[j].ID().String()]; ok { continue } - putDone, exists := e.putToShard(shards[j].hashedShard, j, shards[j].pool, lst[i], getRes.Object()) + putDone, exists := e.putToShard(shards[j].hashedShard, j, shards[j].pool, addr, getRes.Object()) if putDone || exists { if putDone { e.log.Debug("object is moved to another shard", zap.String("from", sidList[n]), zap.Stringer("to", shards[j].ID()), - zap.Stringer("addr", lst[i])) + zap.Stringer("addr", addr)) res.count++ } @@ -172,7 +174,7 @@ mainLoop: return res, fmt.Errorf("%w: %s", errPutShard, lst[i]) } - err = prm.handler(lst[i], getRes.Object()) + err = prm.handler(addr, getRes.Object()) if err != nil { return res, err } diff --git a/pkg/local_object_storage/engine/list.go b/pkg/local_object_storage/engine/list.go index 6abb9167c..0845ded92 100644 --- a/pkg/local_object_storage/engine/list.go +++ b/pkg/local_object_storage/engine/list.go @@ -3,8 +3,8 @@ package engine import ( "sort" + objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/shard" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" ) // ErrEndOfListing is returned from an object listing with cursor @@ -38,12 +38,12 @@ func (p *ListWithCursorPrm) WithCursor(cursor *Cursor) { // ListWithCursorRes contains values returned from ListWithCursor operation. type ListWithCursorRes struct { - addrList []oid.Address + addrList []objectcore.AddressWithType cursor *Cursor } // AddressList returns addresses selected by ListWithCursor operation. -func (l ListWithCursorRes) AddressList() []oid.Address { +func (l ListWithCursorRes) AddressList() []objectcore.AddressWithType { return l.addrList } @@ -60,7 +60,7 @@ func (l ListWithCursorRes) Cursor() *Cursor { // Returns ErrEndOfListing if there are no more objects to return or count // parameter set to zero. func (e *StorageEngine) ListWithCursor(prm ListWithCursorPrm) (ListWithCursorRes, error) { - result := make([]oid.Address, 0, prm.count) + result := make([]objectcore.AddressWithType, 0, prm.count) // 1. Get available shards and sort them. e.mtx.RLock() diff --git a/pkg/local_object_storage/engine/list_test.go b/pkg/local_object_storage/engine/list_test.go index 31bbb9321..2762aa37a 100644 --- a/pkg/local_object_storage/engine/list_test.go +++ b/pkg/local_object_storage/engine/list_test.go @@ -8,7 +8,7 @@ import ( "github.com/nspcc-dev/neofs-node/pkg/core/object" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" "github.com/stretchr/testify/require" ) @@ -24,8 +24,8 @@ func TestListWithCursor(t *testing.T) { const total = 20 - expected := make([]oid.Address, 0, total) - got := make([]oid.Address, 0, total) + expected := make([]object.AddressWithType, 0, total) + got := make([]object.AddressWithType, 0, total) for i := 0; i < total; i++ { containerID := cidtest.ID() @@ -36,7 +36,7 @@ func TestListWithCursor(t *testing.T) { _, err := e.Put(prm) require.NoError(t, err) - expected = append(expected, object.AddressOf(obj)) + expected = append(expected, object.AddressWithType{Type: objectSDK.TypeRegular, Address: object.AddressOf(obj)}) } expected = sortAddresses(expected) @@ -68,9 +68,9 @@ func TestListWithCursor(t *testing.T) { require.Equal(t, expected, got) } -func sortAddresses(addr []oid.Address) []oid.Address { - sort.Slice(addr, func(i, j int) bool { - return addr[i].EncodeToString() < addr[j].EncodeToString() +func sortAddresses(addrWithType []object.AddressWithType) []object.AddressWithType { + sort.Slice(addrWithType, func(i, j int) bool { + return addrWithType[i].Address.EncodeToString() < addrWithType[j].Address.EncodeToString() }) - return addr + return addrWithType } diff --git a/pkg/local_object_storage/metabase/list.go b/pkg/local_object_storage/metabase/list.go index 21701fc64..4bd065be9 100644 --- a/pkg/local_object_storage/metabase/list.go +++ b/pkg/local_object_storage/metabase/list.go @@ -1,8 +1,10 @@ package meta import ( + objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/util/logicerr" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "go.etcd.io/bbolt" ) @@ -38,12 +40,12 @@ func (l *ListPrm) SetCursor(cursor *Cursor) { // ListRes contains values returned from ListWithCursor operation. type ListRes struct { - addrList []oid.Address + addrList []objectcore.AddressWithType cursor *Cursor } // AddressList returns addresses selected by ListWithCursor operation. -func (l ListRes) AddressList() []oid.Address { +func (l ListRes) AddressList() []objectcore.AddressWithType { return l.addrList } @@ -62,7 +64,7 @@ func (db *DB) ListWithCursor(prm ListPrm) (res ListRes, err error) { db.modeMtx.RLock() defer db.modeMtx.RUnlock() - result := make([]oid.Address, 0, prm.count) + result := make([]objectcore.AddressWithType, 0, prm.count) err = db.boltDB.View(func(tx *bbolt.Tx) error { res.addrList, res.cursor, err = db.listWithCursor(tx, result, prm.count, prm.cursor) @@ -72,7 +74,7 @@ func (db *DB) ListWithCursor(prm ListPrm) (res ListRes, err error) { return res, err } -func (db *DB) listWithCursor(tx *bbolt.Tx, result []oid.Address, count int, cursor *Cursor) ([]oid.Address, *Cursor, error) { +func (db *DB) listWithCursor(tx *bbolt.Tx, result []objectcore.AddressWithType, count int, cursor *Cursor) ([]objectcore.AddressWithType, *Cursor, error) { threshold := cursor == nil // threshold is a flag to ignore cursor var bucketName []byte @@ -97,12 +99,17 @@ loop: continue } + var objType object.Type + switch prefix { - case - primaryPrefix, - storageGroupPrefix, - lockersPrefix, - tombstonePrefix: + case primaryPrefix: + objType = object.TypeRegular + case storageGroupPrefix: + objType = object.TypeStorageGroup + case lockersPrefix: + objType = object.TypeLock + case tombstonePrefix: + objType = object.TypeTombstone default: continue } @@ -110,7 +117,7 @@ loop: bkt := tx.Bucket(name) if bkt != nil { copy(rawAddr, cidRaw) - result, offset, cursor = selectNFromBucket(bkt, graveyardBkt, garbageBkt, rawAddr, containerID, + result, offset, cursor = selectNFromBucket(bkt, objType, graveyardBkt, garbageBkt, rawAddr, containerID, result, count, cursor, threshold) } bucketName = name @@ -145,14 +152,15 @@ loop: // selectNFromBucket similar to selectAllFromBucket but uses cursor to find // object to start selecting from. Ignores inhumed objects. func selectNFromBucket(bkt *bbolt.Bucket, // main bucket + objType object.Type, // type of the objects stored in the main bucket graveyardBkt, garbageBkt *bbolt.Bucket, // cached graveyard buckets cidRaw []byte, // container ID prefix, optimization cnt cid.ID, // container ID - to []oid.Address, // listing result + to []objectcore.AddressWithType, // listing result limit int, // stop listing at `limit` items in result cursor *Cursor, // start from cursor object threshold bool, // ignore cursor and start immediately -) ([]oid.Address, []byte, *Cursor) { +) ([]objectcore.AddressWithType, []byte, *Cursor) { if cursor == nil { cursor = new(Cursor) } @@ -186,7 +194,7 @@ func selectNFromBucket(bkt *bbolt.Bucket, // main bucket var a oid.Address a.SetContainer(cnt) a.SetObject(obj) - to = append(to, a) + to = append(to, objectcore.AddressWithType{Address: a, Type: objType}) count++ } diff --git a/pkg/local_object_storage/metabase/list_test.go b/pkg/local_object_storage/metabase/list_test.go index bef33eb13..3a325afa2 100644 --- a/pkg/local_object_storage/metabase/list_test.go +++ b/pkg/local_object_storage/metabase/list_test.go @@ -9,7 +9,6 @@ import ( meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" "github.com/stretchr/testify/require" "go.etcd.io/bbolt" @@ -73,7 +72,7 @@ func TestLisObjectsWithCursor(t *testing.T) { total = containers * 5 // regular + ts + sg + child + lock ) - expected := make([]oid.Address, 0, total) + expected := make([]object.AddressWithType, 0, total) // fill metabase with objects for i := 0; i < containers; i++ { @@ -84,28 +83,28 @@ func TestLisObjectsWithCursor(t *testing.T) { obj.SetType(objectSDK.TypeRegular) err := putBig(db, obj) require.NoError(t, err) - expected = append(expected, object.AddressOf(obj)) + expected = append(expected, object.AddressWithType{Address: object.AddressOf(obj), Type: objectSDK.TypeRegular}) // add one tombstone obj = generateObjectWithCID(t, containerID) obj.SetType(objectSDK.TypeTombstone) err = putBig(db, obj) require.NoError(t, err) - expected = append(expected, object.AddressOf(obj)) + expected = append(expected, object.AddressWithType{Address: object.AddressOf(obj), Type: objectSDK.TypeTombstone}) // add one storage group obj = generateObjectWithCID(t, containerID) obj.SetType(objectSDK.TypeStorageGroup) err = putBig(db, obj) require.NoError(t, err) - expected = append(expected, object.AddressOf(obj)) + expected = append(expected, object.AddressWithType{Address: object.AddressOf(obj), Type: objectSDK.TypeStorageGroup}) // add one lock obj = generateObjectWithCID(t, containerID) obj.SetType(objectSDK.TypeLock) err = putBig(db, obj) require.NoError(t, err) - expected = append(expected, object.AddressOf(obj)) + expected = append(expected, object.AddressWithType{Address: object.AddressOf(obj), Type: objectSDK.TypeLock}) // add one inhumed (do not include into expected) obj = generateObjectWithCID(t, containerID) @@ -127,14 +126,14 @@ func TestLisObjectsWithCursor(t *testing.T) { child.SetSplitID(splitID) err = putBig(db, child) require.NoError(t, err) - expected = append(expected, object.AddressOf(child)) + expected = append(expected, object.AddressWithType{Address: object.AddressOf(child), Type: objectSDK.TypeRegular}) } expected = sortAddresses(expected) t.Run("success with various count", func(t *testing.T) { for countPerReq := 1; countPerReq <= total; countPerReq++ { - got := make([]oid.Address, 0, total) + got := make([]object.AddressWithType, 0, total) res, cursor, err := metaListWithCursor(db, uint32(countPerReq), nil) require.NoError(t, err, "count:%d", countPerReq) @@ -184,8 +183,8 @@ func TestAddObjectDuringListingWithCursor(t *testing.T) { got, cursor, err := metaListWithCursor(db, total/2, nil) require.NoError(t, err) for _, obj := range got { - if _, ok := expected[obj.EncodeToString()]; ok { - expected[obj.EncodeToString()]++ + if _, ok := expected[obj.Address.EncodeToString()]; ok { + expected[obj.Address.EncodeToString()]++ } } @@ -203,8 +202,8 @@ func TestAddObjectDuringListingWithCursor(t *testing.T) { break } for _, obj := range got { - if _, ok := expected[obj.EncodeToString()]; ok { - expected[obj.EncodeToString()]++ + if _, ok := expected[obj.Address.EncodeToString()]; ok { + expected[obj.Address.EncodeToString()]++ } } } @@ -216,14 +215,14 @@ func TestAddObjectDuringListingWithCursor(t *testing.T) { } -func sortAddresses(addr []oid.Address) []oid.Address { - sort.Slice(addr, func(i, j int) bool { - return addr[i].EncodeToString() < addr[j].EncodeToString() +func sortAddresses(addrWithType []object.AddressWithType) []object.AddressWithType { + sort.Slice(addrWithType, func(i, j int) bool { + return addrWithType[i].Address.EncodeToString() < addrWithType[j].Address.EncodeToString() }) - return addr + return addrWithType } -func metaListWithCursor(db *meta.DB, count uint32, cursor *meta.Cursor) ([]oid.Address, *meta.Cursor, error) { +func metaListWithCursor(db *meta.DB, count uint32, cursor *meta.Cursor) ([]object.AddressWithType, *meta.Cursor, error) { var listPrm meta.ListPrm listPrm.SetCount(count) listPrm.SetCursor(cursor) diff --git a/pkg/local_object_storage/shard/list.go b/pkg/local_object_storage/shard/list.go index 5fc4a7d98..eedd4a019 100644 --- a/pkg/local_object_storage/shard/list.go +++ b/pkg/local_object_storage/shard/list.go @@ -3,10 +3,10 @@ package shard import ( "fmt" + objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object" meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "go.uber.org/zap" ) @@ -36,7 +36,7 @@ type ListWithCursorPrm struct { // ListWithCursorRes contains values returned from ListWithCursor operation. type ListWithCursorRes struct { - addrList []oid.Address + addrList []objectcore.AddressWithType cursor *Cursor } @@ -53,7 +53,7 @@ func (p *ListWithCursorPrm) WithCursor(cursor *Cursor) { } // AddressList returns addresses selected by ListWithCursor operation. -func (r ListWithCursorRes) AddressList() []oid.Address { +func (r ListWithCursorRes) AddressList() []objectcore.AddressWithType { return r.addrList } diff --git a/pkg/services/policer/check.go b/pkg/services/policer/check.go index 459133f8a..fa4a3f106 100644 --- a/pkg/services/policer/check.go +++ b/pkg/services/policer/check.go @@ -5,13 +5,14 @@ import ( "errors" "github.com/nspcc-dev/neofs-node/pkg/core/container" + objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" headsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/head" "github.com/nspcc-dev/neofs-node/pkg/services/replicator" "github.com/nspcc-dev/neofs-sdk-go/client" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" "github.com/nspcc-dev/neofs-sdk-go/netmap" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/nspcc-dev/neofs-sdk-go/object" "go.uber.org/zap" ) @@ -64,8 +65,10 @@ func (n *nodeCache) SubmitSuccessfulReplication(node netmap.NodeInfo) { n.submitReplicaHolder(node) } -func (p *Policer) processObject(ctx context.Context, addr oid.Address) { +func (p *Policer) processObject(ctx context.Context, addrWithType objectcore.AddressWithType) { + addr := addrWithType.Address idCnr := addr.Container() + idObj := addr.Object() cnr, err := p.cnrSrc.Get(idCnr) if err != nil { @@ -75,14 +78,14 @@ func (p *Policer) processObject(ctx context.Context, addr oid.Address) { ) if container.IsErrNotFound(err) { var prm engine.InhumePrm - prm.MarkAsGarbage(addr) + prm.MarkAsGarbage(addrWithType.Address) prm.WithForceRemoval() _, err := p.jobQueue.localStorage.Inhume(prm) if err != nil { p.log.Error("could not inhume object with missing container", zap.Stringer("cid", idCnr), - zap.Stringer("oid", addr.Object()), + zap.Stringer("oid", idObj), zap.String("error", err.Error())) } } @@ -91,9 +94,8 @@ func (p *Policer) processObject(ctx context.Context, addr oid.Address) { } policy := cnr.Value.PlacementPolicy() - obj := addr.Object() - nn, err := p.placementBuilder.BuildPlacement(idCnr, &obj, policy) + nn, err := p.placementBuilder.BuildPlacement(idCnr, &idObj, policy) if err != nil { p.log.Error("could not build placement vector for object", zap.Stringer("cid", idCnr), @@ -122,7 +124,7 @@ func (p *Policer) processObject(ctx context.Context, addr oid.Address) { default: } - p.processNodes(c, addr, nn[i], policy.ReplicaNumberByIndex(i), checkedNodes) + p.processNodes(c, addrWithType, nn[i], policy.ReplicaNumberByIndex(i), checkedNodes) } if !c.needLocalCopy { @@ -140,8 +142,10 @@ type processPlacementContext struct { needLocalCopy bool } -func (p *Policer) processNodes(ctx *processPlacementContext, addr oid.Address, +func (p *Policer) processNodes(ctx *processPlacementContext, addrWithType objectcore.AddressWithType, nodes []netmap.NodeInfo, shortage uint32, checkedNodes *nodeCache) { + addr := addrWithType.Address + typ := addrWithType.Type prm := new(headsvc.RemoteHeadPrm).WithObjectAddress(addr) // Number of copies that are stored on maintenance nodes. @@ -162,6 +166,14 @@ func (p *Policer) processNodes(ctx *processPlacementContext, addr oid.Address, ) } + if typ == object.TypeLock { + // all nodes of a container must store the `LOCK` objects + // for correct object removal protection: + // - `LOCK` objects are broadcast on their PUT requests; + // - `LOCK` object removal is a prohibited action in the GC. + shortage = uint32(len(nodes)) + } + for i := 0; shortage > 0 && i < len(nodes); i++ { select { case <-ctx.Done(): diff --git a/pkg/services/policer/process.go b/pkg/services/policer/process.go index ca360968e..bab1b9e18 100644 --- a/pkg/services/policer/process.go +++ b/pkg/services/policer/process.go @@ -5,8 +5,8 @@ import ( "errors" "time" + objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "go.uber.org/zap" ) @@ -21,7 +21,7 @@ func (p *Policer) Run(ctx context.Context) { func (p *Policer) shardPolicyWorker(ctx context.Context) { var ( - addrs []oid.Address + addrs []objectcore.AddressWithType cursor *engine.Cursor err error ) @@ -47,7 +47,7 @@ func (p *Policer) shardPolicyWorker(ctx context.Context) { case <-ctx.Done(): return default: - addr := addrs[i] + addr := addrs[i].Address if p.objsInWork.inWork(addr) { // do not process an object // that is in work @@ -62,7 +62,7 @@ func (p *Policer) shardPolicyWorker(ctx context.Context) { p.objsInWork.add(addr) - p.processObject(ctx, addr) + p.processObject(ctx, addrs[i]) p.cache.Add(addr, time.Now()) p.objsInWork.remove(addr) diff --git a/pkg/services/policer/queue.go b/pkg/services/policer/queue.go index 31e2ee0d2..4b2cc4170 100644 --- a/pkg/services/policer/queue.go +++ b/pkg/services/policer/queue.go @@ -3,15 +3,15 @@ package policer import ( "fmt" + objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" ) type jobQueue struct { localStorage *engine.StorageEngine } -func (q *jobQueue) Select(cursor *engine.Cursor, count uint32) ([]oid.Address, *engine.Cursor, error) { +func (q *jobQueue) Select(cursor *engine.Cursor, count uint32) ([]objectcore.AddressWithType, *engine.Cursor, error) { var prm engine.ListWithCursorPrm prm.WithCursor(cursor) prm.WithCount(count)