Fix snapshot selection during restore-by-index
This commit fixes a bug where restoring to a specific index will incorrectly choose the latest snapshot instead of choosing the latest snapshot that occurred before the given index.
This commit is contained in:
2
Makefile
2
Makefile
@@ -6,7 +6,7 @@ docker:
|
||||
dist-linux:
|
||||
mkdir -p dist
|
||||
cp etc/litestream.yml dist/litestream.yml
|
||||
docker run --rm -v "${PWD}":/usr/src/litestream -w /usr/src/litestream -e GOOS=linux -e GOARCH=amd64 golang:1.16 go build -v -o dist/litestream ./cmd/litestream
|
||||
docker run --rm -v "${PWD}":/usr/src/litestream -w /usr/src/litestream -e GOOS=linux -e GOARCH=amd64 golang:1.16 go build -v -ldflags "-s -w" -o dist/litestream ./cmd/litestream
|
||||
tar -cz -f dist/litestream-linux-amd64.tar.gz -C dist litestream
|
||||
|
||||
dist-linux-arm:
|
||||
|
||||
31
db.go
31
db.go
@@ -1434,7 +1434,7 @@ func (db *DB) monitor() {
|
||||
// replica or generation or it will automatically choose the best one. Finally,
|
||||
// a timestamp can be specified to restore the database to a specific
|
||||
// point-in-time.
|
||||
func RestoreReplica(ctx context.Context, r Replica, opt RestoreOptions) error {
|
||||
func RestoreReplica(ctx context.Context, r Replica, opt RestoreOptions) (err error) {
|
||||
// Validate options.
|
||||
if opt.OutputPath == "" {
|
||||
return fmt.Errorf("output path required")
|
||||
@@ -1462,10 +1462,16 @@ func RestoreReplica(ctx context.Context, r Replica, opt RestoreOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find lastest snapshot that occurs before timestamp.
|
||||
minWALIndex, err := SnapshotIndexAt(ctx, r, opt.Generation, opt.Timestamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot find snapshot index for restore: %w", err)
|
||||
// Find lastest snapshot that occurs before timestamp or index.
|
||||
var minWALIndex int
|
||||
if opt.Index < math.MaxInt32 {
|
||||
if minWALIndex, err = SnapshotIndexByIndex(ctx, r, opt.Generation, opt.Index); err != nil {
|
||||
return fmt.Errorf("cannot find snapshot index: %w", err)
|
||||
}
|
||||
} else {
|
||||
if minWALIndex, err = SnapshotIndexAt(ctx, r, opt.Generation, opt.Timestamp); err != nil {
|
||||
return fmt.Errorf("cannot find snapshot index by timestamp: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Find the maximum WAL index that occurs before timestamp.
|
||||
@@ -1487,7 +1493,7 @@ func RestoreReplica(ctx context.Context, r Replica, opt RestoreOptions) error {
|
||||
|
||||
// Restore each WAL file until we reach our maximum index.
|
||||
for index := minWALIndex; index <= maxWALIndex; index++ {
|
||||
logger.Printf("%s: restoring wal %s/%08x", logPrefix, opt.Generation, index)
|
||||
restoreStartTime := time.Now()
|
||||
if err = restoreWAL(ctx, r, opt.Generation, index, tmpPath); os.IsNotExist(err) && index == minWALIndex && index == maxWALIndex {
|
||||
logger.Printf("%s: no wal available, snapshot only", logPrefix)
|
||||
break // snapshot file only, ignore error
|
||||
@@ -1495,10 +1501,21 @@ func RestoreReplica(ctx context.Context, r Replica, opt RestoreOptions) error {
|
||||
return fmt.Errorf("cannot restore wal: %w", err)
|
||||
}
|
||||
|
||||
logger.Printf("%s: applying wal %s/%08x", logPrefix, opt.Generation, index)
|
||||
fi, err := os.Stat(tmpPath + "-wal")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot stat restored wal: %w", err)
|
||||
}
|
||||
|
||||
applyStartTime := time.Now()
|
||||
if err = applyWAL(ctx, tmpPath); err != nil {
|
||||
return fmt.Errorf("cannot apply wal: %w", err)
|
||||
}
|
||||
logger.Printf("%s: restored wal %s/%08x (sz=%d restore=%s apply=%s)",
|
||||
logPrefix, opt.Generation, index,
|
||||
fi.Size(),
|
||||
applyStartTime.Sub(restoreStartTime).String(),
|
||||
time.Since(applyStartTime).String(),
|
||||
)
|
||||
}
|
||||
|
||||
// Copy file to final location.
|
||||
|
||||
36
replica.go
36
replica.go
@@ -1064,7 +1064,7 @@ func SnapshotIndexAt(ctx context.Context, r Replica, generation string, timestam
|
||||
return 0, ErrNoSnapshots
|
||||
}
|
||||
|
||||
index := -1
|
||||
snapshotIndex := -1
|
||||
var max time.Time
|
||||
for _, snapshot := range snapshots {
|
||||
if !timestamp.IsZero() && snapshot.CreatedAt.After(timestamp) {
|
||||
@@ -1073,14 +1073,42 @@ func SnapshotIndexAt(ctx context.Context, r Replica, generation string, timestam
|
||||
|
||||
// Use snapshot if it newer.
|
||||
if max.IsZero() || snapshot.CreatedAt.After(max) {
|
||||
index, max = snapshot.Index, snapshot.CreatedAt
|
||||
snapshotIndex, max = snapshot.Index, snapshot.CreatedAt
|
||||
}
|
||||
}
|
||||
|
||||
if index == -1 {
|
||||
if snapshotIndex == -1 {
|
||||
return 0, ErrNoSnapshots
|
||||
}
|
||||
return index, nil
|
||||
return snapshotIndex, nil
|
||||
}
|
||||
|
||||
// SnapshotIndexbyIndex returns the highest index for a snapshot within a generation
|
||||
// that occurs before a given index. If index is MaxInt32, returns the latest snapshot.
|
||||
func SnapshotIndexByIndex(ctx context.Context, r Replica, generation string, index int) (int, error) {
|
||||
snapshots, err := r.Snapshots(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if len(snapshots) == 0 {
|
||||
return 0, ErrNoSnapshots
|
||||
}
|
||||
|
||||
snapshotIndex := -1
|
||||
for _, snapshot := range snapshots {
|
||||
if index < math.MaxInt32 && snapshot.Index > index {
|
||||
continue // after index, skip
|
||||
}
|
||||
|
||||
// Use snapshot if it newer.
|
||||
if snapshotIndex == -1 || snapshotIndex >= snapshotIndex {
|
||||
snapshotIndex = snapshot.Index
|
||||
}
|
||||
}
|
||||
|
||||
if snapshotIndex == -1 {
|
||||
return 0, ErrNoSnapshots
|
||||
}
|
||||
return snapshotIndex, nil
|
||||
}
|
||||
|
||||
// WALIndexAt returns the highest index for a WAL file that occurs before maxIndex & timestamp.
|
||||
|
||||
Reference in New Issue
Block a user