Files
qb/recovery/recovery.go
2026-06-13 22:43:17 +00:00

222 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package recovery
import (
"errors"
"os"
"path/filepath"
"strconv"
"strings"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/freelist"
"gordenko.dev/dima/qb/storage"
)
// Result зберігає результати для обох типів файлів
type FileVersion struct {
Name string
Version int
}
// FindMaxSnapshotFiles шукає файли з розширеннями .snapshot_XXX та .wal_XXX
// і повертає структуру з максимальними номерами.
func findFileLatestVersion(dir string, extPrefix string) (FileVersion, error) {
var x FileVersion
if !strings.HasPrefix(extPrefix, ".") {
extPrefix = "." + extPrefix
}
entries, err := os.ReadDir(dir)
if err != nil {
return x, err
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
ext := filepath.Ext(name)
if strings.HasPrefix(ext, extPrefix) {
version, err := strconv.Atoi(strings.TrimPrefix(ext, extPrefix))
if err == nil {
if version > x.Version || x.Name == "" {
x.Name = name
x.Version = version
}
}
}
}
return x, nil
}
var (
ErrLostSnapshot = errors.New("lost snapshot")
)
type RecoveryFiles struct {
Snapshot string
WAL string
SnapshotNumber int
}
func resolveRecoveryFiles(dir string) (_ RecoveryFiles, err error) {
wal, err := findFileLatestVersion(dir, "wal_")
if err != nil {
return
}
snapshot, err := findFileLatestVersion(dir, "snapshot_")
if err != nil {
return
}
if snapshot.Name != "" {
if wal.Name != "" {
if snapshot.Version == wal.Version {
return RecoveryFiles{
Snapshot: snapshot.Name,
WAL: wal.Name,
SnapshotNumber: snapshot.Version,
}, nil
} else if snapshot.Version > wal.Version {
return RecoveryFiles{
Snapshot: snapshot.Name,
SnapshotNumber: snapshot.Version,
}, nil
} else {
// WAL version > snapshot version
err = ErrLostSnapshot
return
}
} else {
return RecoveryFiles{
Snapshot: snapshot.Name,
SnapshotNumber: snapshot.Version,
}, nil
}
} else {
if wal.Name != "" {
if wal.Version == 0 {
return RecoveryFiles{
WAL: wal.Name,
}, nil
} else {
err = ErrLostSnapshot
return
}
} else {
// no files - ok, 1st start of database
return
}
}
}
type RecoveryReport struct {
//Metrics []
SnapshotNumber int
WAL string
IndexFreeList *freelist.FreeList
DataFreeList *freelist.FreeList
}
func Recovery(dir string, databaseName string) (_ RecoveryReport, err error) {
files, err := resolveRecoveryFiles(dir)
if err != nil {
return
}
var (
freeIndexPages []uint32
freeDataPages []uint32
)
if files.Snapshot != "" {
var out storage.ReadSnapshotOut
out, err = storage.ReadSnapshot(files.Snapshot)
if err != nil {
return
}
//
freeIndexPages = out.IndexPageNumbers
freeDataPages = out.DataPageNumbers
}
if files.WAL != "" {
var (
walReader *storage.WALReader
walReplayer *storage.WALReplayer
)
walReader, err = storage.NewWALReader(storage.WALReaderOptions{
//FileName: JoinWALFileName(s.dir, files.WAL),
BufferSize: 8 * 1024 * 1024,
})
if err != nil {
return
}
walReplayer, err = storage.NewWALReplayer(storage.WALReplayerOptions{
WALReader: walReader,
Metrics: snapshot.Metrics,
FreeIndexPages: freeIndexPages,
FreeDataPages: freeDataPages,
})
if err != nil {
return
}
err = walReplayer.Replay()
if err != nil {
return
}
// перезаписую сторінки із останнього комміта
var dataFile *os.File
dataFile, err = os.OpenFile(qb.GetDataFilePath(dir, databaseName), os.O_WRONLY, 0666)
if err != nil {
return
}
err = storage.WriteDataPages(dataFile, walReplayer.DataPages())
if err != nil {
return
}
err = dataFile.Close()
if err != nil {
return
}
var indexFile *os.File
indexFile, err = os.OpenFile(qb.GetIndexFilePath(dir, databaseName), os.O_WRONLY, 0666)
if err != nil {
return
}
err = storage.WriteIndexPages(indexFile, walReplayer.IndexPages())
if err != nil {
return
}
err = indexFile.Close()
if err != nil {
return
}
}
indexFreeList, err := freelist.New(freelist.Options{
PageSize: 2048,
BaseFilePath: filepath.Join(dir, databaseName+".free_index"),
DeltaFilePath: filepath.Join(dir, databaseName+".free_index_delta"),
})
// FIX free pages sync
dataFreeList, err := freelist.New(freelist.Options{
PageSize: 2048,
BaseFilePath: filepath.Join(dir, databaseName+".free_data"),
DeltaFilePath: filepath.Join(dir, databaseName+".free_data_delta"),
})
return RecoveryReport{
SnapshotNumber: files.SnapshotNumber,
WAL: files.WAL,
IndexFreeList: indexFreeList,
DataFreeList: dataFreeList,
}, nil
}
//func (s *Database) relayMetricsToMetrics(replayMetrics map[uint32]*ReplayMetric) {
//for metricID, x := range replayMetrics {
//s.metrics[metricID] = x.ToMetric() FIX
//}
//}