forked from TrueCloudLab/frostfs-node
[#1658] meta: Add logic counter
- Meta now supports (and requires) inc/dec labeled counters - The new logic counter is incremented on `Put` operations and is decremented on `Inhume` and `Delete` operations that are performed on _stored_ objects only - Allow force counters sync. "Force" mode should be used on metabase resync and should not be used on a regular meta start Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
parent
d872862710
commit
ad47e2a985
7 changed files with 437 additions and 140 deletions
|
@ -6,103 +6,266 @@ import (
|
|||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
const objCount = 10
|
||||
|
||||
func TestCounter_Default(t *testing.T) {
|
||||
func TestCounters(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 c meta.ObjectCounters
|
||||
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)
|
||||
t.Run("defaults", func(t *testing.T) {
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, c.Phy())
|
||||
require.Zero(t, c.Logic())
|
||||
})
|
||||
|
||||
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)
|
||||
t.Run("put", func(t *testing.T) {
|
||||
oo := make([]*object.Object, 0, objCount)
|
||||
for i := 0; i < objCount; i++ {
|
||||
oo = append(oo, generateObject(t))
|
||||
}
|
||||
|
||||
require.NoError(t, putBig(db, o))
|
||||
var prm meta.PutPrm
|
||||
|
||||
c, err = db.ObjectCounter()
|
||||
for i := 0; i < objCount; i++ {
|
||||
prm.SetObject(oo[i])
|
||||
|
||||
_, err = db.Put(prm)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, uint64(i+1), c.Phy())
|
||||
require.Equal(t, uint64(i+1), c.Logic())
|
||||
}
|
||||
})
|
||||
|
||||
require.NoError(t, db.Reset())
|
||||
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
oo := putObjs(t, db, objCount, false)
|
||||
|
||||
var prm meta.DeletePrm
|
||||
for i := objCount - 1; i >= 0; i-- {
|
||||
prm.SetAddresses(objectcore.AddressOf(oo[i]))
|
||||
|
||||
res, err := db.Delete(prm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), res.AvailableObjectsRemoved())
|
||||
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, uint64(i), c.Phy())
|
||||
require.Equal(t, uint64(i), c.Logic())
|
||||
}
|
||||
})
|
||||
|
||||
require.NoError(t, db.Reset())
|
||||
|
||||
t.Run("inhume", func(t *testing.T) {
|
||||
oo := putObjs(t, db, objCount, false)
|
||||
|
||||
inhumedObjs := make([]oid.Address, objCount/2)
|
||||
|
||||
for i, o := range oo {
|
||||
if i == len(inhumedObjs) {
|
||||
break
|
||||
}
|
||||
|
||||
inhumedObjs[i] = objectcore.AddressOf(o)
|
||||
}
|
||||
|
||||
var prm meta.InhumePrm
|
||||
prm.SetTombstoneAddress(oidtest.Address())
|
||||
prm.SetAddresses(inhumedObjs...)
|
||||
|
||||
res, err := db.Inhume(prm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(i+1), c)
|
||||
}
|
||||
require.Equal(t, uint64(len(inhumedObjs)), res.AvailableInhumed())
|
||||
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, uint64(objCount), c.Phy())
|
||||
require.Equal(t, uint64(objCount-len(inhumedObjs)), c.Logic())
|
||||
})
|
||||
|
||||
require.NoError(t, db.Reset())
|
||||
|
||||
t.Run("put_split", func(t *testing.T) {
|
||||
parObj := generateObject(t)
|
||||
|
||||
// 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.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(i+1), c.Phy())
|
||||
require.Equal(t, uint64(i+1), c.Logic())
|
||||
}
|
||||
})
|
||||
|
||||
require.NoError(t, db.Reset())
|
||||
|
||||
t.Run("delete_split", func(t *testing.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.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(objCount-i-1), c.Phy())
|
||||
require.Equal(t, uint64(objCount-i-1), c.Logic())
|
||||
}
|
||||
})
|
||||
|
||||
require.NoError(t, db.Reset())
|
||||
|
||||
t.Run("inhume_split", func(t *testing.T) {
|
||||
oo := putObjs(t, db, objCount, true)
|
||||
|
||||
inhumedObjs := make([]oid.Address, objCount/2)
|
||||
|
||||
for i, o := range oo {
|
||||
if i == len(inhumedObjs) {
|
||||
break
|
||||
}
|
||||
|
||||
inhumedObjs[i] = objectcore.AddressOf(o)
|
||||
}
|
||||
|
||||
var prm meta.InhumePrm
|
||||
prm.SetTombstoneAddress(oidtest.Address())
|
||||
prm.SetAddresses(inhumedObjs...)
|
||||
|
||||
_, err = db.Inhume(prm)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, uint64(objCount), c.Phy())
|
||||
require.Equal(t, uint64(objCount-len(inhumedObjs)), c.Logic())
|
||||
})
|
||||
}
|
||||
|
||||
func TestCounter_DeleteSplit(t *testing.T) {
|
||||
db := newDB(t)
|
||||
oo := putObjs(t, db, objCount, true)
|
||||
func TestCounters_Expired(t *testing.T) {
|
||||
// That test is about expired objects without
|
||||
// GCMark yet. Such objects should be treated as
|
||||
// logically available: decrementing logic counter
|
||||
// should be done explicitly and only in `Delete`
|
||||
// and `Inhume` operations, otherwise, it would be
|
||||
// impossible to maintain logic counter.
|
||||
|
||||
// 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)))
|
||||
const epoch = 123
|
||||
|
||||
c, err := db.ObjectCounter()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(objCount-i-1), c)
|
||||
es := &epochState{epoch}
|
||||
db := newDB(t, meta.WithEpochState(es))
|
||||
|
||||
oo := make([]oid.Address, objCount)
|
||||
for i := range oo {
|
||||
oo[i] = putWithExpiration(t, db, object.TypeRegular, epoch+1)
|
||||
}
|
||||
|
||||
// 1. objects are available and counters are correct
|
||||
|
||||
c, err := db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(objCount), c.Phy())
|
||||
require.Equal(t, uint64(objCount), c.Logic())
|
||||
|
||||
for _, o := range oo {
|
||||
_, err := metaGet(db, o, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// 2. objects are expired, not available but logic counter
|
||||
// is the same
|
||||
|
||||
es.e = epoch + 2
|
||||
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(objCount), c.Phy())
|
||||
require.Equal(t, uint64(objCount), c.Logic())
|
||||
|
||||
for _, o := range oo {
|
||||
_, err := metaGet(db, o, true)
|
||||
require.ErrorIs(t, err, objectcore.ErrObjectIsExpired)
|
||||
}
|
||||
|
||||
// 3. inhuming an expired object with GCMark (like it would
|
||||
// the GC do) should decrease the logic counter despite the
|
||||
// expiration fact
|
||||
|
||||
var inhumePrm meta.InhumePrm
|
||||
inhumePrm.SetGCMark()
|
||||
inhumePrm.SetAddresses(oo[0])
|
||||
|
||||
inhumeRes, err := db.Inhume(inhumePrm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), inhumeRes.AvailableInhumed())
|
||||
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, uint64(len(oo)), c.Phy())
|
||||
require.Equal(t, uint64(len(oo)-1), c.Logic())
|
||||
|
||||
// 4. `Delete` an object with GCMark should decrease the
|
||||
// phy counter but does not affect the logic counter (after
|
||||
// that step they should be equal)
|
||||
|
||||
var deletePrm meta.DeletePrm
|
||||
deletePrm.SetAddresses(oo[0])
|
||||
|
||||
deleteRes, err := db.Delete(deletePrm)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, deleteRes.AvailableObjectsRemoved())
|
||||
|
||||
oo = oo[1:]
|
||||
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(len(oo)), c.Phy())
|
||||
require.Equal(t, uint64(len(oo)), c.Logic())
|
||||
|
||||
// 5 `Delete` an expired object (like it would the control
|
||||
// service do) should decrease both counters despite the
|
||||
// expiration fact
|
||||
|
||||
deletePrm.SetAddresses(oo[0])
|
||||
|
||||
deleteRes, err = db.Delete(deletePrm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), deleteRes.AvailableObjectsRemoved())
|
||||
|
||||
oo = oo[1:]
|
||||
|
||||
c, err = db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(len(oo)), c.Phy())
|
||||
require.Equal(t, uint64(len(oo)), c.Logic())
|
||||
}
|
||||
|
||||
func putObjs(t *testing.T, db *meta.DB, count int, withParent bool) []*object.Object {
|
||||
|
@ -123,10 +286,11 @@ func putObjs(t *testing.T, db *meta.DB, count int, withParent bool) []*object.Ob
|
|||
_, err = db.Put(prm)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, err := db.ObjectCounter()
|
||||
c, err := db.ObjectCounters()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, uint64(i+1), c)
|
||||
require.Equal(t, uint64(i+1), c.Phy())
|
||||
require.Equal(t, uint64(i+1), c.Logic())
|
||||
}
|
||||
|
||||
return oo
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue