metabase: Add upgrade from v2 to v3 #1334

Merged
fyrchik merged 1 commit from dstepanov-yadro/frostfs-node:feat/metabase_upgrade into master 2024-09-04 19:51:11 +00:00
4 changed files with 43 additions and 2 deletions

View file

@ -51,9 +51,21 @@ func Upgrade(ctx context.Context, path string, compact bool, log func(a ...any))
if !found {
return fmt.Errorf("unsupported version %d: no update available", version)
}
if err := db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket(shardInfoBucket)
return b.Put(upgradeKey, zeroValue)
}); err != nil {
return fmt.Errorf("set upgrade key %w", err)
}
if err := updater(ctx, db, log); err != nil {
return fmt.Errorf("update metabase schema: %w", err)
}
if err := db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket(shardInfoBucket)
return b.Delete(upgradeKey)
}); err != nil {
return fmt.Errorf("delete upgrade key %w", err)
}
if compact {
log("compacting metabase...")
err := compactDB(db)
acid-ant marked this conversation as resolved Outdated

Looks like we need to remove all files which matches pattern db.info.Path + "." + "*", not only tmpFileName.
Or how about to do cleanup before starting update?

Looks like we need to remove all files which matches pattern `db.info.Path + "." + "*"`, not only `tmpFileName`. Or how about to do cleanup before starting update?

I suppose to not to delete files by pattern to not to delete something needed (manual backup for example)

I suppose to not to delete files by pattern to not to delete something needed (manual backup for example)

That will lead to increasing garbage inside db.info.Path when node killed.

That will lead to increasing garbage inside `db.info.Path` when node killed.

View file

@ -12,13 +12,18 @@ import (
// version contains current metabase version.
const version = 3
var versionKey = []byte("version")
var (
versionKey = []byte("version")
upgradeKey = []byte("upgrade")
)
// ErrOutdatedVersion is returned on initializing
// an existing metabase that is not compatible with
// the current code version.
var ErrOutdatedVersion = logicerr.New("invalid version, resynchronization is required")
var ErrIncompletedUpgrade = logicerr.New("metabase upgrade is not completed")
var errVersionUndefinedNoInfoBucket = errors.New("version undefined: no info bucket")
func checkVersion(tx *bbolt.Tx, initialized bool) error {
@ -35,6 +40,10 @@ func checkVersion(tx *bbolt.Tx, initialized bool) error {
return fmt.Errorf("%w: expected=%d, stored=%d", ErrOutdatedVersion, version, stored)
}
}
data = b.Get(upgradeKey)
if len(data) > 0 {
return ErrIncompletedUpgrade
}
}
if !initialized {

View file

@ -84,4 +84,24 @@ func TestVersion(t *testing.T) {
require.NoError(t, db.Close())
})
})
t.Run("incompleted upgrade", func(t *testing.T) {
db := newDB(t)
require.NoError(t, db.Open(context.Background(), mode.ReadWrite))
require.NoError(t, db.Init())
require.NoError(t, db.Close())
require.NoError(t, db.Open(context.Background(), mode.ReadWrite))
require.NoError(t, db.boltDB.Update(func(tx *bbolt.Tx) error {
return tx.Bucket(shardInfoBucket).Put(upgradeKey, zeroValue)
}))
require.ErrorIs(t, db.Init(), ErrIncompletedUpgrade)
require.NoError(t, db.Close())
require.NoError(t, db.Open(context.Background(), mode.ReadWrite))
require.NoError(t, db.boltDB.Update(func(tx *bbolt.Tx) error {
return tx.Bucket(shardInfoBucket).Delete(upgradeKey)
}))
require.NoError(t, db.Init())
require.NoError(t, db.Close())
})
}

View file

@ -171,7 +171,7 @@ func (s *Shard) initializeComponents(m mode.Mode) error {
for _, component := range components {
if err := component.Init(); err != nil {
if component == s.metaBase {
if errors.Is(err, meta.ErrOutdatedVersion) {
if errors.Is(err, meta.ErrOutdatedVersion) || errors.Is(err, meta.ErrIncompletedUpgrade) {
return fmt.Errorf("metabase initialization: %w", err)
}