wp
This commit is contained in:
314
storage/page_preparer.go
Normal file
314
storage/page_preparer.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
bin "gordenko.dev/dima/bin/little"
|
||||
"gordenko.dev/dima/qb"
|
||||
"gordenko.dev/dima/qb/util"
|
||||
)
|
||||
|
||||
// Задача метода:
|
||||
// - для data сторінок отримати pageNo та reused, задати sizes та prevPageNo, порахувати CRC32
|
||||
// - додати пари до index, згенерувати нові index сторінки
|
||||
// levels []*IndexLevel, dataPages []*DataPage
|
||||
// fix - reduce leves
|
||||
|
||||
type PagePreparer struct {
|
||||
dataChecksumIdx int
|
||||
maxRecordsOnIndexPage int
|
||||
indexPageIncSize int // кратно indexPageSize
|
||||
timestampsSizeIdx int
|
||||
valuesSizeIdx int
|
||||
prevPageIdx int
|
||||
indexRecordsCountIdx int
|
||||
isZeroLevelIdx int
|
||||
indexChecksumIdx int
|
||||
getDataPageNumber func() (uint32, bool, error)
|
||||
getIndexPageNumber func() (uint32, bool, error)
|
||||
}
|
||||
|
||||
type PagePreparerOptions struct {
|
||||
IndexPageSize int
|
||||
IndexPageIncSize int
|
||||
DataPageSize int
|
||||
GetDataPageNumber func() (uint32, bool, error)
|
||||
GetIndexPageNumber func() (uint32, bool, error)
|
||||
}
|
||||
|
||||
func NewPagePreparer(opt PagePreparerOptions) (*PagePreparer, error) {
|
||||
if opt.GetIndexPageNumber == nil {
|
||||
return nil, errors.New("missing required option: GetIndexPageNumber")
|
||||
}
|
||||
if opt.GetDataPageNumber == nil {
|
||||
return nil, errors.New("missing required option: GetDataPageNumber")
|
||||
}
|
||||
if (opt.IndexPageSize % opt.IndexPageIncSize) != 0 {
|
||||
return nil, errors.New("IndexPageIncSize must multiple of IndexPageSize")
|
||||
}
|
||||
s := &PagePreparer{
|
||||
dataChecksumIdx: opt.DataPageSize - 4,
|
||||
indexPageIncSize: opt.IndexPageIncSize,
|
||||
timestampsSizeIdx: opt.DataPageSize - 6,
|
||||
valuesSizeIdx: opt.DataPageSize - 8,
|
||||
prevPageIdx: opt.DataPageSize - 12,
|
||||
indexRecordsCountIdx: opt.IndexPageSize - 6,
|
||||
isZeroLevelIdx: opt.IndexPageSize - 7,
|
||||
indexChecksumIdx: opt.IndexPageSize - 4,
|
||||
getIndexPageNumber: opt.GetIndexPageNumber,
|
||||
getDataPageNumber: opt.GetDataPageNumber,
|
||||
}
|
||||
s.maxRecordsOnIndexPage = (opt.IndexPageSize - 7) / indexRecordSize
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type appendIndexRecordIn struct {
|
||||
Records []byte
|
||||
RecordsCount int
|
||||
Timestamp uint32
|
||||
PageNo uint32
|
||||
}
|
||||
|
||||
func (s *PagePreparer) appendIndexRecord(in appendIndexRecordIn) []byte {
|
||||
if in.RecordsCount < s.maxRecordsOnIndexPage {
|
||||
var (
|
||||
pos int
|
||||
buf = in.Records
|
||||
)
|
||||
if in.RecordsCount > 0 {
|
||||
pos = in.RecordsCount * indexRecordSize
|
||||
} else {
|
||||
buf = make([]byte, s.indexPageIncSize)
|
||||
}
|
||||
bin.PutUint32(buf[pos:], in.Timestamp)
|
||||
bin.PutUint32(buf[pos+4:], in.PageNo)
|
||||
return buf
|
||||
}
|
||||
qb.Abort(qb.NoSpaceOnIndexPage, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
type sealDataPageIn struct {
|
||||
Content []byte
|
||||
PrevPageNo uint32
|
||||
TimestampsSize int
|
||||
ValuesSize int
|
||||
}
|
||||
|
||||
func (s *PagePreparer) sealDataPage(in sealDataPageIn) (checksum uint32) {
|
||||
bin.PutUint16(in.Content[s.timestampsSizeIdx:], uint16(in.TimestampsSize))
|
||||
bin.PutUint16(in.Content[s.valuesSizeIdx:], uint16(in.ValuesSize))
|
||||
bin.PutUint32(in.Content[s.prevPageIdx:], in.PrevPageNo)
|
||||
|
||||
checksum = util.CalcChecksum(in.Content[:s.dataChecksumIdx])
|
||||
bin.PutUint32(in.Content[s.dataChecksumIdx:], checksum)
|
||||
return
|
||||
}
|
||||
|
||||
type sealIndexPageIn struct {
|
||||
Content []byte
|
||||
RecordsCount int
|
||||
LastLevel bool
|
||||
}
|
||||
|
||||
func (s *PagePreparer) sealIndexPage(in sealIndexPageIn) (checksum uint32) {
|
||||
bin.PutUint16(in.Content[s.indexRecordsCountIdx:], uint16(in.RecordsCount))
|
||||
if in.LastLevel {
|
||||
in.Content[s.isZeroLevelIdx] = 1
|
||||
}
|
||||
checksum = util.CalcChecksum(in.Content[:s.indexChecksumIdx])
|
||||
bin.PutUint32(in.Content[s.indexChecksumIdx:], checksum)
|
||||
return
|
||||
}
|
||||
|
||||
type IndexLevelTail struct {
|
||||
Buffer []byte // розмір більший за кількість
|
||||
RecordsCount int
|
||||
}
|
||||
|
||||
type DataPayload struct {
|
||||
Since uint32
|
||||
Content []byte
|
||||
TimestampsSize int
|
||||
ValuesSize int
|
||||
}
|
||||
|
||||
type SealPagesIn struct {
|
||||
LastPageNo uint32
|
||||
IndexLevelTails []IndexLevelTail
|
||||
DataPages []DataPayload
|
||||
}
|
||||
|
||||
type SealedDataPage struct {
|
||||
PageNo uint32
|
||||
Reused bool
|
||||
PrevPageNo uint32
|
||||
Checksum uint32
|
||||
Content []byte
|
||||
}
|
||||
|
||||
type SealedIndexPage struct {
|
||||
Content []byte
|
||||
PageNo uint32
|
||||
Reused bool
|
||||
Checksum uint32
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type SealResult struct {
|
||||
DataPages []SealedDataPage
|
||||
IndexLevels []*SealedIndexLevel
|
||||
}
|
||||
|
||||
func (s *PagePreparer) SealPages(in SealPagesIn) (result SealResult) {
|
||||
var (
|
||||
sealedDataPages []SealedDataPage
|
||||
sealedIndexLevels []*SealedIndexLevel
|
||||
)
|
||||
for _, tail := range in.IndexLevelTails {
|
||||
sealedIndexLevels = append(sealedIndexLevels, &SealedIndexLevel{
|
||||
SkipRecords: tail.RecordsCount,
|
||||
TailRecords: tail.Buffer,
|
||||
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 {
|
||||
prevPageNo = result.DataPages[dataPageIdx-1].PageNo
|
||||
}
|
||||
pageNo, reused, err := s.getDataPageNumber()
|
||||
if err != nil {
|
||||
qb.Abort(qb.FailedGetPageNumber, err)
|
||||
}
|
||||
sealedDataPages = append(sealedDataPages, SealedDataPage{
|
||||
Content: d.Content,
|
||||
PrevPageNo: prevPageNo,
|
||||
PageNo: pageNo,
|
||||
Reused: reused,
|
||||
Checksum: s.sealDataPage(sealDataPageIn{
|
||||
Content: d.Content,
|
||||
PrevPageNo: prevPageNo,
|
||||
TimestampsSize: d.TimestampsSize,
|
||||
ValuesSize: d.ValuesSize,
|
||||
}),
|
||||
})
|
||||
upPageNo = pageNo
|
||||
for {
|
||||
if levelIdx == len(sealedIndexLevels) {
|
||||
// новий root
|
||||
sealedIndexLevels = append(sealedIndexLevels, &SealedIndexLevel{
|
||||
TailRecords: s.appendIndexRecord(appendIndexRecordIn{
|
||||
Records: nil,
|
||||
Timestamp: upTimestamp,
|
||||
PageNo: upPageNo,
|
||||
}),
|
||||
TailRecordsCount: 1,
|
||||
})
|
||||
break
|
||||
}
|
||||
level := sealedIndexLevels[levelIdx]
|
||||
if level.TailRecordsCount < s.maxRecordsOnIndexPage {
|
||||
level.TailRecords = s.appendIndexRecord(appendIndexRecordIn{
|
||||
Records: level.TailRecords,
|
||||
Timestamp: upTimestamp,
|
||||
PageNo: upPageNo,
|
||||
})
|
||||
level.TailRecordsCount++
|
||||
break
|
||||
}
|
||||
// в буфері немає місця для додавання нової пари, отже це
|
||||
// заповнена сторінка
|
||||
pageNo, reused, err := s.getIndexPageNumber()
|
||||
if err != nil {
|
||||
qb.Abort(qb.FailedGetPageNumber, err)
|
||||
}
|
||||
|
||||
filled := SealedIndexPage{
|
||||
Content: level.TailRecords,
|
||||
PageNo: pageNo,
|
||||
Reused: reused,
|
||||
Checksum: s.sealIndexPage(sealIndexPageIn{
|
||||
Content: level.TailRecords,
|
||||
RecordsCount: level.TailRecordsCount,
|
||||
LastLevel: levelIdx == 0,
|
||||
}),
|
||||
}
|
||||
level.IndexPages = append(level.IndexPages, filled)
|
||||
// новий tail на індексному рівні
|
||||
level.TailRecords = s.appendIndexRecord(appendIndexRecordIn{
|
||||
Records: nil,
|
||||
Timestamp: upTimestamp,
|
||||
PageNo: upPageNo,
|
||||
})
|
||||
level.TailRecordsCount = 1
|
||||
//
|
||||
upPageNo = pageNo
|
||||
upTimestamp, _ = bin.GetUint32(filled.Content)
|
||||
levelIdx++
|
||||
}
|
||||
}
|
||||
return SealResult{
|
||||
DataPages: sealedDataPages,
|
||||
IndexLevels: sealedIndexLevels,
|
||||
}
|
||||
}
|
||||
|
||||
// HELPERS
|
||||
|
||||
type SealDataPageIn struct {
|
||||
Content []byte
|
||||
PrevPageNo uint32
|
||||
TimestampsSize int
|
||||
ValuesSize int
|
||||
}
|
||||
|
||||
func SealDataPage(in SealDataPageIn) (checksum uint32) {
|
||||
// bin.PutUint16(in.Content[s.timestampsSizeIdx:], uint16(in.TimestampsSize))
|
||||
// bin.PutUint16(in.Content[s.valuesSizeIdx:], uint16(in.ValuesSize))
|
||||
// bin.PutUint32(in.Content[s.prevPageIdx:], in.PrevPageNo)
|
||||
|
||||
// checksum = util.CalcChecksum(in.Content[:s.dataChecksumIdx])
|
||||
// bin.PutUint32(in.Content[s.dataChecksumIdx:], checksum)
|
||||
return
|
||||
}
|
||||
|
||||
type SealIndexPageIn struct {
|
||||
Content []byte
|
||||
RecordsCount int
|
||||
ZeroLevel bool
|
||||
}
|
||||
|
||||
func SealIndexPage(in SealIndexPageIn) (checksum uint32) {
|
||||
// bin.PutUint16(in.Content[s.indexRecordsCountIdx:], uint16(in.RecordsCount))
|
||||
// if in.LastLevel {
|
||||
// in.Content[s.isZeroLevelIdx] = 1
|
||||
// }
|
||||
// checksum = util.CalcChecksum(in.Content[:s.indexChecksumIdx])
|
||||
// bin.PutUint32(in.Content[s.indexChecksumIdx:], checksum)
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user