Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Changelog for NeoFS Node
- Undocumented ability to fetch the latest contract release for adm update-contracts (#3850)
- `storage.shards.resync_metabase` config option from SN config (#3849)
- One-time netmap contract placement migration routine for v0.49.0+ releases (#3821)
- Metabase v5 to v6 and v6 to v7 migrations (#3864)

### Updated
- `github.com/nspcc-dev/neofs-sdk-go` module to `v1.0.0-rc.17.0.20260224112648-e6342b6bf094` (#3785, #3817, #3808)
Expand All @@ -49,6 +50,10 @@ Sessions are stored in `node.persistent_state.path` DB now.
Delete `storage.shards.resync_metabase` config option from SN config, it's no longer used.
Use `neofs-lens meta resync` command if you need to resync metabase.

Storage nodes no longer automatically migrate metabases from version 5
(NeoFS 0.46.0) to 6 (NeoFS 0.48.0) and from version 6 to version 7 (NeoFS
0.48.1), migrate using SN 0.51.1 or resynchronize with 0.52.0 if needed.

## [0.51.1] - 2026-02-18

### Added
Expand Down
3 changes: 0 additions & 3 deletions pkg/local_object_storage/metabase/inhume.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package meta

import (
"errors"
"fmt"
"slices"

Expand All @@ -12,8 +11,6 @@ import (
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
)

var errBreakBucketForEach = errors.New("bucket ForEach break")

// ErrLockObjectRemoval is returned when inhume operation is being
// performed on lock object, and it is not a forced object removal.
var ErrLockObjectRemoval = logicerr.New("lock object removal")
Expand Down
23 changes: 0 additions & 23 deletions pkg/local_object_storage/metabase/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,29 +429,6 @@ func (x *metaAttributeSeeker) Get(id []byte, attr string) (attributeValue []byte
return key[len(pref):], nil
}

// returns both zero if object is missing.
func resolveContainerByOID(tx *bbolt.Tx, metaOIDKey []byte) (cid.ID, error) {
var res cid.ID

err := tx.ForEach(func(name []byte, bkt *bbolt.Bucket) error {
if len(name) != 1+cid.Size || name[0] != metadataPrefix {
return nil
}

if bkt.Get(metaOIDKey) != nil {
res = cid.ID(name[1:]) // len is checked above
return errBreakBucketForEach
}

return nil
})
if errors.Is(err, errBreakBucketForEach) {
err = nil
}

return res, err
}

func collectChildren(cnrMetaCrs *bbolt.Cursor, cnr cid.ID, parentID oid.ID) ([]oid.ID, error) {
var (
errECParts iec.ErrParts
Expand Down
12 changes: 5 additions & 7 deletions pkg/local_object_storage/metabase/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ var (

// containerGCMarkKey marker key inside meta bucket designating whole container GC-marked.
containerGCMarkKey = []byte{metaPrefixGC}

zeroValue = []byte{0xFF}
)

// Prefix bytes for database keys. All ids and addresses are encoded in binary
Expand Down Expand Up @@ -46,13 +44,13 @@ const (
// ======================

// unusedPrimaryPrefix was deleted in metabase version 6
unusedPrimaryPrefix
unusedPrimaryPrefix //nolint:unused
// unusedLockersPrefix was deleted in metabase version 6
unusedLockersPrefix
unusedLockersPrefix //nolint:unused
// unusedStorageGroupPrefix was deleted in metabase version 6
unusedStorageGroupPrefix
unusedStorageGroupPrefix //nolint:unused
// unusedTombstonePrefix was deleted in metabase version 6
unusedTombstonePrefix
unusedTombstonePrefix //nolint:unused
// unusedSmallPrefix was deleted in metabase version 5
unusedSmallPrefix //nolint:unused
// unusedRootPrefix was deleted in metabase version 5
Expand Down Expand Up @@ -82,7 +80,7 @@ const (
unusedGarbageContainersPrefix

// unusedLinkObjectsPrefix was deleted in metabase version 6
unusedLinkObjectsPrefix
unusedLinkObjectsPrefix //nolint:unused

// unusedFirstObjectIDPrefix was deleted in metabase version 5
unusedFirstObjectIDPrefix //nolint:unused
Expand Down
92 changes: 0 additions & 92 deletions pkg/local_object_storage/metabase/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"fmt"
"slices"

"github.com/mr-tron/base58"
"github.com/nspcc-dev/bbolt"
berrors "github.com/nspcc-dev/bbolt/errors"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/util/logicerr"
Expand Down Expand Up @@ -49,8 +48,6 @@ var (
// and it's hardly acceptable, so in general it's better to log and
// continue rather than return an error.
migrateFrom = map[uint64]func(*DB) error{
5: migrateFrom5Version,
6: migrateFrom6Version,
7: migrateFrom7Version,
8: migrateFrom8Version,
}
Expand Down Expand Up @@ -187,47 +184,9 @@ func iterateContainerBuckets(l *zap.Logger, cs Containers, tx *bbolt.Tx, fromBkt
return name, afterObj, nil
}

func migrateFrom5Version(db *DB) error {
return db.boltDB.Update(func(tx *bbolt.Tx) error {
var (
buckets [][]byte
obsoletePrefixes = []byte{unusedPrimaryPrefix,
unusedLockersPrefix, unusedStorageGroupPrefix,
unusedTombstonePrefix, unusedLinkObjectsPrefix}
)
err := tx.ForEach(func(name []byte, _ *bbolt.Bucket) error {
if slices.Contains(obsoletePrefixes, name[0]) {
buckets = append(buckets, slices.Clone(name))
}
return nil
})
if err != nil {
return fmt.Errorf("iterating buckets: %w", err)
}
for _, name := range buckets {
err := tx.DeleteBucket(name)
if err != nil {
return fmt.Errorf("deleting %v bucket: %w", name, err)
}
}
return updateVersion(tx, 6)
})
}

// garbageObjectsBucketName is pre-version-9 garbage bucket.
var garbageObjectsBucketName = []byte{unusedGarbageObjectsPrefix}

func migrateFrom6Version(db *DB) error {
return db.boltDB.Update(func(tx *bbolt.Tx) error {
if garbageBkt := tx.Bucket(garbageObjectsBucketName); garbageBkt != nil {
if err := fixGarbageBucketKeys(db.log, tx, garbageBkt); err != nil {
return fmt.Errorf("fix garbage bucket keys: %w", err)
}
}
return updateVersion(tx, 7)
})
}

func migrateFrom7Version(db *DB) error {
return db.boltDB.Update(func(tx *bbolt.Tx) error {
type cnrAndSize struct {
Expand Down Expand Up @@ -328,57 +287,6 @@ func migrateFrom7Version(db *DB) error {
})
}

func fixGarbageBucketKeys(log *zap.Logger, tx *bbolt.Tx, garbageBkt *bbolt.Bucket) error {
var rmKeys [][]byte
newItems := make(map[cid.ID][][]byte)
metaOIDKey := [1 + oid.Size]byte{metaPrefixID}

garbageCursor := garbageBkt.Cursor()
for k, _ := garbageCursor.First(); k != nil; k, _ = garbageCursor.Next() {
if len(k) != oid.Size {
continue
}

copy(metaOIDKey[1:], k)

cnr, err := resolveContainerByOID(tx, metaOIDKey[:])
if err != nil {
return fmt.Errorf("resolve container by OID: %w", err)
}

// update after so as not to break the cursor
rmKeys = append(rmKeys, k)

if cnr.IsZero() {
log.Info("failed to resolve container for broken item in garbage bucket, removing...", zap.String("OID", base58.Encode(k)))
continue
}

newItems[cnr] = append(newItems[cnr], k)
}

for i := range rmKeys {
if err := garbageBkt.Delete(rmKeys[i]); err != nil {
return fmt.Errorf("remove broken item: %w", err)
}
}

var newKey [cid.Size + oid.Size]byte
for cnr, objs := range newItems {
copy(newKey[:], cnr[:])

for _, id := range objs {
copy(newKey[cid.Size:], id)

if err := garbageBkt.Put(newKey[:], zeroValue); err != nil {
return fmt.Errorf("put fixed item: %w", err)
}
}
}

return nil
}

func migrateFrom8Version(db *DB) error {
return db.boltDB.Update(func(tx *bbolt.Tx) error {
var (
Expand Down
78 changes: 0 additions & 78 deletions pkg/local_object_storage/metabase/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import (

"github.com/nspcc-dev/bbolt"
checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/test"
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"
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
Expand Down Expand Up @@ -135,82 +133,6 @@ func TestSlicesCloneNil(t *testing.T) {
require.Nil(t, slices.Clone([]byte(nil)))
}

func TestMigrate6to7(t *testing.T) {
db := newDB(t)

const cnrNum = 5
const objsPerCnr = 20
mObjs := make(map[cid.ID][]oid.ID)
trashKey := []byte("trash")
leakingObj := oidtest.ID()

err := db.boltDB.Update(func(tx *bbolt.Tx) error {
garbageBkt, err := tx.CreateBucketIfNotExists([]byte{0x01})
require.NoError(t, err)

cnrs := cidtest.IDs(cnrNum)

for _, cnr := range cnrs {
metaBkt, err := tx.CreateBucketIfNotExists(slices.Concat([]byte{0xFF}, cnr[:]))
require.NoError(t, err)

ids := oidtest.IDs(objsPerCnr)

for j, id := range ids {
metaKey := slices.Concat([]byte{0x00}, id[:])
require.NoError(t, metaBkt.Put(metaKey, nil))

var garbageKey []byte
if j%2 == 0 { // correct
garbageKey = id[:]
} else { // broken
garbageKey = slices.Concat(cnr[:], id[:])
}

require.NoError(t, garbageBkt.Put(garbageKey, nil))
}

mObjs[cnr] = ids
}

// add trash key which is neither OID nor CID+OID
require.NoError(t, garbageBkt.Put(trashKey, nil))
// add random OID which should be cleaned because there is no container for it
require.NoError(t, garbageBkt.Put(leakingObj[:], nil))

// force old version
bkt := tx.Bucket([]byte{0x05})
require.NotNil(t, bkt)
require.NoError(t, bkt.Put([]byte("version"), []byte{0x06, 0, 0, 0, 0, 0, 0, 0}))

return nil
})
require.NoError(t, err)

// migrate
require.NoError(t, db.Init())

gObjs, _, err := db.GetGarbage(cnrNum*objsPerCnr + 2)
require.NoError(t, err)
require.EqualValues(t, cnrNum*objsPerCnr, len(gObjs))

for cnr, ids := range mObjs {
for _, id := range ids {
require.Contains(t, gObjs, oid.NewAddress(cnr, id))
}
}

err = db.boltDB.View(func(tx *bbolt.Tx) error {
// check new version
bkt := tx.Bucket([]byte{0x05})
require.NotNil(t, bkt)
require.Equal(t, []byte{currentMetaVersion, 0, 0, 0, 0, 0, 0, 0}, bkt.Get([]byte("version")))

return nil
})
require.NoError(t, err)
}

func TestMigrate7to8(t *testing.T) {
db := newDB(t)
cnr := cidtest.ID()
Expand Down
Loading