Files
qb/storage/write_preparer.go

279 lines
7.5 KiB
Go
Raw Normal View History

2026-06-13 08:01:42 +03:00
package storage
import (
"bytes"
"io"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb/util"
)
2026-06-14 23:12:03 +03:00
type Packable interface {
Pack(io.Writer)
}
2026-06-13 08:01:42 +03:00
type WritePreparer struct {
pagePreparer *PageIngester
//dataPagePayloadBound int
w *bytes.Buffer
indexPages []PageToWrite
dataPages []PageToWrite
commits []any
}
type WritePreparerOptions struct {
MinWALBufferSize int
PageIngester *PageIngester
}
func NewWritePreparer(opt WritePreparerOptions) *WritePreparer {
//buf := make([]byte, opt.MinWALBufferSize)
s := &WritePreparer{
pagePreparer: opt.PageIngester,
w: bytes.NewBuffer(nil),
}
return s
}
type PreparedData struct {
Packet []byte // пакет для запису в WAL
WriteToIndex []PageToWrite
WriteToData []PageToWrite
Commits []any
}
2026-06-14 23:12:03 +03:00
// const (
// prefixSize = 9
// crc32Size = 4
// )
2026-06-13 08:01:42 +03:00
func (s *WritePreparer) Prepare(input []any) PreparedData {
2026-06-15 01:20:30 +03:00
s.w.Reset()
2026-06-13 08:01:42 +03:00
s.w.Write([]byte{
0, 0, 0, 0, 0, 0, 0, 0, 0, // size (max 9 byte)
})
hasher := util.NewHasher()
2026-06-14 23:12:03 +03:00
multi := io.MultiWriter(s.w, hasher)
2026-06-13 08:01:42 +03:00
// 1. Пакую всі дані в WAL буфер запису
for _, untyped := range input {
switch x := untyped.(type) {
case MetricAdd:
rec := MetricAddRecord{
MetricID: x.MetricID,
MetricType: x.MetricType,
FracDigits: x.FracDigits,
}
2026-06-14 23:12:03 +03:00
rec.Pack(multi)
2026-06-13 08:01:42 +03:00
s.commits = append(s.commits, MetricAddCommited{
MetricID: x.MetricID,
ResultCh: x.ResultCh,
})
case MetricDelete:
// if len(x.FreeIndexPages) > 0 {
// s.indexFreeList.AddPageNumbers(x.FreeIndexPages)
// }
// if len(x.FreeIndexPages) > 0 {
// s.dataFreeList.AddPageNumbers(x.FreeDataPages)
// }
rec := MetricDeleteRecord{
MetricID: x.MetricID,
FreeIndexPages: x.FreeIndexPages,
FreeDataPages: x.FreeDataPages,
}
2026-06-14 23:12:03 +03:00
rec.Pack(multi)
2026-06-13 08:01:42 +03:00
s.commits = append(s.commits, MetricDeleteCommited{
MetricID: x.MetricID,
ResultCh: x.ResultCh,
})
case MeasuresAppend:
rec := MeasuresAppendRecord{
MetricID: x.MetricID,
TimestampsRewindOffset: x.TimestampsRewindOffset,
Timestamps: x.Timestamps,
ValuesRewindOffset: x.ValuesRewindOffset,
Values: x.Values,
}
2026-06-14 23:12:03 +03:00
rec.Pack(multi)
2026-06-13 08:01:42 +03:00
// Дані для Worker
s.commits = append(s.commits, MeasuresAppendCommited{
MetricID: x.MetricID,
ResultCode: x.ResultCode,
WrittenCount: x.WrittenCount,
ResultCh: x.ResultCh,
})
case MeasuresAppendWithGrow:
sealedDataPages, sealedIndexLevels := s.pagePreparer.Ingest(IngestIn{
LastPageNo: x.LastPageNo,
IndexLevelTails: x.IndexLevelTails,
DataPages: x.DataPages,
})
// сторінки для запису в index та data файли
for _, p := range sealedDataPages {
s.dataPages = append(s.dataPages, PageToWrite{
PageNo: p.PageNo,
Content: p.Content,
})
}
for _, level := range sealedIndexLevels {
for _, p := range level.IndexPages {
2026-06-13 07:13:03 +00:00
//fmt.Printf("records: %v\n", getIndexRecords(p.Content, 2))
2026-06-13 08:01:42 +03:00
s.indexPages = append(s.indexPages, PageToWrite{
PageNo: p.PageNo,
Content: p.Content,
})
}
}
var (
// дані для запису в WAL
changedIndexPages []ChangedIndexLevel
// дані для Воркера
indexLevelTails []IndexLevelTail
)
for _, level := range sealedIndexLevels {
if len(level.IndexPages) > 0 || level.SkipRecords != level.TailRecordsCount {
// на індексному внесені зміни
var (
indexPageTail *IndexPageTail
indexPages []SealedIndexPage
)
if len(level.IndexPages) > 0 {
first := level.IndexPages[0]
2026-06-13 07:13:03 +00:00
firstCRC32, _ := bin.GetUint32(first.Content[indexCRC32Idx:])
2026-06-13 22:43:17 +00:00
skipSize := level.SkipRecords * IndexRecordSize
2026-06-13 08:01:42 +03:00
indexPageTail = &IndexPageTail{
PageNo: first.PageNo,
Reused: first.Reused,
2026-06-13 07:13:03 +00:00
CRC32: firstCRC32,
2026-06-13 08:01:42 +03:00
// в WAL файл попадає лише payload
2026-06-13 22:43:17 +00:00
Records: first.Content[skipSize : maxRecordsOnIndexPage*IndexRecordSize],
2026-06-13 08:01:42 +03:00
}
indexPages = level.IndexPages[1:]
} else {
indexPages = level.IndexPages
}
changedIndexPages = append(changedIndexPages, ChangedIndexLevel{
IndexPageTail: indexPageTail,
IndexPages: indexPages,
// в WAL файл попадає лише payload
2026-06-13 22:43:17 +00:00
TailRecords: level.TailRecords[:level.TailRecordsCount*IndexRecordSize],
2026-06-13 08:01:42 +03:00
})
}
// Worker-у відправляю повний index
indexLevelTails = append(indexLevelTails, IndexLevelTail{
// Worker отримує повний буфер
Buffer: level.TailRecords,
RecordsCount: level.TailRecordsCount,
})
}
first := sealedDataPages[0]
firstCRC32, _ := bin.GetUint32(first.Content[dataCRC32Idx:])
rec := MeasuresAppendWithGrowRecord{
MetricID: x.MetricID,
DataPageTail: DataPageTail{
PageNo: first.PageNo,
Reused: first.Reused,
PrevPageNo: x.LastPageNo,
CRC32: firstCRC32,
TimestampsRewindOffset: x.TimestampsRewindOffset,
Timestamps: x.Timestamps,
ValuesRewindOffset: x.ValuesRewindOffset,
Values: x.Values,
},
DataPages: sealedDataPages[1:],
2026-06-13 07:13:03 +00:00
TailTimestamps: x.TailTimestamps,
TailValues: x.TailValues,
2026-06-13 08:01:42 +03:00
ChangedIndexLevels: changedIndexPages,
}
2026-06-14 23:12:03 +03:00
//fmt.Printf("%#v\ns", rec.ChangedIndexLevels[0].IndexPageTail)
2026-06-13 07:13:03 +00:00
2026-06-14 23:12:03 +03:00
rec.Pack(multi)
2026-06-13 08:01:42 +03:00
// Дані для Worker
s.commits = append(s.commits, MeasuresAppendWithGrowCommited{
MetricID: x.MetricID,
LastPageNo: sealedDataPages[len(sealedDataPages)-1].PageNo,
Index: indexLevelTails,
ResultCode: x.ResultCode,
WrittenCount: x.WrittenCount,
ResultCh: x.ResultCh,
})
case MeasuresDelete:
// if len(x.FreeIndexPages) > 0 {
// s.indexFreeList.AddPageNumbers(x.FreeIndexPages)
// }
// if len(x.FreeIndexPages) > 0 {
// s.dataFreeList.AddPageNumbers(x.FreeDataPages)
// }
rec := MeasuresDeleteRecord{
MetricID: x.MetricID,
FreeIndexPages: x.FreeIndexPages,
FreeDataPages: x.FreeDataPages,
}
2026-06-14 23:12:03 +03:00
rec.Pack(multi)
2026-06-13 08:01:42 +03:00
s.commits = append(s.commits, MeasuresDeleteCommited{
MetricID: x.MetricID,
ResultCh: x.ResultCh,
})
//case DeletedMeasuresSince:
}
}
// 2. Додаю розмір пакету і чексуму
2026-06-14 23:12:03 +03:00
packet := finalizePacket(s.w, hasher.Sum32())
2026-06-13 08:01:42 +03:00
//s.written += int64(len(packet)) + 12
//
return PreparedData{
2026-06-14 23:12:03 +03:00
Packet: packet,
2026-06-13 08:01:42 +03:00
WriteToIndex: s.indexPages,
WriteToData: s.dataPages,
Commits: s.commits,
}
}
func (s *WritePreparer) Reset() {
s.w.Reset()
s.indexPages = nil
s.dataPages = nil
s.commits = nil
}
func getIndexRecords(buf []byte, count int) (list []IndexRecord) {
2026-06-15 01:20:30 +03:00
//fmt.Printf("ipage: % x\n", buf)
2026-06-13 08:01:42 +03:00
i := 0
for range count {
var rec IndexRecord
rec.Timestamp, _ = bin.GetUint32(buf[i:])
rec.PageNo, _ = bin.GetUint32(buf[i+4:])
list = append(list, rec)
2026-06-13 22:43:17 +00:00
i += IndexRecordSize
2026-06-13 08:01:42 +03:00
}
return
}
2026-06-14 23:12:03 +03:00
func finalizePacket(w *bytes.Buffer, checksum uint32) []byte {
bin.WriteUint32(w, checksum)
packet := w.Bytes()
bodySize := len(packet) - 13 // 9 bytes prefix + 4 bytes crc32
// cut empty bytes at start
start := 9 - bin.CountVarSize(bodySize)
bin.PutVarSize(packet[start:], bodySize)
2026-06-15 01:20:30 +03:00
2026-06-19 07:25:46 +03:00
//fmt.Printf("checksum: %d\n", checksum)
//fmt.Printf("bodySize: %d\n", bodySize)
2026-06-15 01:20:30 +03:00
//fmt.Printf("start: %d\n", start)
2026-06-19 07:25:46 +03:00
//fmt.Printf("body: % x\n", packet[9:len(packet)-4])
2026-06-15 01:20:30 +03:00
2026-06-14 23:12:03 +03:00
return packet[start:]
}