Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkg/core/object/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ func combineValues(attr string, dbVal []byte, fltVal string) ([]byte, []byte, er
// - decoded filter byte is always 21 while the DB one is always 53
// so we'd get false mismatch. To avoid this, we have to decode each DB val.
dbVal = []byte(base58.Encode(dbVal))
case object.FilterFirstSplitObject, object.FilterParentID:
case object.FilterFirstSplitObject, object.FilterParentID, object.AttributeAssociatedObject:
if len(dbVal) != oid.Size {
return nil, nil, fmt.Errorf("invalid OID len %d != %d", len(dbVal), oid.Size)
}
Expand Down Expand Up @@ -660,7 +660,7 @@ func RestoreIntAttribute(b []byte) (string, error) {

func restoreAttributeValue(attr string, stored []byte) (string, error) {
switch attr {
case object.FilterOwnerID, object.FilterFirstSplitObject, object.FilterParentID:
case object.FilterOwnerID, object.FilterFirstSplitObject, object.FilterParentID, object.AttributeAssociatedObject:
return base58.Encode(stored), nil
//nolint:staticcheck // this is not DB's responsibility to force API rules, DB still may have these values inside
case object.FilterPayloadHomomorphicHash:
Expand Down Expand Up @@ -715,7 +715,7 @@ func PreprocessSearchQuery(fs object.SearchFilters, attrs []string, cursor strin
switch attr := fs[0].Header(); attr {
default:
primValDB = []byte(primVal)
case object.FilterOwnerID, object.FilterFirstSplitObject, object.FilterParentID:
case object.FilterOwnerID, object.FilterFirstSplitObject, object.FilterParentID, object.AttributeAssociatedObject:
var err error
if primValDB, err = base58.Decode(primVal); err != nil {
return nil, nil, fmt.Errorf("%w: decode %q attribute value from Base58: %w", errInvalidPrimaryFilter, attr, err)
Expand Down
10 changes: 5 additions & 5 deletions pkg/local_object_storage/engine/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
"github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -43,14 +43,14 @@ func TestStorageEngine_ContainerCleanUp(t *testing.T) {
)
require.NoError(t, err)
}
o1 := objecttest.Object()
o2 := objecttest.Object()
o1 := generateObjectWithCID(cidtest.ID())
o2 := generateObjectWithCID(cidtest.ID())
o2.SetPayload(make([]byte, errSmallSize+1))

err := e.Put(&o1, nil)
err := e.Put(o1, nil)
require.NoError(t, err)

err = e.Put(&o2, nil)
err = e.Put(o2, nil)
require.NoError(t, err)

require.NoError(t, e.Init())
Expand Down
7 changes: 2 additions & 5 deletions pkg/local_object_storage/engine/gc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,8 @@ func TestGC(t *testing.T) {
objAddr := obj.Address()

lockObj := generateObjectWithCID(cnr)
lockObj.SetType(object.TypeLock)
addExpirationAttribute(lockObj, es.CurrentEpoch()+1) // lock expires after object
lockObj.AssociateLocked(obj.GetID())
addExpirationAttribute(lockObj, es.CurrentEpoch()+1) // lock expires after object
require.NoError(t, e.Put(lockObj, nil))

tickEpoch(es, e)
Expand All @@ -225,7 +224,7 @@ func TestGC(t *testing.T) {

// t.Run("expired tombstone removed", func(t *testing.T) {
tomb := generateObjectWithCID(cnr)
tomb.SetType(object.TypeTombstone)
tomb.AssociateDeleted(oidtest.ID())
addExpirationAttribute(tomb, es.CurrentEpoch())
require.NoError(t, e.Put(tomb, nil))

Expand Down Expand Up @@ -259,7 +258,6 @@ func TestGC(t *testing.T) {
obj = generateObjectWithCID(cnr)
require.NoError(t, e.Put(obj, nil))
tomb = generateObjectWithCID(cnr)
tomb.SetType(object.TypeTombstone)
tomb.AssociateDeleted(obj.GetID())
addExpirationAttribute(tomb, es.CurrentEpoch())
require.NoError(t, e.Put(tomb, nil))
Expand All @@ -278,7 +276,6 @@ func TestGC(t *testing.T) {
objAddr = obj.Address()

tomb = generateObjectWithCID(cnr)
tomb.SetType(object.TypeTombstone)
tomb.AssociateDeleted(obj.GetID())
addExpirationAttribute(tomb, es.CurrentEpoch()+1) // tombstone expires after object
require.NoError(t, e.Put(tomb, nil))
Expand Down
5 changes: 1 addition & 4 deletions pkg/local_object_storage/engine/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,8 @@ func TestSplitObjectLockExpiration(t *testing.T) {

t.Run("split object lock with expiring locker", func(t *testing.T) {
lockObj := generateObjectWithCID(cnr)
lockObj.SetType(object.TypeLock)
addExpirationAttribute(lockObj, es.CurrentEpoch()+1)

lockObj.AssociateLocked(parentID)
addExpirationAttribute(lockObj, es.CurrentEpoch()+1)

require.NoError(t, e.Put(lockObj, nil))

Expand Down Expand Up @@ -461,7 +459,6 @@ func TestSimpleLockExpiration(t *testing.T) {
objAddr := obj.Address()

lock := generateObjectWithCID(cnr)
lock.SetType(object.TypeLock)
lock.AssociateLocked(objID)
addExpirationAttribute(lock, es.CurrentEpoch()+1)

Expand Down
14 changes: 10 additions & 4 deletions pkg/local_object_storage/engine/put_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"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"
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
"github.com/nspcc-dev/neofs-sdk-go/version"
"github.com/stretchr/testify/require"
Expand All @@ -23,11 +22,11 @@ import (
func TestStorageEngine_PutBinary(t *testing.T) {
addr := oidtest.Address()

obj := objecttest.Object()
obj := *generateObjectWithCID(cidtest.ID())
obj.SetContainerID(addr.Container())
obj.SetID(addr.Object())

obj2 := objecttest.Object()
obj2 := *generateObjectWithCID(cidtest.ID())
require.NotEqual(t, obj, obj2)
obj2.SetContainerID(addr.Container())
obj2.SetID(addr.Object())
Expand Down Expand Up @@ -104,6 +103,13 @@ func testPutLock(t *testing.T, shardNum int) {

obj := obj
obj.SetType(typ)
switch typ {
case object.TypeTombstone:
obj.AssociateDeleted(oidtest.ID())
case object.TypeLock:
obj.AssociateLocked(oidtest.ID())
default:
}

require.NoError(t, s.Put(&obj, nil))

Expand Down Expand Up @@ -282,7 +288,7 @@ func testPutTombstone(t *testing.T, shardNum int) {
}},
{name: "target is lock", preset: func(t *testing.T, s *StorageEngine) {
obj := obj
obj.SetType(object.TypeLock)
obj.AssociateLocked(oidtest.ID())
require.NoError(t, s.Put(&obj, nil))
}, assertPutErr: func(t *testing.T, err error) {
require.ErrorIs(t, err, meta.ErrLockObjectRemoval)
Expand Down
3 changes: 1 addition & 2 deletions pkg/local_object_storage/engine/revive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ func TestStorageEngine_ReviveObject(t *testing.T) {
addr := obj.Address()

ts := generateObjectWithCID(cnr)
ts.SetType(object.TypeTombstone)
addAttribute(ts, object.AttributeAssociatedObject, obj.GetID().EncodeToString())
ts.AssociateDeleted(obj.GetID())
addAttribute(ts, object.AttributeExpirationEpoch, "0")
tsAddr := ts.Address()

Expand Down
3 changes: 3 additions & 0 deletions pkg/local_object_storage/metabase/VERSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ The lowest not used bucket index: 20.
Object counters are updated (resynced) to fix potential issues caused by GC mark
double counting. Homomorphic hash field indexes have been deleted.

`__NEOFS__ASSOCIATE` attribute value is rewritten from base58 string to raw
32-byte object ID in metadata indexes.

## Version 10

Object counters are stored inside metadata bucket per container now. New ROOT,
Expand Down
147 changes: 113 additions & 34 deletions pkg/local_object_storage/metabase/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,126 @@ import (
meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase"
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"
"github.com/stretchr/testify/require"
)

func BenchmarkSearch(b *testing.B) {
const objCount = 1000
db := newDB(b, meta.WithMaxBatchDelay(time.Microsecond)) // 1000 puts shouldn't delay us much.
cid := cidtest.ID()
b.Run("user attribute", func(b *testing.B) {
const objCount = 1000
db := newDB(b, meta.WithMaxBatchDelay(time.Microsecond)) // 1000 puts shouldn't delay us much.
cid := cidtest.ID()

for i := range objCount {
var (
attrfp object.Attribute
attrts object.Attribute
)
attrfp.SetKey(object.AttributeFilePath)
attrfp.SetValue("path" + strconv.Itoa(i))
attrts.SetKey(object.AttributeTimestamp)
attrts.SetValue(strconv.Itoa(1748028502 + i))
obj := generateObjectWithCID(b, cid)
obj.SetAttributes(attrfp, attrts)
require.NoError(b, db.Put(obj))
}

fs := object.SearchFilters{}
fs.AddFilter("FilePath", "path100", object.MatchStringEqual)
fs.AddFilter("versioning-state", "", object.MatchNotPresent)

var attrs = []string{object.AttributeFilePath, object.AttributeTimestamp, "versioning-state",
"delete-marker", "metatype", "objversion", object.AttributeExpirationEpoch, "lock-meta"}

sfs, curs, err := objectcore.PreprocessSearchQuery(fs, attrs, "")
if err != nil {
b.Fatal(err)
}

for b.Loop() {
res, _, err := db.Search(cid, sfs, attrs, curs, 1000)
if err != nil {
b.Fatal(err)
}
if len(res) != 1 {
b.Fatalf("failed to search")
}
}
})

b.Run("associated object with attribute", func(b *testing.B) {
const objCount = 1000
db := newDB(b, meta.WithMaxBatchDelay(time.Microsecond))
cid := cidtest.ID()
targets := make([]oid.ID, 0, objCount)

for range objCount {
target := generateObjectWithCID(b, cid)
require.NoError(b, db.Put(target))
targets = append(targets, target.GetID())
}

for i := range objCount {
var (
attrfp object.Attribute
attrts object.Attribute
)
attrfp.SetKey(object.AttributeFilePath)
attrfp.SetValue("path" + strconv.Itoa(i))
attrts.SetKey(object.AttributeTimestamp)
attrts.SetValue(strconv.Itoa(1748028502 + i))
obj := generateObjectWithCID(b, cid)
obj.SetAttributes(attrfp, attrts)
require.NoError(b, db.Put(obj))
}

fs := object.SearchFilters{}
fs.AddFilter("FilePath", "path100", object.MatchStringEqual)
fs.AddFilter("versioning-state", "", object.MatchNotPresent)

var attrs = []string{object.AttributeFilePath, object.AttributeTimestamp, "versioning-state",
"delete-marker", "metatype", "objversion", object.AttributeExpirationEpoch, "lock-meta"}

sfs, curs, err := objectcore.PreprocessSearchQuery(fs, attrs, "")
if err != nil {
b.Fatal(err)
}

for b.Loop() {
res, _, err := db.Search(cid, sfs, attrs, curs, 1000)
for i := range objCount {
lock := generateObjectWithCID(b, cid)
lock.AssociateLocked(targets[i])
require.NoError(b, db.Put(lock))
}

fs := object.SearchFilters{}
fs.AddFilter(object.AttributeAssociatedObject, targets[objCount/2].EncodeToString(), object.MatchStringEqual)

sfs, curs, err := objectcore.PreprocessSearchQuery(fs, []string{object.AttributeAssociatedObject}, "")
if err != nil {
b.Fatal(err)
}
if len(res) != 1 {
b.Fatalf("failed to search")

for b.Loop() {
res, _, err := db.Search(cid, sfs, []string{object.AttributeAssociatedObject}, curs, 1000)
if err != nil {
b.Fatal(err)
}
if len(res) != 1 {
b.Fatalf("failed to search")
}
}
})
}

func BenchmarkIsLocked(b *testing.B) {
db := newDB(b, meta.WithMaxBatchDelay(time.Microsecond))
cid := cidtest.ID()

lockedObj := generateObjectWithCID(b, cid)
require.NoError(b, db.Put(lockedObj))

lock := generateObjectWithCID(b, cid)
lock.AssociateLocked(lockedObj.GetID())
require.NoError(b, db.Put(lock))

unlockedObj := generateObjectWithCID(b, cid)
require.NoError(b, db.Put(unlockedObj))

b.Run("locked", func(b *testing.B) {
for b.Loop() {
locked, err := db.IsLocked(lockedObj.Address())
if err != nil {
b.Fatal(err)
}
if !locked {
b.Fatal("expected object to be locked")
}
}
})

b.Run("unlocked", func(b *testing.B) {
for b.Loop() {
locked, err := db.IsLocked(unlockedObj.Address())
if err != nil {
b.Fatal(err)
}
if locked {
b.Fatal("expected object to be unlocked")
}
}
}
})
}
12 changes: 9 additions & 3 deletions pkg/local_object_storage/metabase/containers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ func TestDB_ContainersCount(t *testing.T) {
for range upload.amount {
obj := generateObject(t)
obj.SetType(upload.typ)
switch upload.typ {
case object.TypeTombstone:
obj.AssociateDeleted(oidtest.ID())
case object.TypeLock:
obj.AssociateLocked(oidtest.ID())
default:
}

err := putBig(db, obj)
require.NoError(t, err)
Expand Down Expand Up @@ -198,9 +205,8 @@ func TestDB_DeleteContainer(t *testing.T) {
require.NoError(t, err)

// put a big one
o2 := objecttest.Object()
o2.SetContainerID(cID)
err = putBig(db, &o2)
o2 := generateObjectWithCID(t, cID)
err = putBig(db, o2)
require.NoError(t, err)

// TS
Expand Down
4 changes: 2 additions & 2 deletions pkg/local_object_storage/metabase/exists_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestDB_Exists(t *testing.T) {

t.Run("tombstone object", func(t *testing.T) {
ts := generateObject(t)
ts.SetType(object.TypeTombstone)
ts.AssociateDeleted(oidtest.ID())

err := putBig(db, ts)
require.NoError(t, err)
Expand All @@ -60,7 +60,7 @@ func TestDB_Exists(t *testing.T) {

t.Run("lock object", func(t *testing.T) {
lock := generateObject(t)
lock.SetType(object.TypeLock)
lock.AssociateLocked(oidtest.ID())

err := putBig(db, lock)
require.NoError(t, err)
Expand Down
6 changes: 6 additions & 0 deletions pkg/local_object_storage/metabase/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ func get(metaCursor *bbolt.Cursor, addr oid.Address, checkStatus, raw bool, curr
obj.SetParentID(id)
case object.FilterPhysical, object.FilterRoot:
// Not real attributes, ignored.
case object.AttributeAssociatedObject:
id, err := oid.DecodeBytes(attrVal)
if err != nil {
return nil, fmt.Errorf("invalid associated object in meta of %s/%s: %w", cnr, objID, err)
}
attrs = append(attrs, object.NewAttribute(string(attrKey), id.EncodeToString()))
default:
attrs = append(attrs, object.NewAttribute(string(attrKey), string(attrVal)))
}
Expand Down
Loading
Loading