2026-06-13 08:01:42 +03:00
|
|
|
|
package storage
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
|
|
|
|
|
|
bin "gordenko.dev/dima/bin/little"
|
|
|
|
|
|
"gordenko.dev/dima/qb/util"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *WritePreparer) Prepare(input []any) PreparedData {
|
|
|
|
|
|
s.w.Write([]byte{
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, // size (max 9 byte)
|
|
|
|
|
|
0, 0, 0, 0, // crc32
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
hasher := util.NewHasher()
|
|
|
|
|
|
|
|
|
|
|
|
w := io.MultiWriter(s.w, hasher)
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Пакую всі дані в WAL буфер запису
|
|
|
|
|
|
for _, untyped := range input {
|
|
|
|
|
|
switch x := untyped.(type) {
|
|
|
|
|
|
case MetricAdd:
|
|
|
|
|
|
rec := MetricAddRecord{
|
|
|
|
|
|
MetricID: x.MetricID,
|
|
|
|
|
|
MetricType: x.MetricType,
|
|
|
|
|
|
FracDigits: x.FracDigits,
|
|
|
|
|
|
}
|
|
|
|
|
|
rec.Pack(w)
|
|
|
|
|
|
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,
|
|
|
|
|
|
}
|
|
|
|
|
|
rec.Pack(w)
|
|
|
|
|
|
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,
|
|
|
|
|
|
}
|
|
|
|
|
|
rec.Pack(w)
|
|
|
|
|
|
// Дані для 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-13 07:13:03 +00:00
|
|
|
|
fmt.Printf("%#v\ns", rec.ChangedIndexLevels[0].IndexPageTail)
|
|
|
|
|
|
|
2026-06-13 08:01:42 +03:00
|
|
|
|
rec.Pack(w)
|
|
|
|
|
|
// Дані для 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,
|
|
|
|
|
|
}
|
|
|
|
|
|
rec.Pack(w)
|
|
|
|
|
|
s.commits = append(s.commits, MeasuresDeleteCommited{
|
|
|
|
|
|
MetricID: x.MetricID,
|
|
|
|
|
|
ResultCh: x.ResultCh,
|
|
|
|
|
|
})
|
|
|
|
|
|
//case DeletedMeasuresSince:
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 2. Додаю розмір пакету і чексуму
|
|
|
|
|
|
|
|
|
|
|
|
//s.written += int64(len(packet)) + 12
|
|
|
|
|
|
|
|
|
|
|
|
packet := s.w.Bytes()
|
|
|
|
|
|
// write size
|
|
|
|
|
|
payloadSize := len(packet) - 13
|
|
|
|
|
|
start := 9 - bin.CountVarSize(payloadSize)
|
|
|
|
|
|
bin.PutVarSize(packet[start:], payloadSize)
|
|
|
|
|
|
// CRC32
|
|
|
|
|
|
bin.PutUint32(packet[9:], hasher.Sum32())
|
|
|
|
|
|
//
|
|
|
|
|
|
return PreparedData{
|
|
|
|
|
|
Packet: packet[start:],
|
|
|
|
|
|
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) {
|
|
|
|
|
|
fmt.Printf("ipage: % x\n", buf)
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|