2026-06-09 16:42:17 +00:00
|
|
|
package database
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
"gordenko.dev/dima/qb/storage"
|
2026-06-09 16:42:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type WALReplayer struct {
|
2026-06-10 06:18:45 +03:00
|
|
|
walReader *storage.WALReader
|
2026-06-09 16:42:17 +00:00
|
|
|
metrics map[uint32]*ReplayMetric
|
|
|
|
|
freeIndexPages []uint32
|
|
|
|
|
freeDataPages []uint32
|
2026-06-10 06:18:45 +03:00
|
|
|
indexPages []storage.PageToWrite
|
|
|
|
|
dataPages []storage.PageToWrite
|
2026-06-09 16:42:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type WALReplayerOptions struct {
|
2026-06-10 06:18:45 +03:00
|
|
|
WALReader *storage.WALReader
|
2026-06-09 16:42:17 +00:00
|
|
|
Metrics map[uint32]*ReplayMetric // from snapshot
|
|
|
|
|
FreeIndexPages []uint32 // from snapshot
|
|
|
|
|
FreeDataPages []uint32 // from snapshot
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewWALReplayer(opt WALReplayerOptions) (*WALReplayer, error) {
|
|
|
|
|
if opt.WALReader == nil {
|
|
|
|
|
return nil, errors.New("required option: WALReader")
|
|
|
|
|
}
|
|
|
|
|
if opt.Metrics == nil {
|
|
|
|
|
return nil, errors.New("required option: Metrics")
|
|
|
|
|
}
|
|
|
|
|
return &WALReplayer{
|
|
|
|
|
walReader: opt.WALReader,
|
|
|
|
|
metrics: opt.Metrics,
|
|
|
|
|
freeIndexPages: opt.FreeIndexPages,
|
|
|
|
|
freeDataPages: opt.FreeDataPages,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
func (s *WALReplayer) IndexPages() []storage.PageToWrite {
|
|
|
|
|
return s.indexPages
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *WALReplayer) DataPages() []storage.PageToWrite {
|
|
|
|
|
return s.dataPages
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 16:42:17 +00:00
|
|
|
func (s *WALReplayer) Replay() error {
|
|
|
|
|
for {
|
2026-06-10 06:18:45 +03:00
|
|
|
records, isLastPacket, err := s.walReader.NextPacket()
|
2026-06-09 16:42:17 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for _, rec := range records {
|
2026-06-10 06:18:45 +03:00
|
|
|
if err = s.replayRecord(rec, isLastPacket); err != nil {
|
2026-06-09 16:42:17 +00:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
func (s *WALReplayer) replayRecord(untyped any, isLastPacket bool) (err error) {
|
2026-06-09 16:42:17 +00:00
|
|
|
switch rec := untyped.(type) {
|
2026-06-10 06:18:45 +03:00
|
|
|
case storage.MeasuresAppendRecord:
|
|
|
|
|
if err = s.onMeasuresAppend(rec); err != nil {
|
2026-06-09 16:42:17 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-06-10 06:18:45 +03:00
|
|
|
case storage.MeasuresAppendWithGrowRecord:
|
|
|
|
|
if err = s.onMeasuresAppendWithGrow(rec, isLastPacket); err != nil {
|
2026-06-09 16:42:17 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-06-10 06:18:45 +03:00
|
|
|
case storage.MetricAddRecord:
|
|
|
|
|
if err = s.onMetricAdd(rec); err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
case storage.MetricDeleteRecord:
|
|
|
|
|
if err = s.onMetricDelete(rec); err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// case storage.DeletedMeasures:
|
|
|
|
|
// metric, ok := s.metrics[rec.MetricID]
|
|
|
|
|
// if ok {
|
|
|
|
|
// metric.DeleteMeasures()
|
|
|
|
|
// if len(rec.FreePageNumbers) > 0 {
|
|
|
|
|
// s.freeList.AddPages(rec.FreePageNumbers)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// default:
|
|
|
|
|
// qb.Abort(qb.UnknownstorageRecordTypeBug,
|
|
|
|
|
// fmt.Errorf("bug: unknown record type %T in TransactionLog", rec))
|
2026-06-09 16:42:17 +00:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
func (s *WALReplayer) onMetricAdd(rec storage.MetricAddRecord) (err error) {
|
2026-06-09 16:42:17 +00:00
|
|
|
_, ok := s.metrics[rec.MetricID]
|
|
|
|
|
if ok {
|
|
|
|
|
return fmt.Errorf("metric %d add failed: already added", rec.MetricID)
|
|
|
|
|
}
|
|
|
|
|
var (
|
2026-06-10 06:18:45 +03:00
|
|
|
buf = make([]byte, storage.DataPageSize)
|
2026-06-09 16:42:17 +00:00
|
|
|
)
|
|
|
|
|
s.metrics[rec.MetricID] = &ReplayMetric{
|
|
|
|
|
metricType: rec.MetricType,
|
|
|
|
|
fracDigits: byte(rec.FracDigits),
|
|
|
|
|
buf: buf,
|
2026-06-10 06:18:45 +03:00
|
|
|
databuf: buf[:storage.DataPagePayloadSize],
|
2026-06-09 16:42:17 +00:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
func (s *WALReplayer) onMetricDelete(rec storage.MetricDeleteRecord) error {
|
2026-06-09 16:42:17 +00:00
|
|
|
_, ok := s.metrics[rec.MetricID]
|
|
|
|
|
if !ok {
|
|
|
|
|
return fmt.Errorf("metric %d deletion failed: not found", rec.MetricID)
|
|
|
|
|
}
|
|
|
|
|
delete(s.metrics, rec.MetricID)
|
|
|
|
|
s.freeIndexPages = append(s.freeIndexPages, rec.FreeIndexPages...)
|
|
|
|
|
s.freeDataPages = append(s.freeDataPages, rec.FreeDataPages...)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
func (s *WALReplayer) onMeasuresAppend(rec storage.MeasuresAppendRecord) (err error) {
|
2026-06-09 16:42:17 +00:00
|
|
|
metric, ok := s.metrics[rec.MetricID]
|
|
|
|
|
if !ok {
|
2026-06-10 06:18:45 +03:00
|
|
|
return fmt.Errorf("append measures failed: metric %d not found", rec.MetricID)
|
2026-06-09 16:42:17 +00:00
|
|
|
}
|
2026-06-10 06:18:45 +03:00
|
|
|
metric.MeasuresAppend(rec)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *WALReplayer) onMeasuresAppendWithGrow(rec storage.MeasuresAppendWithGrowRecord, isLastPacket bool) (err error) {
|
|
|
|
|
metric, ok := s.metrics[rec.MetricID]
|
|
|
|
|
if !ok {
|
|
|
|
|
return fmt.Errorf("append measures failed: metric %d not found", rec.MetricID)
|
2026-06-09 16:42:17 +00:00
|
|
|
}
|
2026-06-10 06:18:45 +03:00
|
|
|
result := metric.MeasuresAppendWithGrow(rec, isLastPacket)
|
2026-06-09 16:42:17 +00:00
|
|
|
//
|
|
|
|
|
if isLastPacket {
|
|
|
|
|
s.indexPages = append(s.indexPages, result.IndexPages...)
|
|
|
|
|
s.dataPages = append(s.dataPages, result.DataPages...)
|
|
|
|
|
}
|
|
|
|
|
//
|
|
|
|
|
s.freeIndexPages = s.freeIndexPages[:len(s.freeIndexPages)-result.ReusedIndexPagesCount]
|
|
|
|
|
s.freeDataPages = s.freeDataPages[:len(s.freeDataPages)-result.ReusedDataPagesCount]
|
2026-06-10 06:18:45 +03:00
|
|
|
return
|
2026-06-09 16:42:17 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
//func (s *WALReplayer) collectPagesToWrite(metric *ReplayMetric, rec storage.WALRecordAppendMeasures) {
|
|
|
|
|
// metric.ApplyHeadDataPage(rec.CompletedDataPage)
|
2026-06-09 16:42:17 +00:00
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
// s.dataPages = append(s.dataPages, storage.PageToWrite{
|
|
|
|
|
// PageNo: p.PageNo,
|
|
|
|
|
// Content: metric.buf,
|
|
|
|
|
// })
|
2026-06-09 16:42:17 +00:00
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
// for _, x := range rec.DataPages {
|
|
|
|
|
// s.dataPages = append(s.dataPages, storage.PageToWrite{
|
|
|
|
|
// PageNo: x.PageNo,
|
|
|
|
|
// Content: x.Content,
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
//metric.ApplyHeadIndexPages(rec.ChangedIndexLevels)
|
2026-06-09 16:42:17 +00:00
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
// func (s *WALReplayer) replayPacket(records []any, isLastPacket bool) {
|
|
|
|
|
// for _, untyped := range records {
|
|
|
|
|
// switch rec := untyped.(type) {
|
|
|
|
|
// case storage.WALRecordAppendMeasures:
|
|
|
|
|
// s.onWALRecordAppendMeasures(rec, isLastPacket)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// return
|
|
|
|
|
// }
|