wp
This commit is contained in:
@@ -3,7 +3,6 @@ package database
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
@@ -12,8 +11,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
bin "gordenko.dev/dima/bin/little"
|
||||
"gordenko.dev/dima/qb"
|
||||
"gordenko.dev/dima/qb/atree"
|
||||
"gordenko.dev/dima/qb/bin"
|
||||
"gordenko.dev/dima/qb/freelist"
|
||||
"gordenko.dev/dima/qb/txlog"
|
||||
)
|
||||
@@ -35,17 +35,18 @@ type Database struct {
|
||||
rLocksToRelease []uint32
|
||||
metrics map[uint32]*_metric
|
||||
//metricLockEntries map[uint32]*metricLockEntry
|
||||
dataFreeList *freelist.FreeList
|
||||
indexFreeList *freelist.FreeList
|
||||
dir string
|
||||
databaseName string
|
||||
txlog *txlog.Writer
|
||||
atree *atree.Atree
|
||||
tcpPort int
|
||||
logfile *os.File
|
||||
logger *log.Logger
|
||||
exitCh chan struct{}
|
||||
waitGroup *sync.WaitGroup
|
||||
dataFreeList *freelist.FreeList
|
||||
indexFreeList *freelist.FreeList
|
||||
dir string
|
||||
databaseName string
|
||||
snapshotNumber int
|
||||
txlog *txlog.Writer
|
||||
atree *atree.Atree
|
||||
tcpPort int
|
||||
logfile *os.File
|
||||
logger *log.Logger
|
||||
exitCh chan struct{}
|
||||
waitGroup *sync.WaitGroup
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
@@ -237,107 +238,9 @@ func (s *Database) recovery() {
|
||||
// }
|
||||
}
|
||||
|
||||
// func (s *Database) searchREDOFiles() ([]string, error) {
|
||||
// var (
|
||||
// reREDO = regexp.MustCompile(`a\d+\.redo`)
|
||||
// fileNames []string
|
||||
// )
|
||||
//
|
||||
|
||||
// entries, err := os.ReadDir(s.redoDir)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// for _, entry := range entries {
|
||||
// if entry.Type().IsRegular() {
|
||||
// baseName := entry.Name()
|
||||
// if reREDO.MatchString(baseName) {
|
||||
// fileNames = append(fileNames, filepath.Join(s.redoDir, baseName))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return fileNames, nil
|
||||
// }
|
||||
|
||||
// func (s *Database) replayREDOFile(fileName string) error {
|
||||
// redoFile, err := redo.ReadREDOFile(redo.ReadREDOFileReq{
|
||||
// FileName: fileName,
|
||||
// DataPageSize: atree.DataPageSize,
|
||||
// IndexPageSize: atree.IndexPageSize,
|
||||
// })
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("can't read REDO file %s: %s", fileName, err)
|
||||
// }
|
||||
|
||||
// metric, ok := s.metrics[redoFile.MetricID]
|
||||
// if !ok {
|
||||
// return fmt.Errorf("has REDOFile, metric %d not found", redoFile.MetricID)
|
||||
// }
|
||||
|
||||
// if metric.Until < redoFile.Timestamp {
|
||||
// waitCh := make(chan struct{})
|
||||
// s.atree.ApplyREDO(atree.WriteTask{
|
||||
// DataPage: redoFile.DataPage,
|
||||
// IndexPages: redoFile.IndexPages,
|
||||
// })
|
||||
// <-waitCh
|
||||
|
||||
// waitCh = s.txlog.WriteAppendedMeasureWithOverflow(
|
||||
// txlog.AppendedMeasureWithOverflow{
|
||||
// MetricID: redoFile.MetricID,
|
||||
// Timestamp: redoFile.Timestamp,
|
||||
// Value: redoFile.Value,
|
||||
// IsDataPageReused: redoFile.IsDataPageReused,
|
||||
// DataPageNo: redoFile.DataPage.PageNo,
|
||||
// IsRootChanged: redoFile.IsRootChanged,
|
||||
// RootPageNo: redoFile.RootPageNo,
|
||||
// ReusedIndexPages: redoFile.ReusedIndexPages,
|
||||
// },
|
||||
// fileName,
|
||||
// false,
|
||||
// )
|
||||
// <-waitCh
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (s *Database) verifySnapshot(fileName string) (_ bool, err error) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if stat.Size() <= 4 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var (
|
||||
payloadSize = stat.Size() - 4
|
||||
hash = crc32.NewIEEE()
|
||||
)
|
||||
|
||||
_, err = io.CopyN(hash, file, payloadSize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
calculatedCRC := hash.Sum32()
|
||||
|
||||
storedCRC, err := bin.ReadUint32(file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if storedCRC != calculatedCRC {
|
||||
return false, fmt.Errorf("strored CRC %d not equal calculated CRC %d",
|
||||
storedCRC, calculatedCRC)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
// зробити object?
|
||||
|
||||
func (s *Database) replayChanges(fileName string) error {
|
||||
walReader, err := txlog.NewReader(txlog.ReaderOptions{
|
||||
@@ -348,120 +251,391 @@ func (s *Database) replayChanges(fileName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var metrics map[uint32]*ReplayMetric
|
||||
|
||||
for {
|
||||
lsn, records, done, err := walReader.ReadPacket()
|
||||
records, isLast, err := walReader.NextPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = lsn
|
||||
|
||||
if done {
|
||||
return nil
|
||||
if isLast {
|
||||
if len(records) > 0 {
|
||||
var appendRecords []txlog.WALRecordAppendMeasures
|
||||
for _, untyped := range records {
|
||||
rec, ok := untyped.(txlog.WALRecordAppendMeasures)
|
||||
if ok {
|
||||
appendRecords = append(appendRecords, rec)
|
||||
}
|
||||
}
|
||||
// prepare pages and rewrite
|
||||
indexPages, dataPages := pagesToWriteFromWALRecords(metrics, appendRecords)
|
||||
|
||||
err = s.txlog.WritePagesToAtree(indexPages, dataPages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// для останнього rec - повторюємо запис сторінок
|
||||
// rewrite write pages
|
||||
//s.txlog.
|
||||
//return nil
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if err = s.replayChangesRecord(record); err != nil {
|
||||
if err = s.replayWALRecord(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Database) replayChangesRecord(untyped any) error {
|
||||
// switch rec := untyped.(type) {
|
||||
// case txlog.AddedMetric:
|
||||
// var (
|
||||
// values qb.ValueCompressor
|
||||
// timestampsBuf = conbuf.New(nil)
|
||||
// valuesBuf = conbuf.New(nil)
|
||||
// )
|
||||
func (s *Database) replayWALRecord(untyped any) error {
|
||||
switch rec := untyped.(type) {
|
||||
case txlog.AddedMetric:
|
||||
s.addMetric(rec)
|
||||
|
||||
// if rec.MetricType == qb.Cumulative {
|
||||
// values = chunkenc.NewReverseCumulativeDeltaCompressor(
|
||||
// valuesBuf, 0, byte(rec.FracDigits))
|
||||
// } else {
|
||||
// values = chunkenc.NewReverseInstantDeltaCompressor(
|
||||
// valuesBuf, 0, byte(rec.FracDigits))
|
||||
case txlog.DeletedMetric:
|
||||
// pages add and delete to tmp file
|
||||
|
||||
// delete(s.metrics, rec.MetricID)
|
||||
// if len(rec.FreePageNumbers) > 0 {
|
||||
// s.freeList.AddPages(rec.FreePageNumbers)
|
||||
// }
|
||||
|
||||
// case txlog.AppendedMeasure:
|
||||
// metric, ok := s.metrics[rec.MetricID]
|
||||
// if ok {
|
||||
// metric.Timestamps.Append(rec.Timestamp)
|
||||
// metric.Values.Append(rec.Value)
|
||||
|
||||
// if metric.Since == 0 {
|
||||
// metric.Since = rec.Timestamp
|
||||
// metric.SinceValue = rec.Value
|
||||
// }
|
||||
|
||||
// s.metrics[rec.MetricID] = &_metric{
|
||||
// MetricType: rec.MetricType,
|
||||
// FracDigits: byte(rec.FracDigits),
|
||||
// TimestampsBuf: timestampsBuf,
|
||||
// ValuesBuf: valuesBuf,
|
||||
// Timestamps: chunkenc.NewReverseTimeDeltaCompressor(timestampsBuf, 0),
|
||||
// Values: values,
|
||||
// }
|
||||
// metric.Until = rec.Timestamp
|
||||
// metric.UntilValue = rec.Value
|
||||
// }
|
||||
|
||||
// case txlog.DeletedMetric:
|
||||
// delete(s.metrics, rec.MetricID)
|
||||
case txlog.WALRecordAppendMeasures:
|
||||
metric, ok := s.metrics[rec.MetricID]
|
||||
if !ok {
|
||||
qb.Abort(qb.MetricNotFoundDuringReplay, nil)
|
||||
}
|
||||
//metric.ReplayAppendedMeasures(rec) FIX
|
||||
|
||||
// case txlog.DeletedMeasures:
|
||||
// metric, ok := s.metrics[rec.MetricID]
|
||||
// if ok {
|
||||
// metric.DeleteMeasures()
|
||||
// if len(rec.FreePageNumbers) > 0 {
|
||||
// s.freeList.AddPages(rec.FreePageNumbers)
|
||||
// }
|
||||
// }
|
||||
|
||||
// case txlog.AppendedMeasure:
|
||||
default:
|
||||
qb.Abort(qb.UnknownTxLogRecordTypeBug,
|
||||
fmt.Errorf("bug: unknown record type %T in TransactionLog", rec))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ReplayMetric struct {
|
||||
metricType qb.MetricType
|
||||
fracDigits byte
|
||||
lastPageNo uint32
|
||||
buf []byte
|
||||
databuf []byte // якщо розмір buf == DataPageSize, то databuf буде менше (мінус футер)
|
||||
tSize int
|
||||
vSize int
|
||||
indexLevelTails []txlog.IndexLevelTail // root - last element
|
||||
}
|
||||
|
||||
func (s *ReplayMetric) ReadFrom(r io.Reader) (err error) {
|
||||
metricType, err := bin.ReadByte(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.metricType = qb.MetricType(metricType)
|
||||
s.fracDigits, err = bin.ReadByte(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.lastPageNo, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.tSize, err = bin.ReadUint16AsInt(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.vSize, err = bin.ReadUint16AsInt(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.buf, s.databuf = allocateBuffers(s.tSize + s.vSize)
|
||||
//
|
||||
err = bin.ReadNInto(r, s.databuf[len(s.databuf)-s.tSize:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = bin.ReadNInto(r, s.databuf[:s.vSize])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//
|
||||
levelsCount, err := bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for range levelsCount {
|
||||
var (
|
||||
buf = make([]byte, atree.IndexPageSize)
|
||||
count int
|
||||
)
|
||||
count, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = bin.ReadNInto(r, buf[:count*indexRecordSize])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.indexLevelTails = append(s.indexLevelTails, txlog.IndexLevelTail{
|
||||
Buffer: buf,
|
||||
RecordsCount: count,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ReplayMetric) ReplayAppendedMeasures(rec txlog.WALRecordAppendMeasures) {
|
||||
// Додати в index level tails недостаючі дані, або замінити
|
||||
for levelIdx, change := range rec.ChangedIndexLevels {
|
||||
if levelIdx < len(s.indexLevelTails) {
|
||||
level := s.indexLevelTails[levelIdx]
|
||||
if change.CompletedIndexPage != nil {
|
||||
// попередній хвіст індексного рівня перетворився на сторінку,
|
||||
// отже TailRecords - це новий хвіст
|
||||
// або копіюю дані, або алокую більший буфер?
|
||||
copy(level.Buffer, change.TailRecords)
|
||||
level.RecordsCount = len(change.TailRecords) / indexRecordSize
|
||||
} else {
|
||||
// TailRecords - це нові дані, які треба додати
|
||||
pos := level.RecordsCount * indexRecordSize
|
||||
copy(level.Buffer[pos:], change.TailRecords)
|
||||
}
|
||||
s.indexLevelTails[levelIdx] = level
|
||||
} else {
|
||||
var (
|
||||
recordsCount = len(change.TailRecords) / indexRecordSize
|
||||
buffer = make([]byte, atree.IndexPageSize)
|
||||
)
|
||||
copy(buffer, change.TailRecords)
|
||||
s.indexLevelTails = append(s.indexLevelTails, txlog.IndexLevelTail{
|
||||
Buffer: buffer,
|
||||
RecordsCount: recordsCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Перезаписати в buffer timestamps та values. Якщо розмір буферу недостатній -
|
||||
// спочатку створити більший
|
||||
|
||||
var (
|
||||
// CompletedDataPage є полюбому, оскільки це Record із мінімум одною заповненою
|
||||
// data сторінкою. Отже TailTimestamps і TailValues - це нові хвости data рівня.
|
||||
|
||||
requiredSpace = len(rec.TailTimestamps) + len(rec.TailValues)
|
||||
databuf []byte
|
||||
)
|
||||
if requiredSpace < len(s.buf) {
|
||||
databuf = s.buf
|
||||
// fix - update sizes?
|
||||
} else {
|
||||
var buf []byte
|
||||
buf, databuf = allocateBuffers(requiredSpace)
|
||||
copy(databuf, rec.TailValues)
|
||||
pos := len(databuf) - len(rec.TailTimestamps)
|
||||
copy(databuf[pos:], rec.TailTimestamps)
|
||||
// fix - replace and remeber sizes?
|
||||
|
||||
s.buf = buf
|
||||
}
|
||||
|
||||
//rec.TailTimestamps
|
||||
//rec.TailValues
|
||||
}
|
||||
|
||||
func (s *Database) rewritePagesFromLastWALPacket(list []any) error {
|
||||
// var (
|
||||
// indexPages []txlog.PageToWrite
|
||||
// dataPages []txlog.PageToWrite
|
||||
// )
|
||||
// for _, untyped := range list {
|
||||
// switch rec := untyped.(type) {
|
||||
// case txlog.WALRecordAppendMeasures:
|
||||
// metric, ok := s.metrics[rec.MetricID]
|
||||
// if ok {
|
||||
// metric.Timestamps.Append(rec.Timestamp)
|
||||
// metric.Values.Append(rec.Value)
|
||||
|
||||
// if metric.Since == 0 {
|
||||
// metric.Since = rec.Timestamp
|
||||
// metric.SinceValue = rec.Value
|
||||
// }
|
||||
|
||||
// metric.Until = rec.Timestamp
|
||||
// metric.UntilValue = rec.Value
|
||||
// }
|
||||
|
||||
// case txlog.AppendedMeasures:
|
||||
// metric, ok := s.metrics[rec.MetricID]
|
||||
// if ok {
|
||||
// for _, measure := range rec.Measures {
|
||||
// metric.Timestamps.Append(measure.Timestamp)
|
||||
// metric.Values.Append(measure.Value)
|
||||
|
||||
// if metric.Since == 0 {
|
||||
// metric.Since = measure.Timestamp
|
||||
// metric.SinceValue = measure.Value
|
||||
// }
|
||||
|
||||
// metric.Until = measure.Timestamp
|
||||
// metric.UntilValue = measure.Value
|
||||
// }
|
||||
// }
|
||||
|
||||
// // case txlog.AppendedMeasureWithOverflow:
|
||||
// // metric, ok := s.metrics[rec.MetricID]
|
||||
// // if ok {
|
||||
// // metric.ReinitBy(rec.Timestamp, rec.Value)
|
||||
// // if rec.IsRootChanged {
|
||||
// // metric.RootPageNo = rec.RootPageNo
|
||||
// // }
|
||||
// // metric.LastPageNo = rec.DataPageNo
|
||||
// // // delete free pages
|
||||
// // if rec.IsDataPageReused {
|
||||
// // s.freeList.DeleteReservedPages([]uint32{
|
||||
// // rec.DataPageNo,
|
||||
// // })
|
||||
// // }
|
||||
// // if len(rec.ReusedIndexPages) > 0 {
|
||||
// // s.freeList.DeleteReservedPages(rec.ReusedIndexPages)
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// case txlog.DeletedMeasures:
|
||||
// metric, ok := s.metrics[rec.MetricID]
|
||||
// if ok {
|
||||
// metric.DeleteMeasures()
|
||||
// if len(rec.FreePageNumbers) > 0 {
|
||||
// s.freeList.AddPages(rec.FreePageNumbers)
|
||||
// }
|
||||
// if !ok {
|
||||
// qb.Abort(qb.MetricNotFoundDuringReplay, nil)
|
||||
// }
|
||||
// //metric.ReplayAppendedMeasures(rec)
|
||||
|
||||
// default:
|
||||
// qb.Abort(qb.UnknownTxLogRecordTypeBug,
|
||||
// fmt.Errorf("bug: unknown record type %T in TransactionLog", rec))
|
||||
// }
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
func pagesToWriteFromWALRecords(metrics map[uint32]*ReplayMetric, records []txlog.WALRecordAppendMeasures) (indexPages []txlog.PageToWrite, dataPages []txlog.PageToWrite) {
|
||||
for _, rec := range records {
|
||||
metric, ok := metrics[rec.MetricID]
|
||||
if !ok {
|
||||
qb.Abort(qb.MetricNotFoundDuringReplay, nil)
|
||||
}
|
||||
p := rec.CompletedDataPage
|
||||
// буфер має бути розміру сторінки
|
||||
if len(metric.buf) != atree.DataPageSize {
|
||||
metric.buf, metric.databuf = growBuffers(growBuffersIn{
|
||||
databuf: metric.databuf,
|
||||
tSize: metric.tSize,
|
||||
vSize: metric.vSize,
|
||||
requiredSpace: atree.DataPageSize,
|
||||
})
|
||||
}
|
||||
// дописую дані по зміщенню
|
||||
pos := len(metric.databuf) - p.TimestampsOffset - len(p.Timestamps)
|
||||
copy(metric.databuf[pos:], p.Timestamps)
|
||||
copy(metric.databuf[p.ValuesOffset:], p.Values)
|
||||
//
|
||||
metric.tSize = p.TimestampsOffset + len(p.Timestamps)
|
||||
metric.tSize = p.ValuesOffset + len(p.Values)
|
||||
|
||||
// fix - заполнить страницу
|
||||
// fix - check checksum
|
||||
|
||||
dataPages = append(dataPages, txlog.PageToWrite{
|
||||
PageNo: p.PageNo,
|
||||
Content: metric.buf,
|
||||
})
|
||||
|
||||
for _, x := range rec.DataPages {
|
||||
dataPages = append(dataPages, txlog.PageToWrite{
|
||||
PageNo: x.PageNo,
|
||||
Content: x.Content,
|
||||
})
|
||||
}
|
||||
|
||||
for levelIdx, changedLevel := range rec.ChangedIndexLevels {
|
||||
p := changedLevel.CompletedIndexPage
|
||||
if p != nil {
|
||||
level := metric.indexLevelTails[levelIdx]
|
||||
copy(level.Buffer[level.RecordsCount*indexRecordSize:], p.Records)
|
||||
|
||||
// fix - заполнить страницу
|
||||
// fix - check checksum
|
||||
|
||||
indexPages = append(dataPages, txlog.PageToWrite{
|
||||
PageNo: p.PageNo,
|
||||
Content: level.Buffer,
|
||||
})
|
||||
}
|
||||
|
||||
for _, x := range changedLevel.IndexPages {
|
||||
indexPages = append(dataPages, txlog.PageToWrite{
|
||||
PageNo: x.PageNo,
|
||||
Content: x.Content,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FIX
|
||||
// УВАГА!
|
||||
// Якщо буфер metric.buffer < розміру сторінки - для timestamps доступний весь буфер.
|
||||
// Якщо буфер досяг розміру сторінки - в timestamps я передаю buffer[:DataPagePayloadSize]
|
||||
|
||||
var dataBufferSizes = []int{
|
||||
1024,
|
||||
2048,
|
||||
4096,
|
||||
8192,
|
||||
}
|
||||
|
||||
func calculateDataBufferSize(payloadSize int) int {
|
||||
for _, bufferSize := range dataBufferSizes {
|
||||
if payloadSize <= bufferSize {
|
||||
return bufferSize
|
||||
}
|
||||
}
|
||||
return atree.DataPageSize
|
||||
}
|
||||
|
||||
func allocateBuffers(requiredSpace int) (buf, databuf []byte) {
|
||||
bufferSize := calculateDataBufferSize(requiredSpace)
|
||||
buf = make([]byte, bufferSize)
|
||||
if bufferSize == atree.DataPageSize {
|
||||
databuf = buf[:atree.DataPagePayloadSize]
|
||||
} else {
|
||||
databuf = buf
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// src - databuf
|
||||
type growBuffersIn struct {
|
||||
databuf []byte
|
||||
tSize int
|
||||
vSize int
|
||||
requiredSpace int
|
||||
}
|
||||
|
||||
func growBuffers(in growBuffersIn) (buf, databuf []byte) {
|
||||
buf, databuf = allocateBuffers(in.requiredSpace)
|
||||
copy(databuf[len(databuf)-in.tSize:], in.databuf[len(in.databuf)-in.tSize:])
|
||||
copy(databuf, in.databuf[:in.vSize])
|
||||
return
|
||||
}
|
||||
|
||||
//func (s *Database) verifySnapshot(fileName string) (_ bool, err error) {
|
||||
// file, err := os.Open(fileName)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// defer file.Close()
|
||||
|
||||
// stat, err := file.Stat()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if stat.Size() <= 4 {
|
||||
// return false, nil
|
||||
// }
|
||||
|
||||
// var (
|
||||
// payloadSize = stat.Size() - 4
|
||||
// hash = crc32.NewIEEE()
|
||||
// )
|
||||
|
||||
// _, err = io.CopyN(hash, file, payloadSize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// calculatedCRC := hash.Sum32()
|
||||
|
||||
// storedCRC, err := bin.ReadUint32(file)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// if storedCRC != calculatedCRC {
|
||||
// return false, fmt.Errorf("strored CRC %d not equal calculated CRC %d",
|
||||
// storedCRC, calculatedCRC)
|
||||
// }
|
||||
// return true, nil
|
||||
// }
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
bin "gordenko.dev/dima/bin/little"
|
||||
"gordenko.dev/dima/qb"
|
||||
"gordenko.dev/dima/qb/atree"
|
||||
"gordenko.dev/dima/qb/txlog"
|
||||
@@ -10,26 +13,25 @@ import (
|
||||
|
||||
const minBufferSize = 1024
|
||||
|
||||
type IndexLevelTail struct {
|
||||
Payload []byte
|
||||
RecordsCount int
|
||||
}
|
||||
var (
|
||||
indexRecordSize = 8
|
||||
)
|
||||
|
||||
type _metric struct {
|
||||
MetricType qb.MetricType
|
||||
FracDigits byte
|
||||
LastPageNo uint32
|
||||
SinceValue float64
|
||||
Since uint32
|
||||
UntilValue float64
|
||||
Until uint32
|
||||
Buffer []byte
|
||||
Timestamps qb.TimestampCompressor
|
||||
Values qb.ValueCompressor
|
||||
metricType qb.MetricType
|
||||
fracDigits byte
|
||||
lastPageNo uint32
|
||||
//SinceValue float64
|
||||
//Since uint32
|
||||
lastValue float64
|
||||
//Until uint32
|
||||
buffer []byte
|
||||
timestamps qb.TimestampCompressor
|
||||
values qb.ValueCompressor
|
||||
XLock bool
|
||||
RLocks int
|
||||
WaitQueue []any
|
||||
IndexLevelTails []txlog.IndexLevelTail // root - last element
|
||||
indexLevelTails []txlog.IndexLevelTail // root - last element
|
||||
}
|
||||
|
||||
//IndexLevels [][]IndexRec // root - last element
|
||||
@@ -73,96 +75,105 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
|
||||
// DataPages []*atree.DataPage // fix - prevPageNo for the 1st data page
|
||||
// }
|
||||
|
||||
timestamps = s.Timestamps
|
||||
values = s.Values
|
||||
timestamps = s.timestamps
|
||||
values = s.values
|
||||
|
||||
indexLevels []txlog.IndexLevelTail
|
||||
dataPages []txlog.DataPayload
|
||||
dataPages []txlog.DataPayload
|
||||
|
||||
//written int
|
||||
//resultCode byte
|
||||
)
|
||||
|
||||
s.Values.CaptureState()
|
||||
s.Timestamps.CaptureState()
|
||||
s.values.CaptureState()
|
||||
s.timestamps.CaptureState()
|
||||
|
||||
for idx, measure := range req.Measures {
|
||||
if s.Since == 0 {
|
||||
s.Since = measure.Timestamp
|
||||
} else {
|
||||
// FIX - у випадку помилки треба в транзакції зберегти що помилка, але також зафіксувати скільки елементів збережено.
|
||||
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в txlog
|
||||
if measure.Timestamp <= s.Until {
|
||||
if idx == 0 {
|
||||
s.Values.ForgetCapturedState()
|
||||
s.Timestamps.ForgetCapturedState()
|
||||
// if s.Since == 0 {
|
||||
// s.Since = measure.Timestamp
|
||||
// } else {
|
||||
// FIX - у випадку помилки треба в транзакції зберегти що помилка, але також зафіксувати скільки елементів збережено.
|
||||
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в txlog
|
||||
if measure.Timestamp <= s.timestamps.LastTimestamp() {
|
||||
if idx == 0 {
|
||||
s.values.ForgetCapturedState()
|
||||
s.timestamps.ForgetCapturedState()
|
||||
|
||||
req.ResultCh <- tryAppendMeasuresResult{
|
||||
ResultCode: ExpiredMeasure,
|
||||
}
|
||||
return
|
||||
req.ResultCh <- tryAppendMeasuresResult{
|
||||
ResultCode: ExpiredMeasure,
|
||||
}
|
||||
|
||||
//resultCode = ExpiredMeasure
|
||||
//written = idx
|
||||
break
|
||||
return
|
||||
}
|
||||
|
||||
if s.MetricType == qb.Cumulative && measure.Value < s.UntilValue {
|
||||
if idx == 0 {
|
||||
s.Values.ForgetCapturedState()
|
||||
s.Timestamps.ForgetCapturedState()
|
||||
|
||||
req.ResultCh <- tryAppendMeasuresResult{
|
||||
ResultCode: NonMonotonicValue,
|
||||
}
|
||||
return
|
||||
}
|
||||
//resultCode = NonMonotonicValue
|
||||
//written = idx
|
||||
break
|
||||
}
|
||||
//resultCode = ExpiredMeasure
|
||||
//written = idx
|
||||
break
|
||||
}
|
||||
|
||||
if s.metricType == qb.Cumulative && measure.Value < s.lastValue {
|
||||
if idx == 0 {
|
||||
s.values.ForgetCapturedState()
|
||||
s.timestamps.ForgetCapturedState()
|
||||
|
||||
req.ResultCh <- tryAppendMeasuresResult{
|
||||
ResultCode: NonMonotonicValue,
|
||||
}
|
||||
return
|
||||
}
|
||||
//resultCode = NonMonotonicValue
|
||||
//written = idx
|
||||
break
|
||||
}
|
||||
//}
|
||||
|
||||
// fix - 1 + 8 bytes
|
||||
|
||||
timestampCompressionWay, timestampRequiredSpace := timestamps.Evaluate(measure.Timestamp)
|
||||
valueCompressionWay, valueRequiredSpace := values.Evaluate(measure.Value)
|
||||
|
||||
totalSpace := timestampRequiredSpace + valueRequiredSpace
|
||||
totalRequiredSpace := timestampRequiredSpace + valueRequiredSpace
|
||||
|
||||
if totalSpace <= atree.DataPagePayloadSize {
|
||||
if totalRequiredSpace <= len(s.buffer) {
|
||||
// накопичую
|
||||
timestamps.Compress(timestampCompressionWay, measure.Timestamp)
|
||||
values.Compress(valueCompressionWay, measure.Value)
|
||||
} else if len(s.buffer) < atree.DataPagePayloadSize {
|
||||
// allocate bigger buffer
|
||||
buffer := make([]byte, len(s.buffer)*2)
|
||||
// copy timestamps
|
||||
// copy values
|
||||
// replace buffer in timestamps and values
|
||||
|
||||
s.buffer = buffer
|
||||
|
||||
timestamps.Compress(timestampCompressionWay, measure.Timestamp)
|
||||
values.Compress(valueCompressionWay, measure.Value)
|
||||
} else {
|
||||
// сторінка заповнена
|
||||
buffer := make([]byte, minBufferSize)
|
||||
since := s.timestamps.ReplaceSinceWithUntil()
|
||||
|
||||
timestampsSize := timestamps.Rotate(buffer)
|
||||
valuesSize := values.Rotate(buffer)
|
||||
// prevPageNo - виставляю в txlog, коли забираю номер сторінки із freeList або генерую новий
|
||||
dataPages = append(dataPages, txlog.DataPayload{
|
||||
Since: s.Since,
|
||||
Content: s.Buffer,
|
||||
TimestampsSize: timestampsSize,
|
||||
ValuesSize: valuesSize,
|
||||
Since: since,
|
||||
Content: s.buffer,
|
||||
TimestampsSize: timestamps.Size(),
|
||||
ValuesSize: values.Size(),
|
||||
})
|
||||
|
||||
buffer := make([]byte, minBufferSize)
|
||||
|
||||
timestamps.Rotate(buffer)
|
||||
values.Rotate(buffer)
|
||||
|
||||
// renew
|
||||
s.Buffer = buffer
|
||||
s.buffer = buffer
|
||||
|
||||
timestampCompressionWay, _ = timestamps.Evaluate(measure.Timestamp)
|
||||
valueCompressionWay, _ = values.Evaluate(measure.Value)
|
||||
|
||||
timestamps.Compress(timestampCompressionWay, measure.Timestamp)
|
||||
values.Compress(valueCompressionWay, measure.Value)
|
||||
|
||||
s.Since = measure.Timestamp
|
||||
}
|
||||
|
||||
s.Until = measure.Timestamp
|
||||
s.UntilValue = measure.Value
|
||||
//
|
||||
s.lastValue = measure.Value
|
||||
}
|
||||
|
||||
// виділити змінені байти.
|
||||
@@ -170,20 +181,13 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
|
||||
|
||||
if len(dataPages) > 0 {
|
||||
// пишу в txlog довгим шляхом через redo файл і запис в data файл
|
||||
for _, tail := range s.IndexLevelTails {
|
||||
indexLevels = append(indexLevels, txlog.IndexLevelTail{
|
||||
Records: tail.Records,
|
||||
RecordsCount: tail.RecordsCount,
|
||||
})
|
||||
}
|
||||
|
||||
sendToStorage(txlog.AppendedMeasures{
|
||||
MetricID: req.MetricID,
|
||||
LastPageNo: s.LastPageNo,
|
||||
LastPageNo: s.lastPageNo,
|
||||
TimestampsOffset: timestamps.Offset(), // state.Pos() з якої позиції дописувати дані на сторінку 0 (при відновленні)
|
||||
ValuesOffset: values.Offset(), // state.Pos()
|
||||
//Payload: s.payload, // fix - timestamps + values ? or timestamps and values (for WAL)
|
||||
IndexLevelTails: indexLevels,
|
||||
IndexLevelTails: s.indexLevelTails,
|
||||
DataPages: dataPages,
|
||||
Timestamps: nil,
|
||||
Values: nil,
|
||||
@@ -199,19 +203,15 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
|
||||
|
||||
func (s *_metric) FinAppendMeasures(rec txlog.AppendMeasuresSummary) {
|
||||
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
|
||||
s.Values.ForgetCapturedState()
|
||||
s.Timestamps.ForgetCapturedState()
|
||||
// fix write index levels
|
||||
// update prev pageNo
|
||||
// if len(rec.DataPages) > 0 {
|
||||
// s.LastPageNo = rec.DataPages[len(rec.DataPages)-1].PageNo
|
||||
// }
|
||||
s.values.ForgetCapturedState()
|
||||
s.timestamps.ForgetCapturedState()
|
||||
|
||||
if rec.LastPageNo > 0 {
|
||||
s.LastPageNo = rec.LastPageNo
|
||||
s.lastPageNo = rec.LastPageNo
|
||||
}
|
||||
// В txlog я передав повний індекс. У нього додали елементи (можливо нові рівні).
|
||||
// Тому проста заміна
|
||||
s.IndexLevelTails = rec.Index
|
||||
s.indexLevelTails = rec.Index
|
||||
}
|
||||
|
||||
// READ
|
||||
@@ -288,38 +288,131 @@ func (s *_metric) StartRangeScan(req tryRangeScanReq) {
|
||||
}
|
||||
|
||||
func (s *_metric) StartFullScan(req tryFullScanReq) {
|
||||
if s.Since == 0 {
|
||||
req.ResultCh <- fullScanResult{
|
||||
ResultCode: QueryDone,
|
||||
}
|
||||
// if s.Since == 0 {
|
||||
// req.ResultCh <- fullScanResult{
|
||||
// ResultCode: QueryDone,
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// timestampDecompressor := s.Timestamps.CreateDecompressor()
|
||||
// valueDecompressor := s.Values.CreateDecompressor()
|
||||
|
||||
// for {
|
||||
// timestamp, done := timestampDecompressor.NextValue()
|
||||
// if done {
|
||||
// break
|
||||
// }
|
||||
// value, done := valueDecompressor.NextValue()
|
||||
// if done {
|
||||
// qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
|
||||
// }
|
||||
// req.ResponseWriter.FeedNoSend(timestamp, value)
|
||||
// }
|
||||
|
||||
// if s.LastPageNo > 0 {
|
||||
// req.ResultCh <- fullScanResult{
|
||||
// ResultCode: UntilFound,
|
||||
// LastPageNo: s.LastPageNo,
|
||||
// FracDigits: s.FracDigits,
|
||||
// }
|
||||
// s.RLocks++
|
||||
// } else {
|
||||
// req.ResultCh <- fullScanResult{
|
||||
// ResultCode: QueryDone,
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// індекси
|
||||
// Metric encode format:
|
||||
// metricID - 4b
|
||||
// metricType - 1b
|
||||
// fracDigits - 1b
|
||||
// lastPageNo - 4b
|
||||
// until - 4b
|
||||
// timestamps size - 2b
|
||||
// timestams payload - Nb
|
||||
// values size - 2b
|
||||
// values payload - Nb
|
||||
// index levels count - varsize
|
||||
// [
|
||||
// records qty - varsize
|
||||
// records - Nb
|
||||
// ]
|
||||
|
||||
func (s *_metric) WriteTo(w io.Writer) (err error) {
|
||||
_, err = w.Write([]byte{
|
||||
byte(s.metricType),
|
||||
s.fracDigits,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
timestampDecompressor := s.Timestamps.CreateDecompressor()
|
||||
valueDecompressor := s.Values.CreateDecompressor(s.FracDigits)
|
||||
|
||||
for {
|
||||
timestamp, done := timestampDecompressor.NextValue()
|
||||
if done {
|
||||
break
|
||||
}
|
||||
value, done := valueDecompressor.NextValue()
|
||||
if done {
|
||||
qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
|
||||
}
|
||||
req.ResponseWriter.FeedNoSend(timestamp, value)
|
||||
err = bin.WriteUint32(w, s.lastPageNo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.LastPageNo > 0 {
|
||||
req.ResultCh <- fullScanResult{
|
||||
ResultCode: UntilFound,
|
||||
LastPageNo: s.LastPageNo,
|
||||
FracDigits: s.FracDigits,
|
||||
}
|
||||
s.RLocks++
|
||||
} else {
|
||||
req.ResultCh <- fullScanResult{
|
||||
ResultCode: QueryDone,
|
||||
}
|
||||
// err = bin.WriteUint32(w, s.Since) // fix unixtime in timestamps
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// err = bin.WriteFloat64(w, s.SinceValue)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
err = bin.WriteUint32(w, s.timestamps.LastTimestamp())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// err = bin.WriteFloat64(w, s.UntilValue)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// FIX - write sizes, then payloads
|
||||
// copy timestamps payload
|
||||
err = s.timestamps.WritePayloadTo(w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// copy values payload
|
||||
err = s.values.WritePayloadTo(w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// indexes
|
||||
_, err = bin.WriteVarSize(w, len(s.indexLevelTails))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, level := range s.indexLevelTails {
|
||||
_, err = bin.WriteVarSize(w, level.RecordsCount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = w.Write(level.Buffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// var (
|
||||
// values qb.ValueCompressor
|
||||
// )
|
||||
// if s.metricType == qb.Cumulative {
|
||||
// values = enc.NewCumulativeDeltaCompressor(s.buffer, s.fracDigits)
|
||||
// } else {
|
||||
// values = enc.NewInstantDeltaCompressor(s.buffer, s.fracDigits)
|
||||
// }
|
||||
// values.RestoreState(valuesSize)
|
||||
// s.timestamps = enc.NewTimeDeltaCompressor(s.buffer)
|
||||
// s.timestamps.RestoreState(timestampsSize, until)
|
||||
// s.values = values
|
||||
// s.lastValue = s.values.LastValue()
|
||||
|
||||
// since - 4b
|
||||
// sinceValue - 8b -
|
||||
// untilValue - 8b
|
||||
|
||||
@@ -5,8 +5,7 @@ import (
|
||||
|
||||
qb "gordenko.dev/dima/qb"
|
||||
"gordenko.dev/dima/qb/atree"
|
||||
"gordenko.dev/dima/qb/chunkenc"
|
||||
"gordenko.dev/dima/qb/conbuf"
|
||||
"gordenko.dev/dima/qb/enc"
|
||||
"gordenko.dev/dima/qb/proto"
|
||||
"gordenko.dev/dima/qb/transform"
|
||||
"gordenko.dev/dima/qb/txlog"
|
||||
@@ -203,8 +202,8 @@ func (s *Database) tryGetMetric(req tryGetMetricReq) {
|
||||
if ok {
|
||||
req.ResultCh <- Metric{
|
||||
ResultCode: Succeed,
|
||||
MetricType: metric.MetricType,
|
||||
FracDigits: metric.FracDigits,
|
||||
MetricType: metric.metricType,
|
||||
FracDigits: metric.fracDigits,
|
||||
}
|
||||
} else {
|
||||
req.ResultCh <- Metric{
|
||||
@@ -343,7 +342,7 @@ func (s *Database) tryRangeScan(req tryRangeScanReq) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if metric.MetricType != req.MetricType {
|
||||
if metric.metricType != req.MetricType {
|
||||
req.ResultCh <- rangeScanResult{
|
||||
ResultCode: WrongMetricType,
|
||||
}
|
||||
@@ -373,7 +372,7 @@ func (s *Database) tryFullScan(req tryFullScanReq) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if metric.MetricType != req.MetricType {
|
||||
if metric.metricType != req.MetricType {
|
||||
req.ResultCh <- fullScanResult{
|
||||
ResultCode: WrongMetricType,
|
||||
}
|
||||
@@ -399,8 +398,8 @@ func (s *Database) tryListCurrentValues(req tryListCurrentValuesReq) {
|
||||
if ok {
|
||||
req.ResponseWriter.BufferValue(transform.CurrentValue{
|
||||
MetricID: metricID,
|
||||
Timestamp: metric.Until,
|
||||
Value: metric.UntilValue,
|
||||
Timestamp: metric.timestamps.LastTimestamp(),
|
||||
Value: metric.lastValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -413,7 +412,7 @@ func (s *Database) applyChanges(req txlog.Changes) {
|
||||
for _, untyped := range req.Records {
|
||||
switch rec := untyped.(type) {
|
||||
case txlog.AddedMetric:
|
||||
s.addMetric(rec)
|
||||
s.applyAddMetric(rec)
|
||||
|
||||
case txlog.DeletedMetric:
|
||||
s.deleteMetric(rec)
|
||||
@@ -432,37 +431,27 @@ func (s *Database) applyChanges(req txlog.Changes) {
|
||||
}
|
||||
}
|
||||
|
||||
if req.MetricsCh != nil {
|
||||
waitCh := make(chan struct{})
|
||||
req.MetricsCh <- txlog.MetricsState{
|
||||
Metrics: s.createMetricsState(),
|
||||
WaitCh: waitCh,
|
||||
if req.SnapshotNumberCh != nil {
|
||||
s.snapshotNumber++
|
||||
err := writeSnapshot(writeSnapshotIn{
|
||||
SnapshotNumber: s.snapshotNumber,
|
||||
Dir: s.dir,
|
||||
WriteBufferSize: 1 * 1024 * 1024, // 1mb
|
||||
Metrics: s.metrics,
|
||||
FrozenIndexPagesCount: req.FrozenIndexPagesCount,
|
||||
IndexPageNumbers: req.IndexPageNumbers,
|
||||
FrozenDataPagesCount: req.FrozenDataPagesCount,
|
||||
DataPageNumbers: req.DataPageNumbers,
|
||||
})
|
||||
if err != nil {
|
||||
qb.Abort(qb.WriteSnapshotFailed, err)
|
||||
}
|
||||
// чекаю поки txlog запише снапшот, а отже можна змінювати буфери
|
||||
<-waitCh
|
||||
|
||||
req.SnapshotNumberCh <- s.snapshotNumber
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Database) createMetricsState() (metrics []txlog.Metric) {
|
||||
for metricID, metric := range s.metrics {
|
||||
x := txlog.Metric{
|
||||
MetricID: metricID,
|
||||
MetricType: metric.MetricType,
|
||||
FracDigits: metric.FracDigits,
|
||||
LastPageNo: metric.LastPageNo,
|
||||
Since: metric.Since,
|
||||
SinceValue: metric.SinceValue,
|
||||
Until: metric.Until, // ?
|
||||
UntilValue: metric.UntilValue, // ?
|
||||
}
|
||||
x.Timestamps, x.TimestampsSize = metric.Timestamps.Snapshot()
|
||||
x.Values, x.ValuesSize = metric.Values.Snapshot()
|
||||
metrics = append(metrics, x)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Database) addMetric(rec txlog.AddedMetric) {
|
||||
func (s *Database) applyAddMetric(rec txlog.AddedMetric) {
|
||||
// fix lock
|
||||
_, ok := s.metrics[rec.MetricID]
|
||||
if ok {
|
||||
@@ -477,29 +466,29 @@ func (s *Database) addMetric(rec txlog.AddedMetric) {
|
||||
// rec.MetricID))
|
||||
// }
|
||||
|
||||
// lockEntry.XLock = false
|
||||
// delete(s.metricLockEntries, rec.MetricID)
|
||||
}
|
||||
|
||||
func (s *Database) addMetric(rec txlog.AddedMetric) {
|
||||
var (
|
||||
values qb.ValueCompressor
|
||||
timestampsBuf = conbuf.New(nil)
|
||||
valuesBuf = conbuf.New(nil)
|
||||
values qb.ValueCompressor
|
||||
buffer = make([]byte, minBufferSize)
|
||||
)
|
||||
|
||||
if rec.MetricType == qb.Cumulative {
|
||||
values = chunkenc.NewCumulativeDeltaCompressor(
|
||||
valuesBuf, 0, byte(rec.FracDigits))
|
||||
values = enc.NewCumulativeDeltaCompressor(buffer, byte(rec.FracDigits))
|
||||
} else {
|
||||
values = chunkenc.NewInstantDeltaCompressor(
|
||||
valuesBuf, 0, byte(rec.FracDigits))
|
||||
values = enc.NewInstantDeltaCompressor(buffer, byte(rec.FracDigits))
|
||||
}
|
||||
|
||||
s.metrics[rec.MetricID] = &_metric{
|
||||
MetricType: rec.MetricType,
|
||||
FracDigits: byte(rec.FracDigits),
|
||||
Timestamps: chunkenc.NewTimeDeltaCompressor(timestampsBuf, 0),
|
||||
Values: values,
|
||||
metricType: rec.MetricType,
|
||||
fracDigits: byte(rec.FracDigits),
|
||||
buffer: buffer,
|
||||
timestamps: enc.NewTimeDeltaCompressor(buffer),
|
||||
values: values,
|
||||
}
|
||||
|
||||
lockEntry.XLock = false
|
||||
delete(s.metricLockEntries, rec.MetricID)
|
||||
}
|
||||
|
||||
func (s *Database) deleteMetric(rec txlog.DeletedMetric) {
|
||||
|
||||
269
database/snapshot.go
Normal file
269
database/snapshot.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
bin "gordenko.dev/dima/bin/little"
|
||||
"gordenko.dev/dima/qb/util"
|
||||
)
|
||||
|
||||
/*
|
||||
Формат:
|
||||
metricsQty - varsize
|
||||
[metric]*
|
||||
де metric - це:
|
||||
|
||||
index free list frozen pages - varsize
|
||||
indexFreeList size - varsize
|
||||
indexFreeList - Nb
|
||||
|
||||
data free list frozen pages - varsize
|
||||
dataFreeList size - varsize
|
||||
dataFreeList - Nb
|
||||
|
||||
CRC32 - 4b
|
||||
*/
|
||||
|
||||
const readBufferSize = 8 * 1024 * 1024 // 8mb
|
||||
|
||||
type writeSnapshotIn struct {
|
||||
SnapshotNumber int
|
||||
Dir string
|
||||
WriteBufferSize int
|
||||
Metrics map[uint32]*_metric
|
||||
FrozenIndexPagesCount int
|
||||
IndexPageNumbers []uint32
|
||||
FrozenDataPagesCount int
|
||||
DataPageNumbers []uint32
|
||||
}
|
||||
|
||||
func writeSnapshot(in writeSnapshotIn) (err error) {
|
||||
var (
|
||||
fileName = filepath.Join(in.Dir, fmt.Sprintf("%d.snapshot", in.SnapshotNumber))
|
||||
hasher = util.NewHasher()
|
||||
)
|
||||
|
||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0666) //0770)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dst := io.MultiWriter(bufio.NewWriterSize(file, in.WriteBufferSize), hasher)
|
||||
|
||||
_, err = bin.WriteVarSize(dst, len(in.Metrics))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for metricID, metric := range in.Metrics {
|
||||
err = bin.WriteUint32(dst, metricID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = metric.WriteTo(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// free index pages
|
||||
_, err = bin.WriteVarSize(dst, in.FrozenIndexPagesCount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = bin.WriteVarSize(dst, len(in.IndexPageNumbers))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, pageNo := range in.IndexPageNumbers {
|
||||
err = bin.WriteUint32(dst, pageNo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// free data pages
|
||||
_, err = bin.WriteVarSize(dst, in.FrozenDataPagesCount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = bin.WriteVarSize(dst, len(in.DataPageNumbers))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, pageNo := range in.DataPageNumbers {
|
||||
err = bin.WriteUint32(dst, pageNo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// CRC32
|
||||
bin.WriteUint32(file, hasher.Sum32())
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// prevLogNumber := logNumber - 1
|
||||
// prevChanges := filepath.Join(s.dir, fmt.Sprintf("%d.changes", prevLogNumber))
|
||||
// prevSnapshot := filepath.Join(s.dir, fmt.Sprintf("%d.snapshot", prevLogNumber))
|
||||
|
||||
// isExist, err := isFileExist(prevChanges)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if isExist {
|
||||
// err = os.Remove(prevChanges)
|
||||
// if err != nil {
|
||||
// qb.Abort(qb.DeletePrevChangesFileFailed, err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// isExist, err = isFileExist(prevSnapshot)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if isExist {
|
||||
// err = os.Remove(prevSnapshot)
|
||||
// if err != nil {
|
||||
// qb.Abort(qb.DeletePrevSnapshotFileFailed, err)
|
||||
// }
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
type readSnapshotOut struct {
|
||||
Metrics map[uint32]*ReplayMetric
|
||||
FrozenIndexPagesCount int
|
||||
IndexPageNumbers []uint32
|
||||
FrozenDataPagesCount int
|
||||
DataPageNumbers []uint32
|
||||
}
|
||||
|
||||
func readSnapshot(fileName string) (out readSnapshotOut, err error) {
|
||||
var (
|
||||
metrics = make(map[uint32]*ReplayMetric)
|
||||
frozenIndexPagesCount int
|
||||
indexPageNumbers []uint32
|
||||
frozenDataPagesCount int
|
||||
dataPageNumbers []uint32
|
||||
hasher = util.NewHasher()
|
||||
)
|
||||
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fileSize := stat.Size()
|
||||
|
||||
if fileSize == 0 {
|
||||
return readSnapshotOut{
|
||||
Metrics: metrics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if fileSize < 4 {
|
||||
err = fmt.Errorf("%s is corrupted", fileName)
|
||||
return
|
||||
}
|
||||
payloadSize := fileSize - 4
|
||||
|
||||
// 2. Створюємо великий буфер для NVMe (4 MB)
|
||||
bufferedReader := bufio.NewReaderSize(file, readBufferSize)
|
||||
|
||||
// 3. Обмежуємо читання лише розміром Payload
|
||||
limitReader := io.LimitReader(bufferedReader, payloadSize)
|
||||
|
||||
// 4. Створюємо хеш та обгортку TeeReader, яка рахує CRC32 на льоту
|
||||
src := io.TeeReader(limitReader, hasher)
|
||||
|
||||
// читаю payload
|
||||
metricsQty, err := bin.ReadVarSize(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for range metricsQty {
|
||||
var (
|
||||
metricID uint32
|
||||
metric = &ReplayMetric{
|
||||
buffer: make([]byte, minBufferSize),
|
||||
}
|
||||
)
|
||||
metricID, err = bin.ReadUint32(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = metric.ReadFrom(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
metrics[metricID] = metric
|
||||
}
|
||||
// index pages
|
||||
frozenIndexPagesCount, err = bin.ReadVarSize(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
indexPageNumbersCount, err := bin.ReadVarSize(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for range indexPageNumbersCount {
|
||||
var pageNo uint32
|
||||
pageNo, err = bin.ReadUint32(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
indexPageNumbers = append(indexPageNumbers, pageNo)
|
||||
}
|
||||
// data pages
|
||||
frozenDataPagesCount, err = bin.ReadVarSize(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dataPageNumbersCount, err := bin.ReadVarSize(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for range dataPageNumbersCount {
|
||||
var pageNo uint32
|
||||
pageNo, err = bin.ReadUint32(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dataPageNumbers = append(dataPageNumbers, pageNo)
|
||||
}
|
||||
|
||||
// verify CRC32
|
||||
expectedCRC, err := bin.ReadUint32(bufferedReader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
calculatedCRC := hasher.Sum32()
|
||||
|
||||
if expectedCRC != calculatedCRC {
|
||||
err = fmt.Errorf("%s is corrupted. Calculated CRC %d not equal expected CRC %d",
|
||||
fileName, calculatedCRC, expectedCRC)
|
||||
return
|
||||
}
|
||||
|
||||
return readSnapshotOut{
|
||||
Metrics: metrics,
|
||||
FrozenIndexPagesCount: frozenIndexPagesCount,
|
||||
IndexPageNumbers: indexPageNumbers,
|
||||
FrozenDataPagesCount: frozenDataPagesCount,
|
||||
DataPageNumbers: dataPageNumbers,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user