You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
3.8 KiB
133 lines
3.8 KiB
package replication
|
|
|
|
import "fmt"
|
|
|
|
// RebuildSource identifies the recovery base.
|
|
type RebuildSource string
|
|
|
|
const (
|
|
RebuildSnapshotTail RebuildSource = "snapshot_tail"
|
|
RebuildFullBase RebuildSource = "full_base"
|
|
)
|
|
|
|
// RebuildPhase tracks rebuild execution progress.
|
|
type RebuildPhase string
|
|
|
|
const (
|
|
RebuildPhaseInit RebuildPhase = "init"
|
|
RebuildPhaseSourceSelect RebuildPhase = "source_select"
|
|
RebuildPhaseTransfer RebuildPhase = "transfer"
|
|
RebuildPhaseTailReplay RebuildPhase = "tail_replay"
|
|
RebuildPhaseCompleted RebuildPhase = "completed"
|
|
RebuildPhaseAborted RebuildPhase = "aborted"
|
|
)
|
|
|
|
// RebuildState tracks rebuild execution. Owned by Session.
|
|
type RebuildState struct {
|
|
Source RebuildSource
|
|
Phase RebuildPhase
|
|
AbortReason string
|
|
SnapshotLSN uint64
|
|
SnapshotValid bool
|
|
TransferredTo uint64
|
|
TailStartLSN uint64
|
|
TailTargetLSN uint64
|
|
TailReplayedTo uint64
|
|
}
|
|
|
|
// NewRebuildState creates a rebuild state in init phase.
|
|
func NewRebuildState() *RebuildState {
|
|
return &RebuildState{Phase: RebuildPhaseInit}
|
|
}
|
|
|
|
// SelectSource chooses rebuild source based on snapshot availability.
|
|
func (rs *RebuildState) SelectSource(snapshotLSN uint64, snapshotValid bool, committedLSN uint64) error {
|
|
if rs.Phase != RebuildPhaseInit {
|
|
return fmt.Errorf("rebuild: source select requires init phase, got %s", rs.Phase)
|
|
}
|
|
rs.SnapshotLSN = snapshotLSN
|
|
rs.SnapshotValid = snapshotValid
|
|
rs.Phase = RebuildPhaseSourceSelect
|
|
if snapshotValid && snapshotLSN > 0 {
|
|
rs.Source = RebuildSnapshotTail
|
|
rs.TailStartLSN = snapshotLSN
|
|
rs.TailTargetLSN = committedLSN
|
|
} else {
|
|
rs.Source = RebuildFullBase
|
|
rs.TailTargetLSN = committedLSN
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (rs *RebuildState) BeginTransfer() error {
|
|
if rs.Phase != RebuildPhaseSourceSelect {
|
|
return fmt.Errorf("rebuild: transfer requires source_select, got %s", rs.Phase)
|
|
}
|
|
rs.Phase = RebuildPhaseTransfer
|
|
return nil
|
|
}
|
|
|
|
func (rs *RebuildState) RecordTransferProgress(transferredTo uint64) error {
|
|
if rs.Phase != RebuildPhaseTransfer {
|
|
return fmt.Errorf("rebuild: progress requires transfer, got %s", rs.Phase)
|
|
}
|
|
if transferredTo <= rs.TransferredTo {
|
|
return fmt.Errorf("rebuild: transfer regression")
|
|
}
|
|
rs.TransferredTo = transferredTo
|
|
return nil
|
|
}
|
|
|
|
func (rs *RebuildState) BeginTailReplay() error {
|
|
if rs.Phase != RebuildPhaseTransfer {
|
|
return fmt.Errorf("rebuild: tail replay requires transfer, got %s", rs.Phase)
|
|
}
|
|
if rs.Source != RebuildSnapshotTail {
|
|
return fmt.Errorf("rebuild: tail replay only for snapshot_tail")
|
|
}
|
|
// Gate: base transfer must have reached at least the snapshot LSN.
|
|
// Without this, tail replay could start on an incomplete base.
|
|
if rs.TransferredTo < rs.SnapshotLSN {
|
|
return fmt.Errorf("rebuild: base transfer incomplete (%d < snapshot %d)",
|
|
rs.TransferredTo, rs.SnapshotLSN)
|
|
}
|
|
rs.Phase = RebuildPhaseTailReplay
|
|
return nil
|
|
}
|
|
|
|
func (rs *RebuildState) RecordTailReplayProgress(replayedTo uint64) error {
|
|
if rs.Phase != RebuildPhaseTailReplay {
|
|
return fmt.Errorf("rebuild: tail progress requires tail_replay, got %s", rs.Phase)
|
|
}
|
|
if replayedTo <= rs.TailReplayedTo {
|
|
return fmt.Errorf("rebuild: tail regression")
|
|
}
|
|
rs.TailReplayedTo = replayedTo
|
|
return nil
|
|
}
|
|
|
|
func (rs *RebuildState) ReadyToComplete() bool {
|
|
switch rs.Source {
|
|
case RebuildSnapshotTail:
|
|
return rs.Phase == RebuildPhaseTailReplay && rs.TailReplayedTo >= rs.TailTargetLSN
|
|
case RebuildFullBase:
|
|
return rs.Phase == RebuildPhaseTransfer && rs.TransferredTo >= rs.TailTargetLSN
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (rs *RebuildState) Complete() error {
|
|
if !rs.ReadyToComplete() {
|
|
return fmt.Errorf("rebuild: not ready (source=%s phase=%s)", rs.Source, rs.Phase)
|
|
}
|
|
rs.Phase = RebuildPhaseCompleted
|
|
return nil
|
|
}
|
|
|
|
func (rs *RebuildState) Abort(reason string) {
|
|
if rs.Phase == RebuildPhaseCompleted || rs.Phase == RebuildPhaseAborted {
|
|
return
|
|
}
|
|
rs.Phase = RebuildPhaseAborted
|
|
rs.AbortReason = reason
|
|
}
|