Skip to content

Commit a5ffd32

Browse files
authored
Fix negative logic object counter (#3555)
Closes #3477.
2 parents 2de83ae + 33ff78b commit a5ffd32

File tree

5 files changed

+131
-1
lines changed

5 files changed

+131
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Changelog for NeoFS Node
88
### Fixed
99
- Send on closed channel panic in node's new epoch handler (#3529)
1010
- Tomstoned objects revival not working (#3542)
11+
- Negative logic object counters in metabase (#3555)
1112

1213
### Changed
1314
- Stream payload without buffering to reduce memory usage in CLI `Get/Put` operations (#3535)

pkg/local_object_storage/metabase/inhume.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@ func (db *DB) InhumeContainer(cID cid.ID) (uint64, error) {
228228
return fmt.Errorf("put GC mark for container: %w", err)
229229
}
230230

231-
_, removedAvailable = getCounters(tx)
231+
info := db.containerInfo(tx, cID)
232+
removedAvailable = info.ObjectsNumber
232233

233234
err = db.updateCounter(tx, logical, removedAvailable, false)
234235
if err != nil {

pkg/local_object_storage/metabase/version.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,11 @@ func migrateFrom7Version(db *DB) error {
605605
}
606606
}
607607

608+
err = syncCounter(tx, currEpoch, true)
609+
if err != nil {
610+
return fmt.Errorf("syncing counters: %w", err)
611+
}
612+
608613
return updateVersion(tx, 8)
609614
})
610615
}

pkg/local_object_storage/metabase/version_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,9 @@ func TestMigrate7to8(t *testing.T) {
854854
o.SetContainerID(cnr)
855855
totalSize += o.PayloadSize()
856856

857+
require.NoError(t, db.updateCounter(tx, phy, 1, true))
858+
require.NoError(t, db.updateCounter(tx, logical, 1, true))
859+
857860
return PutMetadataForObject(tx, o, true)
858861
})
859862
require.NoError(t, err)
@@ -875,6 +878,18 @@ func TestMigrate7to8(t *testing.T) {
875878
return err
876879
}
877880

881+
// corrupt counters
882+
b := tx.Bucket(shardInfoBucket)
883+
require.NotNil(t, b)
884+
require.Equal(t, uint64(objsNum), binary.LittleEndian.Uint64(b.Get(objectPhyCounterKey)))
885+
require.Equal(t, uint64(objsNum), binary.LittleEndian.Uint64(b.Get(objectLogicCounterKey)))
886+
cc := make([]byte, 8)
887+
binary.LittleEndian.PutUint64(cc, uint64(100)) // corrupt physical counter
888+
require.NoError(t, b.Put(objectPhyCounterKey, cc))
889+
cc = make([]byte, 8)
890+
binary.LittleEndian.PutUint64(cc, uint64(100)) // corrupt logical counter
891+
require.NoError(t, b.Put(objectLogicCounterKey, cc))
892+
878893
bkt := tx.Bucket([]byte{0x05})
879894
require.NotNil(t, bkt)
880895
require.NoError(t, bkt.Put([]byte("version"), []byte{0x07, 0, 0, 0, 0, 0, 0, 0}))
@@ -907,6 +922,9 @@ func TestMigrate7to8(t *testing.T) {
907922
require.NotNil(t, bkt)
908923
require.Equal(t, []byte{currentMetaVersion, 0, 0, 0, 0, 0, 0, 0}, bkt.Get([]byte("version")))
909924

925+
pc, lc := getCounters(tx)
926+
require.Equal(t, uint64(objsNum), pc)
927+
require.Equal(t, uint64(objsNum), lc)
910928
return nil
911929
})
912930
require.NoError(t, err)

pkg/local_object_storage/shard/metrics_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package shard_test
22

33
import (
4+
"encoding/binary"
45
"path/filepath"
56
"testing"
67

@@ -9,9 +10,11 @@ import (
910
meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase"
1011
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/shard"
1112
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/shard/mode"
13+
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
1214
"github.com/nspcc-dev/neofs-sdk-go/object"
1315
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
1416
"github.com/stretchr/testify/require"
17+
"go.etcd.io/bbolt"
1518
)
1619

1720
type metricsStore struct {
@@ -149,6 +152,108 @@ func TestCounters(t *testing.T) {
149152
})
150153
}
151154

