Files
qb/storage/replay_metric.go

219 lines
6.6 KiB
Go
Raw Normal View History

2026-06-13 22:43:17 +00:00
package storage
2026-06-09 16:42:17 +00:00
import (
2026-06-10 06:18:45 +03:00
"fmt"
2026-06-09 16:42:17 +00:00
2026-06-10 06:18:45 +03:00
"gordenko.dev/dima/qb"
2026-06-09 16:42:17 +00:00
)
type ReplayMetric struct {
2026-06-14 07:57:01 +03:00
MetricType qb.MetricType
FracDigits byte
LastPageNo uint32
Buf []byte
TimestampsSize int
ValuesSize int
IndexLevelTails []IndexLevelTail // root - last element
2026-06-09 16:42:17 +00:00
}
2026-06-14 07:57:01 +03:00
// return occupied after write
func writeTimestampsWithRewind(buf []byte, occupied int, payload []byte, rewindOffset int) int {
// timestamps writes right-to-left
pos := DataPagePayloadSize - occupied
// timestamps rewind offset moves pos left-to-right
pos += rewindOffset
pos -= len(payload)
copy(buf[pos:], payload)
return DataPagePayloadSize - pos
2026-06-09 16:42:17 +00:00
}
2026-06-14 07:57:01 +03:00
func writeValuesWithRewind(buf []byte, occupied int, payload []byte, rewindOffset int) int {
// values writes left-to-right
pos := occupied
// values rewind offset moves pos right-to-left
pos -= rewindOffset
copy(buf[pos:], payload)
return pos + len(payload)
}
2026-06-10 06:18:45 +03:00
2026-06-13 22:43:17 +00:00
func (s *ReplayMetric) MeasuresAppend(rec MeasuresAppendRecord) {
2026-06-14 07:57:01 +03:00
s.TimestampsSize = writeTimestampsWithRewind(s.Buf, s.TimestampsSize,
rec.Timestamps, rec.TimestampsRewindOffset)
s.ValuesSize = writeValuesWithRewind(s.Buf, s.ValuesSize,
rec.Values, rec.ValuesRewindOffset)
2026-06-10 06:18:45 +03:00
}
2026-06-14 23:12:03 +03:00
type ReplayMeasuresAppendWithGrowResult struct {
2026-06-14 07:57:01 +03:00
IndexPagesToRewrite []PageToWrite
DataPagesToRewrite []PageToWrite
2026-06-09 16:42:17 +00:00
ReusedIndexPagesCount int
ReusedDataPagesCount int
}
2026-06-14 23:12:03 +03:00
func (s *ReplayMetric) MeasuresAppendWithGrow(rec MeasuresAppendWithGrowRecord, isLastPacket bool) (_ ReplayMeasuresAppendWithGrowResult) {
2026-06-10 06:18:45 +03:00
var (
reusedIndexPages int
reusedDataPages int
2026-06-13 22:43:17 +00:00
indexPages []PageToWrite
dataPages []PageToWrite
2026-06-10 06:18:45 +03:00
)
2026-06-09 16:42:17 +00:00
// Додати в index level tails недостаючі дані, або замінити
for levelIdx, change := range rec.ChangedIndexLevels {
2026-06-10 06:18:45 +03:00
var (
2026-06-13 08:01:42 +03:00
head = change.IndexPageTail
2026-06-13 22:43:17 +00:00
newRecordsCount = len(change.TailRecords) / IndexRecordSize
2026-06-10 06:18:45 +03:00
)
// 1. набиваю сторінки для перезапису в index файлі
if isLastPacket {
if head != nil {
2026-06-14 07:57:01 +03:00
indexPages = append(indexPages, s.composeHeadIndexPage(levelIdx, head))
2026-06-10 06:18:45 +03:00
}
for _, x := range change.IndexPages {
2026-06-13 22:43:17 +00:00
indexPages = append(indexPages, PageToWrite{
2026-06-10 06:18:45 +03:00
PageNo: x.PageNo,
Content: x.Content,
})
}
}
// 2. Вношу зміни в поточні індексні хвости
2026-06-14 07:57:01 +03:00
if levelIdx < len(s.IndexLevelTails) {
level := s.IndexLevelTails[levelIdx]
2026-06-10 06:18:45 +03:00
if head != nil {
// попередній хвіст індексного рівня перетворився на head сторінку,
2026-06-09 16:42:17 +00:00
// отже TailRecords - це новий хвіст
2026-06-14 23:12:03 +03:00
clear(level.Buffer)
2026-06-09 16:42:17 +00:00
copy(level.Buffer, change.TailRecords)
2026-06-10 06:18:45 +03:00
level.RecordsCount = newRecordsCount
2026-06-09 16:42:17 +00:00
} else {
// TailRecords - це нові дані, які треба додати
2026-06-13 22:43:17 +00:00
pos := level.RecordsCount * IndexRecordSize
2026-06-09 16:42:17 +00:00
copy(level.Buffer[pos:], change.TailRecords)
2026-06-10 06:18:45 +03:00
level.RecordsCount += newRecordsCount
2026-06-09 16:42:17 +00:00
}
2026-06-14 07:57:01 +03:00
s.IndexLevelTails[levelIdx] = level
2026-06-09 16:42:17 +00:00
} else {
2026-06-10 06:18:45 +03:00
// додаю новий індексний рівень
2026-06-13 22:43:17 +00:00
buf := make([]byte, IndexPageSize)
2026-06-10 06:18:45 +03:00
copy(buf, change.TailRecords)
//
2026-06-14 07:57:01 +03:00
s.IndexLevelTails = append(s.IndexLevelTails, IndexLevelTail{
2026-06-10 06:18:45 +03:00
Buffer: buf,
RecordsCount: newRecordsCount,
2026-06-09 16:42:17 +00:00
})
}
2026-06-10 06:18:45 +03:00
// 3. рахую кількість reused індексних сторінок
if head != nil {
if head.Reused {
reusedIndexPages++
}
}
for _, x := range change.IndexPages {
if x.Reused {
reusedIndexPages++
}
}
2026-06-09 16:42:17 +00:00
}
2026-06-10 06:18:45 +03:00
// 4. набиваю сторінки для перезапису в data файлі
if isLastPacket {
2026-06-14 07:57:01 +03:00
dataPages = append(dataPages, s.composeHeadDataPage(rec.DataPageTail))
2026-06-09 16:42:17 +00:00
2026-06-10 06:18:45 +03:00
for _, x := range rec.DataPages {
2026-06-13 22:43:17 +00:00
dataPages = append(dataPages, PageToWrite{
2026-06-10 06:18:45 +03:00
PageNo: x.PageNo,
Content: x.Content,
})
}
}
2026-06-09 16:42:17 +00:00
2026-06-10 06:18:45 +03:00
// рахую кількість reused дата сторінок
2026-06-13 08:01:42 +03:00
if rec.DataPageTail.Reused {
2026-06-10 06:18:45 +03:00
reusedDataPages++
}
for _, x := range rec.DataPages {
if x.Reused {
reusedDataPages++
}
}
// HeadDataPage є полюбому, оскільки це транзакція із мінімум однією заповненою
// data сторінкою. Отже TailTimestamps і TailValues - це нові хвости data рівня.
2026-06-14 23:12:03 +03:00
clear(s.Buf)
2026-06-14 07:57:01 +03:00
copy(s.Buf, rec.TailValues)
s.ValuesSize = len(rec.TailValues)
2026-06-10 06:18:45 +03:00
2026-06-14 07:57:01 +03:00
pos := DataPagePayloadSize - len(rec.TailTimestamps)
copy(s.Buf[pos:], rec.TailTimestamps)
s.TimestampsSize = len(rec.TailTimestamps)
2026-06-10 06:18:45 +03:00
if len(rec.DataPages) == 0 {
2026-06-14 07:57:01 +03:00
s.LastPageNo = rec.DataPageTail.PageNo
2026-06-09 16:42:17 +00:00
} else {
2026-06-14 07:57:01 +03:00
s.LastPageNo = rec.DataPages[len(rec.DataPages)-1].PageNo
2026-06-10 06:18:45 +03:00
}
2026-06-09 16:42:17 +00:00
2026-06-14 23:12:03 +03:00
return ReplayMeasuresAppendWithGrowResult{
2026-06-14 07:57:01 +03:00
IndexPagesToRewrite: indexPages,
DataPagesToRewrite: dataPages,
2026-06-10 06:18:45 +03:00
ReusedIndexPagesCount: reusedIndexPages,
ReusedDataPagesCount: reusedDataPages,
2026-06-09 16:42:17 +00:00
}
2026-06-10 06:18:45 +03:00
}
2026-06-09 16:42:17 +00:00
2026-06-10 06:18:45 +03:00
// HELPERS
2026-06-14 07:57:01 +03:00
func (s *ReplayMetric) composeHeadIndexPage(levelIdx int, head *IndexPageTail) PageToWrite {
level := s.IndexLevelTails[levelIdx]
2026-06-10 06:18:45 +03:00
// розраховую pos, з якого буду дописувати хвіст
2026-06-13 22:43:17 +00:00
pos := level.RecordsCount * IndexRecordSize
2026-06-10 06:18:45 +03:00
// створюю копію сторінки
2026-06-13 22:43:17 +00:00
page := make([]byte, IndexPageSize)
2026-06-10 06:18:45 +03:00
copy(page, level.Buffer[:pos]) // поточні дані
copy(page[pos:], head.Records)
// запечатати сторінку
2026-06-13 22:43:17 +00:00
calculatedCRC := SealIndexPage(SealIndexPageIn{
2026-06-10 06:18:45 +03:00
Content: page,
2026-06-13 22:43:17 +00:00
RecordsCount: level.RecordsCount + len(head.Records)/IndexRecordSize,
2026-06-10 06:18:45 +03:00
ZeroLevel: levelIdx == 0,
})
// перевірка CRC
2026-06-13 08:01:42 +03:00
if calculatedCRC != head.CRC32 {
2026-06-10 06:18:45 +03:00
qb.Abort(qb.WALReplayFailed,
fmt.Errorf("calculated CRC %d not equal expected %d of head page %d on index level %d",
2026-06-13 08:01:42 +03:00
calculatedCRC, head.CRC32, head.PageNo, levelIdx))
2026-06-10 06:18:45 +03:00
}
2026-06-13 22:43:17 +00:00
return PageToWrite{
2026-06-10 06:18:45 +03:00
PageNo: head.PageNo,
Content: page,
}
2026-06-09 16:42:17 +00:00
}
2026-06-14 07:57:01 +03:00
func (s *ReplayMetric) composeHeadDataPage(head DataPageTail) PageToWrite {
// copy page
page := make([]byte, DataPageSize)
copy(page, s.Buf)
// append data
2026-06-14 23:12:03 +03:00
timestampsSize := writeTimestampsWithRewind(page, s.TimestampsSize,
2026-06-14 07:57:01 +03:00
head.Timestamps, head.TimestampsRewindOffset)
2026-06-14 23:12:03 +03:00
valuesSize := writeValuesWithRewind(page, s.ValuesSize,
2026-06-14 07:57:01 +03:00
head.Values, head.ValuesRewindOffset)
2026-06-10 06:18:45 +03:00
// запечатати сторінку
2026-06-13 22:43:17 +00:00
calculatedCRC := SealDataPage(SealDataPageIn{
2026-06-10 06:18:45 +03:00
Content: page,
PrevPageNo: head.PrevPageNo,
TimestampsSize: timestampsSize,
2026-06-14 07:57:01 +03:00
ValuesSize: valuesSize,
2026-06-10 06:18:45 +03:00
})
// перевірка CRC
2026-06-13 08:01:42 +03:00
if calculatedCRC != head.CRC32 {
2026-06-10 06:18:45 +03:00
qb.Abort(qb.WALReplayFailed,
fmt.Errorf("calculated CRC %d not equal expected %d of head data page %d",
2026-06-13 08:01:42 +03:00
calculatedCRC, head.CRC32, head.PageNo))
2026-06-09 16:42:17 +00:00
}
2026-06-13 22:43:17 +00:00
return PageToWrite{
2026-06-10 06:18:45 +03:00
PageNo: head.PageNo,
Content: page,
}
}