diff --git a/pkg/local_object_storage/metabase/VERSION.md b/pkg/local_object_storage/metabase/VERSION.md index 7ad674b6a..b7fa5a7f5 100644 --- a/pkg/local_object_storage/metabase/VERSION.md +++ b/pkg/local_object_storage/metabase/VERSION.md @@ -31,6 +31,7 @@ This file describes changes between the metabase versions. - Keys and values - `id` -> shard id as bytes - `version` -> metabase version as little-endian uint64 + - `counter` -> shard's object counter as little-endian uint64 ### Unique index buckets - Buckets containing objects of REGULAR type @@ -83,7 +84,9 @@ This file describes changes between the metabase versions. - Value: list of object IDs -# History +## Version 2 + +- Added shard's object counter to the info bucket ## Version 1 diff --git a/pkg/local_object_storage/metabase/control.go b/pkg/local_object_storage/metabase/control.go index 2b7f9019f..189a2ba67 100644 --- a/pkg/local_object_storage/metabase/control.go +++ b/pkg/local_object_storage/metabase/control.go @@ -80,6 +80,7 @@ func (db *DB) init(reset bool) error { string(graveyardBucketName): {}, string(toMoveItBucketName): {}, string(garbageBucketName): {}, + string(shardInfoBucket): {}, } return db.boltDB.Update(func(tx *bbolt.Tx) error { diff --git a/pkg/local_object_storage/metabase/counter.go b/pkg/local_object_storage/metabase/counter.go new file mode 100644 index 000000000..2d02159e4 --- /dev/null +++ b/pkg/local_object_storage/metabase/counter.go @@ -0,0 +1,59 @@ +package meta + +import ( + "encoding/binary" + + "go.etcd.io/bbolt" +) + +var shardCounterKey = []byte("counter") + +// ObjectCounter returns object count that metabase has +// tracked since it was opened and initialized. +// +// Returns only the errors that do not allow reading counter +// in Bolt database. +func (db *DB) ObjectCounter() (counter uint64, err error) { + err = db.boltDB.View(func(tx *bbolt.Tx) error { + b := tx.Bucket(shardInfoBucket) + if b != nil { + data := b.Get(shardCounterKey) + if len(data) == 8 { + counter = binary.LittleEndian.Uint64(data) + } + } + + return nil + }) + + return +} + +// updateCounter updates the object counter. Tx MUST be writable. +// If inc == `true`, increases the counter, decreases otherwise. +func (db *DB) updateCounter(tx *bbolt.Tx, delta uint64, inc bool) error { + b := tx.Bucket(shardInfoBucket) + if b == nil { + return nil + } + + var counter uint64 + + data := b.Get(shardCounterKey) + if len(data) == 8 { + counter = binary.LittleEndian.Uint64(data) + } + + if inc { + counter += delta + } else if counter <= delta { + counter = 0 + } else { + counter -= delta + } + + newCounter := make([]byte, 8) + binary.LittleEndian.PutUint64(newCounter, counter) + + return b.Put(shardCounterKey, newCounter) +} diff --git a/pkg/local_object_storage/metabase/counter_test.go b/pkg/local_object_storage/metabase/counter_test.go new file mode 100644 index 000000000..c8cb80782 --- /dev/null +++ b/pkg/local_object_storage/metabase/counter_test.go @@ -0,0 +1,133 @@ +package meta_test + +import ( + "testing" + + objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object" + meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase" + "github.com/nspcc-dev/neofs-sdk-go/object" + "github.com/stretchr/testify/require" +) + +const objCount = 10 + +func TestCounter_Default(t *testing.T) { + db := newDB(t) + + c, err := db.ObjectCounter() + require.NoError(t, err) + require.Zero(t, c) +} + +func TestCounter(t *testing.T) { + db := newDB(t) + + var c uint64 + var err error + + oo := make([]*object.Object, 0, objCount) + for i := 0; i < objCount; i++ { + oo = append(oo, generateObject(t)) + } + + var prm meta.PutPrm + + for i := 0; i < objCount; i++ { + prm.SetObject(oo[i]) + + _, err = db.Put(prm) + require.NoError(t, err) + + c, err = db.ObjectCounter() + require.NoError(t, err) + + require.Equal(t, uint64(i+1), c) + } +} + +func TestCounter_Dec(t *testing.T) { + db := newDB(t) + oo := putObjs(t, db, objCount, false) + + var err error + var c uint64 + + var prm meta.DeletePrm + for i := objCount - 1; i >= 0; i-- { + prm.SetAddresses(objectcore.AddressOf(oo[i])) + + _, err = db.Delete(prm) + require.NoError(t, err) + + c, err = db.ObjectCounter() + require.NoError(t, err) + + require.Equal(t, uint64(i), c) + } +} + +func TestCounter_PutSplit(t *testing.T) { + db := newDB(t) + + parObj := generateObject(t) + var err error + var c uint64 + + // put objects and check that parent info + // does not affect the counter + for i := 0; i < objCount; i++ { + o := generateObject(t) + if i < objCount/2 { // half of the objs will have the parent + o.SetParent(parObj) + } + + require.NoError(t, putBig(db, o)) + + c, err = db.ObjectCounter() + require.NoError(t, err) + require.Equal(t, uint64(i+1), c) + } +} + +func TestCounter_DeleteSplit(t *testing.T) { + db := newDB(t) + oo := putObjs(t, db, objCount, true) + + // delete objects that have parent info + // and check that it does not affect + // the counter + for i, o := range oo { + require.NoError(t, metaDelete(db, objectcore.AddressOf(o))) + + c, err := db.ObjectCounter() + require.NoError(t, err) + require.Equal(t, uint64(objCount-i-1), c) + } +} + +func putObjs(t *testing.T, db *meta.DB, count int, withParent bool) []*object.Object { + var prm meta.PutPrm + var err error + parent := generateObject(t) + + oo := make([]*object.Object, 0, count) + for i := 0; i < count; i++ { + o := generateObject(t) + if withParent { + o.SetParent(parent) + } + + oo = append(oo, o) + + prm.SetObject(o) + _, err = db.Put(prm) + require.NoError(t, err) + + c, err := db.ObjectCounter() + require.NoError(t, err) + + require.Equal(t, uint64(i+1), c) + } + + return oo +} diff --git a/pkg/local_object_storage/metabase/delete.go b/pkg/local_object_storage/metabase/delete.go index 3cf50ff37..f0d5e9d14 100644 --- a/pkg/local_object_storage/metabase/delete.go +++ b/pkg/local_object_storage/metabase/delete.go @@ -11,6 +11,7 @@ import ( objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "go.etcd.io/bbolt" + "go.uber.org/zap" ) // DeletePrm groups the parameters of Delete operation. @@ -60,11 +61,22 @@ func (db *DB) deleteGroup(tx *bbolt.Tx, addrs []oid.Address) error { refCounter := make(referenceCounter, len(addrs)) currEpoch := db.epochState.CurrentEpoch() + var rawDeleted uint64 for i := range addrs { - err := db.delete(tx, addrs[i], refCounter, currEpoch) + removed, err := db.delete(tx, addrs[i], refCounter, currEpoch) if err != nil { return err // maybe log and continue? } + + if removed { + rawDeleted++ + } + } + + err := db.updateCounter(tx, rawDeleted, false) + if err != nil { + db.log.Error("could not decrease object counter", + zap.Error(err)) } for _, refNum := range refCounter { @@ -79,13 +91,13 @@ func (db *DB) deleteGroup(tx *bbolt.Tx, addrs []oid.Address) error { return nil } -func (db *DB) delete(tx *bbolt.Tx, addr oid.Address, refCounter referenceCounter, currEpoch uint64) error { +func (db *DB) delete(tx *bbolt.Tx, addr oid.Address, refCounter referenceCounter, currEpoch uint64) (bool, error) { // remove record from the garbage bucket garbageBKT := tx.Bucket(garbageBucketName) if garbageBKT != nil { err := garbageBKT.Delete(addressKey(addr)) if err != nil { - return fmt.Errorf("could not remove from garbage bucket: %w", err) + return false, fmt.Errorf("could not remove from garbage bucket: %w", err) } } @@ -93,10 +105,10 @@ func (db *DB) delete(tx *bbolt.Tx, addr oid.Address, refCounter referenceCounter obj, err := db.get(tx, addr, false, true, currEpoch) if err != nil { if errors.As(err, new(apistatus.ObjectNotFound)) { - return nil + return false, nil } - return err + return false, err } // if object is an only link to a parent, then remove parent @@ -119,7 +131,12 @@ func (db *DB) delete(tx *bbolt.Tx, addr oid.Address, refCounter referenceCounter } // remove object - return db.deleteObject(tx, obj, false) + err = db.deleteObject(tx, obj, false) + if err != nil { + return false, fmt.Errorf("could not remove object: %w", err) + } + + return true, nil } func (db *DB) deleteObject( diff --git a/pkg/local_object_storage/metabase/put.go b/pkg/local_object_storage/metabase/put.go index f47b5e0ec..2c129ce00 100644 --- a/pkg/local_object_storage/metabase/put.go +++ b/pkg/local_object_storage/metabase/put.go @@ -14,6 +14,7 @@ import ( objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "go.etcd.io/bbolt" + "go.uber.org/zap" ) type ( @@ -142,6 +143,14 @@ func (db *DB) put( } } + if !isParent { + err = db.updateCounter(tx, 1, true) + if err != nil { + db.log.Error("could not increase object counter: %w", + zap.Error(err)) + } + } + return nil }