Files
qb/storage/page_ingester.go

248 lines
6.9 KiB
Go
Raw Normal View History

2026-06-10 06:18:45 +03:00
package storage
2026-06-05 19:43:01 +00:00
import (
"errors"
2026-06-10 06:18:45 +03:00
bin "gordenko.dev/dima/bin/little"
2026-06-05 19:43:01 +00:00
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/util"
)
// Задача метода:
// - для data сторінок отримати pageNo та reused, задати sizes та prevPageNo, порахувати CRC32
// - додати пари до index, згенерувати нові index сторінки
// levels []*IndexLevel, dataPages []*DataPage
// fix - reduce leves
2026-06-13 08:01:42 +03:00
type PageIngester struct {
2026-06-12 06:11:57 +00:00
getDataPageNumber func() (uint32, bool, error)
getIndexPageNumber func() (uint32, bool, error)
2026-06-05 19:43:01 +00:00
}
2026-06-13 08:01:42 +03:00
type PageIngesterOptions struct {
2026-06-05 19:43:01 +00:00
GetDataPageNumber func() (uint32, bool, error)
GetIndexPageNumber func() (uint32, bool, error)
}
2026-06-13 08:01:42 +03:00
func NewPageIngester(opt PageIngesterOptions) (*PageIngester, error) {
2026-06-05 19:43:01 +00:00
if opt.GetIndexPageNumber == nil {
return nil, errors.New("missing required option: GetIndexPageNumber")
}
if opt.GetDataPageNumber == nil {
return nil, errors.New("missing required option: GetDataPageNumber")
}
2026-06-13 08:01:42 +03:00
s := &PageIngester{
2026-06-12 06:11:57 +00:00
getIndexPageNumber: opt.GetIndexPageNumber,
getDataPageNumber: opt.GetDataPageNumber,
2026-06-05 19:43:01 +00:00
}
return s, nil
}
type IndexLevelTail struct {
2026-06-09 08:16:16 +03:00
Buffer []byte // розмір більший за кількість
2026-06-05 19:43:01 +00:00
RecordsCount int
}
type DataPayload struct {
Since uint32
Content []byte
TimestampsSize int
ValuesSize int
}
2026-06-13 08:01:42 +03:00
type IngestIn struct {
2026-06-05 19:43:01 +00:00
LastPageNo uint32
IndexLevelTails []IndexLevelTail
DataPages []DataPayload
}
type SealedDataPage struct {
2026-06-13 08:01:42 +03:00
PageNo uint32
Reused bool
Content []byte
2026-06-05 19:43:01 +00:00
}
type SealedIndexPage struct {
2026-06-13 08:01:42 +03:00
PageNo uint32
Reused bool
Content []byte
2026-06-05 19:43:01 +00:00
}
// SealedIndexLevel - як зрозуміти чи потрібно щось писати в WAL чи index файл,
// чи додавання нових data сторінок не зачепило індексний рівень?
// Якщо є IndexPages - пишемо весь рівень в WAL і готові сторінки в index файл.
// Якщо IndexPages пустий - порівнюємо Offset і len(Payload) - якщо довжина
// більша за змішення - дані додані на незаповнену сторінку рівня,
// отже різницю пишемо в WAL.
type SealedIndexLevel struct {
// Якщо є IndexPages, offset - це зміщення із якого починаються дані першої сторінки,
// які ще не зписані в WAL.
// Якщо IndexPages пустий, offset - це зміщення із якого починаються дані payload,
// які ще не зписані в WAL.
// В payload в будь-якому разі дані останньої індексної сторінки рівня (незаповненої).
SkipRecords int
IndexPages []SealedIndexPage
TailRecords []byte
TailRecordsCount int
}
2026-06-13 08:01:42 +03:00
func (s *PageIngester) Ingest(in IngestIn) ([]SealedDataPage, []*SealedIndexLevel) {
2026-06-05 19:43:01 +00:00
var (
sealedDataPages []SealedDataPage
sealedIndexLevels []*SealedIndexLevel
)
for _, tail := range in.IndexLevelTails {
sealedIndexLevels = append(sealedIndexLevels, &SealedIndexLevel{
SkipRecords: tail.RecordsCount,
2026-06-09 08:16:16 +03:00
TailRecords: tail.Buffer,
2026-06-05 19:43:01 +00:00
TailRecordsCount: tail.RecordsCount,
})
}
for dataPageIdx, d := range in.DataPages {
var (
upTimestamp = d.Since
upPageNo uint32
levelIdx = 0
prevPageNo uint32
)
if dataPageIdx == 0 {
prevPageNo = in.LastPageNo
} else {
2026-06-13 08:01:42 +03:00
prevPageNo = sealedDataPages[dataPageIdx-1].PageNo
2026-06-05 19:43:01 +00:00
}
pageNo, reused, err := s.getDataPageNumber()
if err != nil {
qb.Abort(qb.FailedGetPageNumber, err)
}
2026-06-13 08:01:42 +03:00
SealDataPage(SealDataPageIn{
Content: d.Content,
PrevPageNo: prevPageNo,
TimestampsSize: d.TimestampsSize,
ValuesSize: d.ValuesSize,
})
2026-06-05 19:43:01 +00:00
sealedDataPages = append(sealedDataPages, SealedDataPage{
2026-06-13 08:01:42 +03:00
Content: d.Content,
PageNo: pageNo,
Reused: reused,
2026-06-05 19:43:01 +00:00
})
upPageNo = pageNo
for {
if levelIdx == len(sealedIndexLevels) {
// новий root
sealedIndexLevels = append(sealedIndexLevels, &SealedIndexLevel{
2026-06-13 08:01:42 +03:00
TailRecords: appendIndexRecord(appendIndexRecordIn{
2026-06-05 19:43:01 +00:00
Timestamp: upTimestamp,
PageNo: upPageNo,
}),
TailRecordsCount: 1,
})
break
}
level := sealedIndexLevels[levelIdx]
2026-06-12 06:11:57 +00:00
if level.TailRecordsCount < maxRecordsOnIndexPage {
2026-06-13 08:01:42 +03:00
level.TailRecords = appendIndexRecord(appendIndexRecordIn{
Records: level.TailRecords,
RecordsCount: level.TailRecordsCount,
Timestamp: upTimestamp,
PageNo: upPageNo,
2026-06-05 19:43:01 +00:00
})
level.TailRecordsCount++
break
}
// в буфері немає місця для додавання нової пари, отже це
// заповнена сторінка
pageNo, reused, err := s.getIndexPageNumber()
if err != nil {
qb.Abort(qb.FailedGetPageNumber, err)
}
2026-06-13 08:01:42 +03:00
SealIndexPage(SealIndexPageIn{
Content: level.TailRecords,
RecordsCount: level.TailRecordsCount,
ZeroLevel: levelIdx == 0,
})
2026-06-05 19:43:01 +00:00
filled := SealedIndexPage{
Content: level.TailRecords,
PageNo: pageNo,
Reused: reused,
}
level.IndexPages = append(level.IndexPages, filled)
// новий tail на індексному рівні
2026-06-13 08:01:42 +03:00
level.TailRecords = appendIndexRecord(appendIndexRecordIn{
2026-06-05 19:43:01 +00:00
Timestamp: upTimestamp,
PageNo: upPageNo,
})
level.TailRecordsCount = 1
//
upPageNo = pageNo
2026-06-10 06:18:45 +03:00
upTimestamp, _ = bin.GetUint32(filled.Content)
2026-06-05 19:43:01 +00:00
levelIdx++
}
}
2026-06-13 08:01:42 +03:00
return sealedDataPages, sealedIndexLevels
2026-06-05 19:43:01 +00:00
}
2026-06-10 06:18:45 +03:00
// HELPERS
type SealDataPageIn struct {
Content []byte
PrevPageNo uint32
TimestampsSize int
ValuesSize int
}
func SealDataPage(in SealDataPageIn) (checksum uint32) {
2026-06-12 06:11:57 +00:00
bin.PutUint16(in.Content[timestampsSizeIdx:], uint16(in.TimestampsSize))
bin.PutUint16(in.Content[valuesSizeIdx:], uint16(in.ValuesSize))
bin.PutUint32(in.Content[prevPageIdx:], in.PrevPageNo)
2026-06-10 06:18:45 +03:00
2026-06-13 08:01:42 +03:00
checksum = util.CalculateCRC32(in.Content[:dataCRC32Idx])
2026-06-12 06:11:57 +00:00
bin.PutUint32(in.Content[dataCRC32Idx:], checksum)
2026-06-10 06:18:45 +03:00
return
}
type SealIndexPageIn struct {
Content []byte
RecordsCount int
ZeroLevel bool
}
func SealIndexPage(in SealIndexPageIn) (checksum uint32) {
2026-06-12 06:11:57 +00:00
bin.PutUint16(in.Content[indexRecordsCountIdx:], uint16(in.RecordsCount))
if in.ZeroLevel {
in.Content[isZeroLevelIdx] = 1
}
2026-06-13 08:01:42 +03:00
checksum = util.CalculateCRC32(in.Content[:indexCRC32Idx])
2026-06-12 06:11:57 +00:00
bin.PutUint32(in.Content[indexCRC32Idx:], checksum)
2026-06-10 06:18:45 +03:00
return
}
2026-06-13 08:01:42 +03:00
type appendIndexRecordIn struct {
Records []byte
RecordsCount int
Timestamp uint32
PageNo uint32
}
func appendIndexRecord(in appendIndexRecordIn) []byte {
if in.RecordsCount < maxRecordsOnIndexPage {
var (
pos int
buf = in.Records
)
if in.RecordsCount > 0 {
pos = in.RecordsCount * indexRecordSize
} else {
buf = make([]byte, IndexPageSize) // IndexPageIncSize
}
bin.PutUint32(buf[pos:], in.Timestamp)
bin.PutUint32(buf[pos+4:], in.PageNo)
return buf
}
qb.Abort(qb.NoSpaceOnIndexPage, nil)
return nil
}