wp
This commit is contained in:
207
storage/data_preparer.go
Normal file
207
storage/data_preparer.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
bin "gordenko.dev/dima/bin/little"
|
||||
"gordenko.dev/dima/qb/util"
|
||||
)
|
||||
|
||||
type DataPreparer struct {
|
||||
pagePreparer *PagePreparer
|
||||
//dataPagePayloadBound int
|
||||
w *bytes.Buffer
|
||||
indexPages []PageToWrite
|
||||
dataPages []PageToWrite
|
||||
writeResults []any
|
||||
}
|
||||
|
||||
type DataPreparerOptions struct {
|
||||
MinWALBufferSize int
|
||||
PagePreparer *PagePreparer
|
||||
}
|
||||
|
||||
func NewDataPreparer(opt DataPreparerOptions) *DataPreparer {
|
||||
buf := make([]byte, opt.MinWALBufferSize)
|
||||
s := &DataPreparer{
|
||||
pagePreparer: opt.PagePreparer,
|
||||
w: bytes.NewBuffer(buf),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type PreparedData struct {
|
||||
Packet []byte // пакет для запису в WAL
|
||||
WriteToIndex []PageToWrite
|
||||
WriteToData []PageToWrite
|
||||
WriteResults []any
|
||||
}
|
||||
|
||||
func (s *DataPreparer) 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 MetricAddRecord:
|
||||
x.Pack(w)
|
||||
// case DeletedMetric:
|
||||
// if len(x.FreePageNumbers) > 0 {
|
||||
// s.freeList.AddPageNumbers(x.FreePageNumbers)
|
||||
// }
|
||||
case AppendedMeasures:
|
||||
sealResult := s.pagePreparer.SealPages(SealPagesIn{
|
||||
LastPageNo: x.LastPageNo,
|
||||
IndexLevelTails: x.IndexLevelTails,
|
||||
DataPages: x.DataPages,
|
||||
})
|
||||
|
||||
// сторінки для запису в index та data файли
|
||||
for _, p := range sealResult.DataPages {
|
||||
s.dataPages = append(s.dataPages, PageToWrite{
|
||||
PageNo: p.PageNo,
|
||||
Content: p.Content,
|
||||
})
|
||||
}
|
||||
for _, level := range sealResult.IndexLevels {
|
||||
for _, p := range level.IndexPages {
|
||||
s.indexPages = append(s.indexPages, PageToWrite{
|
||||
PageNo: p.PageNo,
|
||||
Content: p.Content,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// дані для запису в WAL
|
||||
changedIndexPages []ChangedIndexLevel
|
||||
// дані для Воркера
|
||||
indexLevelTails []IndexLevelTail
|
||||
)
|
||||
|
||||
for _, level := range sealResult.IndexLevels {
|
||||
if len(level.IndexPages) == 0 && level.SkipRecords == level.TailRecordsCount {
|
||||
break
|
||||
}
|
||||
var completedIndexPage *HeadIndexPage
|
||||
if len(level.IndexPages) > 0 {
|
||||
first := level.IndexPages[0]
|
||||
skipSize := level.SkipRecords * indexRecordSize
|
||||
completedIndexPage = &HeadIndexPage{
|
||||
PageNo: first.PageNo,
|
||||
Reused: first.Reused,
|
||||
Checksum: first.Checksum,
|
||||
Records: first.Content[skipSize:],
|
||||
}
|
||||
}
|
||||
changedIndexPages = append(changedIndexPages, ChangedIndexLevel{
|
||||
HeadIndexPage: completedIndexPage,
|
||||
IndexPages: level.IndexPages[1:],
|
||||
TailRecords: level.TailRecords[:level.TailRecordsCount*indexRecordSize],
|
||||
})
|
||||
|
||||
indexLevelTails = append(indexLevelTails, IndexLevelTail{
|
||||
Buffer: level.TailRecords,
|
||||
RecordsCount: level.TailRecordsCount,
|
||||
})
|
||||
}
|
||||
|
||||
first := sealResult.DataPages[0]
|
||||
|
||||
rec := MeasuresAppendWithGrowRecord{
|
||||
MetricID: x.MetricID,
|
||||
HeadDataPage: HeadDataPage{
|
||||
PageNo: first.PageNo,
|
||||
Reused: first.Reused,
|
||||
PrevPageNo: first.PrevPageNo,
|
||||
Checksum: first.Checksum,
|
||||
TimestampsOffset: x.TimestampsOffset,
|
||||
Timestamps: x.Timestamps,
|
||||
ValuesOffset: x.ValuesOffset,
|
||||
Values: x.Values,
|
||||
},
|
||||
DataPages: sealResult.DataPages[1:],
|
||||
ChangedIndexLevels: changedIndexPages,
|
||||
TailTimestamps: x.Timestamps,
|
||||
TailValues: x.Values,
|
||||
}
|
||||
|
||||
rec.WriteTo(s.w)
|
||||
|
||||
// Дані для Worker
|
||||
s.writeResults = append(s.writeResults, AppendMeasuresSummary{
|
||||
MetricID: x.MetricID,
|
||||
LastPageNo: sealResult.DataPages[len(sealResult.DataPages)-1].PageNo,
|
||||
Index: indexLevelTails,
|
||||
ResultCode: x.ResultCode,
|
||||
WrittenCount: x.WrittenCount,
|
||||
ResultCh: x.ResultCh,
|
||||
})
|
||||
|
||||
//case DeletedMeasures:
|
||||
//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)
|
||||
// checksum
|
||||
bin.PutUint32(packet[9:], hasher.Sum32())
|
||||
//
|
||||
return PreparedData{
|
||||
Packet: packet[start:],
|
||||
WriteToIndex: s.indexPages,
|
||||
WriteToData: s.dataPages,
|
||||
WriteResults: s.writeResults,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DataPreparer) Reset() {
|
||||
s.w.Reset()
|
||||
s.indexPages = nil
|
||||
s.dataPages = nil
|
||||
s.writeResults = nil
|
||||
}
|
||||
|
||||
// MeasuresToWrite
|
||||
type AppendedMeasures struct {
|
||||
MetricID uint32
|
||||
LastPageNo uint32
|
||||
TimestampsOffset int // offset on 1st page
|
||||
Timestamps []byte
|
||||
ValuesOffset int // offset on 1st page
|
||||
Values []byte
|
||||
IndexLevelTails []IndexLevelTail
|
||||
DataPages []DataPayload
|
||||
TailTimestamps []byte // data level tail
|
||||
TailValues []byte // data level tail
|
||||
ResultCode int
|
||||
WrittenCount int
|
||||
ResultCh chan struct{}
|
||||
}
|
||||
|
||||
// WRITE RESULTS
|
||||
|
||||
// результат
|
||||
type AppendMeasuresSummary struct {
|
||||
MetricID uint32
|
||||
LastPageNo uint32
|
||||
Index []IndexLevelTail
|
||||
ResultCode int
|
||||
WrittenCount int
|
||||
ResultCh chan struct{}
|
||||
}
|
||||
202
storage/helpers.go
Normal file
202
storage/helpers.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
bin "gordenko.dev/dima/bin/little"
|
||||
)
|
||||
|
||||
const recordSize = 8
|
||||
const chunkSize = 24
|
||||
|
||||
func copyPayloadFromChunks(w *bytes.Buffer, chunks [][]byte, size int, offset int) {
|
||||
bin.WriteUint16(w, uint16(size))
|
||||
var (
|
||||
chunkIdx = offset / chunkSize
|
||||
byteIdx = offset % chunkSize
|
||||
)
|
||||
for _, chunk := range chunks[chunkIdx:] {
|
||||
available := len(chunk) - byteIdx
|
||||
if available <= size {
|
||||
w.Write(chunk[byteIdx:])
|
||||
size -= available
|
||||
if size == 0 {
|
||||
return
|
||||
}
|
||||
byteIdx = 0
|
||||
} else {
|
||||
w.Write(chunk[byteIdx : byteIdx+size])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
|
||||
// type DataPage struct {
|
||||
// PageNo uint32
|
||||
// Reused bool
|
||||
// PrevPageNo uint32
|
||||
// Checksum uint32
|
||||
// Timestamps []byte
|
||||
// Values []byte
|
||||
// }
|
||||
|
||||
// type IndexPage struct {
|
||||
// PageNo uint32
|
||||
// Reused bool
|
||||
// Checksum uint32
|
||||
// Records []byte
|
||||
// }
|
||||
|
||||
// type IndexLevel struct {
|
||||
// Pages []IndexPage
|
||||
// Records []byte // unfilled
|
||||
// }
|
||||
|
||||
// Для декодінга
|
||||
// type TxAppendedMeasures struct {
|
||||
// MetricID uint32
|
||||
// DataPages []DataPage
|
||||
// // offsets потрібні тому що дані не додаються в кінець, а перезаписують кілька
|
||||
// // останніх байтів unfilled даних. Потрібно для коректного recovery.
|
||||
// // Якщо є DataPages, то застосовуються для 1-ї Data сторінки, інакше - до timestamps і values (unfilled)
|
||||
// TimestampsOffset int
|
||||
// ValuesOffset int
|
||||
// Timestamps []byte // unfilled
|
||||
// Values []byte // unfilled
|
||||
// IndexLevels []IndexLevel
|
||||
// }
|
||||
|
||||
// func (s *TxAppendedMeasures) Read(r *bytes.Buffer) (err error) {
|
||||
// s.MetricID, err = bin.ReadUint32(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// dataPagesQty, err := bin.ReadVarSize(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range dataPagesQty {
|
||||
// var (
|
||||
// p DataPage
|
||||
// timestampsSize, valuesSize int
|
||||
// //b byte
|
||||
// )
|
||||
// p.PageNo, err = bin.ReadUint32(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// p.Reused, err = bin.ReadBool(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// p.PrevPageNo, err = bin.ReadUint32(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// p.Checksum, err = bin.ReadUint32(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// timestampsSize, err = bin.ReadUint16AsInt(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// p.Timestamps, err = bin.ReadN(r, timestampsSize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// valuesSize, err = bin.ReadUint16AsInt(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// p.Values, err = bin.ReadN(r, valuesSize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.DataPages = append(s.DataPages, p)
|
||||
// }
|
||||
// s.TimestampsOffset, err = bin.ReadUint16AsInt(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.ValuesOffset, err = bin.ReadUint16AsInt(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// var (
|
||||
// timestampsSize, valuesSize int
|
||||
// )
|
||||
// timestampsSize, err = bin.ReadVarSize(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.Timestamps, err = bin.ReadN(r, timestampsSize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// valuesSize, err = bin.ReadVarSize(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.Values, err = bin.ReadN(r, valuesSize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// if len(s.DataPages) == 0 {
|
||||
// return
|
||||
// }
|
||||
// indexLevelsQty, err := bin.ReadVarSize(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range indexLevelsQty {
|
||||
// var (
|
||||
// indexPagesQty int
|
||||
// level IndexLevel
|
||||
// recordsQty int
|
||||
// )
|
||||
// indexPagesQty, err = bin.ReadVarSize(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range indexPagesQty {
|
||||
// var (
|
||||
// p IndexPage
|
||||
// recordsQty int
|
||||
// )
|
||||
// p.PageNo, err = bin.ReadUint32(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// p.Reused, err = bin.ReadBool(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// p.Checksum, err = bin.ReadUint32(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// recordsQty, err = bin.ReadVarSize(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// p.Records, err = bin.ReadN(r, recordsQty*recordSize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// level.Pages = append(level.Pages, p)
|
||||
// }
|
||||
// recordsQty, err = bin.ReadVarSize(r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// level.Records, err = bin.ReadN(r, recordsQty*recordSize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.IndexLevels = append(s.IndexLevels, level)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
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
|
||||
}
|
||||
32
storage/storage.go
Normal file
32
storage/storage.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package storage
|
||||
|
||||
const (
|
||||
|
||||
//PageNoSize = 4
|
||||
|
||||
// data page
|
||||
DataPageSize = 8192
|
||||
DataPageFooterSize int = 12
|
||||
DataPagePayloadSize int = DataPageSize - DataPageFooterSize
|
||||
|
||||
dataCRC32Idx = DataPageSize - 4
|
||||
timestampsSizeIdx = DataPageSize - 6
|
||||
valuesSizeIdx = DataPageSize - 8
|
||||
prevPageIdx = DataPageSize - 12
|
||||
|
||||
// index page
|
||||
IndexPageSize = 1024
|
||||
IndexPageFooterSize = 7
|
||||
indexRecordSize = 8
|
||||
maxRecordsOnIndexPage = (IndexPageSize - IndexPageFooterSize) / indexRecordSize
|
||||
|
||||
//indexPageIncSize = IndexPageIncSize
|
||||
indexCRC32Idx = IndexPageSize - 4
|
||||
indexRecordsCountIdx = IndexPageSize - 6
|
||||
isZeroLevelIdx = IndexPageSize - 7
|
||||
|
||||
// timestampSize = 4
|
||||
// pairSize = timestampSize + PageNoSize
|
||||
// indexFooterIdx = indexRecordsQtyIdx
|
||||
// dataFooterIdx = timestampsSizeIdx
|
||||
)
|
||||
362
storage/wal_reader.go
Normal file
362
storage/wal_reader.go
Normal file
@@ -0,0 +1,362 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
bin "gordenko.dev/dima/bin/little"
|
||||
"gordenko.dev/dima/qb/util"
|
||||
)
|
||||
|
||||
const readBufferSize = 8 * 1024 * 1024
|
||||
|
||||
type WALReader struct {
|
||||
file *os.File
|
||||
reader *bufio.Reader
|
||||
current []any
|
||||
next []any
|
||||
}
|
||||
|
||||
type WALReaderOptions struct {
|
||||
FileName string
|
||||
BufferSize int
|
||||
}
|
||||
|
||||
func NewWALReader(opt WALReaderOptions) (*WALReader, error) {
|
||||
if opt.FileName == "" {
|
||||
return nil, errors.New("FileName option is required")
|
||||
}
|
||||
if opt.BufferSize <= 0 {
|
||||
return nil, errors.New("BufferSize option is required")
|
||||
}
|
||||
|
||||
file, err := os.Open(opt.FileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &WALReader{
|
||||
file: file,
|
||||
reader: bufio.NewReaderSize(file, readBufferSize),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *WALReader) Close() {
|
||||
s.file.Close()
|
||||
}
|
||||
|
||||
func (s *WALReader) NextPacket() ([]any, bool, error) {
|
||||
var err error
|
||||
if s.current == nil {
|
||||
s.current, err = s.readPacket()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if len(s.current) > 0 {
|
||||
s.next, err = s.readPacket()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current := s.current
|
||||
done := s.next == nil
|
||||
|
||||
s.current = s.next
|
||||
s.next, err = s.readPacket()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return current, done, nil
|
||||
}
|
||||
|
||||
func (s *WALReader) readPacket() (_ []any, err error) {
|
||||
payloadSize, err := bin.ReadVarSize(s.reader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
storedCRC, err := bin.ReadUint32(s.reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err := bin.ReadN(s.reader, int(payloadSize))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hasher := util.NewHasher()
|
||||
hasher.Write(body)
|
||||
|
||||
calculatedCRC := hasher.Sum32()
|
||||
|
||||
if calculatedCRC == storedCRC {
|
||||
return s.parseRecords(body)
|
||||
}
|
||||
//s.logger.Printf("stored CRC %d != calculated CRC %d", storedCRC, calculatedCRC)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *WALReader) parseRecords(body []byte) ([]any, error) {
|
||||
var (
|
||||
src = bytes.NewBuffer(body)
|
||||
records []any
|
||||
)
|
||||
|
||||
for {
|
||||
recordType, err := src.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return records, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch recordType {
|
||||
case CodeMetricAdd:
|
||||
rec := new(MetricAddRecord)
|
||||
if err = rec.Parse(src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, rec)
|
||||
|
||||
case CodeMetricDelete:
|
||||
rec := new(MetricDeleteRecord)
|
||||
if err = rec.Parse(src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, rec)
|
||||
|
||||
// case CodeAppendedMeasure:
|
||||
// rec := new(AppendedMeasure)
|
||||
// if err = rec.Parse(src); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// records = append(records, rec)
|
||||
|
||||
// case CodeAppendedMeasures:
|
||||
// rec := new(AppendedMeasures)
|
||||
// if err = rec.Parse(src); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// records = append(records, rec)
|
||||
|
||||
// case CodeAppendedPages:
|
||||
// rec := new(AppendedPages)
|
||||
// if err = rec.Parse(src); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// records = append(records, rec)
|
||||
|
||||
case CodeMeasuresDelete:
|
||||
rec := new(DeletedMeasures)
|
||||
if err = rec.Parse(src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, rec)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown record type code: %d", recordType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func (s *Reader) readAddedMetric(src *bytes.Buffer) (_ AddedMetric, err error) {
|
||||
// arr, err := bin.ReadN(src, 6)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// return AddedMetric{
|
||||
// MetricID: bin.GetUint32(arr),
|
||||
// MetricType: diploma.MetricType(arr[4]),
|
||||
// FracDigits: int(arr[5]),
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
// func (s *Reader) readDeletedMetric(src *bytes.Buffer) (_ DeletedMetric, err error) {
|
||||
// var rec DeletedMetric
|
||||
// rec.MetricID, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// // free data pages
|
||||
// dataQty, _, err := bin.ReadVarUint64(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range dataQty {
|
||||
// var pageNo uint32
|
||||
// pageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// rec.FreeDataPages = append(rec.FreeDataPages, pageNo)
|
||||
// }
|
||||
// // free index pages
|
||||
// indexQty, _, err := bin.ReadVarUint64(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range indexQty {
|
||||
// var pageNo uint32
|
||||
// pageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// rec.FreeIndexPages = append(rec.FreeIndexPages, pageNo)
|
||||
// }
|
||||
// return rec, nil
|
||||
// }
|
||||
|
||||
// func (s *Reader) readAppendedMeasure(src *bytes.Buffer) (_ AppendedMeasure, err error) {
|
||||
// arr, err := bin.ReadN(src, 16)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// return AppendedMeasure{
|
||||
// MetricID: bin.GetUint32(arr[0:]),
|
||||
// Timestamp: bin.GetUint32(arr[4:]),
|
||||
// Value: bin.GetFloat64(arr[8:]),
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
// func (s *Reader) readAppendedMeasures(src *bytes.Buffer) (_ AppendedMeasures, err error) {
|
||||
// var rec AppendedMeasures
|
||||
// rec.MetricID, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// qty, err := bin.ReadUint16(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range qty {
|
||||
// var measure proto.Measure
|
||||
// measure.Timestamp, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// measure.Value, err = bin.ReadFloat64(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// rec.Measures = append(rec.Measures, measure)
|
||||
// }
|
||||
// return rec, nil
|
||||
// }
|
||||
|
||||
// func (s *Reader) readAppendedMeasureWithOverflow(src *bytes.Buffer) (_ AppendedMeasureWithOverflow, err error) {
|
||||
// var (
|
||||
// b byte
|
||||
// rec AppendedMeasureWithOverflow
|
||||
// )
|
||||
// rec.MetricID, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// rec.Timestamp, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// rec.Value, err = bin.ReadFloat64(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// b, err = src.ReadByte()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// rec.IsDataPageReused = b == 1
|
||||
|
||||
// rec.DataPageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// b, err = src.ReadByte()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// if b == 1 {
|
||||
// rec.IsRootChanged = true
|
||||
// rec.RootPageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// // index pages
|
||||
// indexQty, err := src.ReadByte()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range indexQty {
|
||||
// var pageNo uint32
|
||||
// pageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// rec.ReusedIndexPages = append(rec.ReusedIndexPages, pageNo)
|
||||
// }
|
||||
// return rec, nil
|
||||
// }
|
||||
|
||||
// func (s *Reader) readDeletedMeasures(src *bytes.Buffer) (_ DeletedMeasures, err error) {
|
||||
// var (
|
||||
// rec DeletedMeasures
|
||||
// )
|
||||
// rec.MetricID, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// // free data pages
|
||||
// rec.FreeDataPages, err = s.readFreePageNumbers(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// // free index pages
|
||||
// rec.FreeIndexPages, err = s.readFreePageNumbers(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// return rec, nil
|
||||
// }
|
||||
|
||||
// HELPERS
|
||||
|
||||
// func (s *Reader) readFreePageNumbers(src *bytes.Buffer) ([]uint32, error) {
|
||||
// var freePages []uint32
|
||||
// qty, _, err := bin.ReadVarUint64(src)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// for range qty {
|
||||
// var pageNo uint32
|
||||
// pageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// freePages = append(freePages, pageNo)
|
||||
// }
|
||||
// return freePages, nil
|
||||
// }
|
||||
|
||||
func (s *WALReader) Seek(offset int64) error {
|
||||
ret, err := s.file.Seek(offset, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ret != offset {
|
||||
return fmt.Errorf("ret %d != offset %d", ret, offset)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
666
storage/wal_records.go
Normal file
666
storage/wal_records.go
Normal file
@@ -0,0 +1,666 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
bin "gordenko.dev/dima/bin/little"
|
||||
"gordenko.dev/dima/qb"
|
||||
)
|
||||
|
||||
type HeadDataPage struct {
|
||||
PageNo uint32
|
||||
Reused bool
|
||||
PrevPageNo uint32
|
||||
Checksum uint32
|
||||
TimestampsOffset int // offset on 1st page
|
||||
Timestamps []byte
|
||||
ValuesOffset int // offset on 1st page
|
||||
Values []byte
|
||||
}
|
||||
|
||||
type HeadIndexPage struct {
|
||||
PageNo uint32
|
||||
Reused bool
|
||||
Checksum uint32
|
||||
Records []byte // offset не потрібен, оскільки Records додаються в кінець
|
||||
}
|
||||
|
||||
type ChangedIndexLevel struct {
|
||||
HeadIndexPage *HeadIndexPage
|
||||
IndexPages []SealedIndexPage
|
||||
TailRecords []byte // payload only
|
||||
}
|
||||
|
||||
type MeasuresAppendWithGrowRecord struct {
|
||||
MetricID uint32
|
||||
HeadDataPage HeadDataPage
|
||||
DataPages []SealedDataPage
|
||||
ChangedIndexLevels []ChangedIndexLevel
|
||||
TailTimestamps []byte // data level tail
|
||||
TailValues []byte // data level tail
|
||||
}
|
||||
|
||||
/*
|
||||
Format appended measures:
|
||||
1b - tx type
|
||||
4b - metricID
|
||||
4b - pageNo
|
||||
1b - reused
|
||||
4b - prevPageNo
|
||||
4b - page crc32
|
||||
Nb - (varsize) timestamps offset
|
||||
Nb - (varsize) timestamps tail size on 1st filled data page
|
||||
Nb - timestamps tail on 1st filled data page
|
||||
Nb - (varsize) values offset
|
||||
Nb - (varsize) values tail size on 1st filled data page
|
||||
Nb - values tail on 1st filled data page
|
||||
|
||||
Nb - (varsize) filled data pages qty
|
||||
[
|
||||
|
||||
4b - pageNo
|
||||
1b - reused
|
||||
Nb - (data page size) page content
|
||||
|
||||
]
|
||||
Nb - (varsize) timestamps payload size on tail
|
||||
Nb - timestamps payload
|
||||
Nb - (varsize) values payload size on tail
|
||||
Nb - values payload
|
||||
Nb - (varsize) qty of index levels
|
||||
[
|
||||
|
||||
// NOTE!
|
||||
// - idx = 0 - zeroLevel
|
||||
// - skipped = maxRecordsOnIndexPage - records count
|
||||
4b - pageNo of 1st index page
|
||||
1b - reused
|
||||
4b - page crc32
|
||||
Nb - (varsize) records tail count on 1st filled index page
|
||||
Nb - records tail on 1st filled index page
|
||||
|
||||
|
||||
Nb - (varsize) qty of level filled pages
|
||||
[
|
||||
4b - pageNo
|
||||
1b - reused
|
||||
Nb - (index page size) page content
|
||||
]
|
||||
Nb - (varsize) size of records level tail
|
||||
Nb - level tail records
|
||||
|
||||
]
|
||||
*/
|
||||
func (s MeasuresAppendWithGrowRecord) WriteTo(w *bytes.Buffer) {
|
||||
w.WriteByte(CodeMeasuresAppendWithGrow)
|
||||
bin.WriteUint32(w, s.MetricID)
|
||||
// completed data page
|
||||
completed := s.HeadDataPage
|
||||
bin.WriteUint32(w, completed.PageNo)
|
||||
bin.WriteBool(w, completed.Reused)
|
||||
bin.WriteUint32(w, completed.PrevPageNo)
|
||||
bin.WriteUint32(w, completed.Checksum)
|
||||
bin.WriteVarSize(w, completed.TimestampsOffset)
|
||||
bin.WriteVarSize(w, len(completed.Timestamps))
|
||||
w.Write(completed.Timestamps)
|
||||
bin.WriteVarSize(w, completed.ValuesOffset)
|
||||
bin.WriteVarSize(w, len(completed.Values))
|
||||
w.Write(completed.Values)
|
||||
// data pages
|
||||
bin.WriteVarSize(w, len(s.DataPages))
|
||||
for _, p := range s.DataPages {
|
||||
bin.WriteUint32(w, p.PageNo)
|
||||
bin.WriteBool(w, p.Reused)
|
||||
bin.WriteVarSize(w, len(p.Content))
|
||||
w.Write(p.Content)
|
||||
}
|
||||
// data tail
|
||||
bin.WriteVarSize(w, len(s.TailTimestamps))
|
||||
w.Write(s.TailTimestamps)
|
||||
bin.WriteVarSize(w, len(s.TailValues))
|
||||
w.Write(s.TailValues)
|
||||
// changed levels
|
||||
bin.WriteVarSize(w, len(s.ChangedIndexLevels))
|
||||
for _, level := range s.ChangedIndexLevels {
|
||||
// completed index page
|
||||
completed := level.HeadIndexPage
|
||||
bin.WriteUint32(w, completed.PageNo)
|
||||
bin.WriteBool(w, completed.Reused)
|
||||
bin.WriteUint32(w, completed.Checksum)
|
||||
bin.WriteVarSize(w, len(completed.Records)/indexRecordSize)
|
||||
w.Write(completed.Records)
|
||||
// full index pages
|
||||
for _, p := range level.IndexPages {
|
||||
bin.WriteUint32(w, p.PageNo)
|
||||
bin.WriteBool(w, p.Reused)
|
||||
bin.WriteVarSize(w, len(p.Content))
|
||||
w.Write(p.Content)
|
||||
}
|
||||
// level tail
|
||||
bin.WriteVarSize(w, len(level.TailRecords)/indexRecordSize)
|
||||
w.Write(level.TailRecords)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MeasuresAppendWithGrowRecord) Parse(r *bytes.Buffer) (err error) {
|
||||
var (
|
||||
size int
|
||||
recordsCount int
|
||||
)
|
||||
s.MetricID, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.HeadDataPage.PageNo, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.HeadDataPage.Reused, err = bin.ReadBool(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.HeadDataPage.PrevPageNo, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.HeadDataPage.Checksum, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.HeadDataPage.TimestampsOffset, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.HeadDataPage.Timestamps, err = bin.ReadN(r, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.HeadDataPage.Values, err = bin.ReadN(r, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// data pages
|
||||
dataPagesQty, err := bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for range dataPagesQty {
|
||||
var (
|
||||
p SealedDataPage
|
||||
)
|
||||
p.PageNo, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Reused, err = bin.ReadBool(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Content, err = bin.ReadN(r, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.DataPages = append(s.DataPages, p)
|
||||
}
|
||||
// data tail
|
||||
size, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.TailTimestamps, err = bin.ReadN(r, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.TailValues, err = bin.ReadN(r, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// changed levels
|
||||
changedIndexLevelsCount, err := bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for range changedIndexLevelsCount {
|
||||
var (
|
||||
indexPagesCount int
|
||||
level ChangedIndexLevel
|
||||
)
|
||||
// completed index page
|
||||
level.HeadIndexPage.PageNo, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
level.HeadIndexPage.Reused, err = bin.ReadBool(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
level.HeadIndexPage.Checksum, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
recordsCount, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
level.HeadIndexPage.Records, err = bin.ReadN(r, recordsCount*indexRecordSize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//
|
||||
indexPagesCount, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for range indexPagesCount {
|
||||
var p SealedIndexPage
|
||||
p.PageNo, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Reused, err = bin.ReadBool(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Checksum, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Content, err = bin.ReadN(r, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
level.IndexPages = append(level.IndexPages, p)
|
||||
}
|
||||
recordsCount, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
level.TailRecords, err = bin.ReadN(r, recordsCount*indexRecordSize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.ChangedIndexLevels = append(s.ChangedIndexLevels, level)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type MeasuresAppendRecord struct {
|
||||
MetricID uint32
|
||||
TimestampsOffset int
|
||||
Timestamps []byte
|
||||
ValuesOffset int
|
||||
Values []byte
|
||||
}
|
||||
|
||||
func (s MeasuresAppendRecord) WriteTo(w *bytes.Buffer) {
|
||||
w.WriteByte(CodeMeasuresAppendWithGrow)
|
||||
bin.WriteUint32(w, s.MetricID)
|
||||
bin.WriteVarSize(w, s.TimestampsOffset)
|
||||
bin.WriteVarSize(w, len(s.Timestamps))
|
||||
w.Write(s.Timestamps)
|
||||
bin.WriteVarSize(w, s.ValuesOffset)
|
||||
bin.WriteVarSize(w, len(s.Values))
|
||||
w.Write(s.Values)
|
||||
}
|
||||
|
||||
func (s *MeasuresAppendRecord) Parse(r *bytes.Buffer) (err error) {
|
||||
var size int
|
||||
s.MetricID, err = bin.ReadUint32(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.TimestampsOffset, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Timestamps, err = bin.ReadN(r, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.ValuesOffset, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = bin.ReadVarSize(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Values, err = bin.ReadN(r, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type MetricAddRecord struct {
|
||||
MetricID uint32
|
||||
MetricType qb.MetricType
|
||||
FracDigits int
|
||||
}
|
||||
|
||||
func (s MetricAddRecord) Pack(w io.Writer) {
|
||||
arr := []byte{
|
||||
CodeMetricAdd,
|
||||
0, 0, 0, 0, //
|
||||
byte(s.MetricType),
|
||||
byte(s.FracDigits),
|
||||
}
|
||||
bin.PutUint32(arr[1:], s.MetricID)
|
||||
w.Write(arr)
|
||||
}
|
||||
|
||||
func (s *MetricAddRecord) Parse(src io.Reader) (err error) {
|
||||
arr, err := bin.ReadN(src, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.MetricID, _ = bin.GetUint32(arr)
|
||||
s.MetricType = qb.MetricType(arr[4])
|
||||
s.FracDigits = int(arr[5])
|
||||
return nil
|
||||
}
|
||||
|
||||
type MetricDeleteRecord struct {
|
||||
MetricID uint32
|
||||
FreeIndexPages []uint32
|
||||
FreeDataPages []uint32
|
||||
}
|
||||
|
||||
func (s MetricDeleteRecord) Pack(w io.Writer) {
|
||||
arr := []byte{
|
||||
CodeMetricDelete,
|
||||
0, 0, 0, 0, // metricID
|
||||
}
|
||||
bin.PutUint32(arr[1:], s.MetricID)
|
||||
w.Write(arr)
|
||||
// fix
|
||||
// bin.WriteVarSize(w, len(s.FreePageNumbers))
|
||||
// for _, pageNo := range s.FreePageNumbers {
|
||||
// bin.WriteUint32(w, pageNo)
|
||||
// }
|
||||
}
|
||||
|
||||
func (s *MetricDeleteRecord) Parse(src io.Reader) (err error) {
|
||||
s.MetricID, err = bin.ReadUint32(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// free pages
|
||||
// fix
|
||||
// qty, err := bin.ReadVarSize(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range qty {
|
||||
// var pageNo uint32
|
||||
// pageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// //s.FreePageNumbers = append(s.FreePageNumbers, pageNo)
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
// fix - add changed pages
|
||||
type DeletedMeasures struct {
|
||||
MetricID uint32
|
||||
FreePageNumbers []uint32
|
||||
}
|
||||
|
||||
func (s DeletedMeasures) Pack(w io.Writer) {
|
||||
arr := []byte{
|
||||
CodeMeasuresDelete,
|
||||
0, 0, 0, 0,
|
||||
}
|
||||
bin.PutUint32(arr[1:], s.MetricID)
|
||||
w.Write(arr)
|
||||
bin.WriteVarSize(w, len(s.FreePageNumbers))
|
||||
for _, pageNo := range s.FreePageNumbers {
|
||||
bin.WriteUint32(w, pageNo)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DeletedMeasures) Parse(src io.Reader) (err error) {
|
||||
s.MetricID, err = bin.ReadUint32(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// free pages
|
||||
qty, err := bin.ReadVarSize(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for range qty {
|
||||
var pageNo uint32
|
||||
pageNo, err = bin.ReadUint32(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.FreePageNumbers = append(s.FreePageNumbers, pageNo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// type AppendedMeasure struct {
|
||||
// MetricID uint32
|
||||
// Timestamp uint32
|
||||
// Value float64
|
||||
// }
|
||||
|
||||
// func (s AppendedMeasure) Pack(w io.Writer) {
|
||||
// arr := []byte{
|
||||
// CodeAppendedMeasure,
|
||||
// 0, 0, 0, 0, // metricID
|
||||
// 0, 0, 0, 0, // timestamp
|
||||
// 0, 0, 0, 0, 0, 0, 0, 0, // value
|
||||
// }
|
||||
// bin.PutUint32(arr[1:], s.MetricID)
|
||||
// bin.PutUint32(arr[5:], s.Timestamp)
|
||||
// bin.PutFloat64(arr[9:], s.Value)
|
||||
// w.Write(arr)
|
||||
// }
|
||||
|
||||
// func (s *AppendedMeasure) Parse(src io.Reader) (err error) {
|
||||
// arr, err := bin.ReadN(src, 16)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.MetricID, _ = bin.GetUint32(arr[0:])
|
||||
// s.Timestamp, _ = bin.GetUint32(arr[4:])
|
||||
// s.Value, _ = bin.GetFloat64(arr[8:])
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// type AppendedMeasures struct {
|
||||
// MetricID uint32
|
||||
// Measures []proto.Measure
|
||||
// }
|
||||
|
||||
// func (s AppendedMeasures) Pack(w io.Writer) {
|
||||
// arr := []byte{
|
||||
// CodeAppendedMeasures,
|
||||
// 0, 0, 0, 0, // metricID
|
||||
// 0, 0, // qty
|
||||
// }
|
||||
// bin.PutUint32(arr[1:], s.MetricID)
|
||||
// bin.PutUint16(arr[5:], uint16(len(s.Measures)))
|
||||
// w.Write(arr)
|
||||
// for _, measure := range s.Measures {
|
||||
// bin.WriteUint32(w, measure.Timestamp)
|
||||
// bin.WriteFloat64(w, measure.Value)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func (s *AppendedMeasures) Parse(src io.Reader) (err error) {
|
||||
// s.MetricID, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// qty, err := bin.ReadUint16(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range qty {
|
||||
// var measure proto.Measure
|
||||
// measure.Timestamp, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// measure.Value, err = bin.ReadFloat64(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.Measures = append(s.Measures, measure)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// type AppendedPagesReq struct {
|
||||
// Legs []atree.PathLeg
|
||||
// MetricID uint32
|
||||
// Timestamp uint32 // last measure
|
||||
// Value float64 // last measure
|
||||
// //RootPageNo uint32
|
||||
// LastPageNo uint32
|
||||
// Pages []atree.NotLinkedDataPage
|
||||
// TimestampsChunks [][]byte
|
||||
// TimestampsSize uint16
|
||||
// ValuesChunks [][]byte
|
||||
// ValuesSize uint16
|
||||
// }
|
||||
|
||||
/*
|
||||
4b metricID
|
||||
4b timestamp
|
||||
8b value
|
||||
4b LastPageNo (0 if no new root page)
|
||||
4b newRootPageNo (0 if no new root page)
|
||||
Nb - varsize (pages qty)
|
||||
[
|
||||
|
||||
4b pageNo
|
||||
1b isReused
|
||||
Xb page (page size)
|
||||
|
||||
]
|
||||
2b - timestamps size (not filled page)
|
||||
Nb - timestamps payload
|
||||
2b - values size (not filled page)
|
||||
Nb - values payload
|
||||
*/
|
||||
// type AppendedPages struct {
|
||||
// MetricID uint32
|
||||
// Timestamp uint32 // last measure
|
||||
// Value float64 // last measure
|
||||
// NewRootPageNo uint32
|
||||
// LastPageNo uint32
|
||||
// Pages []atree.PageToWrite
|
||||
// TimestampsChunks [][]byte
|
||||
// TimestampsSize int
|
||||
// ValuesChunks [][]byte
|
||||
// ValuesSize int
|
||||
// }
|
||||
|
||||
// func (s *AppendedPages) Pack(w io.Writer) {
|
||||
// w.Write([]byte{
|
||||
// CodeAppendedPages,
|
||||
// })
|
||||
// bin.WriteUint32(w, s.MetricID)
|
||||
// bin.WriteUint32(w, s.Timestamp)
|
||||
// bin.WriteFloat64(w, s.Value)
|
||||
// bin.WriteUint32(w, s.LastPageNo)
|
||||
// bin.WriteUint32(w, s.NewRootPageNo)
|
||||
// //
|
||||
// bin.WriteVarSize(w, len(s.Pages))
|
||||
// for _, p := range s.Pages {
|
||||
// bin.WriteUint32(w, p.PageNo)
|
||||
// if p.IsReused {
|
||||
// w.Write([]byte{1})
|
||||
// } else {
|
||||
// w.Write([]byte{0})
|
||||
// }
|
||||
// w.Write(p.Data)
|
||||
// }
|
||||
// // timestamps
|
||||
// writeChunkedPayloadTo(s.TimestampsChunks, int(s.TimestampsSize), w)
|
||||
// // values
|
||||
// writeChunkedPayloadTo(s.ValuesChunks, int(s.ValuesSize), w)
|
||||
// }
|
||||
|
||||
// func (s *AppendedPages) Parse(src io.Reader) (err error) {
|
||||
// s.MetricID, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.Timestamp, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.Value, err = bin.ReadFloat64(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.LastPageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.NewRootPageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// pagesQty, err := bin.ReadVarSize(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// for range pagesQty {
|
||||
// var page atree.PageToWrite
|
||||
// page.PageNo, err = bin.ReadUint32(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// page.IsReused, err = bin.ReadBool(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// page.Data, err = bin.ReadN(src, atree.PageSize)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.Pages = append(s.Pages, page)
|
||||
// }
|
||||
// s.TimestampsChunks, s.TimestampsSize, err = readChunkedPayload(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// s.ValuesChunks, s.ValuesSize, err = readChunkedPayload(src)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
464
storage/wal_writer.go
Normal file
464
storage/wal_writer.go
Normal file
@@ -0,0 +1,464 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"gordenko.dev/dima/qb"
|
||||
"gordenko.dev/dima/qb/atree"
|
||||
"gordenko.dev/dima/qb/freelist"
|
||||
)
|
||||
|
||||
const (
|
||||
lsnSize = 4
|
||||
packetPrefixSize = 12 // 4 lsn + 4 packet length + 4 crc32
|
||||
|
||||
lengthIdx = 0
|
||||
checksumIdx = 4
|
||||
lsnIdx = 8
|
||||
|
||||
filePerm = 0770
|
||||
|
||||
dumpSnapshotAfterNBytes = 1024 * 1024 * 1024 // 1 GB
|
||||
|
||||
writeBufferSize = 4 * 1024 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
CodeMetricAdd byte = 1
|
||||
CodeMetricDelete byte = 2
|
||||
CodeMeasuresAppendWithGrow byte = 5
|
||||
CodeMeasuresAppend = 6
|
||||
CodeMeasuresDelete byte = 7
|
||||
)
|
||||
|
||||
type FreeList interface {
|
||||
// використовується в allocPage
|
||||
GetPageNumber() (uint32, error)
|
||||
}
|
||||
|
||||
func JoinChangesFileName(dir string, logNumber int) string {
|
||||
return filepath.Join(dir, fmt.Sprintf("%d.changes", logNumber))
|
||||
}
|
||||
|
||||
type MetricsState struct {
|
||||
//Metrics []Metric
|
||||
WaitCh chan struct{}
|
||||
}
|
||||
|
||||
type Changes struct {
|
||||
Records []any
|
||||
SnapshotNumberCh chan int // log number
|
||||
FrozenIndexPagesCount int
|
||||
IndexPageNumbers []uint32
|
||||
FrozenDataPagesCount int
|
||||
DataPageNumbers []uint32
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
mutex sync.Mutex
|
||||
dataPagesCount uint32
|
||||
indexPagesCount uint32
|
||||
dataFreeList *freelist.FreeList
|
||||
indexFreeList *freelist.FreeList
|
||||
atree *atree.Atree
|
||||
//logNumber int
|
||||
dir string
|
||||
w *bytes.Buffer // wal буфер для упаковки даних перед записом на диск
|
||||
wal *os.File
|
||||
dataFile *os.File
|
||||
indexFile *os.File
|
||||
indexPageSize int
|
||||
dataPageSize int
|
||||
input []any
|
||||
dataPreparer *DataPreparer
|
||||
appendToWorkerQueue func(any)
|
||||
//lsn uint32
|
||||
written int64
|
||||
isExited bool
|
||||
exitCh chan struct{}
|
||||
waitGroup *sync.WaitGroup
|
||||
signalCh chan struct{}
|
||||
}
|
||||
|
||||
type WriterOptions struct {
|
||||
IndexPageSize int
|
||||
DataPageSize int
|
||||
Dir string
|
||||
SnapshotNumber int // номер журнала
|
||||
AppendToWorkerQueue func(any)
|
||||
DataFreeList *freelist.FreeList
|
||||
IndexFreeList *freelist.FreeList
|
||||
Atree *atree.Atree
|
||||
ExitCh chan struct{}
|
||||
WaitGroup *sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewWriter(opt WriterOptions) (*Writer, error) {
|
||||
if (opt.IndexPageSize % 2) != 0 {
|
||||
return nil, errors.New("IndexPageSize must be multiple of 2")
|
||||
}
|
||||
if (opt.DataPageSize % 2) != 0 {
|
||||
return nil, errors.New("DataPageSize must be multiple of 2")
|
||||
}
|
||||
if opt.Dir == "" {
|
||||
return nil, errors.New("Dir option is required")
|
||||
}
|
||||
if opt.AppendToWorkerQueue == nil {
|
||||
return nil, errors.New("AppendToWorkerQueue option is required")
|
||||
}
|
||||
if opt.DataFreeList == nil {
|
||||
return nil, errors.New("DataFreeList option is required")
|
||||
}
|
||||
if opt.IndexFreeList == nil {
|
||||
return nil, errors.New("IndexFreeList option is required")
|
||||
}
|
||||
if opt.Atree == nil {
|
||||
return nil, errors.New("Atree option is required")
|
||||
}
|
||||
if opt.ExitCh == nil {
|
||||
return nil, errors.New("ExitCh option is required")
|
||||
}
|
||||
if opt.WaitGroup == nil {
|
||||
return nil, errors.New("WaitGroup option is required")
|
||||
}
|
||||
|
||||
s := &Writer{
|
||||
indexPageSize: opt.IndexPageSize,
|
||||
dataPageSize: opt.DataPageSize,
|
||||
dir: opt.Dir,
|
||||
appendToWorkerQueue: opt.AppendToWorkerQueue,
|
||||
dataFreeList: opt.DataFreeList,
|
||||
indexFreeList: opt.IndexFreeList,
|
||||
atree: opt.Atree,
|
||||
exitCh: opt.ExitCh,
|
||||
waitGroup: opt.WaitGroup,
|
||||
signalCh: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if opt.SnapshotNumber <= 0 {
|
||||
log.Fatalln("FIX FUCK NUMBER")
|
||||
}
|
||||
s.wal, err = os.OpenFile(
|
||||
JoinChangesFileName(opt.Dir, opt.SnapshotNumber),
|
||||
os.O_APPEND|os.O_WRONLY,
|
||||
filePerm,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Writer) Run() {
|
||||
for {
|
||||
select {
|
||||
case <-s.signalCh:
|
||||
if err := s.packAndWrite(); err != nil {
|
||||
qb.Abort(qb.FailedWriteToTxLog, err)
|
||||
}
|
||||
|
||||
case <-s.exitCh:
|
||||
s.exit()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Writer) getDataPageNumber() (uint32, bool, error) {
|
||||
pageNo, err := s.dataFreeList.GetPageNumber()
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
if pageNo > 0 {
|
||||
return pageNo, true, nil
|
||||
}
|
||||
if s.dataPagesCount < math.MaxUint32 {
|
||||
s.dataPagesCount++
|
||||
return s.dataPagesCount, false, nil
|
||||
}
|
||||
return 0, false, errors.New("no space")
|
||||
}
|
||||
|
||||
func (s *Writer) getIndexPageNumber() (uint32, bool, error) {
|
||||
pageNo, err := s.indexFreeList.GetPageNumber()
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
if pageNo > 0 {
|
||||
return pageNo, true, nil
|
||||
}
|
||||
if s.indexPagesCount < math.MaxUint32 {
|
||||
s.indexPagesCount++
|
||||
return s.indexPagesCount, false, nil
|
||||
}
|
||||
return 0, false, errors.New("no space")
|
||||
}
|
||||
|
||||
func (s *Writer) packAndWrite() (err error) {
|
||||
s.mutex.Lock()
|
||||
//isExited := s.isExited
|
||||
input := s.input
|
||||
s.input = nil
|
||||
|
||||
// var exitWaitGroup *sync.WaitGroup
|
||||
// if s.isExited {
|
||||
// exitWaitGroup = s.waitGroup
|
||||
// }
|
||||
s.mutex.Unlock()
|
||||
|
||||
prepared := s.dataPreparer.Prepare(input)
|
||||
|
||||
// 3. Пишу на диск WAL (append)
|
||||
n, err := s.wal.Write(prepared.Packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != len(prepared.Packet) {
|
||||
return fmt.Errorf("written %d != total size %d", n, len(prepared.Packet))
|
||||
}
|
||||
if err = s.wal.Sync(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Пишу в atree сторінки
|
||||
err = s.WritePagesToAtree(prepared.WriteToIndex, prepared.WriteToData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 6. відправляю input - worker-у
|
||||
|
||||
var (
|
||||
forceSnapshot bool
|
||||
)
|
||||
|
||||
if s.written > dumpSnapshotAfterNBytes {
|
||||
forceSnapshot = true
|
||||
}
|
||||
|
||||
// if isExited && s.written > 0 {
|
||||
// forceSnapshot = true
|
||||
// }
|
||||
|
||||
if forceSnapshot {
|
||||
if err := s.wal.Close(); err != nil {
|
||||
return fmt.Errorf("close wal file: %s", err)
|
||||
}
|
||||
s.wal = nil
|
||||
s.written = 0
|
||||
|
||||
snapshotNumberCh := make(chan int, 1)
|
||||
|
||||
s.appendToWorkerQueue(Changes{
|
||||
Records: prepared.WriteResults,
|
||||
SnapshotNumberCh: snapshotNumberCh,
|
||||
FrozenIndexPagesCount: s.indexFreeList.Pages(),
|
||||
IndexPageNumbers: s.indexFreeList.Cached(),
|
||||
FrozenDataPagesCount: s.dataFreeList.Pages(),
|
||||
DataPageNumbers: s.dataFreeList.Cached(),
|
||||
})
|
||||
|
||||
snapshotNumber := <-snapshotNumberCh
|
||||
|
||||
// копіюю сторінки із delta файла в base файл і потім роблю Truncate
|
||||
err = s.indexFreeList.Merge()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.dataFreeList.Merge()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
s.wal, err = os.OpenFile(
|
||||
JoinChangesFileName(s.dir, snapshotNumber),
|
||||
os.O_CREATE|os.O_WRONLY,
|
||||
filePerm,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create new changes file: %s", err)
|
||||
}
|
||||
} else {
|
||||
s.appendToWorkerQueue(Changes{
|
||||
Records: prepared.WriteResults,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Writer) Append(req any) {
|
||||
s.mutex.Lock()
|
||||
s.input = append(s.input, req)
|
||||
s.mutex.Unlock()
|
||||
|
||||
select {
|
||||
case s.signalCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// func (s *Writer) reset() {
|
||||
// s.buf.Reset()
|
||||
// s.buf.Write([]byte{
|
||||
// 0, 0, 0, 0, // packet length
|
||||
// 0, 0, 0, 0, // crc32
|
||||
// 0, 0, 0, 0, // lsn
|
||||
|
||||
// })
|
||||
// s.pagesToWrite = nil
|
||||
// s.workerReqs = nil
|
||||
// s.waitCh = make(chan struct{})
|
||||
// }
|
||||
|
||||
// func (s *Writer) flush(w *bytes.Buffer) error {
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (s *Writer) exit() {
|
||||
s.mutex.Lock()
|
||||
s.isExited = true
|
||||
s.mutex.Unlock()
|
||||
|
||||
if err := s.packAndWrite(); err != nil {
|
||||
qb.Abort(qb.FailedWriteToTxLog, err)
|
||||
}
|
||||
}
|
||||
|
||||
// type IndexPayloadToWrite struct {
|
||||
// PageNo uint32
|
||||
// Payload []byte
|
||||
// ZeroLevel bool
|
||||
// }
|
||||
|
||||
type PageToWrite struct {
|
||||
PageNo uint32
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// writePagesToAtree - записує сторінки в .data та .index файли
|
||||
func (s *Writer) WritePagesToAtree(indexPages []PageToWrite, dataPages []PageToWrite) (err error) {
|
||||
for _, p := range dataPages {
|
||||
if len(p.Content) != s.dataPageSize {
|
||||
return fmt.Errorf("wrong data page size: %d", len(p.Content))
|
||||
}
|
||||
var (
|
||||
off = int(p.PageNo-1) * s.dataPageSize
|
||||
n int
|
||||
)
|
||||
n, err = s.dataFile.WriteAt(p.Content, int64(off))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != s.dataPageSize {
|
||||
return fmt.Errorf("write %d instead of %d", n, s.dataPageSize)
|
||||
}
|
||||
}
|
||||
for _, p := range indexPages {
|
||||
if len(p.Content) != s.indexPageSize {
|
||||
return fmt.Errorf("wrong index page size: %d", len(p.Content))
|
||||
}
|
||||
var (
|
||||
off = int(p.PageNo-1) * s.indexPageSize
|
||||
n int
|
||||
)
|
||||
n, err = s.indexFile.WriteAt(p.Content, int64(off))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != s.indexPageSize {
|
||||
return fmt.Errorf("write %d instead of %d", n, s.indexPageSize)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// FIX - add
|
||||
// type DeletedMeasuresSince struct {
|
||||
// MetricID uint32
|
||||
// LastPageNo uint32
|
||||
// IsRootChanged bool
|
||||
// RootPageNo uint32
|
||||
// FreeDataPages []uint32
|
||||
// FreeIndexPages []uint32
|
||||
// TimestampsBuf []byte
|
||||
// ValuesBuf []byte
|
||||
// }
|
||||
|
||||
func (s *Writer) sendSignal() {
|
||||
select {
|
||||
case s.signalCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Якщо (Pages) > 0 - незаповнену сторінку кодую в стисненому вигляді.
|
||||
// Якщо (Pages) = 0 - кодую просто пари timestamp / value
|
||||
// func (s *Writer) packWriteAppended(w *bytes.Buffer, req AppendedPagesReq) {
|
||||
// // завантажений path. Отже просто додаємо в дерево data сторінки, створюємо нові індексні
|
||||
// // без звернення до диску.
|
||||
// report := s.atree.AppendDataPages(atree.AppendDataPagesReq{
|
||||
// LastPageNo: req.LastPageNo,
|
||||
// Legs: req.Legs,
|
||||
// DataPages: req.Pages,
|
||||
// })
|
||||
|
||||
// m := AppendedPages{
|
||||
// MetricID: req.MetricID,
|
||||
// Timestamp: req.Timestamp,
|
||||
// Value: req.Value,
|
||||
// NewRootPageNo: report.NewRootPageNo,
|
||||
// LastPageNo: report.LastPageNo,
|
||||
// Pages: report.Pages,
|
||||
// TimestampsChunks: req.TimestampsChunks,
|
||||
// TimestampsSize: int(req.TimestampsSize),
|
||||
// ValuesChunks: req.ValuesChunks,
|
||||
// ValuesSize: int(req.ValuesSize),
|
||||
// }
|
||||
// m.Pack(w)
|
||||
// }
|
||||
|
||||
// helpers
|
||||
|
||||
// type Metric struct {
|
||||
// MetricID uint32
|
||||
// MetricType qb.MetricType
|
||||
// FracDigits byte
|
||||
// LastPageNo uint32
|
||||
// Since uint32
|
||||
// SinceValue float64
|
||||
// Until uint32
|
||||
// UntilValue float64
|
||||
// Timestamps []byte // payload FIX
|
||||
// Values []byte // payload FIX add lastDelta + h ?
|
||||
// }
|
||||
|
||||
// func writeChunks(dst io.Writer, chunks [][]byte, size int) (err error) {
|
||||
// remaining := size
|
||||
// for _, buf := range chunks {
|
||||
// if remaining < len(buf) {
|
||||
// buf = buf[:remaining]
|
||||
// }
|
||||
// _, err = dst.Write(buf)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// remaining -= len(buf)
|
||||
// if remaining == 0 {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
Reference in New Issue
Block a user