This commit is contained in:
2026-06-10 06:18:45 +03:00
parent 4089ca40c9
commit 9ec86282ad
47 changed files with 3493 additions and 4502 deletions

View File

@@ -6,13 +6,13 @@ import (
"io"
"net"
qb "gordenko.dev/dima/qb"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/bufreader"
"gordenko.dev/dima/qb/proto"
"gordenko.dev/dima/qb/storage"
"gordenko.dev/dima/qb/transform"
"gordenko.dev/dima/qb/txlog"
)
var (
@@ -213,7 +213,7 @@ func (s *Database) AddMetric(req proto.AddMetricReq) uint16 {
switch resultCode {
case Succeed:
s.txlog.Append(txlog.AddedMetric{
s.storage.Append(storage.MetricAddRecord{
MetricID: req.MetricID,
MetricType: req.MetricType,
FracDigits: req.FracDigits,
@@ -286,20 +286,20 @@ func (s *Database) DeleteMetric(req proto.DeleteMetricReq) uint16 {
switch result.ResultCode {
case Succeed:
var (
freePageNumbers []uint32
)
if result.RootPageNo > 0 {
var err error
freePageNumbers, err = s.atree.GetAllPages(result.RootPageNo)
if err != nil {
qb.Abort(qb.FailedAtreeRequest, err)
}
}
s.txlog.Append(txlog.DeletedMetric{
MetricID: req.MetricID,
FreePageNumbers: freePageNumbers,
})
// var (
// //freePageNumbers []uint32
// )
// if result.RootPageNo > 0 {
// var err error
// freePageNumbers, err = s.atree.GetAllPages(result.RootPageNo)
// if err != nil {
// qb.Abort(qb.FailedAtreeRequest, err)
// }
// }
// s.storage.Append(storage.DeletedMetric{
// MetricID: req.MetricID,
// //FreePageNumbers: freePageNumbers, FIX
// })
//<-waitCh
case NoMetric:
@@ -343,7 +343,7 @@ type FilledPage struct {
// switch result.ResultCode {
// case CanAppend:
// waitCh := s.txlog.Append(txlog.AppendedMeasure{
// waitCh := s.storage.Append(storage.AppendedMeasure{
// MetricID: req.MetricID,
// Timestamp: req.Timestamp,
// Value: req.Value,
@@ -390,9 +390,9 @@ type FilledPage struct {
// // }
// // _ = report
// //waitCh := s.txlog.WriteAppended(path, report)
// //waitCh := s.storage.WriteAppended(path, report)
// waitCh := s.txlog.Append(txlog.AppendedPagesReq{
// waitCh := s.storage.Append(storage.AppendedPagesReq{
// Legs: path.Legs,
// MetricID: req.MetricID,
// Timestamp: req.Timestamp,
@@ -459,8 +459,8 @@ func (s *Database) AppendMeasures(req proto.AppendMeasuresReq) uint16 {
// _ = report
// if len(toAppendMeasures) > 0 {
// waitCh := s.txlog.WriteAppendMeasures(
// txlog.AppendedMeasures{
// waitCh := s.storage.WriteAppendMeasures(
// storage.AppendedMeasures{
// MetricID: req.MetricID,
// Measures: toAppendMeasures,
// },
@@ -500,7 +500,7 @@ func (s *Database) DeleteMeasures(req proto.DeleteMeasuresReq) uint16 {
case DeleteFromAtreeNotNeeded:
// регистрирую удаление в TransactionLog
s.txlog.Append(txlog.DeletedMeasures{
s.storage.Append(storage.DeletedMeasures{
MetricID: req.MetricID,
})
//<-waitCh
@@ -512,7 +512,7 @@ func (s *Database) DeleteMeasures(req proto.DeleteMeasuresReq) uint16 {
qb.Abort(qb.FailedAtreeRequest, err)
}
// регистрирую удаление в TransactionLog
s.txlog.Append(txlog.DeletedMeasures{
s.storage.Append(storage.DeletedMeasures{
MetricID: req.MetricID,
FreePageNumbers: pageNumbers,
})

View File

@@ -12,7 +12,7 @@ import (
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/freelist"
"gordenko.dev/dima/qb/txlog"
"gordenko.dev/dima/qb/storage"
)
func JoinSnapshotFileName(dir string, snapshotNumber int) string {
@@ -41,7 +41,7 @@ type Database struct {
dir string
databaseName string
snapshotNumber int
txlog *txlog.Writer
storage *storage.Writer
atree *atree.Atree
tcpPort int
logfile *os.File
@@ -186,7 +186,7 @@ func (s *Database) recovery() {
// logNumber = recipe.LogNumber
// }
// s.txlog, err = txlog.NewWriter(txlog.WriterOptions{
// s.storage, err = storage.NewWriter(storage.WriterOptions{
// Dir: s.dir,
// LogNumber: logNumber,
// AppendToWorkerQueue: s.appendJobToWorkerQueue,
@@ -199,7 +199,7 @@ func (s *Database) recovery() {
// qb.Abort(qb.CreateChangesWriterFailed, err)
// }
// go s.txlog.Run()
// go s.storage.Run()
// // fileNames, err := s.searchREDOFiles()
// // if err != nil {
@@ -249,7 +249,7 @@ func (s *Database) replayChanges(snapshotNumber int) (err error) {
return
}
walReader, err := txlog.NewReader(txlog.ReaderOptions{
walReader, err := storage.NewWALReader(storage.WALReaderOptions{
FileName: JoinWALFileName(s.dir, snapshotNumber),
BufferSize: 8 * 1024 * 1024,
})
@@ -271,29 +271,21 @@ func (s *Database) replayChanges(snapshotNumber int) (err error) {
if err != nil {
return
}
err = s.storage.WritePagesToAtree(walReplayer.IndexPages(), walReplayer.DataPages())
if err != nil {
return err
}
// FIX free pages sync
return nil
}
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 {
// 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 (s *Database) relayMetricsToMetrics(replayMetrics map[uint32]*ReplayMetric) {
for metricID, x := range replayMetrics {
s.metrics[metricID] = x.ToMetric()
}
}
// FIX
@@ -314,14 +306,14 @@ func calculateDataBufferSize(payloadSize int) int {
return bufferSize
}
}
return atree.DataPageSize
return storage.DataPageSize
}
func allocateBuffers(requiredSpace int) (buf, databuf []byte) {
bufferSize := calculateDataBufferSize(requiredSpace)
buf = make([]byte, bufferSize)
if bufferSize == atree.DataPageSize {
databuf = buf[:atree.DataPagePayloadSize]
if bufferSize == storage.DataPageSize {
databuf = buf[:storage.DataPagePayloadSize]
} else {
databuf = buf
}
@@ -343,6 +335,20 @@ func growBuffers(in growBuffersIn) (buf, databuf []byte) {
return
}
type shrinkBufferIn struct {
Buffer []byte
TimestampsSize int
ValuesSize int
NewBufferSize int
}
func shrinkBuffer(in shrinkBufferIn) []byte {
buf := make([]byte, in.NewBufferSize)
copy(buf, in.Buffer[:in.ValuesSize])
copy(buf[:len(buf)-in.TimestampsSize], in.Buffer[len(in.Buffer)-in.TimestampsSize:])
return buf
}
//func (s *Database) verifySnapshot(fileName string) (_ bool, err error) {
// file, err := os.Open(fileName)
// if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"testing"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/storage"
)
func TestWriteReadSnapshot(t *testing.T) {
@@ -87,3 +88,26 @@ func cmpMetric(replay *ReplayMetric, origin *_metric) error {
// }
return nil
}
func TestComposeHeadIndexPage(t *testing.T) {
var (
levelIdx = 0
level = storage.IndexLevelTail{
Buffer: []byte{
1, 2, 3,
},
RecordsCount: 2,
}
head = &storage.HeadIndexPage{
PageNo: 100,
Checksum: 12345,
Records: []byte{},
}
)
page := composeHeadIndexPage(levelIdx, level, head)
if page.PageNo != head.PageNo {
t.Fatalf("PageNo: got %d are not equal expected %v",
page.PageNo, head.PageNo)
}
// fix compare pages
}

View File

@@ -6,17 +6,17 @@ import (
"os"
"time"
diploma "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/proto"
)
func timeBoundsOfAggregation(since, until proto.TimeBound, groupBy diploma.GroupBy, firstHourOfDay int) (s time.Time, u time.Time) {
func timeBoundsOfAggregation(since, until proto.TimeBound, groupBy qb.GroupBy, firstHourOfDay int) (s time.Time, u time.Time) {
switch groupBy {
case diploma.GroupByHour, diploma.GroupByDay:
case qb.GroupByHour, qb.GroupByDay:
s = time.Date(since.Year, since.Month, since.Day, 0, 0, 0, 0, time.Local)
u = time.Date(until.Year, until.Month, until.Day, 0, 0, 0, 0, time.Local)
case diploma.GroupByMonth:
case qb.GroupByMonth:
s = time.Date(since.Year, since.Month, 1, 0, 0, 0, 0, time.Local)
u = time.Date(until.Year, until.Month, 1, 0, 0, 0, 0, time.Local)
}

View File

@@ -5,8 +5,7 @@ import (
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/txlog"
"gordenko.dev/dima/qb/storage"
)
// METRIC
@@ -31,7 +30,7 @@ type _metric struct {
XLock bool
RLocks int
WaitQueue []any
indexLevelTails []txlog.IndexLevelTail // root - last element
indexLevelTails []storage.IndexLevelTail // root - last element
}
//IndexLevels [][]IndexRec // root - last element
@@ -60,7 +59,7 @@ func (s *_metric) DeleteMeasures() {
// s.UntilValue = 0
}
// func (s *_metric) startAppendMeasures(req tryAppendMeasuresReq, sendToStorage func(txlog.AppendedMeasures)) {
// func (s *_metric) startAppendMeasures(req tryAppendMeasuresReq, sendToStorage func(storage.AppendedMeasures)) {
func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage func(any)) {
var (
// AppendedMeasures struct {
@@ -78,7 +77,7 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
timestamps = s.timestamps
values = s.values
dataPages []txlog.DataPayload
dataPages []storage.DataPayload
//written int
//resultCode byte
@@ -92,7 +91,7 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
// s.Since = measure.Timestamp
// } else {
// FIX - у випадку помилки треба в транзакції зберегти що помилка, але також зафіксувати скільки елементів збережено.
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в txlog
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в storage
if measure.Timestamp <= s.timestamps.LastTimestamp() {
if idx == 0 {
s.values.ForgetCapturedState()
@@ -136,7 +135,7 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
// накопичую
timestamps.Compress(timestampCompressionWay, measure.Timestamp)
values.Compress(valueCompressionWay, measure.Value)
} else if len(s.buffer) < atree.DataPagePayloadSize {
} else if len(s.buffer) < storage.DataPagePayloadSize {
// allocate bigger buffer
buffer := make([]byte, len(s.buffer)*2)
// copy timestamps
@@ -151,7 +150,7 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
// сторінка заповнена
since := s.timestamps.ReplaceSinceWithUntil()
dataPages = append(dataPages, txlog.DataPayload{
dataPages = append(dataPages, storage.DataPayload{
Since: since,
Content: s.buffer,
TimestampsSize: timestamps.Size(),
@@ -180,8 +179,8 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
// скопіювати. Причому можна скопіювати зрізи chunks
if len(dataPages) > 0 {
// пишу в txlog довгим шляхом через redo файл і запис в data файл
sendToStorage(txlog.AppendedMeasures{
// пишу в storage довгим шляхом через redo файл і запис в data файл
sendToStorage(storage.AppendedMeasures{
MetricID: req.MetricID,
LastPageNo: s.lastPageNo,
TimestampsOffset: timestamps.Offset(), // state.Pos() з якої позиції дописувати дані на сторінку 0 (при відновленні)
@@ -196,12 +195,12 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
ResultCh: nil,
})
} else {
// короткий шлях - запис лише в txlog
// короткий шлях - запис лише в storage
}
}
func (s *_metric) FinAppendMeasures(rec txlog.AppendMeasuresSummary) {
func (s *_metric) FinAppendMeasures(rec storage.AppendMeasuresSummary) {
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
s.values.ForgetCapturedState()
s.timestamps.ForgetCapturedState()
@@ -209,7 +208,7 @@ func (s *_metric) FinAppendMeasures(rec txlog.AppendMeasuresSummary) {
if rec.LastPageNo > 0 {
s.lastPageNo = rec.LastPageNo
}
// В txlog я передав повний індекс. У нього додали елементи (можливо нові рівні).
// В storage я передав повний індекс. У нього додали елементи (можливо нові рівні).
// Тому проста заміна
s.indexLevelTails = rec.Index
}
@@ -388,20 +387,6 @@ func (s *_metric) WriteTo(w io.Writer) (err error) {
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

View File

@@ -7,8 +7,8 @@ import (
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/enc"
"gordenko.dev/dima/qb/proto"
"gordenko.dev/dima/qb/storage"
"gordenko.dev/dima/qb/transform"
"gordenko.dev/dima/qb/txlog"
)
// AddMetric - create metric in map and set Xlock, rwad - append to queue
@@ -83,7 +83,7 @@ func (s *Database) DoWork() {
case tryAppendMeasuresReq:
s.tryAppendMeasures(req)
case txlog.Changes:
case storage.Changes:
s.applyChanges(req) // all metrics only
case tryListCurrentValuesReq:
@@ -132,7 +132,7 @@ func (s *Database) processMetricQueue(metricID uint32, metric *_metric) {
s.tryGetMetric(req)
case tryAppendMeasuresReq:
metric.StartAppendMeasures(req, s.txlog.Append)
metric.StartAppendMeasures(req, s.storage.Append)
case tryDeleteMetricReq:
s.startDeleteMetric(metric, req)
@@ -294,7 +294,7 @@ func (s *Database) startDeleteMeasures(metric *_metric, req tryDeleteMeasuresReq
// }
}
func (s *Database) finAppendMeasures(rec txlog.AppendMeasuresSummary) {
func (s *Database) finAppendMeasures(rec storage.AppendMeasuresSummary) {
metric, ok := s.metrics[rec.MetricID]
if !ok {
qb.Abort(qb.NoMetricBug,
@@ -322,7 +322,7 @@ func (s *Database) tryAppendMeasures(req tryAppendMeasuresReq) {
metric.WaitQueue = append(metric.WaitQueue, req)
return
}
metric.StartAppendMeasures(req, s.txlog.Append)
metric.StartAppendMeasures(req, s.storage.Append)
}
type tryRangeScanReq struct {
@@ -408,25 +408,25 @@ func (s *Database) tryListCurrentValues(req tryListCurrentValuesReq) {
///////////////////////////////////////////////////////
func (s *Database) applyChanges(req txlog.Changes) {
func (s *Database) applyChanges(req storage.Changes) {
for _, untyped := range req.Records {
switch rec := untyped.(type) {
case txlog.AddedMetric:
case storage.MetricAddRecord:
s.applyAddMetric(rec)
case txlog.DeletedMetric:
case storage.MetricDeleteRecord:
s.deleteMetric(rec)
// case txlog.AppendedMeasure:
// case storage.AppendedMeasure:
// s.appendMeasure(rec)
case txlog.AppendMeasuresSummary:
case storage.AppendMeasuresSummary:
s.finAppendMeasures(rec)
// case txlog.AppendedMeasureWithOverflowExtended:
// case storage.AppendedMeasureWithOverflowExtended:
// s.appendMeasureAfterOverflow(rec)
case txlog.DeletedMeasures:
case storage.DeletedMeasures:
s.deleteMeasures(rec)
}
}
@@ -436,7 +436,7 @@ func (s *Database) applyChanges(req txlog.Changes) {
err := writeSnapshot(writeSnapshotIn{
SnapshotNumber: s.snapshotNumber,
Dir: s.dir,
WriteBufferSize: 1 * 1024 * 1024, // 1mb
WriteBufferSize: 4 * 1024 * 1024, // 1mb
Metrics: s.metrics,
FrozenIndexPagesCount: req.FrozenIndexPagesCount,
IndexPageNumbers: req.IndexPageNumbers,
@@ -451,7 +451,7 @@ func (s *Database) applyChanges(req txlog.Changes) {
}
}
func (s *Database) applyAddMetric(rec txlog.AddedMetric) {
func (s *Database) applyAddMetric(rec storage.MetricAddRecord) {
// fix lock
_, ok := s.metrics[rec.MetricID]
if ok {
@@ -470,28 +470,26 @@ func (s *Database) applyAddMetric(rec txlog.AddedMetric) {
// delete(s.metricLockEntries, rec.MetricID)
}
func (s *Database) addMetric(rec txlog.AddedMetric) {
func (s *Database) addMetric(rec storage.MetricAddRecord) {
var (
values qb.ValueCompressor
buffer = make([]byte, minBufferSize)
)
if rec.MetricType == qb.Cumulative {
values = enc.NewCumulativeDeltaCompressor(buffer, byte(rec.FracDigits))
values = enc.NewCumulativeDeltaCompressor(byte(rec.FracDigits), buffer, 0)
} else {
values = enc.NewInstantDeltaCompressor(buffer, byte(rec.FracDigits))
values = enc.NewInstantDeltaCompressor(byte(rec.FracDigits), buffer, 0)
}
s.metrics[rec.MetricID] = &_metric{
metricType: rec.MetricType,
fracDigits: byte(rec.FracDigits),
buffer: buffer,
timestamps: enc.NewTimeDeltaCompressor(buffer),
timestamps: enc.NewTimeDeltaCompressor(buffer, 0),
values: values,
}
}
func (s *Database) deleteMetric(rec txlog.DeletedMetric) {
func (s *Database) deleteMetric(rec storage.MetricDeleteRecord) {
metric, ok := s.metrics[rec.MetricID]
if !ok {
qb.Abort(qb.NoMetricBug,
@@ -552,7 +550,7 @@ func (s *Database) deleteMetric(rec txlog.DeletedMetric) {
}
delete(s.metrics, rec.MetricID)
// ADD in txlog
// ADD in storage
// if len(rec.FreePageNumbers) > 0 {
// s.freeList.AddPages(rec.FreePageNumbers)
// }
@@ -562,7 +560,7 @@ func (s *Database) deleteMetric(rec txlog.DeletedMetric) {
}
}
func (s *Database) deleteMeasures(rec txlog.DeletedMeasures) {
func (s *Database) deleteMeasures(rec storage.DeletedMeasures) {
metric, ok := s.metrics[rec.MetricID]
if !ok {
qb.Abort(qb.NoMetricBug,
@@ -577,7 +575,7 @@ func (s *Database) deleteMeasures(rec txlog.DeletedMeasures) {
}
metric.DeleteMeasures()
metric.XLock = false
// FIX add in txlog
// FIX add in storage
// if len(rec.FreePageNumbers) > 0 {
// s.freeList.AddPages(rec.FreePageNumbers)
// }

View File

@@ -1,12 +1,13 @@
package database
import (
"fmt"
"io"
bin "gordenko.dev/dima/bin/little"
qb "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/txlog"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/enc"
"gordenko.dev/dima/qb/storage"
)
type ReplayMetric struct {
@@ -17,7 +18,7 @@ type ReplayMetric struct {
databuf []byte // якщо розмір buf == DataPageSize, то databuf буде менше (мінус футер)
tSize int
vSize int
indexLevelTails []txlog.IndexLevelTail // root - last element
indexLevelTails []storage.IndexLevelTail // root - last element
}
func (s *ReplayMetric) ReadFrom(r io.Reader) (err error) {
@@ -57,7 +58,7 @@ func (s *ReplayMetric) ReadFrom(r io.Reader) (err error) {
}
for range levelsCount {
var (
buf = make([]byte, atree.IndexPageSize)
buf = make([]byte, storage.IndexPageSize)
count int
)
count, err = bin.ReadVarSize(r)
@@ -68,7 +69,7 @@ func (s *ReplayMetric) ReadFrom(r io.Reader) (err error) {
if err != nil {
return
}
s.indexLevelTails = append(s.indexLevelTails, txlog.IndexLevelTail{
s.indexLevelTails = append(s.indexLevelTails, storage.IndexLevelTail{
Buffer: buf,
RecordsCount: count,
})
@@ -76,115 +77,256 @@ func (s *ReplayMetric) ReadFrom(r io.Reader) (err error) {
return
}
// func appendNewRecordsToIndexTail(level storage.IndexLevelTail, records []byte) {
// }
// func countReusedIndexPages(changedIndexLevels []storage.ChangedIndexLevel) (count int) {
// return
// }
func (s *ReplayMetric) MeasuresAppend(rec storage.MeasuresAppendRecord) {
timestampsSize := rec.TimestampsOffset - len(rec.Timestamps)
// копіюю нові дані із WAL з урахуванням offset-ів
copy(s.databuf[rec.ValuesOffset:], rec.Values)
copy(s.databuf[len(s.databuf)-timestampsSize:], rec.Timestamps)
//
s.tSize = timestampsSize
s.vSize = rec.ValuesOffset - len(rec.Values)
}
type AppendMeasuresResult struct {
IndexPages []txlog.PageToWrite
DataPages []txlog.PageToWrite
IndexPages []storage.PageToWrite
DataPages []storage.PageToWrite
ReusedIndexPagesCount int
ReusedDataPagesCount int
}
func (s *ReplayMetric) AppendedMeasures(rec txlog.WALRecordAppendMeasures, pagesNeeded bool) (result AppendMeasuresResult) {
func (s *ReplayMetric) MeasuresAppendWithGrow(rec storage.MeasuresAppendWithGrowRecord, isLastPacket bool) (_ AppendMeasuresResult) {
var (
reusedIndexPages int
reusedDataPages int
indexPages []storage.PageToWrite
dataPages []storage.PageToWrite
)
// Додати в index level tails недостаючі дані, або замінити
for levelIdx, change := range rec.ChangedIndexLevels {
var (
head = change.HeadIndexPage
newRecordsCount = len(change.TailRecords) / indexRecordSize
)
// 1. набиваю сторінки для перезапису в index файлі
if isLastPacket {
if head != nil {
indexPages = append(indexPages,
composeHeadIndexPage(levelIdx, s.indexLevelTails[levelIdx], head))
}
for _, x := range change.IndexPages {
indexPages = append(indexPages, storage.PageToWrite{
PageNo: x.PageNo,
Content: x.Content,
})
}
}
// 2. Вношу зміни в поточні індексні хвости
if levelIdx < len(s.indexLevelTails) {
level := s.indexLevelTails[levelIdx]
if change.CompletedIndexPage != nil {
// попередній хвіст індексного рівня перетворився на сторінку,
if head != nil {
// попередній хвіст індексного рівня перетворився на head сторінку,
// отже TailRecords - це новий хвіст
// або копіюю дані, або алокую більший буфер?
copy(level.Buffer, change.TailRecords)
level.RecordsCount = len(change.TailRecords) / indexRecordSize
level.RecordsCount = newRecordsCount
} else {
// TailRecords - це нові дані, які треба додати
pos := level.RecordsCount * indexRecordSize
copy(level.Buffer[pos:], change.TailRecords)
level.RecordsCount += newRecordsCount
}
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,
// додаю новий індексний рівень
buf := make([]byte, storage.IndexPageSize)
copy(buf, change.TailRecords)
//
s.indexLevelTails = append(s.indexLevelTails, storage.IndexLevelTail{
Buffer: buf,
RecordsCount: newRecordsCount,
})
}
// 3. рахую кількість reused індексних сторінок
if head != nil {
if head.Reused {
reusedIndexPages++
}
}
for _, x := range change.IndexPages {
if x.Reused {
reusedIndexPages++
}
}
}
// 4. набиваю сторінки для перезапису в data файлі
if isLastPacket {
dataPages = append(dataPages, composeHeadDataPage(s.databuf, rec.HeadDataPage))
for _, x := range rec.DataPages {
dataPages = append(dataPages, storage.PageToWrite{
PageNo: x.PageNo,
Content: x.Content,
})
}
}
// Перезаписати в buffer timestamps та values. Якщо розмір буферу недостатній -
// спочатку створити більший
// рахую кількість reused дата сторінок
if rec.HeadDataPage.Reused {
reusedDataPages++
}
for _, x := range rec.DataPages {
if x.Reused {
reusedDataPages++
}
}
// HeadDataPage є полюбому, оскільки це транзакція із мінімум однією заповненою
// data сторінкою. Отже TailTimestamps і TailValues - це нові хвости data рівня.
copy(s.databuf, rec.TailValues)
pos := len(s.databuf) - len(rec.TailTimestamps)
copy(s.databuf[pos:], rec.TailTimestamps)
var (
// CompletedDataPage є полюбому, оскільки це Record із мінімум одною заповненою
// data сторінкою. Отже TailTimestamps і TailValues - це нові хвости data рівня.
s.tSize = len(rec.TailTimestamps)
s.vSize = len(rec.TailValues)
requiredSpace = len(rec.TailTimestamps) + len(rec.TailValues)
databuf []byte
)
if requiredSpace < len(s.buf) {
databuf = s.buf
// fix - update sizes?
if len(rec.DataPages) == 0 {
s.lastPageNo = rec.HeadDataPage.PageNo
} 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
s.lastPageNo = rec.DataPages[len(rec.DataPages)-1].PageNo
}
//rec.TailTimestamps
//rec.TailValues
return
return AppendMeasuresResult{
IndexPages: indexPages,
DataPages: dataPages,
ReusedIndexPagesCount: reusedIndexPages,
ReusedDataPagesCount: reusedDataPages,
}
}
func (s *ReplayMetric) ApplyHeadDataPage(p txlog.CompletedDataPage) {
// буфер має бути розміру сторінки
if len(s.buf) != atree.DataPageSize {
s.buf, s.databuf = growBuffers(growBuffersIn{
databuf: s.databuf,
tSize: s.tSize,
vSize: s.vSize,
requiredSpace: atree.DataPageSize,
// HELPERS
func composeHeadIndexPage(levelIdx int, level storage.IndexLevelTail, head *storage.HeadIndexPage) storage.PageToWrite {
// розраховую pos, з якого буду дописувати хвіст
pos := level.RecordsCount * indexRecordSize
// створюю копію сторінки
page := make([]byte, storage.IndexPageSize)
copy(page, level.Buffer[:pos]) // поточні дані
copy(page[pos:], head.Records)
// запечатати сторінку
calculatedCRC := storage.SealIndexPage(storage.SealIndexPageIn{
Content: page,
RecordsCount: level.RecordsCount + len(head.Records)/indexRecordSize,
ZeroLevel: levelIdx == 0,
})
// перевірка CRC
if calculatedCRC != head.Checksum {
qb.Abort(qb.WALReplayFailed,
fmt.Errorf("calculated CRC %d not equal expected %d of head page %d on index level %d",
calculatedCRC, head.Checksum, head.PageNo, levelIdx))
}
return storage.PageToWrite{
PageNo: head.PageNo,
Content: page,
}
}
func composeHeadDataPage(databuf []byte, head storage.HeadDataPage) storage.PageToWrite {
// створюю копію сторінки
var (
page = make([]byte, storage.DataPageSize)
timestampsSize = head.TimestampsOffset - len(head.Timestamps)
)
// копіюю поточні дані
copy(page, databuf)
// копіюю нові дані із WAL з урахуванням offset-ів
copy(page[head.ValuesOffset:], head.Values)
copy(page[len(page)-timestampsSize:], head.Timestamps)
// запечатати сторінку
calculatedCRC := storage.SealDataPage(storage.SealDataPageIn{
Content: page,
PrevPageNo: head.PrevPageNo,
TimestampsSize: timestampsSize,
ValuesSize: head.ValuesOffset + len(head.Values),
})
// перевірка CRC
if calculatedCRC != head.Checksum {
qb.Abort(qb.WALReplayFailed,
fmt.Errorf("calculated CRC %d not equal expected %d of head data page %d",
calculatedCRC, head.Checksum, head.PageNo))
}
return storage.PageToWrite{
PageNo: head.PageNo,
Content: page,
}
}
func (s *ReplayMetric) ToMetric() *_metric {
var (
values qb.ValueCompressor
requiredSpace = s.tSize + s.vSize
)
bufferSize := calculateDataBufferSize(requiredSpace)
if len(s.buf) > bufferSize {
s.buf = shrinkBuffer(shrinkBufferIn{
Buffer: s.buf,
TimestampsSize: s.tSize,
ValuesSize: s.vSize,
NewBufferSize: bufferSize,
})
}
// дописую дані по зміщенню
pos := len(s.databuf) - p.TimestampsOffset - len(p.Timestamps)
copy(s.databuf[pos:], p.Timestamps)
copy(s.databuf[p.ValuesOffset:], p.Values)
//
s.tSize = p.TimestampsOffset + len(p.Timestamps)
s.tSize = p.ValuesOffset + len(p.Values)
// fix - заполнить страницу
// fix - check checksum
if s.metricType == qb.Cumulative {
values = enc.NewCumulativeDeltaCompressor(s.fracDigits, s.buf, s.vSize)
} else {
values = enc.NewInstantDeltaCompressor(s.fracDigits, s.buf, s.vSize)
}
return &_metric{
metricType: s.metricType,
fracDigits: s.fracDigits,
lastPageNo: s.lastPageNo,
lastValue: values.LastValue(),
buffer: s.buf,
timestamps: enc.NewTimeDeltaCompressor(s.buf, s.tSize),
values: values,
indexLevelTails: s.indexLevelTails,
}
}
// func (s *ReplayMetric) ApplyHeadIndexPages(changedIndexLevels txlog.ChangedIndexLevel) {
// func (s *ReplayMetric) ApplyHeadDataPage(p storage.HeadDataPage) {
// // буфер має бути розміру сторінки
// if len(s.buf) != atree.DataPageSize {
// s.buf, s.databuf = growBuffers(growBuffersIn{
// databuf: s.databuf,
// tSize: s.tSize,
// vSize: s.vSize,
// requiredSpace: atree.DataPageSize,
// })
// }
// // дописую дані по зміщенню
// pos := len(s.databuf) - p.TimestampsOffset - len(p.Timestamps)
// copy(s.databuf[pos:], p.Timestamps)
// copy(s.databuf[p.ValuesOffset:], p.Values)
// //
// s.tSize = p.TimestampsOffset + len(p.Timestamps)
// s.tSize = p.ValuesOffset + len(p.Values)
// // fix - заполнить страницу
// // fix - check checksum
// }
// func (s *ReplayMetric) ApplyHeadIndexPages(changedIndexLevels storage.ChangedIndexLevel) {
// for levelIdx, changedLevel := range changedIndexLevels {
// p := changedLevel.CompletedIndexPage
// if p != nil {
// level := s.indexLevelTails[levelIdx]
// copy(level.Buffer[level.RecordsCount*indexRecordSize:], p.Records)
//
// // fix - заполнить страницу
// // fix - check checksum
// s.indexPages = append(s.indexPages, txlog.PageToWrite{
// PageNo: p.PageNo,
// Content: level.Buffer,
// })
// }
// for _, x := range changedLevel.IndexPages {
// s.indexPages = append(s.indexPages, txlog.PageToWrite{
// PageNo: x.PageNo,
// Content: x.Content,
// })
// }
//
// }
// }

View File

@@ -8,7 +8,7 @@ import (
"path/filepath"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/storage"
"gordenko.dev/dima/qb/util"
)
@@ -177,11 +177,11 @@ func readSnapshot(fileName string) (out readSnapshotOut, err error) {
for range metricsQty {
var (
metricID uint32
buf = make([]byte, atree.DataPageSize)
buf = make([]byte, storage.DataPageSize)
// для простоти створюю буфер максимального розміру
metric = &ReplayMetric{
buf: buf,
databuf: buf[:atree.DataPagePayloadSize],
databuf: buf[:storage.DataPagePayloadSize],
}
)
metricID, err = bin.ReadUint32(src)

View File

@@ -4,22 +4,20 @@ import (
"errors"
"fmt"
qb "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/txlog"
"gordenko.dev/dima/qb/storage"
)
type WALReplayer struct {
walReader *txlog.Reader
walReader *storage.WALReader
metrics map[uint32]*ReplayMetric
freeIndexPages []uint32
freeDataPages []uint32
indexPages []txlog.PageToWrite
dataPages []txlog.PageToWrite
indexPages []storage.PageToWrite
dataPages []storage.PageToWrite
}
type WALReplayerOptions struct {
WALReader *txlog.Reader
WALReader *storage.WALReader
Metrics map[uint32]*ReplayMetric // from snapshot
FreeIndexPages []uint32 // from snapshot
FreeDataPages []uint32 // from snapshot
@@ -40,122 +38,80 @@ func NewWALReplayer(opt WALReplayerOptions) (*WALReplayer, error) {
}, nil
}
func (s *WALReplayer) IndexPages() []storage.PageToWrite {
return s.indexPages
}
func (s *WALReplayer) DataPages() []storage.PageToWrite {
return s.dataPages
}
func (s *WALReplayer) Replay() error {
for {
records, isLast, err := s.walReader.NextPacket()
records, isLastPacket, err := s.walReader.NextPacket()
if err != nil {
return err
}
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 _, rec := range records {
if err = s.replayWALRecord(rec); err != nil {
if err = s.replayRecord(rec, isLastPacket); err != nil {
return err
}
}
}
}
// func (s *WALReplayer) replayPacket(records []any, isLastPacket bool) {
// for _, untyped := range records {
// switch rec := untyped.(type) {
// case txlog.WALRecordAppendMeasures:
// s.onWALRecordAppendMeasures(rec, isLastPacket)
// }
// }
// return
// }
func (s *WALReplayer) replayWALRecord(untyped any) (err error) {
func (s *WALReplayer) replayRecord(untyped any, isLastPacket bool) (err error) {
switch rec := untyped.(type) {
case txlog.AddedMetric:
if err = s.onAddedMetric(rec); err != nil {
case storage.MeasuresAppendRecord:
if err = s.onMeasuresAppend(rec); err != nil {
return
}
case txlog.DeletedMetric:
if err = s.onDeletedMetric(rec); err != nil {
case storage.MeasuresAppendWithGrowRecord:
if err = s.onMeasuresAppendWithGrow(rec, isLastPacket); err != nil {
return
}
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)
// }
// }
// 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
// }
// metric.Until = rec.Timestamp
// metric.UntilValue = rec.Value
// }
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)
// }
// }
default:
qb.Abort(qb.UnknownTxLogRecordTypeBug,
fmt.Errorf("bug: unknown record type %T in TransactionLog", rec))
// default:
// qb.Abort(qb.UnknownstorageRecordTypeBug,
// fmt.Errorf("bug: unknown record type %T in TransactionLog", rec))
}
return nil
}
func (s *WALReplayer) onAddedMetric(rec txlog.AddedMetric) (err error) {
func (s *WALReplayer) onMetricAdd(rec storage.MetricAddRecord) (err error) {
_, ok := s.metrics[rec.MetricID]
if ok {
return fmt.Errorf("metric %d add failed: already added", rec.MetricID)
}
var (
buf = make([]byte, atree.DataPageSize)
buf = make([]byte, storage.DataPageSize)
)
s.metrics[rec.MetricID] = &ReplayMetric{
metricType: rec.MetricType,
fracDigits: byte(rec.FracDigits),
buf: buf,
databuf: buf[:atree.DataPagePayloadSize],
databuf: buf[:storage.DataPagePayloadSize],
}
return
}
func (s *WALReplayer) onDeletedMetric(rec txlog.DeletedMetric) error {
func (s *WALReplayer) onMetricDelete(rec storage.MetricDeleteRecord) error {
_, ok := s.metrics[rec.MetricID]
if !ok {
return fmt.Errorf("metric %d deletion failed: not found", rec.MetricID)
@@ -166,15 +122,21 @@ func (s *WALReplayer) onDeletedMetric(rec txlog.DeletedMetric) error {
return nil
}
func (s *WALReplayer) onAppendMeasures(rec txlog.WALRecordAppendMeasures, isLastPacket bool) {
func (s *WALReplayer) onMeasuresAppend(rec storage.MeasuresAppendRecord) (err error) {
metric, ok := s.metrics[rec.MetricID]
if !ok {
qb.Abort(qb.MetricNotFoundDuringReplay, nil)
return fmt.Errorf("append measures failed: metric %d not found", rec.MetricID)
}
if isLastPacket {
s.collectPagesToWrite(metric, rec)
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)
}
result := metric.AppendedMeasures(rec, isLastPacket)
result := metric.MeasuresAppendWithGrow(rec, isLastPacket)
//
if isLastPacket {
s.indexPages = append(s.indexPages, result.IndexPages...)
@@ -183,22 +145,33 @@ func (s *WALReplayer) onAppendMeasures(rec txlog.WALRecordAppendMeasures, isLast
//
s.freeIndexPages = s.freeIndexPages[:len(s.freeIndexPages)-result.ReusedIndexPagesCount]
s.freeDataPages = s.freeDataPages[:len(s.freeDataPages)-result.ReusedDataPagesCount]
return
}
func (s *WALReplayer) collectPagesToWrite(metric *ReplayMetric, rec txlog.WALRecordAppendMeasures) {
metric.ApplyHeadDataPage(rec.CompletedDataPage)
//func (s *WALReplayer) collectPagesToWrite(metric *ReplayMetric, rec storage.WALRecordAppendMeasures) {
// metric.ApplyHeadDataPage(rec.CompletedDataPage)
s.dataPages = append(s.dataPages, txlog.PageToWrite{
PageNo: p.PageNo,
Content: metric.buf,
})
// s.dataPages = append(s.dataPages, storage.PageToWrite{
// PageNo: p.PageNo,
// Content: metric.buf,
// })
for _, x := range rec.DataPages {
s.dataPages = append(s.dataPages, txlog.PageToWrite{
PageNo: x.PageNo,
Content: x.Content,
})
}
//metric.ApplyHeadIndexPages(rec.ChangedIndexLevels)
// for _, x := range rec.DataPages {
// s.dataPages = append(s.dataPages, storage.PageToWrite{
// PageNo: x.PageNo,
// Content: x.Content,
// })
// }
//metric.ApplyHeadIndexPages(rec.ChangedIndexLevels)
}
//}
// func (s *WALReplayer) replayPacket(records []any, isLastPacket bool) {
// for _, untyped := range records {
// switch rec := untyped.(type) {
// case storage.WALRecordAppendMeasures:
// s.onWALRecordAppendMeasures(rec, isLastPacket)
// }
// }
// return
// }