Files
etcd/server/storage/schema/migration.go
ahrtr bfd5170f66 add a txPostLockHook into the backend
Previously the SetConsistentIndex() is called during the apply workflow,
but it's outside the db transaction. If a commit happens between SetConsistentIndex
and the following apply workflow, and etcd crashes for whatever reason right
after the commit, then etcd commits an incomplete transaction to db.
Eventually etcd runs into the data inconsistency issue.

In this commit, we move the SetConsistentIndex into a txPostLockHook, so
it will be executed inside the transaction lock.
2022-04-07 05:35:13 +08:00

114 lines
3.3 KiB
Go

// Copyright 2021 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package schema
import (
"fmt"
"github.com/coreos/go-semver/semver"
"go.etcd.io/etcd/server/v3/storage/backend"
"go.uber.org/zap"
)
type migrationPlan []migrationStep
func newPlan(lg *zap.Logger, current semver.Version, target semver.Version) (plan migrationPlan, err error) {
current = trimToMinor(current)
target = trimToMinor(target)
if current.Major != target.Major {
lg.Error("Changing major storage version is not supported",
zap.String("storage-version", current.String()),
zap.String("target-storage-version", target.String()),
)
return plan, fmt.Errorf("changing major storage version is not supported")
}
for !current.Equal(target) {
isUpgrade := current.Minor < target.Minor
changes, err := schemaChangesForVersion(current, isUpgrade)
if err != nil {
return plan, err
}
step := newMigrationStep(current, isUpgrade, changes)
plan = append(plan, step)
current = step.target
}
return plan, nil
}
func (p migrationPlan) Execute(lg *zap.Logger, tx backend.BatchTx) error {
tx.LockWithoutHook()
defer tx.Unlock()
return p.unsafeExecute(lg, tx)
}
func (p migrationPlan) unsafeExecute(lg *zap.Logger, tx backend.BatchTx) (err error) {
for _, s := range p {
err = s.unsafeExecute(lg, tx)
if err != nil {
return err
}
lg.Info("updated storage version", zap.String("new-storage-version", s.target.String()))
}
return nil
}
// migrationStep represents a single migrationStep of migrating etcd storage between two minor versions.
type migrationStep struct {
target semver.Version
actions ActionList
}
func newMigrationStep(v semver.Version, isUpgrade bool, changes []schemaChange) (step migrationStep) {
step.actions = make(ActionList, len(changes))
for i, change := range changes {
if isUpgrade {
step.actions[i] = change.upgradeAction()
} else {
step.actions[len(changes)-1-i] = change.downgradeAction()
}
}
if isUpgrade {
step.target = semver.Version{Major: v.Major, Minor: v.Minor + 1}
} else {
step.target = semver.Version{Major: v.Major, Minor: v.Minor - 1}
}
return step
}
// execute runs actions required to migrate etcd storage between two minor versions.
func (s migrationStep) execute(lg *zap.Logger, tx backend.BatchTx) error {
tx.LockWithoutHook()
defer tx.Unlock()
return s.unsafeExecute(lg, tx)
}
// unsafeExecute is non thread-safe version of execute.
func (s migrationStep) unsafeExecute(lg *zap.Logger, tx backend.BatchTx) error {
err := s.actions.unsafeExecute(lg, tx)
if err != nil {
return err
}
// Storage version is available since v3.6, downgrading target v3.5 should clean this field.
if !s.target.LessThan(V3_6) {
UnsafeSetStorageVersion(tx, &s.target)
}
return nil
}
func trimToMinor(ver semver.Version) semver.Version {
return semver.Version{Major: ver.Major, Minor: ver.Minor}
}