diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a040f6cfa..2ed1c1302c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Changelog for NeoFS Node - Treat only HALTed main transactions as successfully executed, retry the rest (#3868) - `different object owner and session issuer` for stored objects (#3929) - SearchV2 method returning zero result instead of "bad request" error for incorrect numeric filters (#3934) +- Garbage double-counting in metrics (#3933) ### Changed - `object search` CLI command is now the same as `object searchv2` (#3931) diff --git a/cmd/neofs-lancet/internal/meta/remove.go b/cmd/neofs-lancet/internal/meta/remove.go index 5ac206622b..11bcfee1b1 100644 --- a/cmd/neofs-lancet/internal/meta/remove.go +++ b/cmd/neofs-lancet/internal/meta/remove.go @@ -2,9 +2,11 @@ package meta import ( "fmt" + "slices" common "github.com/nspcc-dev/neofs-node/cmd/neofs-lancet/internal" blobstorcommon "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/common" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/spf13/cobra" ) @@ -49,13 +51,36 @@ func removeFunc(cmd *cobra.Command, _ []string) error { return fmt.Errorf("can't init metabase: %w", err) } - res, err := db.Delete(addrs) - if err != nil { - return fmt.Errorf("can't remove objects: %w", err) + var ( + cnr cid.ID + oids []oid.ID + ) + + var delGroup = func(cnr cid.ID, oids []oid.ID) error { + res, err := db.Delete(cnr, oids) + if err != nil { + return fmt.Errorf("can't remove objects: %w", err) + } + for _, r := range res.RemovedObjects { + cmd.Println("Removed:", r.ID.String()) + } + return nil } - for _, r := range res.RemovedObjects { - cmd.Println("Removed:", r.Address.String()) + slices.SortFunc(addrs, oid.Address.Compare) + + for i := range addrs { + if cnr != addrs[i].Container() { + if len(oids) != 0 { + err = delGroup(cnr, oids) + if err != nil { + return err + } + } + cnr = addrs[i].Container() + oids = oids[:0] + } + oids = append(oids, addrs[i].Object()) } - return nil + return delGroup(cnr, oids) } diff --git a/pkg/local_object_storage/engine/delete.go b/pkg/local_object_storage/engine/delete.go index 0d7ec70812..0e9751beee 100644 --- a/pkg/local_object_storage/engine/delete.go +++ b/pkg/local_object_storage/engine/delete.go @@ -4,6 +4,7 @@ import ( "slices" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/shard" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" ) @@ -24,8 +25,8 @@ func (e *StorageEngine) Delete(addr oid.Address) error { if e.blockErr != nil { return e.blockErr } - return e.processAddrDelete(addr, func(sh *shard.Shard, addrs []oid.Address) error { - return sh.MarkGarbage(addrs...) + return e.processAddrDelete(addr, func(sh *shard.Shard, cnr cid.ID, addrs []oid.ID) error { + return sh.MarkGarbage(cnr, addrs) }) } @@ -73,8 +74,8 @@ func (e *StorageEngine) DeleteRedundantCopies(addr oid.Address, shardIDs []strin return nil } - return e.processAddrDeleteOnShards(deleteShards, addr, func(sh *shard.Shard, addrs []oid.Address) error { - return sh.MarkGarbage(addrs...) + return e.processAddrDeleteOnShards(deleteShards, addr, func(sh *shard.Shard, cnr cid.ID, addrs []oid.ID) error { + return sh.MarkGarbage(cnr, addrs) }) } diff --git a/pkg/local_object_storage/engine/inhume.go b/pkg/local_object_storage/engine/inhume.go index c50d45b700..17ada63a94 100644 --- a/pkg/local_object_storage/engine/inhume.go +++ b/pkg/local_object_storage/engine/inhume.go @@ -43,13 +43,13 @@ func (e *StorageEngine) InhumeContainer(cID cid.ID) error { } // processAddrDelete processes deletion (inhume or immediate delete) of an object by its address. -func (e *StorageEngine) processAddrDelete(addr oid.Address, deleteFunc func(*shard.Shard, []oid.Address) error) error { +func (e *StorageEngine) processAddrDelete(addr oid.Address, deleteFunc func(*shard.Shard, cid.ID, []oid.ID) error) error { return e.processAddrDeleteOnShards(e.sortedShards(addr.Object()), addr, deleteFunc) } -func (e *StorageEngine) processAddrDeleteOnShards(shards []shardWrapper, addr oid.Address, deleteFunc func(*shard.Shard, []oid.Address) error) error { +func (e *StorageEngine) processAddrDeleteOnShards(shards []shardWrapper, addr oid.Address, deleteFunc func(*shard.Shard, cid.ID, []oid.ID) error) error { var ( - children []oid.Address + children []oid.ID err error root bool siNoLink *object.SplitInfo @@ -126,13 +126,13 @@ func (e *StorageEngine) processAddrDeleteOnShards(shards []shardWrapper, addr oi break } - children = measuredObjsToAddresses(addr.Container(), link.Objects()) + children = measuredObjsToOIDs(addr.Container(), link.Objects()) } else { // v1 split - children = oIDsToAddresses(addr.Container(), linkObj.Children()) + children = linkObj.Children() } - children = append(children, linkAddr) + children = append(children, linkID) break } @@ -141,7 +141,7 @@ func (e *StorageEngine) processAddrDeleteOnShards(shards []shardWrapper, addr oi continue } - err = deleteFunc(sh.Shard, []oid.Address{addr}) + err = deleteFunc(sh.Shard, addr.Container(), []oid.ID{addr.Object()}) if err != nil { if !errors.Is(err, logicerr.Error) { e.reportShardError(sh, "could not inhume object in shard", err, zap.Stringer("addr", addr)) @@ -159,14 +159,14 @@ func (e *StorageEngine) processAddrDeleteOnShards(shards []shardWrapper, addr oi } var ( - addrs = append(children, addr) + addrs = append(children, addr.Object()) ok bool retErr error ) // has not found the object on any shard, so delete on the most probable one for _, sh := range shards { - err = deleteFunc(sh.Shard, addrs) + err = deleteFunc(sh.Shard, addr.Container(), addrs) if err != nil { var errLocked apistatus.ObjectLocked @@ -193,7 +193,7 @@ func (e *StorageEngine) processAddrDeleteOnShards(shards []shardWrapper, addr oi return retErr } -func (e *StorageEngine) collectChildrenWithoutLink(addr oid.Address, si *object.SplitInfo) []oid.Address { +func (e *StorageEngine) collectChildrenWithoutLink(addr oid.Address, si *object.SplitInfo) []oid.ID { e.log.Info("root object has no link object in split upload", zap.Stringer("addrBeingInhumed", addr)) @@ -203,7 +203,7 @@ func (e *StorageEngine) collectChildrenWithoutLink(addr oid.Address, si *object. case !firstID.IsZero(): res, err := e.collectRawWithAttribute(addr.Container(), object.FilterFirstSplitObject, firstID[:]) if err == nil { - res = append(res, oid.NewAddress(addr.Container(), firstID)) + res = append(res, firstID) return res } e.log.Warn("failed to collect objects with first ID", zap.Stringer("addrBeingInhumed", addr), zap.Error(err)) @@ -276,27 +276,10 @@ func (e *StorageEngine) processExpiredObjects(addrs []oid.Address) { } } -func measuredObjsToAddresses(cID cid.ID, mm []object.MeasuredObject) []oid.Address { - var addr oid.Address - addr.SetContainer(cID) - - res := make([]oid.Address, 0, len(mm)) +func measuredObjsToOIDs(cID cid.ID, mm []object.MeasuredObject) []oid.ID { + res := make([]oid.ID, 0, len(mm)) for i := range mm { - addr.SetObject(mm[i].ObjectID()) - res = append(res, addr) - } - - return res -} - -func oIDsToAddresses(cID cid.ID, oo []oid.ID) []oid.Address { - var addr oid.Address - addr.SetContainer(cID) - - res := make([]oid.Address, 0, len(oo)) - for _, o := range oo { - addr.SetObject(o) - res = append(res, addr) + res = append(res, mm[i].ObjectID()) } return res diff --git a/pkg/local_object_storage/engine/put.go b/pkg/local_object_storage/engine/put.go index b4f744fc32..8ae58cd916 100644 --- a/pkg/local_object_storage/engine/put.go +++ b/pkg/local_object_storage/engine/put.go @@ -221,9 +221,8 @@ func (e *StorageEngine) broadcastObject(obj *object.Object, objBin []byte) error if isFatal && len(goodShards) > 0 { // Revert potential damage. - var addrs = []oid.Address{addr} for _, sh := range goodShards { - var err = sh.Delete(addrs) + var err = sh.Delete(addr.Container(), []oid.ID{addr.Object()}) if err != nil { e.log.Warn("failed to rollback incorrect put", zap.Stringer("shard", sh.ID()), diff --git a/pkg/local_object_storage/engine/select.go b/pkg/local_object_storage/engine/select.go index e1184aac35..25e910b9e7 100644 --- a/pkg/local_object_storage/engine/select.go +++ b/pkg/local_object_storage/engine/select.go @@ -110,7 +110,7 @@ func (e *StorageEngine) Search(cnr cid.ID, fs []objectcore.SearchFilter, attrs [ return res[:count], c, nil } -func (e *StorageEngine) collectRawWithAttribute(cnr cid.ID, attr string, val []byte) ([]oid.Address, error) { +func (e *StorageEngine) collectRawWithAttribute(cnr cid.ID, attr string, val []byte) ([]oid.ID, error) { var ( err error shards = e.unsortedShards() @@ -123,14 +123,14 @@ func (e *StorageEngine) collectRawWithAttribute(cnr cid.ID, attr string, val []b return nil, fmt.Errorf("shard %s: %w", sh.ID(), err) } } - return mergeOIDs(cnr, ids), nil + return mergeOIDs(ids), nil } -// mergeOIDs merges given set of lists of object IDs into a single flat -// list of addresses in the same given container. ids are expected to be -// sorted and the result contains no duplicates from different original -// list (lists are expected to not contain any inner duplicates). -func mergeOIDs(cnr cid.ID, ids [][]oid.ID) []oid.Address { +// mergeOIDs merges given set of lists of object IDs into a single flat list. +// ids are expected to be sorted and the result contains no duplicates from +// different original lists (slices are expected to not contain any inner +// duplicates). +func mergeOIDs(ids [][]oid.ID) []oid.ID { var numOfRes int for i := range ids { @@ -143,7 +143,7 @@ func mergeOIDs(cnr cid.ID, ids [][]oid.ID) []oid.Address { var ( idx = make([]int, len(ids)) - res = make([]oid.Address, 0, numOfRes) + res = make([]oid.ID, 0, numOfRes) ) var haveUnhandledID = func() bool { @@ -179,7 +179,7 @@ func mergeOIDs(cnr cid.ID, ids [][]oid.ID) []oid.Address { } } } - res = append(res, oid.NewAddress(cnr, minID)) + res = append(res, minID) idx[minIdx]++ } diff --git a/pkg/local_object_storage/engine/select_test.go b/pkg/local_object_storage/engine/select_test.go index f1962eafd4..8c7b37ba7a 100644 --- a/pkg/local_object_storage/engine/select_test.go +++ b/pkg/local_object_storage/engine/select_test.go @@ -4,86 +4,66 @@ import ( "slices" "testing" - cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/stretchr/testify/require" ) func TestMergeOIDs(t *testing.T) { - var cnr = cid.ID{0xff} - - var equalAddresses = func(a, b oid.Address) bool { + var equalIDs = func(a, b oid.ID) bool { return a.Compare(b) == 0 } t.Run("nil list", func(t *testing.T) { - require.Nil(t, mergeOIDs(cnr, nil)) + require.Nil(t, mergeOIDs(nil)) }) t.Run("empty list", func(t *testing.T) { - require.Nil(t, mergeOIDs(cnr, make([][]oid.ID, 0))) + require.Nil(t, mergeOIDs(make([][]oid.ID, 0))) }) t.Run("empty elements", func(t *testing.T) { - require.Nil(t, mergeOIDs(cnr, make([][]oid.ID, 4))) + require.Nil(t, mergeOIDs(make([][]oid.ID, 4))) }) t.Run("single list", func(t *testing.T) { var ( - expected []oid.Address + expected []oid.ID ids = []oid.ID{{1}, {2}, {3}} ) - for _, id := range ids { - expected = append(expected, oid.NewAddress(cnr, id)) - } - require.Equal(t, expected, mergeOIDs(cnr, [][]oid.ID{ids})) + expected = slices.Clone(ids) + require.Equal(t, expected, mergeOIDs([][]oid.ID{ids})) }) t.Run("two lists", func(t *testing.T) { var ( - expected []oid.Address + expected []oid.ID ids1 = []oid.ID{{1}, {2}, {3}} ids2 = []oid.ID{{4}, {5}, {6}} ) - for _, id := range ids1 { - expected = append(expected, oid.NewAddress(cnr, id)) - } - for _, id := range ids2 { - expected = append(expected, oid.NewAddress(cnr, id)) - } - require.Equal(t, expected, mergeOIDs(cnr, [][]oid.ID{ids1, ids2})) + expected = slices.Concat(ids1, ids2) + require.Equal(t, expected, mergeOIDs([][]oid.ID{ids1, ids2})) }) t.Run("two mixed lists", func(t *testing.T) { var ( - expected []oid.Address + expected []oid.ID ids1 = []oid.ID{{1}, {3}, {5}} ids2 = []oid.ID{{2}, {4}, {6}, {7}} ) - for _, id := range ids1 { - expected = append(expected, oid.NewAddress(cnr, id)) - } - for _, id := range ids2 { - expected = append(expected, oid.NewAddress(cnr, id)) - } - slices.SortFunc(expected, oid.Address.Compare) - require.Equal(t, expected, mergeOIDs(cnr, [][]oid.ID{ids1, ids2})) + expected = slices.Concat(ids1, ids2) + slices.SortFunc(expected, oid.ID.Compare) + require.Equal(t, expected, mergeOIDs([][]oid.ID{ids1, ids2})) }) t.Run("two lists with dups", func(t *testing.T) { var ( - expected []oid.Address + expected []oid.ID ids1 = []oid.ID{{1}, {2}, {3}} ids2 = []oid.ID{{2}, {3}, {4}, {5}} ) - for _, id := range ids1 { - expected = append(expected, oid.NewAddress(cnr, id)) - } - for _, id := range ids2 { - expected = append(expected, oid.NewAddress(cnr, id)) - } - slices.SortFunc(expected, oid.Address.Compare) - expected = slices.CompactFunc(expected, equalAddresses) + expected = slices.Concat(ids1, ids2) + slices.SortFunc(expected, oid.ID.Compare) + expected = slices.CompactFunc(expected, equalIDs) require.Len(t, expected, 5) // Ensure sort/compact. - require.Equal(t, expected, mergeOIDs(cnr, [][]oid.ID{ids1, ids2})) + require.Equal(t, expected, mergeOIDs([][]oid.ID{ids1, ids2})) }) t.Run("four lists with dups", func(t *testing.T) { var ( - expected []oid.Address + expected []oid.ID ids1 = []oid.ID{{1}, {2}, {3}} ids2 = []oid.ID{{2}, {3}, {4}, {5}} ids3 = []oid.ID{{3}, {4}, {5}} @@ -91,13 +71,11 @@ func TestMergeOIDs(t *testing.T) { idz = [][]oid.ID{ids1, ids2, ids3, ids4} ) for _, ids := range idz { - for _, id := range ids { - expected = append(expected, oid.NewAddress(cnr, id)) - } + expected = append(expected, ids...) } - slices.SortFunc(expected, oid.Address.Compare) - expected = slices.CompactFunc(expected, equalAddresses) + slices.SortFunc(expected, oid.ID.Compare) + expected = slices.CompactFunc(expected, equalIDs) require.Len(t, expected, 7) // Ensure sort/compact. - require.Equal(t, expected, mergeOIDs(cnr, idz)) + require.Equal(t, expected, mergeOIDs(idz)) }) } diff --git a/pkg/local_object_storage/metabase/containers_test.go b/pkg/local_object_storage/metabase/containers_test.go index f28e886b40..eddcb9feae 100644 --- a/pkg/local_object_storage/metabase/containers_test.go +++ b/pkg/local_object_storage/metabase/containers_test.go @@ -8,6 +8,7 @@ import ( cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" "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" objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" "github.com/stretchr/testify/require" @@ -161,7 +162,7 @@ func TestDB_ContainerSize(t *testing.T) { volume := cids[cnr] for i, obj := range list { - _, err := db.MarkGarbage(obj.Address()) + _, err := db.MarkGarbage(obj.GetContainerID(), []oid.ID{obj.GetID()}) require.NoError(t, err) volume -= int(obj.PayloadSize()) diff --git a/pkg/local_object_storage/metabase/counter_test.go b/pkg/local_object_storage/metabase/counter_test.go index 4245271bda..5460619f78 100644 --- a/pkg/local_object_storage/metabase/counter_test.go +++ b/pkg/local_object_storage/metabase/counter_test.go @@ -96,7 +96,7 @@ func TestCounters(t *testing.T) { oo := putTypedObjs(t, db, typ, objCount, false, payloadSize) for i := objCount - 1; i >= 0; i-- { - res, err := db.Delete([]oid.Address{oo[i].Address()}) + res, err := db.Delete(oo[i].GetContainerID(), []oid.ID{oo[i].GetID()}) require.NoError(t, err) require.Equal(t, -1, res.Counters.Phy) @@ -267,7 +267,7 @@ func TestCounters(t *testing.T) { require.Zero(t, c.Lock) require.Zero(t, c.GC) - _, err := db.MarkGarbage(par.Address()) + _, err := db.MarkGarbage(par.GetContainerID(), []oid.ID{par.GetID()}) require.NoError(t, err) c, err = db.ObjectCounters() @@ -291,12 +291,12 @@ func TestCounters(t *testing.T) { } // no parent removals, `Delete` is not expected to face parents - chainAddrs := make([]oid.Address, 0, len(chain)) + chainAddrs := make([]oid.ID, 0, len(chain)) for _, ch := range chain { - chainAddrs = append(chainAddrs, ch.Address()) + chainAddrs = append(chainAddrs, ch.GetID()) } - _, err = db.Delete(chainAddrs) + _, err = db.Delete(cnr, chainAddrs) require.NoError(t, err) c, err = db.ObjectCounters() diff --git a/pkg/local_object_storage/metabase/delete.go b/pkg/local_object_storage/metabase/delete.go index 01783ae70f..823e7652f9 100644 --- a/pkg/local_object_storage/metabase/delete.go +++ b/pkg/local_object_storage/metabase/delete.go @@ -17,7 +17,7 @@ import ( // RemovedObjects describes single item handled by [DB.Delete]. type RemovedObject struct { - Address oid.Address + ID oid.ID PayloadLen uint64 } @@ -37,7 +37,7 @@ type DeleteRes struct { // Delete also looks up for objects that are hardly linked with elements of // addrs list but not in the list themselves. If there are any, they are also // deleted. -func (db *DB) Delete(addrs []oid.Address) (DeleteRes, error) { +func (db *DB) Delete(cnr cid.ID, addrs []oid.ID) (DeleteRes, error) { db.modeMtx.RLock() defer db.modeMtx.RUnlock() @@ -52,14 +52,19 @@ func (db *DB) Delete(addrs []oid.Address) (DeleteRes, error) { var removed []RemovedObject err = db.boltDB.Update(func(tx *bbolt.Tx) error { + var metaBucket = tx.Bucket(metaBucketKey(cnr)) + if metaBucket == nil { + return nil + } + var metaCursor = metaBucket.Cursor() // We need to clear slice because tx can try to execute multiple times. - diff, removed, err = db.deleteGroup(tx, addrs) + diff, removed, err = db.deleteGroup(metaCursor, cnr, addrs) return err }) if err == nil { for i := range addrs { storagelog.Write(db.log, - storagelog.AddressField(addrs[i]), + storagelog.AddressField(oid.NewAddress(cnr, addrs[i])), storagelog.OpField("metabase DELETE")) } } @@ -75,23 +80,24 @@ func (db *DB) Delete(addrs []oid.Address) (DeleteRes, error) { // objects that were stored. The second return value is a logical objects // removed number: objects that were available (without Tombstones, GCMarks // non-expired, etc.) -func (db *DB) deleteGroup(tx *bbolt.Tx, addrs []oid.Address) (CountersDiff, []RemovedObject, error) { +func (db *DB) deleteGroup(metaCursor *bbolt.Cursor, cnr cid.ID, addrs []oid.ID) (CountersDiff, []RemovedObject, error) { var errorCount int var firstErr error var diff CountersDiff - removedObjs, err := supplementRemovedObjects(tx, addrs) + removedObjs, err := supplementRemovedObjects(metaCursor, addrs) if err != nil { return diff, nil, fmt.Errorf("extend removed objects: %w", err) } for i := range removedObjs { - objectDiff, err := db.delete(tx, removedObjs[i].Address) + objectDiff, err := db.delete(metaCursor, cnr, removedObjs[i].ID) if err != nil { errorCount++ - db.log.Warn("failed to delete object", zap.Stringer("addr", removedObjs[i].Address), zap.Error(err)) + var addr = oid.NewAddress(cnr, removedObjs[i].ID) + db.log.Warn("failed to delete object", zap.Stringer("addr", addr), zap.Error(err)) if firstErr == nil { - firstErr = fmt.Errorf("%s object delete fail: %w", removedObjs[i].Address, err) + firstErr = fmt.Errorf("%s object delete fail: %w", addr, err) } continue @@ -111,15 +117,8 @@ func (db *DB) deleteGroup(tx *bbolt.Tx, addrs []oid.Address) (CountersDiff, []Re } // delete removes object indexes from the metabase. -func (db *DB) delete(tx *bbolt.Tx, addr oid.Address) (CountersDiff, error) { - cID := addr.Container() - metaBucket := tx.Bucket(metaBucketKey(cID)) - if metaBucket == nil { - return CountersDiff{}, nil - } - var metaCursor = metaBucket.Cursor() - - diff, err := deleteMetadata(metaCursor, db.log, addr.Container(), addr.Object(), false) +func (db *DB) delete(metaCursor *bbolt.Cursor, cnr cid.ID, addr oid.ID) (CountersDiff, error) { + diff, err := deleteMetadata(metaCursor, db.log, cnr, addr, false) if err != nil { if !errors.Is(err, errNonPhy) { return CountersDiff{}, fmt.Errorf("can't remove metadata indexes: %w", err) @@ -129,53 +128,28 @@ func (db *DB) delete(tx *bbolt.Tx, addr oid.Address) (CountersDiff, error) { return diff, nil } -// forms list of objects from addrs and their missing parts. +// forms list of objects from oid list and their missing parts. // [RemovedObject.PayloadLen] is not initialized. -func supplementRemovedObjects(tx *bbolt.Tx, addrs []oid.Address) ([]RemovedObject, error) { - cnrMetaBktKey := make([]byte, 1+cid.Size) - cnrMetaBktKey[0] = metadataPrefix - - res := make([]RemovedObject, len(addrs)) +func supplementRemovedObjects(cur *bbolt.Cursor, addrs []oid.ID) ([]RemovedObject, error) { + var ( + err error + res = make([]RemovedObject, len(addrs)) + ) for i := range addrs { - res[i].Address = addrs[i] + res[i].ID = addrs[i] } - - slices.SortFunc(res, func(a, b RemovedObject) int { - return a.Address.Compare(b.Address) // Container-only sorting is sufficient here, but Compare() is more convenient anyway. - }) - - var err error - var cnrMetaBkt *bbolt.Bucket - var cnrMetaCrs *bbolt.Cursor - for i := range res { - cnr := res[i].Address.Container() - - if i == 0 || cnr != res[i-1].Address.Container() { - copy(cnrMetaBktKey[1:], cnr[:]) - - cnrMetaBkt = tx.Bucket(cnrMetaBktKey) - if cnrMetaBkt == nil { - continue - } - cnrMetaCrs = cnrMetaBkt.Cursor() - } else if cnrMetaBkt == nil { - continue - } - - res, err = supplementRemovedECParts(res, cnrMetaCrs, addrs, res[i].Address) + for i := range addrs { + res, err = supplementRemovedECParts(res, cur, addrs, addrs[i]) if err != nil { - return nil, fmt.Errorf("collect EC parts for %s: %w", res[i].Address, err) + return nil, fmt.Errorf("collect EC parts for %s: %w", addrs[i], err) } } return res, nil } -// extends res with EC parts of addr which are not in addrs and returns updated res. -func supplementRemovedECParts(res []RemovedObject, cnrMetaCrs *bbolt.Cursor, addrs []oid.Address, addr oid.Address) ([]RemovedObject, error) { - cnr := addr.Container() - parent := addr.Object() - +// extends res with EC parts of parent which are not in addrs and returns updated res. +func supplementRemovedECParts(res []RemovedObject, cnrMetaCrs *bbolt.Cursor, addrs []oid.ID, parent oid.ID) ([]RemovedObject, error) { var partCrs *bbolt.Cursor var ecPref []byte for id := range iterAttrVal(cnrMetaCrs, object.FilterParentID, parent[:]) { @@ -198,9 +172,9 @@ func supplementRemovedECParts(res []RemovedObject, cnrMetaCrs *bbolt.Cursor, add continue } - if !slices.ContainsFunc(addrs, func(addr oid.Address) bool { return addr.Container() == cnr && addr.Object() == id }) { + if !slices.Contains(addrs, id) { res = append(res, RemovedObject{ - Address: oid.NewAddress(cnr, id), + ID: id, }) } } diff --git a/pkg/local_object_storage/metabase/delete_test.go b/pkg/local_object_storage/metabase/delete_test.go index 20bcc701e5..f21af87acc 100644 --- a/pkg/local_object_storage/metabase/delete_test.go +++ b/pkg/local_object_storage/metabase/delete_test.go @@ -3,7 +3,6 @@ package meta_test import ( "errors" "math/rand/v2" - "slices" "testing" iec "github.com/nspcc-dev/neofs-node/internal/ec" @@ -36,7 +35,7 @@ func TestDB_Delete(t *testing.T) { require.NoError(t, err) // try to remove parent, should be no-op, error-free - res, err := db.Delete([]oid.Address{parent.Address()}) + res, err := db.Delete(cnr, []oid.ID{idParent}) require.NoError(t, err) require.Zero(t, res.Counters.Phy) @@ -47,7 +46,7 @@ func TestDB_Delete(t *testing.T) { require.NoError(t, err) // delete object - err = metaDelete(db, child.Address()) + _, err = db.Delete(cnr, []oid.ID{child.GetID()}) require.NoError(t, err) // check if the child is still inhumed (deletion should not affect @@ -64,7 +63,6 @@ func TestDB_Delete(t *testing.T) { t.Run("EC", func(t *testing.T) { cnr1 := cidtest.ID() - cnr2 := cidtest.ID() const anyRuleIdx = 1 anySigner := neofscryptotest.Signer() @@ -88,26 +86,23 @@ func TestDB_Delete(t *testing.T) { return parent, ecParts } - parent1, ecParts1 := newGroup(cnr1, 3) - parent2, ecParts2 := newGroup(cnr2, 5) + parent1, ecParts1 := newGroup(cnr1, 5) - parent1Addr := parent1.Address() - parent2Addr := parent2.Address() + parent1ID := parent1.GetID() - res, err := db.Delete([]oid.Address{parent1Addr, parent2Addr}) + res, err := db.Delete(cnr1, []oid.ID{parent1ID}) require.NoError(t, err) - all := 2 + len(ecParts1) + len(ecParts2) - require.EqualValues(t, -(all - 2), res.Counters.Phy) + all := 1 + len(ecParts1) + require.EqualValues(t, -(all - 1), res.Counters.Phy) require.Len(t, res.RemovedObjects, all) - require.ElementsMatch(t, res.RemovedObjects[:2], []meta.RemovedObject{ - {Address: parent1Addr, PayloadLen: 0}, - {Address: parent2Addr, PayloadLen: 0}, + require.ElementsMatch(t, res.RemovedObjects[:1], []meta.RemovedObject{ + {ID: parent1ID, PayloadLen: 0}, }) - for _, partObj := range slices.Concat(ecParts1, ecParts2) { + for _, partObj := range ecParts1 { require.Contains(t, res.RemovedObjects, meta.RemovedObject{ - Address: partObj.Address(), + ID: partObj.GetID(), PayloadLen: partObj.PayloadSize(), }) } @@ -132,15 +127,15 @@ func TestContainerInfo(t *testing.T) { require.Equal(t, uint64(1), info.ObjectsNumber) require.Equal(t, payloadSize, info.StorageSize) - addr := obj.Address() + objID := obj.GetID() - res, err := db.Delete([]oid.Address{addr}) + res, err := db.Delete(cID, []oid.ID{objID}) require.NoError(t, err) require.Equal(t, -1, res.Counters.Phy) require.Len(t, res.RemovedObjects, 1) require.Equal(t, payloadSize, res.RemovedObjects[0].PayloadLen) - require.Equal(t, addr, res.RemovedObjects[0].Address) + require.Equal(t, objID, res.RemovedObjects[0].ID) info, err = db.GetContainerInfo(cID) require.NoError(t, err) @@ -178,7 +173,7 @@ func TestDeleteAllChildren(t *testing.T) { require.True(t, errors.As(err, &siErr)) // remove all children in single call - err = metaDelete(db, child1.Address(), child2.Address()) + _, err = db.Delete(cnr, []oid.ID{child1.GetID(), child2.GetID()}) require.NoError(t, err) // parent should not be found now @@ -196,7 +191,8 @@ func TestGraveOnlyDelete(t *testing.T) { require.NoError(t, metaInhume(db, addr, oidtest.Address())) // delete the object data - require.NoError(t, metaDelete(db, addr)) + _, err := db.Delete(addr.Container(), []oid.ID{addr.Object()}) + require.NoError(t, err) } func TestExpiredObject(t *testing.T) { @@ -204,71 +200,10 @@ func TestExpiredObject(t *testing.T) { checkExpiredObjects(t, db, func(exp, nonExp *object.Object) { // removing expired object should be error-free - require.NoError(t, metaDelete(db, exp.Address())) - - require.NoError(t, metaDelete(db, nonExp.Address())) - }) -} - -func metaDelete(db *meta.DB, addrs ...oid.Address) error { - _, err := db.Delete(addrs) - return err -} - -func BenchmarkDB_Delete(b *testing.B) { - db := newDB(b) - const containerNum = 100 - const objectsPerContainer = 100 - const totalObjects = containerNum * objectsPerContainer - - bench := func(b *testing.B, addrs []oid.Address) { - for b.Loop() { - _, err := db.Delete(addrs) - require.NoError(b, err) - } - } - - b.Run("grouped", func(b *testing.B) { - bench := func(b *testing.B, sortFn func(a, b cid.ID) int) { - cnrs := cidtest.IDs(containerNum) - slices.SortFunc(cnrs, sortFn) - - addrs := make([]oid.Address, 0, totalObjects) - for i := range cnrs { - for range objectsPerContainer { - addrs = append(addrs, oid.NewAddress(cnrs[i], oidtest.ID())) - } - } - - bench(b, addrs) - } - b.Run("sorted", func(b *testing.B) { - bench(b, cid.ID.Compare) - }) - b.Run("reverse", func(b *testing.B) { - bench(b, func(a, b cid.ID) int { return b.Compare(a) }) - }) - }) - - b.Run("mixed", func(b *testing.B) { - bench := func(b *testing.B, sortFn func(a, b cid.ID) int) { - cnrs := cidtest.IDs(containerNum) - slices.SortFunc(cnrs, sortFn) - - addrs := make([]oid.Address, 0, totalObjects) - for range objectsPerContainer { - for i := range cnrs { - addrs = append(addrs, oid.NewAddress(cnrs[i], oidtest.ID())) - } - } + _, err := db.Delete(exp.GetContainerID(), []oid.ID{exp.GetID()}) + require.NoError(t, err) - bench(b, addrs) - } - b.Run("sorted", func(b *testing.B) { - bench(b, cid.ID.Compare) - }) - b.Run("reverse", func(b *testing.B) { - bench(b, func(a, b cid.ID) int { return b.Compare(a) }) - }) + _, err = db.Delete(nonExp.GetContainerID(), []oid.ID{nonExp.GetID()}) + require.NoError(t, err) }) } diff --git a/pkg/local_object_storage/metabase/ec_test.go b/pkg/local_object_storage/metabase/ec_test.go index 5c39bd77e5..666d9e17f0 100644 --- a/pkg/local_object_storage/metabase/ec_test.go +++ b/pkg/local_object_storage/metabase/ec_test.go @@ -81,7 +81,6 @@ func TestDB_ResolveECPart(t *testing.T) { } parentObj := newBlankObject(cnr, parentID) - parentAddr := parentObj.Address() newPart := func(t *testing.T, pi iec.PartInfo) object.Object { // artificially make parts of diff len, in reality they are the same @@ -109,8 +108,6 @@ func TestDB_ResolveECPart(t *testing.T) { tomb := newBlankObject(cnr, oidtest.OtherID(partID, locker.GetID())) tomb.AssociateDeleted(parentID) - partAddr := oid.NewAddress(cnr, partID) - type testcase struct { name string assertErr func(*testing.T, error) @@ -148,12 +145,12 @@ func TestDB_ResolveECPart(t *testing.T) { require.NoError(t, db.Put(&tomb)) }}, {name: "garbage mark only", assertErr: assertObjectNotFoundError, preset: func(t *testing.T, db *meta.DB) { - _, err := db.MarkGarbage(partAddr) + _, err := db.MarkGarbage(cnr, []oid.ID{partID}) require.NoError(t, err) }}, {name: "stored with garbage mark", assertErr: assertObjectNotFoundError, preset: func(t *testing.T, db *meta.DB) { require.NoError(t, db.Put(&partObj)) - _, err := db.MarkGarbage(parentAddr) + _, err := db.MarkGarbage(cnr, []oid.ID{parentID}) require.NoError(t, err) }}, {name: "container garbage mark only", assertErr: assertObjectNotFoundError, preset: func(t *testing.T, db *meta.DB) { @@ -171,7 +168,7 @@ func TestDB_ResolveECPart(t *testing.T) { }}, {name: "expired with garbage mark", assertErr: assertObjectExpiredError, preset: func(t *testing.T, db *meta.DB) { require.NoError(t, db.Put(&expiredObj)) - _, err := db.MarkGarbage(partAddr) + _, err := db.MarkGarbage(cnr, []oid.ID{partID}) require.NoError(t, err) }}, {name: "expired with container garbage mark", assertErr: assertObjectNotFoundError, preset: func(t *testing.T, db *meta.DB) { @@ -263,7 +260,7 @@ func TestDB_ResolveECPart(t *testing.T) { {name: "stored with garbage mark and locker", preset: func(t *testing.T) *meta.DB { db := newDB(t) require.NoError(t, db.Put(&partObj)) - _, err := db.MarkGarbage(partAddr) + _, err := db.MarkGarbage(cnr, []oid.ID{partID}) require.NoError(t, err) require.NoError(t, db.Put(&locker)) return db @@ -712,10 +709,12 @@ func testInhumeEC(t *testing.T) { signer := neofscryptotest.Signer() parent := *generateObjectWithCID(t, cnr) - parentAddr := parent.Address() - var parts []object.Object - var partAddrs []oid.Address + var ( + parts []object.Object + partAddrs []oid.Address + partIDs []oid.ID + ) for i := range partNum { part, err := iec.FormObjectForECPart(signer, parent, nil, iec.PartInfo{ RuleIndex: 123, // any @@ -726,6 +725,7 @@ func testInhumeEC(t *testing.T) { require.NoError(t, db.Put(&part)) parts = append(parts, part) + partIDs = append(partIDs, parts[i].GetID()) partAddrs = append(partAddrs, parts[i].Address()) } @@ -734,7 +734,7 @@ func testInhumeEC(t *testing.T) { err := db.Put(createTSForObject(cnr, parent.GetID())) require.NoError(t, err) - allAddrs := append(partAddrs, parentAddr) + allAddrs := append(partAddrs, parent.Address()) for _, addr := range allAddrs { _, err = db.Exists(addr, true) @@ -745,17 +745,19 @@ func testInhumeEC(t *testing.T) { assertObjectAlreadyRemovedError(t, err) } - g, _, err := db.GetGarbage(100) + trash, err := db.GetGarbage(100) require.NoError(t, err) - require.ElementsMatch(t, g, append(partAddrs, parentAddr)) + require.Len(t, trash, 1) + require.Equal(t, cnr, trash[0].Container) + require.ElementsMatch(t, append(partIDs, parent.GetID()), trash[0].Objects) - g = g[:0] + var g []oid.ID err = db.IterateOverGarbage(func(id oid.ID) error { - g = append(g, oid.NewAddress(cnr, id)) + g = append(g, id) return nil }, cnr, oid.ID{}) require.NoError(t, err) - require.ElementsMatch(t, g, append(partAddrs, parentAddr)) + require.ElementsMatch(t, g, append(partIDs, parent.GetID())) } func testMarkGarbageEC(t *testing.T) { @@ -768,8 +770,11 @@ func testMarkGarbageEC(t *testing.T) { parent := *generateObjectWithCID(t, cnr) parentAddr := parent.Address() - var parts []object.Object - var partAddrs []oid.Address + var ( + parts []object.Object + partAddrs []oid.Address + partIDs []oid.ID + ) for i := range partNum { part, err := iec.FormObjectForECPart(signer, parent, nil, iec.PartInfo{ RuleIndex: 123, // any @@ -780,12 +785,13 @@ func testMarkGarbageEC(t *testing.T) { require.NoError(t, db.Put(&part)) parts = append(parts, part) + partIDs = append(partIDs, parts[i].GetID()) partAddrs = append(partAddrs, parts[i].Address()) } assertECGroupAvailable(t, db, parent, parts) - inhumed, err := db.MarkGarbage(parentAddr) + inhumed, err := db.MarkGarbage(cnr, []oid.ID{parent.GetID()}) require.NoError(t, err) allAddrs := append(partAddrs, parentAddr) @@ -799,19 +805,21 @@ func testMarkGarbageEC(t *testing.T) { assertObjectNotFoundError(t, err) } - g, _, err := db.GetGarbage(100) + trash, err := db.GetGarbage(100) require.NoError(t, err) - require.ElementsMatch(t, g, append(partAddrs, parentAddr)) + require.Len(t, trash, 1) + require.Equal(t, cnr, trash[0].Container) + require.ElementsMatch(t, append(partIDs, parent.GetID()), trash[0].Objects) - g = g[:0] + var g []oid.ID err = db.IterateOverGarbage(func(id oid.ID) error { - g = append(g, oid.NewAddress(cnr, id)) + g = append(g, id) return nil }, cnr, oid.ID{}) require.NoError(t, err) - require.ElementsMatch(t, g, append(partAddrs, parentAddr)) + require.ElementsMatch(t, g, append(partIDs, parent.GetID())) - require.EqualValues(t, len(parts)+1, inhumed[0].NewGarbage) // parent also has a GC mark in the shard now + require.EqualValues(t, len(parts)+1, inhumed.NewGarbage) // parent also has a GC mark in the shard now } func assertECGroupAvailable(t *testing.T, db *meta.DB, parent object.Object, parts []object.Object) { diff --git a/pkg/local_object_storage/metabase/get_test.go b/pkg/local_object_storage/metabase/get_test.go index 17fd0eb047..20b0f17d96 100644 --- a/pkg/local_object_storage/metabase/get_test.go +++ b/pkg/local_object_storage/metabase/get_test.go @@ -119,7 +119,7 @@ func TestDB_Get(t *testing.T) { obj = oidtest.Address() - _, err = db.MarkGarbage(obj) + _, err = db.MarkGarbage(obj.Container(), []oid.ID{obj.Object()}) require.NoError(t, err) _, err = metaGet(db, obj, false) require.ErrorAs(t, err, new(apistatus.ObjectNotFound)) diff --git a/pkg/local_object_storage/metabase/graveyard.go b/pkg/local_object_storage/metabase/graveyard.go index a2e7e972ab..ab278079b1 100644 --- a/pkg/local_object_storage/metabase/graveyard.go +++ b/pkg/local_object_storage/metabase/graveyard.go @@ -9,6 +9,13 @@ import ( oid "github.com/nspcc-dev/neofs-sdk-go/object/id" ) +// TrashBin groups objects and containers that should be deleted. Empty objects +// list means container is empty and can be deleted completely. +type TrashBin struct { + Container cid.ID + Objects []oid.ID +} + // IterateOverGarbage iterates over all objects marked with GC mark in // the given container. // @@ -62,27 +69,25 @@ func (db *DB) iterateIDs(c *bbolt.Cursor, h func(oid.ID) error, offset oid.ID) e // GetGarbage returns garbage according to the metabase state. Garbage includes // objects marked with GC mark (expired, tombstoned but not deleted from disk, // extra replicated, etc.) and removed containers. -// The first return value describes garbage objects. These objects should be -// removed. The second return value describes garbage containers whose _all_ -// garbage objects were included in the first return value and, therefore, -// these containers can be deleted (if their objects are handled and deleted too). -func (db *DB) GetGarbage(limit int) ([]oid.Address, []cid.ID, error) { +// Garbage is retuned as a set of per-container TrashBins. Objects from a +// TrashBin should be removed if present. If TrashBin object list is empty +// then the respective container is empty and can be safely deleted. +func (db *DB) GetGarbage(limit int) ([]TrashBin, error) { if limit <= 0 { - return nil, nil, nil + return nil, nil } db.modeMtx.RLock() defer db.modeMtx.RUnlock() if db.mode.NoMetabase() { - return nil, nil, ErrDegradedMode + return nil, ErrDegradedMode } - const reasonableLimit = 1000 - initCap := min(limit, reasonableLimit) - - resObjects := make([]oid.Address, 0, initCap) - resContainers := make([]cid.ID, 0) + var ( + res []TrashBin + objNum int + ) err := db.boltDB.View(func(tx *bbolt.Tx) error { err := tx.ForEach(func(name []byte, b *bbolt.Bucket) error { @@ -93,7 +98,6 @@ func (db *DB) GetGarbage(limit int) ([]oid.Address, []cid.ID, error) { var ( cur = b.Cursor() deadContainer bool - err error objPrefix = metaPrefixGarbage ) if containerMarkedGC(b.Cursor()) { @@ -101,16 +105,19 @@ func (db *DB) GetGarbage(limit int) ([]oid.Address, []cid.ID, error) { objPrefix = metaPrefixID } - resObjects, err = listGarbageObjects(cur, objPrefix, cnr, resObjects, limit) - if err != nil { - return fmt.Errorf("listing objects for %s container: %w", cnr, err) + objs := listGarbageObjects(cur, objPrefix, cnr, limit) + if len(objs) != 0 { + res = append(res, TrashBin{Container: cnr, Objects: objs}) + objNum += len(objs) + if objNum >= limit { + return ErrInterruptIterator + } + return nil } - if len(resObjects) >= limit { - return ErrInterruptIterator - } else if deadContainer { + if deadContainer { // all the objects from the container were listed, // container can be removed - resContainers = append(resContainers, cnr) + res = append(res, TrashBin{Container: cnr}) } return nil }) @@ -121,16 +128,18 @@ func (db *DB) GetGarbage(limit int) ([]oid.Address, []cid.ID, error) { return nil }) - return resObjects, resContainers, err + return res, err } -func listGarbageObjects(cur *bbolt.Cursor, prefix byte, cnr cid.ID, objs []oid.Address, limit int) ([]oid.Address, error) { +func listGarbageObjects(cur *bbolt.Cursor, prefix byte, cnr cid.ID, limit int) []oid.ID { + var objs []oid.ID + for obj := range iterPrefixedIDs(cur, []byte{prefix}, oid.ID{}) { if len(objs) >= limit { break } - objs = append(objs, oid.NewAddress(cnr, obj)) + objs = append(objs, obj) } - return objs, nil + return objs } diff --git a/pkg/local_object_storage/metabase/graveyard_test.go b/pkg/local_object_storage/metabase/graveyard_test.go index 61c30b0dbe..8d6ecb11f7 100644 --- a/pkg/local_object_storage/metabase/graveyard_test.go +++ b/pkg/local_object_storage/metabase/graveyard_test.go @@ -63,7 +63,7 @@ func TestDB_Iterate_OffsetNotFound(t *testing.T) { err = putBig(db, obj1) require.NoError(t, err) - _, err = db.MarkGarbage(obj1.Address()) + _, err = db.MarkGarbage(obj1.GetContainerID(), []oid.ID{obj1.GetID()}) require.NoError(t, err) var counter int @@ -131,7 +131,7 @@ func TestDB_IterateDeletedObjects(t *testing.T) { require.NoError(t, err) // inhume with GC mark - _, err = db.MarkGarbage(obj3.Address(), obj4.Address()) + _, err = db.MarkGarbage(cnr, []oid.ID{obj3.GetID(), obj4.GetID()}) require.NoError(t, err) var buriedGC []oid.Address @@ -182,8 +182,8 @@ func TestDB_IterateOverGarbage_Offset(t *testing.T) { err = putBig(db, obj4) require.NoError(t, err) - _, err = db.MarkGarbage(obj1.Address(), obj2.Address(), - obj3.Address(), obj4.Address()) + _, err = db.MarkGarbage(cnr, []oid.ID{obj1.GetID(), obj2.GetID(), + obj3.GetID(), obj4.GetID()}) require.NoError(t, err) @@ -266,21 +266,17 @@ func TestDB_GetGarbage(t *testing.T) { require.NoError(t, err) for i := range numOfObjs { - garbageObjs, garbageContainers, err := db.GetGarbage(i + 1) + trash, err := db.GetGarbage(i + 1) require.NoError(t, err) - require.Len(t, garbageObjs, i+1) - - // we inhumed 5 objects container and requested 5 garbage objects - // max, so no info about if we have the 6-th one to delete, - // so can't say if this container can be deleted totally - require.Len(t, garbageContainers, 0) + require.Len(t, trash, 1) + require.Equal(t, cID, trash[0].Container) + require.Len(t, trash[0].Objects, i+1) } // check the whole container garbage case - garbageObjs, garbageContainers, err := db.GetGarbage(numOfObjs + 1) + trash, err := db.GetGarbage(numOfObjs + 1) require.NoError(t, err) - - require.Len(t, garbageObjs, numOfObjs) // still only numOfObjs are removed - require.Len(t, garbageContainers, 1) // but container can be deleted now - require.Equal(t, garbageContainers[0], cID) + require.Len(t, trash, 1) + require.Equal(t, cID, trash[0].Container) + require.Len(t, trash[0].Objects, numOfObjs) // still only numOfObjs are removed } diff --git a/pkg/local_object_storage/metabase/inhume.go b/pkg/local_object_storage/metabase/inhume.go index 293c98d54f..4ed359a3b7 100644 --- a/pkg/local_object_storage/metabase/inhume.go +++ b/pkg/local_object_storage/metabase/inhume.go @@ -1,9 +1,9 @@ package meta import ( + "bytes" "encoding/binary" "fmt" - "slices" "github.com/nspcc-dev/bbolt" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/util/logicerr" @@ -18,80 +18,66 @@ var ErrLockObjectRemoval = logicerr.New("lock object removal") // ContainerGarbageDiff groups [DB.MarkGarbage] operation counters changes by a container. type ContainerGarbageDiff struct { - CID cid.ID NewGarbage int PayloadDiff int64 } // MarkGarbage marks objects to be physically removed from shard. -func (db *DB) MarkGarbage(addrs ...oid.Address) ([]ContainerGarbageDiff, error) { +func (db *DB) MarkGarbage(cnr cid.ID, addrs []oid.ID) (ContainerGarbageDiff, error) { db.modeMtx.RLock() defer db.modeMtx.RUnlock() if db.mode.NoMetabase() { - return nil, ErrDegradedMode + return ContainerGarbageDiff{}, ErrDegradedMode } else if db.mode.ReadOnly() { - return nil, ErrReadOnlyMode + return ContainerGarbageDiff{}, ErrReadOnlyMode } var ( - currEpoch = db.epochState.CurrentEpoch() - err error - objsInCnr []oid.ID - containersCountersDiff []ContainerGarbageDiff + currEpoch = db.epochState.CurrentEpoch() + err error + objsInCnr = make([]oid.ID, 0, len(addrs)) + counterDiff ContainerGarbageDiff ) err = db.boltDB.Update(func(tx *bbolt.Tx) error { + metaBucket := tx.Bucket(metaBucketKey(cnr)) + if metaBucket == nil { + return nil + } + metaCursor := metaBucket.Cursor() + if containerMarkedGC(metaCursor) { + return nil + } + // collect children // TODO: Do not extend addrs, do in the main loop. This likely would be more efficient regarding memory. for i := range addrs { - objsInCnr = objsInCnr[:0] - - cnr := addrs[i].Container() - if slices.ContainsFunc(addrs[:i], func(a oid.Address) bool { return a.Container() == cnr }) { - continue // already handled, see loop below - } - - metaBucket := tx.Bucket(metaBucketKey(cnr)) - if metaBucket == nil { - continue - } - metaCursor := metaBucket.Cursor() - - for j := range addrs[i:] { - if j != 0 && addrs[i+j].Container() != cnr { - continue - } - parObj := addrs[i+j].Object() - partIDs, err := collectChildren(metaCursor, cnr, parObj) - if err != nil { - return fmt.Errorf("collect EC parts: %w", err) - } - objsInCnr = append(objsInCnr, parObj) - objsInCnr = append(objsInCnr, partIDs...) - } - - var diff = ContainerGarbageDiff{CID: cnr} - err = markGarbageInContainer(metaCursor, &diff, cnr, objsInCnr, currEpoch) - if err != nil { - return fmt.Errorf("marking objects for %s container: %w", cnr, err) - } - - err := updateCounter(metaBucket, gcCounter, int64(len(objsInCnr))) + parObj := addrs[i] + partIDs, err := collectChildren(metaCursor, cnr, parObj) if err != nil { - return fmt.Errorf("update %s container's gc counter to %d: %w", cnr, len(objsInCnr), err) - } - err = updateCounter(metaBucket, payloadCounter, diff.PayloadDiff) - if err != nil { - return fmt.Errorf("update %s container's user payload counter to %d: %w", cnr, diff.PayloadDiff, err) + return fmt.Errorf("collect EC parts: %w", err) } + objsInCnr = append(objsInCnr, parObj) + objsInCnr = append(objsInCnr, partIDs...) + } + err = markGarbageInContainer(metaCursor, &counterDiff, cnr, objsInCnr, currEpoch) + if err != nil { + return fmt.Errorf("marking objects for %s container: %w", cnr, err) + } - containersCountersDiff = append(containersCountersDiff, diff) + err := updateCounter(metaBucket, gcCounter, int64(counterDiff.NewGarbage)) + if err != nil { + return fmt.Errorf("update %s container's gc counter to %d: %w", cnr, counterDiff.NewGarbage, err) + } + err = updateCounter(metaBucket, payloadCounter, counterDiff.PayloadDiff) + if err != nil { + return fmt.Errorf("update %s container's user payload counter to %d: %w", cnr, counterDiff.PayloadDiff, err) } return nil }) - return containersCountersDiff, err + return counterDiff, err } func markGarbageInContainer(metaCursor *bbolt.Cursor, diff *ContainerGarbageDiff, cnr cid.ID, objs []oid.ID, currEpoch uint64) error { @@ -100,9 +86,15 @@ func markGarbageInContainer(metaCursor *bbolt.Cursor, diff *ContainerGarbageDiff metaBucket = metaCursor.Bucket() ) addr.SetContainer(cnr) - diff.NewGarbage += len(objs) for _, id := range objs { + var garbKey = mkGarbageKey(id) + + k, _ := metaCursor.Seek(garbKey) + if bytes.Equal(k, garbKey) { + continue + } + addr.SetObject(id) obj, err := get(metaCursor, addr, false, true, currEpoch) @@ -111,11 +103,11 @@ func markGarbageInContainer(metaCursor *bbolt.Cursor, diff *ContainerGarbageDiff diff.PayloadDiff -= int64(obj.PayloadSize()) } } - - err = metaBucket.Put(mkGarbageKey(id), nil) + err = metaBucket.Put(garbKey, nil) if err != nil { return err } + diff.NewGarbage++ } return nil diff --git a/pkg/local_object_storage/metabase/lock_test.go b/pkg/local_object_storage/metabase/lock_test.go index 3dcca50e8d..3eab907bb2 100644 --- a/pkg/local_object_storage/metabase/lock_test.go +++ b/pkg/local_object_storage/metabase/lock_test.go @@ -162,7 +162,7 @@ func TestDB_Lock_Removed(t *testing.T) { }}, {name: "with target and GC mark", preset: func(t *testing.T, db *meta.DB) { require.NoError(t, db.Put(&obj)) - _, err := db.MarkGarbage(objAddr) + _, err := db.MarkGarbage(cnr, []oid.ID{objID}) require.NoError(t, err) }}, } { diff --git a/pkg/local_object_storage/metabase/metadata_test.go b/pkg/local_object_storage/metabase/metadata_test.go index 4002221551..b5eaa3cb0d 100644 --- a/pkg/local_object_storage/metabase/metadata_test.go +++ b/pkg/local_object_storage/metabase/metadata_test.go @@ -573,7 +573,7 @@ func TestDB_SearchObjects(t *testing.T) { // all available check(t, ids) t.Run("garbage mark", func(t *testing.T) { - _, err := db.MarkGarbage(oid.NewAddress(cnr, ids[1])) + _, err := db.MarkGarbage(cnr, []oid.ID{ids[1]}) require.NoError(t, err) check(t, slices.Concat(ids[:1], ids[2:])) // resurrect the object @@ -599,7 +599,7 @@ func TestDB_SearchObjects(t *testing.T) { check(t, slices.Concat(ids[:3], ids[4:])) }) t.Run("rm", func(t *testing.T) { - _, err := db.Delete([]oid.Address{oid.NewAddress(cnr, ids[4])}) + _, err := db.Delete(cnr, []oid.ID{ids[4]}) require.NoError(t, err) check(t, slices.Concat(ids[:3], ids[5:])) }) diff --git a/pkg/local_object_storage/metabase/put_test.go b/pkg/local_object_storage/metabase/put_test.go index 2be15f0ecc..5bb27025fd 100644 --- a/pkg/local_object_storage/metabase/put_test.go +++ b/pkg/local_object_storage/metabase/put_test.go @@ -141,10 +141,11 @@ func TestDB_Put_ObjectWithTombstone(t *testing.T) { require.ErrorIs(t, err, meta.ErrEndOfListing) }) t.Run("get garbage", func(t *testing.T) { - gObjs, gCnrs, err := db.GetGarbage(100) + trash, err := db.GetGarbage(100) require.NoError(t, err) - require.Empty(t, gCnrs) - require.Equal(t, []oid.Address{addr}, gObjs) + require.Len(t, trash, 1) + require.Equal(t, cnr, trash[0].Container) + require.Equal(t, []oid.ID{addr.Object()}, trash[0].Objects) }) t.Run("iterate garbage", func(t *testing.T) { var collected []oid.Address @@ -156,10 +157,10 @@ func TestDB_Put_ObjectWithTombstone(t *testing.T) { require.Equal(t, []oid.Address{addr}, collected) }) t.Run("mark garbage", func(t *testing.T) { - // any GC mark should be considered as a GC counter increasing - n, err := db.MarkGarbage(addr) + // obj is already marked as garbage, so counter is not increased + n, err := db.MarkGarbage(obj.GetContainerID(), []oid.ID{obj.GetID()}) require.NoError(t, err) - require.EqualValues(t, 1, n[0].NewGarbage) + require.EqualValues(t, 0, n.NewGarbage) }) }) @@ -172,9 +173,10 @@ func TestDB_Put_ObjectWithTombstone(t *testing.T) { t.Run("after revival", func(t *testing.T) { // tombstone is deleted and the garbage was cleared t.Run("get garbage", func(t *testing.T) { - gObjs, _, err := db.GetGarbage(100) + trash, err := db.GetGarbage(100) require.NoError(t, err) - require.NotContains(t, gObjs, addr) + require.Len(t, trash, 0) + // require.NotContains(t, gObjs, addr) }) t.Run("iterate garbage", func(t *testing.T) { var collected []oid.Address @@ -185,7 +187,7 @@ func TestDB_Put_ObjectWithTombstone(t *testing.T) { require.NoError(t, err) require.NotContains(t, collected, addr) }) - _, err = db.Delete([]oid.Address{tsAddr}) + _, err = db.Delete(ts.GetContainerID(), []oid.ID{ts.GetID()}) require.NoError(t, err) assertObjectAvailability(t, db, addr, obj) @@ -219,9 +221,12 @@ func assertObjectAvailability(t *testing.T, db *meta.DB, addr oid.Address, obj o require.ErrorIs(t, err, meta.ErrEndOfListing) }) t.Run("get garbage", func(t *testing.T) { - gObjs, _, err := db.GetGarbage(100) + trash, err := db.GetGarbage(100) require.NoError(t, err) - require.NotContains(t, gObjs, addr) + require.Len(t, trash, 0) + // require.Equal(t, cnr, trash[0].Container) + // require.Equal(t, []oid.Address{addr}, trash[0].Objects) + // require.NotContains(t, gObjs, addr) }) t.Run("iterate garbage", func(t *testing.T) { var collected []oid.Address @@ -307,9 +312,9 @@ func TestDB_Put_Lock(t *testing.T) { {name: "with target and GC mark", preset: func(t *testing.T, db *meta.DB) { require.NoError(t, db.Put(&obj)) - n, err := db.MarkGarbage(objAddr) + n, err := db.MarkGarbage(obj.GetContainerID(), []oid.ID{obj.GetID()}) require.NoError(t, err) - require.EqualValues(t, 1, n[0].NewGarbage) + require.EqualValues(t, 1, n.NewGarbage) }}, } { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/local_object_storage/metabase/revive.go b/pkg/local_object_storage/metabase/revive.go index 84c960626b..8e666046e6 100644 --- a/pkg/local_object_storage/metabase/revive.go +++ b/pkg/local_object_storage/metabase/revive.go @@ -110,7 +110,7 @@ func (db *DB) ReviveObject(addr oid.Address) (res ReviveStatus, err error) { return errors.New("reported as deleted, but no tombstone found") } var tombAddress = oid.NewAddress(cnr, tombOID) - _, err := db.delete(tx, tombAddress) + _, err := db.delete(metaCursor, cnr, tombOID) if err != nil { return err } diff --git a/pkg/local_object_storage/metabase/revive_test.go b/pkg/local_object_storage/metabase/revive_test.go index e9753fa046..09c61c8a7a 100644 --- a/pkg/local_object_storage/metabase/revive_test.go +++ b/pkg/local_object_storage/metabase/revive_test.go @@ -5,6 +5,7 @@ import ( meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" + 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" ) @@ -62,7 +63,7 @@ func TestDB_ReviveObject(t *testing.T) { require.True(t, exists) // inhume with GC mark - _, err = db.MarkGarbage(raw.Address()) + _, err = db.MarkGarbage(raw.GetContainerID(), []oid.ID{raw.GetID()}) require.NoError(t, err) _, err = metaExists(db, raw.Address()) diff --git a/pkg/local_object_storage/metabase/status_test.go b/pkg/local_object_storage/metabase/status_test.go index 11148400e7..6888425831 100644 --- a/pkg/local_object_storage/metabase/status_test.go +++ b/pkg/local_object_storage/metabase/status_test.go @@ -89,9 +89,9 @@ func TestDB_ObjectStatus(t *testing.T) { addr := oid.NewAddress(obj.GetContainerID(), obj.GetID()) - n, err := db.MarkGarbage(addr) + n, err := db.MarkGarbage(obj.GetContainerID(), []oid.ID{obj.GetID()}) require.NoError(t, err) - require.EqualValues(t, 1, n[0].NewGarbage) + require.EqualValues(t, 1, n.NewGarbage) st, err := db.ObjectStatus(addr) require.NoError(t, err) diff --git a/pkg/local_object_storage/metabase/version_test.go b/pkg/local_object_storage/metabase/version_test.go index 322d1e3f58..8aaecec112 100644 --- a/pkg/local_object_storage/metabase/version_test.go +++ b/pkg/local_object_storage/metabase/version_test.go @@ -371,12 +371,12 @@ func TestMigrate7to8(t *testing.T) { }) require.NoError(t, err) - // Verify GetGarbage sees the container via meta marker (should list all objects and list the container) - gObjs, gCnrs, err := db.GetGarbage(inhumeObjsNum + 5) + // Verify GetGarbage sees the container via meta marker (should list all objects) + trash, err := db.GetGarbage(inhumeObjsNum + 5) require.NoError(t, err) - require.Len(t, gObjs, inhumeObjsNum) - require.Len(t, gCnrs, 1) - require.Equal(t, inhumeCnr, gCnrs[0]) + require.Len(t, trash, 1) + require.Equal(t, inhumeCnr, trash[0].Container) + require.Len(t, trash[0].Objects, inhumeObjsNum) } type ObjectCounters8Version struct { diff --git a/pkg/local_object_storage/shard/delete.go b/pkg/local_object_storage/shard/delete.go index 4071bb14b3..ef0b610b27 100644 --- a/pkg/local_object_storage/shard/delete.go +++ b/pkg/local_object_storage/shard/delete.go @@ -4,20 +4,21 @@ import ( "errors" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/writecache" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "go.uber.org/zap" ) // Delete removes data from the shard's writeCache, metaBase and // blobStor. -func (s *Shard) Delete(addrs []oid.Address) error { +func (s *Shard) Delete(cnr cid.ID, addrs []oid.ID) error { s.m.RLock() defer s.m.RUnlock() - return s.deleteObjs(addrs) + return s.deleteObjs(cnr, addrs) } -func (s *Shard) deleteObjs(addrs []oid.Address) error { +func (s *Shard) deleteObjs(cnr cid.ID, addrs []oid.ID) error { if s.info.Mode.ReadOnly() { return ErrReadOnlyMode } else if s.info.Mode.NoMetabase() { @@ -31,21 +32,21 @@ func (s *Shard) deleteObjs(addrs []oid.Address) error { hasWriteCache := s.hasWriteCache() if hasWriteCache { for _, addr := range addrs { - err := s.writeCache.Delete(addr) + err := s.writeCache.Delete(oid.NewAddress(cnr, addr)) if err != nil && !IsErrNotFound(err) && !errors.Is(err, writecache.ErrReadOnly) { s.log.Warn("can't delete object from write cache", zap.Error(err)) } } } - res, err := s.metaBase.Delete(addrs) + res, err := s.metaBase.Delete(cnr, addrs) if err != nil { return err // stop on metabase error ? } if hasWriteCache { for i := range res.RemovedObjects[len(addrs):] { // the rest are addrs, removed above - err := s.writeCache.Delete(res.RemovedObjects[i].Address) + err := s.writeCache.Delete(oid.NewAddress(cnr, res.RemovedObjects[i].ID)) if err != nil && !IsErrNotFound(err) && !errors.Is(err, writecache.ErrReadOnly) { s.log.Warn("can't delete object from write cache", zap.Error(err)) } @@ -62,24 +63,24 @@ func (s *Shard) deleteObjs(addrs []oid.Address) error { var totalRemovedPayload uint64 for i := range res.RemovedObjects { - removedPayload := res.RemovedObjects[i].PayloadLen - totalRemovedPayload += removedPayload - s.addToContainerSize(res.RemovedObjects[i].Address.Container().EncodeToString(), -int64(removedPayload)) + totalRemovedPayload += res.RemovedObjects[i].PayloadLen - err = s.blobStor.Delete(res.RemovedObjects[i].Address) + var addr = oid.NewAddress(cnr, res.RemovedObjects[i].ID) + err = s.blobStor.Delete(addr) if err == nil { - logOp(s.log, deleteOp, res.RemovedObjects[i].Address) + logOp(s.log, deleteOp, addr) } else { if IsErrNotFound(err) { continue } s.log.Debug("can't remove object from blobStor", - zap.Stringer("object_address", res.RemovedObjects[i].Address), + zap.Stringer("object_address", addr), zap.Error(err)) } } + s.addToContainerSize(cnr.EncodeToString(), -int64(totalRemovedPayload)) s.addToPayloadCounter(-int64(totalRemovedPayload)) return nil diff --git a/pkg/local_object_storage/shard/delete_test.go b/pkg/local_object_storage/shard/delete_test.go index b5e7461e1a..2fafbeaebd 100644 --- a/pkg/local_object_storage/shard/delete_test.go +++ b/pkg/local_object_storage/shard/delete_test.go @@ -37,7 +37,7 @@ func testShardDelete(t *testing.T, hasWriteCache bool) { _, err = sh.Get(obj.Address(), false) require.NoError(t, err) - err = sh.Delete([]oid.Address{obj.Address()}) + err = sh.Delete(obj.GetContainerID(), []oid.ID{obj.GetID()}) require.NoError(t, err) _, err = sh.Get(obj.Address(), false) @@ -55,7 +55,7 @@ func testShardDelete(t *testing.T, hasWriteCache bool) { _, err = sh.Get(obj.Address(), false) require.NoError(t, err) - err = sh.Delete([]oid.Address{obj.Address()}) + err = sh.Delete(obj.GetContainerID(), []oid.ID{obj.GetID()}) require.NoError(t, err) _, err = sh.Get(obj.Address(), false) diff --git a/pkg/local_object_storage/shard/gc.go b/pkg/local_object_storage/shard/gc.go index ce03568795..4f8552f5ad 100644 --- a/pkg/local_object_storage/shard/gc.go +++ b/pkg/local_object_storage/shard/gc.go @@ -142,7 +142,7 @@ func (s *Shard) removeGarbage() { s.collectExpiredObjects() - gObjs, gContainers, err := s.metaBase.GetGarbage(s.rmBatchSize) + bins, err := s.metaBase.GetGarbage(s.rmBatchSize) if err != nil { s.log.Warn("fetching garbage objects", zap.Error(err), @@ -151,25 +151,22 @@ func (s *Shard) removeGarbage() { return } - // delete accumulated objects - err = s.deleteObjs(gObjs) - if err != nil { - s.log.Warn("could not delete the objects", - zap.Error(err), - ) - - return - } - - // objects are removed, clean up empty container (all the object - // were deleted from the disk) information from the metabase - for _, cID := range gContainers { - err = s.metaBase.DeleteContainer(cID) - if err != nil { - s.log.Warn("clean up container in metabase", - zap.Stringer("cID", cID), - zap.Error(err), - ) + for _, bin := range bins { + if len(bin.Objects) == 0 { + // objects are removed, clean up empty container (all the object + // were deleted from the disk) information from the metabase + err = s.metaBase.DeleteContainer(bin.Container) + if err != nil { + s.log.Warn("clean up container in metabase", + zap.Stringer("cID", bin.Container), + zap.Error(err), + ) + } + } else { + err := s.deleteObjs(bin.Container, bin.Objects) + if err != nil { + s.log.Warn("can't delete objects", zap.Error(err)) + } } } } @@ -190,8 +187,9 @@ func (s *Shard) collectExpiredObjects() { } var ( - toDeleteTombstones []oid.Address - expiredObjects []oid.Address + tombBins []meta.TrashBin + tombCount int + expiredObjects []oid.Address ) log := s.log.With(zap.Uint64("epoch", epoch)) log.Debug("started expired objects handling") @@ -200,7 +198,13 @@ func (s *Shard) collectExpiredObjects() { err := s.metaBase.IterateExpired(epoch, func(addr oid.Address, typ object.Type) error { switch typ { case object.TypeTombstone: - toDeleteTombstones = append(toDeleteTombstones, addr) + if len(tombBins) != 0 && + tombBins[len(tombBins)-1].Container == addr.Container() { + tombBins[len(tombBins)-1].Objects = append(tombBins[len(tombBins)-1].Objects, addr.Object()) + } else { + tombBins = append(tombBins, meta.TrashBin{Container: addr.Container(), Objects: []oid.ID{addr.Object()}}) + } + tombCount++ default: expiredObjects = append(expiredObjects, addr) } @@ -217,16 +221,13 @@ func (s *Shard) collectExpiredObjects() { s.gc.processedEpoch.Store(epoch) } - log.Debug("collected expired tombstones", zap.Int("num", len(toDeleteTombstones))) - if len(toDeleteTombstones) > 0 { - err = s.deleteObjs(toDeleteTombstones) + log.Debug("collected expired tombstones", zap.Int("num", tombCount)) + for _, bin := range tombBins { + err = s.deleteObjs(bin.Container, bin.Objects) if err != nil { - log.Warn("could not delete tombstones", - zap.Error(err), - ) + log.Warn("can't delete tombstones", zap.Error(err)) } } - log.Debug("collected expired objects", zap.Int("num", len(expiredObjects))) if len(expiredObjects) > 0 && s.expiredObjectsCallback != nil { s.expiredObjectsCallback(expiredObjects) diff --git a/pkg/local_object_storage/shard/gc_test.go b/pkg/local_object_storage/shard/gc_test.go index 27f7a779c7..a243760d87 100644 --- a/pkg/local_object_storage/shard/gc_test.go +++ b/pkg/local_object_storage/shard/gc_test.go @@ -68,7 +68,9 @@ func TestGC_ExpiredObjectWithExpiredLock(t *testing.T) { meta.WithMaxBatchDelay(time.Microsecond), ), shard.WithExpiredObjectsCallback(func(aa []oid.Address) { - require.NoError(t, sh.Delete(aa)) + for _, a := range aa { + require.NoError(t, sh.Delete(a.Container(), []oid.ID{a.Object()})) + } }), shard.WithGCRemoverSleepInterval(gcInterval), shard.WithContainerPayments(&testContainerPayments{}), @@ -186,8 +188,10 @@ func TestExpiration(t *testing.T) { meta.WithMaxBatchDelay(time.Microsecond), ), shard.WithExpiredObjectsCallback( - func(addresses []oid.Address) { - require.NoError(t, sh.Delete(addresses)) + func(aa []oid.Address) { + for _, a := range aa { + require.NoError(t, sh.Delete(a.Container(), []oid.ID{a.Object()})) + } }, ), shard.WithGCRemoverSleepInterval(gcInterval), @@ -249,8 +253,10 @@ func TestContainerPayments(t *testing.T) { meta.WithMaxBatchDelay(time.Microsecond), ), shard.WithExpiredObjectsCallback( - func(addresses []oid.Address) { - require.NoError(t, sh.Delete(addresses)) + func(aa []oid.Address) { + for _, a := range aa { + require.NoError(t, sh.Delete(a.Container(), []oid.ID{a.Object()})) + } }, ), shard.WithGCRemoverSleepInterval(100 * time.Millisecond), diff --git a/pkg/local_object_storage/shard/inhume.go b/pkg/local_object_storage/shard/inhume.go index 2ce06aa283..b6e4c00c09 100644 --- a/pkg/local_object_storage/shard/inhume.go +++ b/pkg/local_object_storage/shard/inhume.go @@ -17,7 +17,7 @@ var ErrLockObjectRemoval = meta.ErrLockObjectRemoval // mark that overrides any restrictions imposed on object deletion (to be used // by control service and other manual intervention cases). Otherwise similar // to [Shard.Inhume], but doesn't need a tombstone. -func (s *Shard) MarkGarbage(addrs ...oid.Address) error { +func (s *Shard) MarkGarbage(cnr cid.ID, addrs []oid.ID) error { s.m.RLock() defer s.m.RUnlock() @@ -27,7 +27,7 @@ func (s *Shard) MarkGarbage(addrs ...oid.Address) error { return ErrDegradedMode } - inhumed, err := s.metaBase.MarkGarbage(addrs...) + inhumed, err := s.metaBase.MarkGarbage(cnr, addrs) if err != nil { s.log.Debug("could not mark object to delete in metabase", zap.Error(err), @@ -36,17 +36,15 @@ func (s *Shard) MarkGarbage(addrs ...oid.Address) error { return fmt.Errorf("metabase inhume: %w", err) } - for _, cnrDiff := range inhumed { - cnrStr := cnrDiff.CID.EncodeToString() + cnrStr := cnr.EncodeToString() - s.addObjectCounter(gcObjType, cnrDiff.NewGarbage) - s.addToContainerSize(cnrStr, cnrDiff.PayloadDiff) - s.addToPayloadCounter(cnrDiff.PayloadDiff) - } + s.addObjectCounter(gcObjType, inhumed.NewGarbage) + s.addToContainerSize(cnrStr, inhumed.PayloadDiff) + s.addToPayloadCounter(inhumed.PayloadDiff) if s.hasWriteCache() { for i := range addrs { - _ = s.writeCache.Delete(addrs[i]) + _ = s.writeCache.Delete(oid.NewAddress(cnr, addrs[i])) } } diff --git a/pkg/local_object_storage/shard/lock_test.go b/pkg/local_object_storage/shard/lock_test.go index 0d14e56050..6aceb39d8e 100644 --- a/pkg/local_object_storage/shard/lock_test.go +++ b/pkg/local_object_storage/shard/lock_test.go @@ -79,7 +79,7 @@ func TestShard_Lock(t *testing.T) { }) t.Run("force objects inhuming", func(t *testing.T) { - err = sh.MarkGarbage(lock.Address()) + err = sh.MarkGarbage(lock.GetContainerID(), []oid.ID{lock.GetID()}) require.NoError(t, err) // it should be possible to remove @@ -191,7 +191,7 @@ func TestShard_Lock_Removed(t *testing.T) { }}, {name: "with target and GC mark", preset: func(t *testing.T, sh *shard.Shard) { require.NoError(t, sh.Put(&obj, nil)) - err := sh.MarkGarbage(objAddr) + err := sh.MarkGarbage(obj.GetContainerID(), []oid.ID{obj.GetID()}) require.NoError(t, err) }}, } { diff --git a/pkg/local_object_storage/shard/metrics_test.go b/pkg/local_object_storage/shard/metrics_test.go index 99208d5b50..dbb09a3e07 100644 --- a/pkg/local_object_storage/shard/metrics_test.go +++ b/pkg/local_object_storage/shard/metrics_test.go @@ -138,7 +138,7 @@ func TestCounters(t *testing.T) { inhumedNumber := objNumber / 4 for i := range inhumedNumber { - err := sh.MarkGarbage(oo[i].Address()) + err := sh.MarkGarbage(oo[i].GetContainerID(), []oid.ID{oo[i].GetID()}) require.NoError(t, err) } @@ -191,8 +191,10 @@ func TestCounters(t *testing.T) { deletedNumber := int(phy / 4) - err := sh.Delete(addrFromObjs(oo[:deletedNumber])) - require.NoError(t, err) + for _, obj := range oo[:deletedNumber] { + err := sh.Delete(obj.GetContainerID(), []oid.ID{obj.GetID()}) + require.NoError(t, err) + } require.Equal(t, phy-uint64(deletedNumber), mm.objectCounters[phyObj]) require.Equal(t, root-uint64(deletedNumber), mm.objectCounters[rootObj]) @@ -228,7 +230,7 @@ func TestCounters(t *testing.T) { phyBefore := mm.objectCounters[phyObj] sumPldSizeBefore := mm.payloadSize - require.NoError(t, sh.Delete([]oid.Address{parent.Address()})) + require.NoError(t, sh.Delete(cnr, []oid.ID{parent.GetID()})) require.EqualValues(t, child.PayloadSize(), mm.containerSize[cnr]) require.Equal(t, phyBefore, mm.objectCounters[phyObj]) @@ -317,13 +319,3 @@ func shardWithMetrics(t *testing.T, path string) (*shard.Shard, *metricsStore) { return sh, mm } - -func addrFromObjs(oo []*object.Object) []oid.Address { - aa := make([]oid.Address, len(oo)) - - for i := range oo { - aa[i] = oo[i].Address() - } - - return aa -} diff --git a/pkg/local_object_storage/shard/put_test.go b/pkg/local_object_storage/shard/put_test.go index 8cef3d56fd..76f58e834e 100644 --- a/pkg/local_object_storage/shard/put_test.go +++ b/pkg/local_object_storage/shard/put_test.go @@ -138,7 +138,7 @@ func TestShard_Put_Lock(t *testing.T) { {name: "with target and GC mark", preset: func(t *testing.T, sh *shard.Shard) { require.NoError(t, sh.Put(&obj, nil)) - err := sh.MarkGarbage(objAddr) + err := sh.MarkGarbage(obj.GetContainerID(), []oid.ID{obj.GetID()}) require.NoError(t, err) }}, } { diff --git a/pkg/local_object_storage/shard/revive.go b/pkg/local_object_storage/shard/revive.go index 68041741af..b413c0ac9b 100644 --- a/pkg/local_object_storage/shard/revive.go +++ b/pkg/local_object_storage/shard/revive.go @@ -22,7 +22,7 @@ func (s *Shard) ReviveObject(addr oid.Address) (meta.ReviveStatus, error) { st, err := s.metaBase.ReviveObject(addr) if err == nil { if ts := st.TombstoneAddress(); !ts.Object().IsZero() { - if delErr := s.deleteObjs([]oid.Address{ts}); delErr != nil { + if delErr := s.deleteObjs(addr.Container(), []oid.ID{ts.Object()}); delErr != nil { s.log.Debug("tombstone delete after revive failed", zap.Stringer("tombstone", ts), zap.Error(delErr)) } else { s.log.Debug("tombstone deleted after revive", zap.Stringer("tombstone", ts))