155+
func TestInhumeContainerCounters(t *testing.T) {
156+
sh, mm := shardWithMetrics(t, t.TempDir())
157+
158+
c1 := cidtest.ID()
159+
c2 := cidtest.ID()
160+
161+
var objsC1, objsC2 = 5, 7
162+
var sizeC1, sizeC2 int64 = 0, 0
163+
164+
for range objsC1 {
165+
obj := generateObjectWithCID(c1)
166+
require.NoError(t, sh.Put(obj, nil))
167+
sizeC1 += int64(obj.PayloadSize())
168+
}
169+
for range objsC2 {
170+
obj := generateObjectWithCID(c2)
171+
require.NoError(t, sh.Put(obj, nil))
172+
sizeC2 += int64(obj.PayloadSize())
173+
}
174+
175+
total := uint64(objsC1 + objsC2)
176+
initialPayload := mm.payloadSize
177+
require.Equal(t, sizeC1+sizeC2, initialPayload)
178+
require.Equal(t, mm.objectCounters[physical], total)
179+
require.Equal(t, mm.objectCounters[logical], total)
180+
181+
require.NoError(t, sh.InhumeContainer(c1))
182+
183+
require.Equal(t, mm.objectCounters[physical], total)
184+
require.Equal(t, mm.objectCounters[logical], uint64(objsC2))
185+
require.Empty(t, mm.containerSize[c1.EncodeToString()])
186+
require.Equal(t, mm.containerSize[c2.EncodeToString()], sizeC2)
187+
// payload size must remain unchanged after logical removal
188+
require.Equal(t, initialPayload, mm.payloadSize)
189+
190+
require.NoError(t, sh.InhumeContainer(c2))
191+
192+
require.Equal(t, mm.objectCounters[physical], total)
193+
require.Empty(t, mm.objectCounters[logical])
194+
require.Empty(t, mm.containerSize[c1.EncodeToString()])
195+
require.Empty(t, mm.containerSize[c2.EncodeToString()])
196+
// payload size still unchanged
197+
require.Equal(t, initialPayload, mm.payloadSize)
198+
}
199+
200+
func TestShardCounterMigration(t *testing.T) {
201+
path := t.TempDir()
202+
sh, mm := shardWithMetrics(t, path)
203+
204+
const n = 11
205+
for range n {
206+
obj := generateObject()
207+
require.NoError(t, sh.Put(obj, nil))
208+
}
209+
require.Equal(t, uint64(n), mm.objectCounters[physical])
210+
require.Equal(t, uint64(n), mm.objectCounters[logical])
211+
212+
// Close shard so we can mutate Bolt file.
213+
require.NoError(t, sh.Close())
214+
215+
metaPath := filepath.Join(path, "meta")
216+
mdb, err := bbolt.Open(metaPath, 0o600, nil)
217+
require.NoError(t, err)
218+
219+
var (
220+
bucketPrefix = []byte{5} // shardInfoPrefix
221+
objectPhyCounterKey = []byte("phy_counter")
222+
objectLogicCounterKey = []byte("logic_counter")
223+
corruptPhysical = uint64(100)
224+
corruptLogical = uint64(200)
225+
)
226+
227+
require.NoError(t, mdb.Update(func(tx *bbolt.Tx) error {
228+
b := tx.Bucket(bucketPrefix)
229+
require.NotNil(t, b)
230+
231+
require.Equal(t, uint64(n), binary.LittleEndian.Uint64(b.Get(objectPhyCounterKey)))
232+
require.Equal(t, uint64(n), binary.LittleEndian.Uint64(b.Get(objectLogicCounterKey)))
233+
234+
cc := make([]byte, 8)
235+
binary.LittleEndian.PutUint64(cc, corruptPhysical)
236+
require.NoError(t, b.Put(objectPhyCounterKey, cc))
237+
cc = make([]byte, 8)
238+
binary.LittleEndian.PutUint64(cc, corruptLogical)
239+
require.NoError(t, b.Put(objectLogicCounterKey, cc))
240+
241+
// force metabase version to 7 to restore counters
242+
bkt := tx.Bucket([]byte{0x05})
243+
require.NotNil(t, bkt)
244+
require.NoError(t, bkt.Put([]byte("version"), []byte{0x07, 0, 0, 0, 0, 0, 0, 0}))
245+
return nil
246+
}))
247+
require.NoError(t, mdb.Close())
248+
249+
// re-open shard, initMetrics should force sync counters and restore real values
250+
sh, mm = shardWithMetrics(t, path)
251+
252+
require.Equal(t, uint64(n), mm.objectCounters[physical])
253+
require.Equal(t, uint64(n), mm.objectCounters[logical])
254+
require.NoError(t, sh.Close())
255+
}
256+
152257
func shardWithMetrics(t *testing.T, path string) (*shard.Shard, *metricsStore) {
153258
mm := &metricsStore{
154259
objectCounters: map[string]uint64{

0 commit comments

Comments
 (0)