This commit is contained in:
2026-05-21 23:38:52 +03:00
parent 793df29e11
commit e7ec6083cb
21 changed files with 1234 additions and 1017 deletions

View File

@@ -3,12 +3,12 @@ package atree
import ( import (
"errors" "errors"
"fmt" "fmt"
"hash/crc32"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/util"
) )
const ( const (
@@ -52,25 +52,12 @@ const (
FlagNewRoot byte = 2 // новая страница FlagNewRoot byte = 2 // новая страница
) )
var (
castagnoliTable = crc32.MakeTable(crc32.Castagnoli)
)
func calcChecksum(page []byte) uint32 {
return crc32.Checksum(page, castagnoliTable)
}
type PageToWrite struct { type PageToWrite struct {
PageNo uint32 PageNo uint32
Data []byte Data []byte
IsReused bool IsReused bool
} }
type FreeList interface {
// використовується в allocPage
ReservePage() uint32
}
type _page struct { type _page struct {
PageNo uint32 PageNo uint32
Buf []byte Buf []byte
@@ -80,7 +67,6 @@ type _page struct {
type Atree struct { type Atree struct {
file *os.File file *os.File
mutex sync.Mutex mutex sync.Mutex
freelist FreeList
allocatedPagesQty uint32 allocatedPagesQty uint32
pages map[uint32]*_page pages map[uint32]*_page
pageWaits map[uint32][]chan readResult pageWaits map[uint32][]chan readResult
@@ -93,7 +79,6 @@ type Atree struct {
type Options struct { type Options struct {
Dir string Dir string
DatabaseName string DatabaseName string
FreeList FreeList
} }
func New(opt Options) (*Atree, error) { func New(opt Options) (*Atree, error) {
@@ -106,9 +91,7 @@ func New(opt Options) (*Atree, error) {
if opt.DatabaseName == "" { if opt.DatabaseName == "" {
return nil, errors.New("DatabaseName option is required") return nil, errors.New("DatabaseName option is required")
} }
if opt.FreeList == nil {
return nil, errors.New("FreeList option is required")
}
// открываю или создаю dbName.data и dbName.index файлы // открываю или создаю dbName.data и dbName.index файлы
var ( var (
fileName = filepath.Join(opt.Dir, opt.DatabaseName+".db") fileName = filepath.Join(opt.Dir, opt.DatabaseName+".db")
@@ -136,7 +119,6 @@ func New(opt Options) (*Atree, error) {
} }
tree := &Atree{ tree := &Atree{
freelist: opt.FreeList,
file: file, file: file,
allocatedPagesQty: allocatedPagesQty, allocatedPagesQty: allocatedPagesQty,
pages: make(map[uint32]*_page), pages: make(map[uint32]*_page),
@@ -261,7 +243,7 @@ type NotLinkedDataPage struct {
func (s NotLinkedDataPage) SetPrevPageNo(prevPageNo uint32) { func (s NotLinkedDataPage) SetPrevPageNo(prevPageNo uint32) {
bin.PutUint32(s.Data[prevPageIdx:], prevPageNo) bin.PutUint32(s.Data[prevPageIdx:], prevPageNo)
bin.PutUint32(s.Data[crc32Idx:], calcChecksum(s.Data[:crc32Idx])) bin.PutUint32(s.Data[crc32Idx:], util.CalcChecksum(s.Data[:crc32Idx]))
} }
type AppendDataPagesReq struct { type AppendDataPagesReq struct {

View File

@@ -9,6 +9,7 @@ import (
"gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/util"
) )
// fix - додати ID, щоб потім перемістити із тимчасового буфера в pages, або звільнити // fix - додати ID, щоб потім перемістити із тимчасового буфера в pages, або звільнити
@@ -362,7 +363,7 @@ func openFile(fileName string, pageSize int) (_ *os.File, _ uint32, err error) {
func (s *Atree) verifyCRC(data []byte, pageSize int) error { func (s *Atree) verifyCRC(data []byte, pageSize int) error {
var ( var (
pos = pageSize - 4 pos = pageSize - 4
calculatedCRC = calcChecksum(data[:pos]) calculatedCRC = util.CalcChecksum(data[:pos])
storedCRC = bin.GetUint32(data[pos:]) storedCRC = bin.GetUint32(data[pos:])
) )
if calculatedCRC != storedCRC { if calculatedCRC != storedCRC {

View File

@@ -1,6 +1,9 @@
package atree package atree
import "gordenko.dev/dima/qb/bin" import (
"gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/util"
)
const ( const (
payloadSize = 24 payloadSize = 24
@@ -16,6 +19,7 @@ type DataPage struct {
ValuesSize int ValuesSize int
PageNo uint32 PageNo uint32
IsReused bool IsReused bool
Checksum uint32
} }
type IndexPage struct { type IndexPage struct {
@@ -23,6 +27,7 @@ type IndexPage struct {
Data []byte Data []byte
PageNo uint32 PageNo uint32
IsReused bool IsReused bool
Checksum uint32
} }
type IndexLevel struct { type IndexLevel struct {
// вже записані дані (лише для першої в списку індексної сторінки, беремо із _metric) // вже записані дані (лише для першої в списку індексної сторінки, беремо із _metric)
@@ -30,7 +35,7 @@ type IndexLevel struct {
Offset int // (заповнені одразу) Offset int // (заповнені одразу)
LowerTimestamp uint32 // (заповнені одразу) LowerTimestamp uint32 // (заповнені одразу)
Data []byte // поточні дані index сторінки (заповнені одразу) Data []byte // поточні дані index сторінки (заповнені одразу)
Filled []IndexPage // пусто Filled []*IndexPage // пусто
} }
func appendIndexRecord(data []byte, timestamp uint32, pageNo uint32) []byte { func appendIndexRecord(data []byte, timestamp uint32, pageNo uint32) []byte {
@@ -46,12 +51,13 @@ func appendIndexRecord(data []byte, timestamp uint32, pageNo uint32) []byte {
type ChunksToDataPageReq struct { type ChunksToDataPageReq struct {
PrevPageNo uint32 PrevPageNo uint32
Timestamps [][]byte // chunks Timestamps [][]byte // chunks
TimestampsSize uint16 TimestampsSize int
Values [][]byte // chunks Values [][]byte // chunks
ValuesSize uint16 ValuesSize int
} }
func ChunksToDataPage(buf []byte, req ChunksToDataPageReq) { // return checksum
func ChunksToDataPage(buf []byte, req ChunksToDataPageReq) (checksum uint32) {
var ( var (
remainingSize = int(req.TimestampsSize) remainingSize = int(req.TimestampsSize)
pos = 0 pos = 0
@@ -80,25 +86,31 @@ func ChunksToDataPage(buf []byte, req ChunksToDataPageReq) {
break break
} }
} }
bin.PutUint16(buf[timestampsSizeIdx:], req.TimestampsSize) bin.PutUint16(buf[timestampsSizeIdx:], uint16(req.TimestampsSize))
bin.PutUint16(buf[valuesSizeIdx:], req.ValuesSize) bin.PutUint16(buf[valuesSizeIdx:], uint16(req.ValuesSize))
bin.PutUint32(buf[prevPageIdx:], req.PrevPageNo) bin.PutUint32(buf[prevPageIdx:], req.PrevPageNo)
bin.PutUint32(buf[dataCRC32Idx:], calcChecksum(buf[:dataCRC32Idx]))
checksum = util.CalcChecksum(buf[:dataCRC32Idx])
bin.PutUint32(buf[dataCRC32Idx:], checksum)
return
} }
func DataToIndexPage(buf []byte, data []byte, lastLevel bool) { func DataToIndexPage(buf []byte, data []byte, lastLevel bool) (checksum uint32) {
copy(buf, data) copy(buf, data)
bin.PutUint16(buf[indexRecordsQtyIdx:], uint16(len(data)/indexRecordSize)) bin.PutUint16(buf[indexRecordsQtyIdx:], uint16(len(data)/indexRecordSize))
if lastLevel { if lastLevel {
buf[isLastLevelIdx] = 1 buf[isLastLevelIdx] = 1
} }
bin.PutUint32(buf[indexCRC32Idx:], calcChecksum(buf[:indexCRC32Idx])) checksum = util.CalcChecksum(buf[:indexCRC32Idx])
bin.PutUint32(buf[indexCRC32Idx:], checksum)
return
} }
type Pager interface { type Pager interface {
GetPageNumber() (uint32, bool) GetPageNumber() (uint32, bool)
} }
// fix - reduce leves
func AppendDataPagesToTree(pager Pager, levels []*IndexLevel, dataPages []*DataPage) { func AppendDataPagesToTree(pager Pager, levels []*IndexLevel, dataPages []*DataPage) {
for i, d := range dataPages[1:] { for i, d := range dataPages[1:] {
d.PageNo, d.IsReused = pager.GetPageNumber() d.PageNo, d.IsReused = pager.GetPageNumber()
@@ -127,7 +139,7 @@ func AppendDataPagesToTree(pager Pager, levels []*IndexLevel, dataPages []*DataP
} }
// в буфері немає місця для додавання нової пари, отже це // в буфері немає місця для додавання нової пари, отже це
// заповнена сторінка // заповнена сторінка
filled := IndexPage{ filled := &IndexPage{
LowerTimestamp: level.LowerTimestamp, LowerTimestamp: level.LowerTimestamp,
Data: level.Data, Data: level.Data,
} }

View File

@@ -76,8 +76,9 @@ func (s *ReverseCumulativeDeltaCompressor) Size() int {
return s.pos return s.pos
} }
//func (s *ReverseCumulativeDeltaCompressor) CalcRequiredSpace(value float64) int { func (s *ReverseCumulativeDeltaCompressor) CalcRequiredSpace(value float64) int {
//} return 0
}
func (s *ReverseCumulativeDeltaCompressor) Append(value float64) { func (s *ReverseCumulativeDeltaCompressor) Append(value float64) {
if s.pos == 0 { if s.pos == 0 {

View File

@@ -157,6 +157,37 @@ func (s *ReverseInstantDeltaCompressor) GetState() InstantDeltaBound {
} }
} }
func (s *ReverseInstantDeltaCompressor) Lock() {
// s.state = &CumulativeDeltaBound{
// Pos: s.pos - 1 - s.lastDeltaSize,
// H: s.h,
// LastDelta: s.lastDelta,
// Chunks: s.buf.Chunks(),
// }
}
func (s *ReverseInstantDeltaCompressor) Unlock() {
//s.state = nil
}
func (s *ReverseInstantDeltaCompressor) Renew() {
s.buf = conbuf.New(nil)
s.pos = 0
//
s.baseValue = 0
s.lastDelta = 0
s.lastDeltaSize = 0
s.h = 0
}
func (s *ReverseInstantDeltaCompressor) CalcRequiredSpace(value float64) int {
return 0
}
func (s *ReverseInstantDeltaCompressor) Chunks() [][]byte {
return s.buf.Chunks()
}
// DECOMPRESSOR // DECOMPRESSOR
type ReverseInstantDeltaDecompressor struct { type ReverseInstantDeltaDecompressor struct {

View File

@@ -45,8 +45,13 @@ func (s *ReverseTimeDeltaCompressor) Size() int {
return s.pos return s.pos
} }
//func (s *ReverseTimeDeltaCompressor) CalcRequiredSpace(value float64) int { func (s *ReverseTimeDeltaCompressor) Chunks() [][]byte {
//} return s.buf.Chunks()
}
func (s *ReverseTimeDeltaCompressor) CalcRequiredSpace(value uint32) int {
return 0
}
func (s *ReverseTimeDeltaCompressor) Append(unixtime uint32) { func (s *ReverseTimeDeltaCompressor) Append(unixtime uint32) {
if s.lastUnixtime > 0 { if s.lastUnixtime > 0 {
@@ -154,7 +159,7 @@ type TimeDeltaBound struct {
} }
// delta h // delta h
func (s *ReverseTimeDeltaCompressor) GetState() TimeDeltaBound { func (s *ReverseTimeDeltaCompressor) GetState() TimeDeltaBound { // fix replace by Lock
bound := TimeDeltaBound{ bound := TimeDeltaBound{
LastUnixtime: s.lastUnixtime, LastUnixtime: s.lastUnixtime,
} }
@@ -166,6 +171,24 @@ func (s *ReverseTimeDeltaCompressor) GetState() TimeDeltaBound {
return bound return bound
} }
func (s *ReverseTimeDeltaCompressor) Lock() {
// fix
}
func (s *ReverseTimeDeltaCompressor) Unlock() {
//s.state = nil // fix
}
func (s *ReverseTimeDeltaCompressor) Renew() {
// s.buf = conbuf.New(nil)
// s.pos = 0
// //
// s.baseValue = 0
// s.lastDelta = 0
// s.lastDeltaSize = 0
// s.h = 0
}
// DECOMPRESSOR // DECOMPRESSOR
type ReverseTimeDeltaDecompressor struct { type ReverseTimeDeltaDecompressor struct {

View File

@@ -6,7 +6,7 @@ import (
"io" "io"
"net" "net"
diploma "gordenko.dev/dima/qb" qb "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree" "gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/bufreader" "gordenko.dev/dima/qb/bufreader"
@@ -192,11 +192,11 @@ func (s *Database) AddMetric(req proto.AddMetricReq) uint16 {
if req.MetricID == 0 { if req.MetricID == 0 {
return proto.ErrEmptyMetricID return proto.ErrEmptyMetricID
} }
if byte(req.FracDigits) > diploma.MaxFracDigits { if byte(req.FracDigits) > qb.MaxFracDigits {
return proto.ErrWrongFracDigits return proto.ErrWrongFracDigits
} }
switch req.MetricType { switch req.MetricType {
case diploma.Cumulative, diploma.Instant: case qb.Cumulative, qb.Instant:
// ok // ok
default: default:
return proto.ErrWrongMetricType return proto.ErrWrongMetricType
@@ -213,24 +213,24 @@ func (s *Database) AddMetric(req proto.AddMetricReq) uint16 {
switch resultCode { switch resultCode {
case Succeed: case Succeed:
waitCh := s.txlog.Append(txlog.AddedMetric{ s.txlog.Append(txlog.AddedMetric{
MetricID: req.MetricID, MetricID: req.MetricID,
MetricType: req.MetricType, MetricType: req.MetricType,
FracDigits: req.FracDigits, FracDigits: req.FracDigits,
}) })
<-waitCh //<-waitCh
case MetricDuplicate: case MetricDuplicate:
return proto.ErrDuplicate return proto.ErrDuplicate
default: default:
diploma.Abort(diploma.WrongResultCodeBug, ErrWrongResultCodeBug) qb.Abort(qb.WrongResultCodeBug, ErrWrongResultCodeBug)
} }
return 0 return 0
} }
type Metric struct { type Metric struct {
MetricType diploma.MetricType MetricType qb.MetricType
FracDigits byte FracDigits byte
ResultCode byte ResultCode byte
} }
@@ -264,7 +264,7 @@ func (s *Database) GetMetric(conn io.Writer, req proto.GetMetricReq) error {
reply(conn, proto.ErrNoMetric) reply(conn, proto.ErrNoMetric)
default: default:
diploma.Abort(diploma.WrongResultCodeBug, ErrWrongResultCodeBug) qb.Abort(qb.WrongResultCodeBug, ErrWrongResultCodeBug)
} }
return nil return nil
} }
@@ -293,20 +293,20 @@ func (s *Database) DeleteMetric(req proto.DeleteMetricReq) uint16 {
var err error var err error
freePageNumbers, err = s.atree.GetAllPages(result.RootPageNo) freePageNumbers, err = s.atree.GetAllPages(result.RootPageNo)
if err != nil { if err != nil {
diploma.Abort(diploma.FailedAtreeRequest, err) qb.Abort(qb.FailedAtreeRequest, err)
} }
} }
waitCh := s.txlog.Append(txlog.DeletedMetric{ s.txlog.Append(txlog.DeletedMetric{
MetricID: req.MetricID, MetricID: req.MetricID,
FreePageNumbers: freePageNumbers, FreePageNumbers: freePageNumbers,
}) })
<-waitCh //<-waitCh
case NoMetric: case NoMetric:
return proto.ErrNoMetric return proto.ErrNoMetric
default: default:
diploma.Abort(diploma.WrongResultCodeBug, ErrWrongResultCodeBug) qb.Abort(qb.WrongResultCodeBug, ErrWrongResultCodeBug)
} }
return 0 return 0
} }
@@ -358,15 +358,15 @@ type FilledPage struct {
// path, err := s.atree.FindPathToLastPage(filled.RootPageNo) // path, err := s.atree.FindPathToLastPage(filled.RootPageNo)
// if err != nil { // if err != nil {
// // FIX // // FIX
// diploma.Abort(diploma.WriteToAtreeFailed, err) // qb.Abort(qb.WriteToAtreeFailed, err)
// } // }
// // for _, leg := range path.Legs { // // for _, leg := range path.Legs {
// // pagesToRelease = append(pagesToRelease, leg.PageNo) // // pagesToRelease = append(pagesToRelease, leg.PageNo)
// // } // // }
// if path.LastPageNo != filled.PrevPageNo { // if path.LastPageNo != filled.PrevPageNo {
// diploma.Abort( // qb.Abort(
// diploma.WrongPrevPageNo, // qb.WrongPrevPageNo,
// fmt.Errorf("bug: last pageNo %d in tree != prev pageNo %d in _metric", // fmt.Errorf("bug: last pageNo %d in tree != prev pageNo %d in _metric",
// path.LastPageNo, filled.PrevPageNo), // path.LastPageNo, filled.PrevPageNo),
// ) // )
@@ -386,7 +386,7 @@ type FilledPage struct {
// // ValuesSize: filled.ValuesSize, // // ValuesSize: filled.ValuesSize,
// // }) // // })
// // if err != nil { // // if err != nil {
// // diploma.Abort(diploma.WriteToAtreeFailed, err) // // qb.Abort(qb.WriteToAtreeFailed, err)
// // } // // }
// // _ = report // // _ = report
@@ -417,7 +417,7 @@ type FilledPage struct {
// return proto.ErrNonMonotonicValue // return proto.ErrNonMonotonicValue
// default: // default:
// diploma.Abort(diploma.WrongResultCodeBug, ErrWrongResultCodeBug) // qb.Abort(qb.WrongResultCodeBug, ErrWrongResultCodeBug)
// } // }
// return 0 // return 0
// } // }
@@ -425,7 +425,7 @@ type FilledPage struct {
type tryAppendMeasuresResult struct { type tryAppendMeasuresResult struct {
ResultCode byte ResultCode byte
Written int Written int
// MetricType diploma.MetricType // MetricType qb.MetricType
// FracDigits byte // FracDigits byte
// Since uint32 // Since uint32
// Until uint32 // Until uint32
@@ -434,8 +434,8 @@ type tryAppendMeasuresResult struct {
// PrevPageNo uint32 // PrevPageNo uint32
// TimestampsBuf *conbuf.ContinuousBuffer // TimestampsBuf *conbuf.ContinuousBuffer
// ValuesBuf *conbuf.ContinuousBuffer // ValuesBuf *conbuf.ContinuousBuffer
// Timestamps diploma.TimestampCompressor // Timestamps qb.TimestampCompressor
// Values diploma.ValueCompressor // Values qb.ValueCompressor
} }
func (s *Database) AppendMeasures(req proto.AppendMeasuresReq) uint16 { func (s *Database) AppendMeasures(req proto.AppendMeasuresReq) uint16 {
@@ -454,7 +454,7 @@ func (s *Database) AppendMeasures(req proto.AppendMeasuresReq) uint16 {
// report, err := s.atree.AppendDataPage(atree.AppendDataPageReq{}) // report, err := s.atree.AppendDataPage(atree.AppendDataPageReq{})
// if err != nil { // if err != nil {
// diploma.Abort(diploma.WriteToAtreeFailed, err) // qb.Abort(qb.WriteToAtreeFailed, err)
// } // }
// _ = report // _ = report
@@ -473,7 +473,7 @@ func (s *Database) AppendMeasures(req proto.AppendMeasuresReq) uint16 {
return proto.ErrNoMetric return proto.ErrNoMetric
default: default:
diploma.Abort(diploma.WrongResultCodeBug, ErrWrongResultCodeBug) qb.Abort(qb.WrongResultCodeBug, ErrWrongResultCodeBug)
} }
return 0 return 0
} }
@@ -500,29 +500,29 @@ func (s *Database) DeleteMeasures(req proto.DeleteMeasuresReq) uint16 {
case DeleteFromAtreeNotNeeded: case DeleteFromAtreeNotNeeded:
// регистрирую удаление в TransactionLog // регистрирую удаление в TransactionLog
waitCh := s.txlog.Append(txlog.DeletedMeasures{ s.txlog.Append(txlog.DeletedMeasures{
MetricID: req.MetricID, MetricID: req.MetricID,
}) })
<-waitCh //<-waitCh
case DeleteFromAtreeRequired: case DeleteFromAtreeRequired:
// собираю номера всех data и index страниц метрики (типа запись REDO лога). // собираю номера всех data и index страниц метрики (типа запись REDO лога).
pageNumbers, err := s.atree.GetAllPages(req.MetricID) pageNumbers, err := s.atree.GetAllPages(req.MetricID)
if err != nil { if err != nil {
diploma.Abort(diploma.FailedAtreeRequest, err) qb.Abort(qb.FailedAtreeRequest, err)
} }
// регистрирую удаление в TransactionLog // регистрирую удаление в TransactionLog
waitCh := s.txlog.Append(txlog.DeletedMeasures{ s.txlog.Append(txlog.DeletedMeasures{
MetricID: req.MetricID, MetricID: req.MetricID,
FreePageNumbers: pageNumbers, FreePageNumbers: pageNumbers,
}) })
<-waitCh //<-waitCh
case NoMetric: case NoMetric:
return proto.ErrNoMetric return proto.ErrNoMetric
default: default:
diploma.Abort(diploma.WrongResultCodeBug, ErrWrongResultCodeBug) qb.Abort(qb.WrongResultCodeBug, ErrWrongResultCodeBug)
} }
return 0 return 0
} }
@@ -540,7 +540,7 @@ func (s *Database) ListAllInstantMeasures(conn net.Conn, req proto.ListAllInstan
return s.fullScan(fullScanReq{ return s.fullScan(fullScanReq{
MetricID: req.MetricID, MetricID: req.MetricID,
MetricType: diploma.Instant, MetricType: qb.Instant,
Conn: conn, Conn: conn,
ResponseWriter: responseWriter, ResponseWriter: responseWriter,
}) })
@@ -551,7 +551,7 @@ func (s *Database) ListAllCumulativeMeasures(conn io.Writer, req proto.ListAllCu
return s.fullScan(fullScanReq{ return s.fullScan(fullScanReq{
MetricID: req.MetricID, MetricID: req.MetricID,
MetricType: diploma.Cumulative, MetricType: qb.Cumulative,
Conn: conn, Conn: conn,
ResponseWriter: responseWriter, ResponseWriter: responseWriter,
}) })
@@ -567,7 +567,7 @@ func (s *Database) ListInstantMeasures(conn net.Conn, req proto.ListInstantMeasu
return s.rangeScan(rangeScanReq{ return s.rangeScan(rangeScanReq{
MetricID: req.MetricID, MetricID: req.MetricID,
MetricType: diploma.Instant, MetricType: qb.Instant,
Since: req.Since, Since: req.Since,
Until: req.Until - 1, Until: req.Until - 1,
Conn: conn, Conn: conn,
@@ -585,7 +585,7 @@ func (s *Database) ListCumulativeMeasures(conn net.Conn, req proto.ListCumulativ
return s.rangeScan(rangeScanReq{ return s.rangeScan(rangeScanReq{
MetricID: req.MetricID, MetricID: req.MetricID,
MetricType: diploma.Cumulative, MetricType: qb.Cumulative,
Since: req.Since, Since: req.Since,
Until: req.Until - 1, Until: req.Until - 1,
Conn: conn, Conn: conn,
@@ -620,7 +620,7 @@ func (s *Database) ListInstantPeriods(conn net.Conn, req proto.ListInstantPeriod
return s.rangeScan(rangeScanReq{ return s.rangeScan(rangeScanReq{
MetricID: req.MetricID, MetricID: req.MetricID,
MetricType: diploma.Instant, MetricType: qb.Instant,
Since: uint32(since.Unix()), Since: uint32(since.Unix()),
Until: uint32(until.Unix()), Until: uint32(until.Unix()),
Conn: conn, Conn: conn,
@@ -647,7 +647,7 @@ func (s *Database) ListCumulativePeriods(conn net.Conn, req proto.ListCumulative
return s.rangeScan(rangeScanReq{ return s.rangeScan(rangeScanReq{
MetricID: req.MetricID, MetricID: req.MetricID,
MetricType: diploma.Cumulative, MetricType: qb.Cumulative,
Since: uint32(since.Unix()), Since: uint32(since.Unix()),
Until: uint32(until.Unix()), Until: uint32(until.Unix()),
Conn: conn, Conn: conn,
@@ -657,7 +657,7 @@ func (s *Database) ListCumulativePeriods(conn net.Conn, req proto.ListCumulative
type rangeScanReq struct { type rangeScanReq struct {
MetricID uint32 MetricID uint32
MetricType diploma.MetricType MetricType qb.MetricType
Since uint32 Since uint32
Until uint32 Until uint32
Conn io.Writer Conn io.Writer
@@ -720,14 +720,14 @@ func (s *Database) rangeScan(req rangeScanReq) error {
reply(req.Conn, proto.ErrWrongMetricType) reply(req.Conn, proto.ErrWrongMetricType)
default: default:
diploma.Abort(diploma.WrongResultCodeBug, ErrWrongResultCodeBug) qb.Abort(qb.WrongResultCodeBug, ErrWrongResultCodeBug)
} }
return nil return nil
} }
type fullScanReq struct { type fullScanReq struct {
MetricID uint32 MetricID uint32
MetricType diploma.MetricType MetricType qb.MetricType
Conn io.Writer Conn io.Writer
ResponseWriter atree.PeriodsWriter ResponseWriter atree.PeriodsWriter
} }
@@ -768,7 +768,7 @@ func (s *Database) fullScan(req fullScanReq) error {
reply(req.Conn, proto.ErrWrongMetricType) reply(req.Conn, proto.ErrWrongMetricType)
default: default:
diploma.Abort(diploma.WrongResultCodeBug, ErrWrongResultCodeBug) qb.Abort(qb.WrongResultCodeBug, ErrWrongResultCodeBug)
} }
return nil return nil
} }

View File

@@ -12,11 +12,9 @@ import (
"sync" "sync"
"time" "time"
diploma "gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree" "gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/chunkenc"
"gordenko.dev/dima/qb/conbuf"
"gordenko.dev/dima/qb/freelist" "gordenko.dev/dima/qb/freelist"
"gordenko.dev/dima/qb/recovery" "gordenko.dev/dima/qb/recovery"
"gordenko.dev/dima/qb/txlog" "gordenko.dev/dima/qb/txlog"
@@ -84,13 +82,25 @@ func New(opt Options) (_ *Database, err error) {
return nil, errors.New("WaitGroup option is required") return nil, errors.New("WaitGroup option is required")
} }
filePath := filepath.Join(opt.Dir, opt.DatabaseName+".free")
freeListFile, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
freeList, err := freelist.New(freelist.Options{
PageSize: 2048,
File: freeListFile,
})
s := &Database{ s := &Database{
workerSignalCh: make(chan struct{}, 1), workerSignalCh: make(chan struct{}, 1),
dir: opt.Dir, dir: opt.Dir,
databaseName: opt.DatabaseName, databaseName: opt.DatabaseName,
metrics: make(map[uint32]*_metric), metrics: make(map[uint32]*_metric),
//metricLockEntries: make(map[uint32]*metricLockEntry), //metricLockEntries: make(map[uint32]*metricLockEntry),
freeList: freelist.New(), freeList: freeList,
tcpPort: opt.TCPPort, tcpPort: opt.TCPPort,
logfile: opt.Logfile, logfile: opt.Logfile,
logger: log.New(opt.Logfile, "", log.LstdFlags), logger: log.New(opt.Logfile, "", log.LstdFlags),
@@ -143,7 +153,7 @@ func (s *Database) recovery() {
recipe, err := advisor.GetRecipe() recipe, err := advisor.GetRecipe()
if err != nil { if err != nil {
diploma.Abort(diploma.GetRecoveryRecipeFailed, err) qb.Abort(qb.GetRecoveryRecipeFailed, err)
} }
var logNumber int var logNumber int
@@ -152,13 +162,13 @@ func (s *Database) recovery() {
if recipe.Snapshot != "" { if recipe.Snapshot != "" {
err = s.loadSnapshot(recipe.Snapshot) err = s.loadSnapshot(recipe.Snapshot)
if err != nil { if err != nil {
diploma.Abort(diploma.LoadSnapshotFailed, err) qb.Abort(qb.LoadSnapshotFailed, err)
} }
} }
for _, changesFileName := range recipe.Changes { for _, changesFileName := range recipe.Changes {
err = s.replayChanges(changesFileName) err = s.replayChanges(changesFileName)
if err != nil { if err != nil {
diploma.Abort(diploma.ReplayChangesFailed, err) qb.Abort(qb.ReplayChangesFailed, err)
} }
} }
logNumber = recipe.LogNumber logNumber = recipe.LogNumber
@@ -174,28 +184,28 @@ func (s *Database) recovery() {
WaitGroup: s.waitGroup, WaitGroup: s.waitGroup,
}) })
if err != nil { if err != nil {
diploma.Abort(diploma.CreateChangesWriterFailed, err) qb.Abort(qb.CreateChangesWriterFailed, err)
} }
go s.txlog.Run() go s.txlog.Run()
// fileNames, err := s.searchREDOFiles() // fileNames, err := s.searchREDOFiles()
// if err != nil { // if err != nil {
// diploma.Abort(diploma.SearchREDOFilesFailed, err) // qb.Abort(qb.SearchREDOFilesFailed, err)
// } // }
// if len(fileNames) > 0 { // if len(fileNames) > 0 {
// for _, fileName := range fileNames { // for _, fileName := range fileNames {
// err = s.replayREDOFile(fileName) // err = s.replayREDOFile(fileName)
// if err != nil { // if err != nil {
// diploma.Abort(diploma.ReplayREDOFileFailed, err) // qb.Abort(qb.ReplayREDOFileFailed, err)
// } // }
// } // }
// for _, fileName := range fileNames { // for _, fileName := range fileNames {
// err = os.Remove(fileName) // err = os.Remove(fileName)
// if err != nil { // if err != nil {
// diploma.Abort(diploma.RemoveREDOFileFailed, err) // qb.Abort(qb.RemoveREDOFileFailed, err)
// } // }
// } // }
// } // }
@@ -204,14 +214,14 @@ func (s *Database) recovery() {
if recipe.CompleteSnapshot { if recipe.CompleteSnapshot {
err = s.dumpSnapshot(logNumber) err = s.dumpSnapshot(logNumber)
if err != nil { if err != nil {
diploma.Abort(diploma.DumpSnapshotFailed, err) qb.Abort(qb.DumpSnapshotFailed, err)
} }
} }
for _, fileName := range recipe.ToDelete { for _, fileName := range recipe.ToDelete {
err = os.Remove(fileName) err = os.Remove(fileName)
if err != nil { if err != nil {
diploma.Abort(diploma.RemoveRecipeFileFailed, err) qb.Abort(qb.RemoveRecipeFileFailed, err)
} }
} }
} }
@@ -348,100 +358,100 @@ func (s *Database) replayChanges(fileName string) error {
} }
func (s *Database) replayChangesRecord(untyped any) error { func (s *Database) replayChangesRecord(untyped any) error {
switch rec := untyped.(type) { // switch rec := untyped.(type) {
case txlog.AddedMetric: // case txlog.AddedMetric:
var ( // var (
values diploma.ValueCompressor // values qb.ValueCompressor
timestampsBuf = conbuf.New(nil) // timestampsBuf = conbuf.New(nil)
valuesBuf = conbuf.New(nil) // valuesBuf = conbuf.New(nil)
) // )
if rec.MetricType == diploma.Cumulative { // if rec.MetricType == qb.Cumulative {
values = chunkenc.NewReverseCumulativeDeltaCompressor( // values = chunkenc.NewReverseCumulativeDeltaCompressor(
valuesBuf, 0, byte(rec.FracDigits)) // valuesBuf, 0, byte(rec.FracDigits))
} else { // } else {
values = chunkenc.NewReverseInstantDeltaCompressor( // values = chunkenc.NewReverseInstantDeltaCompressor(
valuesBuf, 0, byte(rec.FracDigits)) // valuesBuf, 0, byte(rec.FracDigits))
} // }
s.metrics[rec.MetricID] = &_metric{ // s.metrics[rec.MetricID] = &_metric{
MetricType: rec.MetricType, // MetricType: rec.MetricType,
FracDigits: byte(rec.FracDigits), // FracDigits: byte(rec.FracDigits),
TimestampsBuf: timestampsBuf, // TimestampsBuf: timestampsBuf,
ValuesBuf: valuesBuf, // ValuesBuf: valuesBuf,
Timestamps: chunkenc.NewReverseTimeDeltaCompressor(timestampsBuf, 0), // Timestamps: chunkenc.NewReverseTimeDeltaCompressor(timestampsBuf, 0),
Values: values, // Values: values,
} // }
case txlog.DeletedMetric: // case txlog.DeletedMetric:
delete(s.metrics, rec.MetricID) // delete(s.metrics, rec.MetricID)
if len(rec.FreePageNumbers) > 0 { // if len(rec.FreePageNumbers) > 0 {
s.freeList.AddPages(rec.FreePageNumbers) // s.freeList.AddPages(rec.FreePageNumbers)
} // }
case txlog.AppendedMeasure: // case txlog.AppendedMeasure:
metric, ok := s.metrics[rec.MetricID]
if ok {
metric.Timestamps.Append(rec.Timestamp)
metric.Values.Append(rec.Value)
if metric.Since == 0 {
metric.Since = rec.Timestamp
metric.SinceValue = rec.Value
}
metric.Until = rec.Timestamp
metric.UntilValue = rec.Value
}
case txlog.AppendedMeasures:
metric, ok := s.metrics[rec.MetricID]
if ok {
for _, measure := range rec.Measures {
metric.Timestamps.Append(measure.Timestamp)
metric.Values.Append(measure.Value)
if metric.Since == 0 {
metric.Since = measure.Timestamp
metric.SinceValue = measure.Value
}
metric.Until = measure.Timestamp
metric.UntilValue = measure.Value
}
}
// case txlog.AppendedMeasureWithOverflow:
// metric, ok := s.metrics[rec.MetricID] // metric, ok := s.metrics[rec.MetricID]
// if ok { // if ok {
// metric.ReinitBy(rec.Timestamp, rec.Value) // metric.Timestamps.Append(rec.Timestamp)
// if rec.IsRootChanged { // metric.Values.Append(rec.Value)
// metric.RootPageNo = rec.RootPageNo
// if metric.Since == 0 {
// metric.Since = rec.Timestamp
// metric.SinceValue = rec.Value
// } // }
// metric.LastPageNo = rec.DataPageNo
// // delete free pages // metric.Until = rec.Timestamp
// if rec.IsDataPageReused { // metric.UntilValue = rec.Value
// s.freeList.DeleteReservedPages([]uint32{
// rec.DataPageNo,
// })
// } // }
// if len(rec.ReusedIndexPages) > 0 {
// s.freeList.DeleteReservedPages(rec.ReusedIndexPages) // case txlog.AppendedMeasures:
// metric, ok := s.metrics[rec.MetricID]
// if ok {
// for _, measure := range rec.Measures {
// metric.Timestamps.Append(measure.Timestamp)
// metric.Values.Append(measure.Value)
// if metric.Since == 0 {
// metric.Since = measure.Timestamp
// metric.SinceValue = measure.Value
// }
// metric.Until = measure.Timestamp
// metric.UntilValue = measure.Value
// } // }
// } // }
case txlog.DeletedMeasures: // // case txlog.AppendedMeasureWithOverflow:
metric, ok := s.metrics[rec.MetricID] // // metric, ok := s.metrics[rec.MetricID]
if ok { // // if ok {
metric.DeleteMeasures() // // metric.ReinitBy(rec.Timestamp, rec.Value)
if len(rec.FreePageNumbers) > 0 { // // if rec.IsRootChanged {
s.freeList.AddPages(rec.FreePageNumbers) // // metric.RootPageNo = rec.RootPageNo
} // // }
} // // metric.LastPageNo = rec.DataPageNo
// // // delete free pages
// // if rec.IsDataPageReused {
// // s.freeList.DeleteReservedPages([]uint32{
// // rec.DataPageNo,
// // })
// // }
// // if len(rec.ReusedIndexPages) > 0 {
// // s.freeList.DeleteReservedPages(rec.ReusedIndexPages)
// // }
// // }
default: // case txlog.DeletedMeasures:
diploma.Abort(diploma.UnknownTxLogRecordTypeBug, // metric, ok := s.metrics[rec.MetricID]
fmt.Errorf("bug: unknown record type %T in TransactionLog", rec)) // if ok {
} // metric.DeleteMeasures()
// if len(rec.FreePageNumbers) > 0 {
// s.freeList.AddPages(rec.FreePageNumbers)
// }
// }
// default:
// qb.Abort(qb.UnknownTxLogRecordTypeBug,
// fmt.Errorf("bug: unknown record type %T in TransactionLog", rec))
// }
return nil return nil
} }

View File

@@ -2,7 +2,6 @@ package database
import ( import (
"gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/conbuf"
) )
// METRIC // METRIC
@@ -10,59 +9,32 @@ import (
type _metric struct { type _metric struct {
MetricType qb.MetricType MetricType qb.MetricType
FracDigits byte FracDigits byte
RootPageNo uint32
LastPageNo uint32 LastPageNo uint32
SinceValue float64 SinceValue float64
Since uint32 Since uint32
UntilValue float64 UntilValue float64
Until uint32 Until uint32
//TimestampsBuf *conbuf.ContinuousBuffer
//ValuesBuf *conbuf.ContinuousBuffer
Timestamps qb.TimestampCompressor Timestamps qb.TimestampCompressor
Values qb.ValueCompressor Values qb.ValueCompressor
} }
func (s *_metric) ReinitBy(timestamp uint32, value float64) { func (s *_metric) ReinitBy(timestamp uint32, value float64) {
// s.TimestampsBuf = conbuf.New(nil) s.Timestamps.Renew()
// s.ValuesBuf = conbuf.New(nil) s.Values.Renew()
// //
// s.Timestamps = chunkenc.NewReverseTimeDeltaOfDeltaCompressor(
// s.TimestampsBuf, 0)
// if s.MetricType == octopus.Cumulative { s.Timestamps.Append(timestamp)
// s.Values = chunkenc.NewReverseCumulativeDeltaCompressor( s.Values.Append(value)
// s.ValuesBuf, 0, s.FracDigits)
// } else {
// s.Values = chunkenc.NewReverseInstantDeltaCompressor(
// s.ValuesBuf, 0, s.FracDigits)
// }
// s.Timestamps.Append(timestamp) s.Since = timestamp
// s.Values.Append(value) s.SinceValue = value
s.Until = timestamp
// s.Since = timestamp s.UntilValue = value
// s.SinceValue = value
// s.Until = timestamp
// s.UntilValue = value
} }
func (s *_metric) DeleteMeasures() { func (s *_metric) DeleteMeasures() {
s.TimestampsBuf = conbuf.New(nil) s.Timestamps.Renew()
s.ValuesBuf = conbuf.New(nil) s.Values.Renew()
// fix
// s.Timestamps = chunkenc.NewReverseTimeDeltaOfDeltaCompressor(
// s.TimestampsBuf, 0)
// if s.MetricType == octopus.Cumulative {
// s.Values = chunkenc.NewReverseCumulativeDeltaCompressor(
// s.ValuesBuf, 0, s.FracDigits)
// } else {
// s.Values = chunkenc.NewReverseInstantDeltaCompressor(
// s.ValuesBuf, 0, s.FracDigits)
// }
// END OF FIX
s.RootPageNo = 0
s.LastPageNo = 0 s.LastPageNo = 0
s.Since = 0 s.Since = 0
s.SinceValue = 0 s.SinceValue = 0

View File

@@ -7,21 +7,22 @@ import (
"os" "os"
"path/filepath" "path/filepath"
octopus "gordenko.dev/dima/qb" bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/chunkenc"
"gordenko.dev/dima/qb/conbuf"
"gordenko.dev/dima/qb/freelist" "gordenko.dev/dima/qb/freelist"
) )
/* /*
Формат: Формат:
//lsn - varuint (останній LSN, що змінив дані у RAM)
metricsQty - varuint metricsQty - varuint
[metric]* [metric]*
где metric - це: де metric - це:
metricID - 4b metricID - 4b
metricType - 1b metricType - 1b
fracDigits - 1b fracDigits - 1b
rootPageNo - 4b
lastPageNo - 4b lastPageNo - 4b
since - 4b since - 4b
sinceValue - 8b sinceValue - 8b
@@ -31,14 +32,14 @@ timestamps size - 2b
values size - 2b values size - 2b
timestams payload - Nb timestams payload - Nb
values payload - Nb values payload - Nb
dataFreeList size - varuint dataFreeList size - varsize
dataFreeList - Nb dataFreeList - Nb
indexFreeList size - varuint indexFreeList size - varsize
indexFreeList - Nb indexFreeList - Nb
CRC32 - 4b CRC32 - 4b
*/ */
const metricHeaderSize = 42 const metricHeaderSize = 38
func (s *Database) dumpSnapshot(logNumber int) (err error) { func (s *Database) dumpSnapshot(logNumber int) (err error) {
var ( var (
@@ -66,49 +67,23 @@ func (s *Database) dumpSnapshot(logNumber int) (err error) {
bin.PutUint32(prefix[0:], metricID) bin.PutUint32(prefix[0:], metricID)
prefix[4] = byte(metric.MetricType) prefix[4] = byte(metric.MetricType)
prefix[5] = metric.FracDigits prefix[5] = metric.FracDigits
bin.PutUint32(prefix[6:], metric.RootPageNo) bin.PutUint32(prefix[6:], metric.LastPageNo)
bin.PutUint32(prefix[10:], metric.LastPageNo) bin.PutUint32(prefix[10:], metric.Since)
bin.PutUint32(prefix[14:], metric.Since) bin.PutFloat64(prefix[14:], metric.SinceValue)
bin.PutFloat64(prefix[18:], metric.SinceValue) bin.PutUint32(prefix[22:], metric.Until)
bin.PutUint32(prefix[26:], metric.Until) bin.PutFloat64(prefix[26:], metric.UntilValue)
bin.PutFloat64(prefix[30:], metric.UntilValue) bin.PutUint16(prefix[34:], uint16(tSize))
bin.PutUint16(prefix[38:], uint16(tSize)) bin.PutUint16(prefix[36:], uint16(vSize))
bin.PutUint16(prefix[40:], uint16(vSize))
_, err = dst.Write(prefix) _, err = dst.Write(prefix)
if err != nil { if err != nil {
return return
} }
// copy timestamps // copy timestamps
remaining := tSize writeChunks(dst, metric.Timestamps.Chunks(), tSize)
for _, buf := range metric.TimestampsBuf.Chunks() {
if remaining < len(buf) {
buf = buf[:remaining]
}
_, err = dst.Write(buf)
if err != nil {
return
}
remaining -= len(buf)
if remaining == 0 {
break
}
}
// copy values // copy values
remaining = vSize writeChunks(dst, metric.Values.Chunks(), vSize)
for _, buf := range metric.ValuesBuf.Chunks() {
if remaining < len(buf) {
buf = buf[:remaining]
}
_, err = dst.Write(buf)
if err != nil {
return
}
remaining -= len(buf)
if remaining == 0 {
break
}
}
} }
// free data pages // free data pages
err = freeListWriteTo(s.freeList, dst) err = freeListWriteTo(s.freeList, dst)
@@ -133,123 +108,124 @@ func (s *Database) dumpSnapshot(logNumber int) (err error) {
return return
} }
prevLogNumber := logNumber - 1 // prevLogNumber := logNumber - 1
prevChanges := filepath.Join(s.dir, fmt.Sprintf("%d.changes", prevLogNumber)) // prevChanges := filepath.Join(s.dir, fmt.Sprintf("%d.changes", prevLogNumber))
prevSnapshot := filepath.Join(s.dir, fmt.Sprintf("%d.snapshot", prevLogNumber)) // prevSnapshot := filepath.Join(s.dir, fmt.Sprintf("%d.snapshot", prevLogNumber))
isExist, err := isFileExist(prevChanges) // isExist, err := isFileExist(prevChanges)
if err != nil { // if err != nil {
return // return
} // }
if isExist { // if isExist {
err = os.Remove(prevChanges) // err = os.Remove(prevChanges)
if err != nil { // if err != nil {
octopus.Abort(octopus.DeletePrevChangesFileFailed, err) // qb.Abort(qb.DeletePrevChangesFileFailed, err)
} // }
} // }
isExist, err = isFileExist(prevSnapshot) // isExist, err = isFileExist(prevSnapshot)
if err != nil { // if err != nil {
return // return
} // }
if isExist { // if isExist {
err = os.Remove(prevSnapshot) // err = os.Remove(prevSnapshot)
if err != nil { // if err != nil {
octopus.Abort(octopus.DeletePrevSnapshotFileFailed, err) // qb.Abort(qb.DeletePrevSnapshotFileFailed, err)
} // }
} // }
return return
} }
func (s *Database) loadSnapshot(fileName string) (err error) { func (s *Database) loadSnapshot(fileName string) (err error) {
// FIX var (
// var ( hasher = crc32.NewIEEE()
// hasher = crc32.NewIEEE() header = make([]byte, metricHeaderSize)
// metricsQty int body = make([]byte, atree.PageSize)
// header = make([]byte, metricHeaderSize) )
// body = make([]byte, atree.PageSize)
// )
// file, err := os.Open(fileName) file, err := os.Open(fileName)
// if err != nil { if err != nil {
// return return
// } }
// src := io.TeeReader(file, hasher) src := io.TeeReader(file, hasher)
// u64, _, err := bin.ReadVarUint64(src) metricsQty, err := bin.ReadVarSize(src)
// if err != nil { if err != nil {
// return return
// } }
// metricsQty = int(u64)
// for range metricsQty { for range metricsQty {
// var metric _metric var metric _metric
// err = bin.ReadNInto(src, header) err = bin.ReadNInto(src, header)
// if err != nil { if err != nil {
// return return
// } }
// metricID := bin.GetUint32(header[0:]) metricID, _ := bin.GetUint32(header[0:])
// metric.MetricType = octopus.MetricType(header[4]) metric.MetricType = qb.MetricType(header[4])
// metric.FracDigits = header[5] metric.FracDigits = header[5]
// metric.RootPageNo = bin.GetUint32(header[6:]) metric.LastPageNo, _ = bin.GetUint32(header[6:])
// metric.LastPageNo = bin.GetUint32(header[10:]) metric.Since, _ = bin.GetUint32(header[10:])
// metric.Since = bin.GetUint32(header[14:]) metric.SinceValue, _ = bin.GetFloat64(header[14:])
// metric.SinceValue = bin.GetFloat64(header[18:]) metric.Until, _ = bin.GetUint32(header[22:])
// metric.Until = bin.GetUint32(header[26:]) metric.UntilValue, _ = bin.GetFloat64(header[26:])
// metric.UntilValue = bin.GetFloat64(header[30:]) tSize, _ := bin.GetUint16(header[34:])
// tSize := bin.GetUint16(header[38:]) vSize, _ := bin.GetUint16(header[36:])
// vSize := bin.GetUint16(header[40:])
// buf := body[:tSize] buf := body[:tSize]
// err = bin.ReadNInto(src, buf) err = bin.ReadNInto(src, buf)
// if err != nil { if err != nil {
// return return
// } }
// metric.TimestampsBuf = conbuf.NewFromBuffer(buf) metric.Timestamps = chunkenc.NewReverseTimeDeltaCompressor(
conbuf.NewFromBuffer(buf),
int(tSize),
)
// buf = body[:vSize] buf = body[:vSize]
// err = bin.ReadNInto(src, buf) err = bin.ReadNInto(src, buf)
// if err != nil { if err != nil {
// return return
// } }
// metric.ValuesBuf = conbuf.NewFromBuffer(buf)
// metric.Timestamps = chunkenc.NewReverseTimeDeltaOfDeltaCompressor( if metric.MetricType == qb.Cumulative {
// metric.TimestampsBuf, int(tSize)) metric.Values = chunkenc.NewReverseCumulativeDeltaCompressor(
conbuf.NewFromBuffer(buf),
int(vSize),
metric.FracDigits,
)
} else {
metric.Values = chunkenc.NewReverseInstantDeltaCompressor(
conbuf.NewFromBuffer(buf),
int(vSize),
metric.FracDigits,
)
}
s.metrics[metricID] = &metric
}
// if metric.MetricType == octopus.Cumulative { err = restoreFreeList(s.freeList, src)
// metric.Values = chunkenc.NewReverseCumulativeDeltaCompressor( if err != nil {
// metric.ValuesBuf, int(vSize), metric.FracDigits) return fmt.Errorf("restore dataFreeList: %s", err)
// } else { }
// metric.Values = chunkenc.NewReverseInstantDeltaCompressor(
// metric.ValuesBuf, int(vSize), metric.FracDigits)
// }
// s.metrics[metricID] = &metric
// }
// err = restoreFreeList(s.freeList, src) err = restoreFreeList(s.freeList, src)
// if err != nil { if err != nil {
// return fmt.Errorf("restore dataFreeList: %s", err) return fmt.Errorf("restore indexFreeList: %s", err)
// } }
// err = restoreFreeList(s.freeList, src) calculatedChecksum := hasher.Sum32()
// if err != nil {
// return fmt.Errorf("restore indexFreeList: %s", err)
// }
// calculatedChecksum := hasher.Sum32() writtenChecksum, err := bin.ReadUint32(file)
if err != nil {
return
}
// writtenChecksum, err := bin.ReadUint32(file) if calculatedChecksum != writtenChecksum {
// if err != nil { return fmt.Errorf("calculated checksum %d not equal written checksum %d", calculatedChecksum, writtenChecksum)
// return }
// }
// if calculatedChecksum != writtenChecksum {
// return fmt.Errorf("calculated checksum %d not equal written checksum %d", calculatedChecksum, writtenChecksum)
// }
return return
} }
@@ -258,9 +234,9 @@ func (s *Database) loadSnapshot(fileName string) (err error) {
func freeListWriteTo(freeList *freelist.FreeList, dst io.Writer) error { func freeListWriteTo(freeList *freelist.FreeList, dst io.Writer) error {
serialized, err := freeList.Serialize() serialized, err := freeList.Serialize()
if err != nil { if err != nil {
octopus.Abort(octopus.FailedFreeListSerialize, err) qb.Abort(qb.FailedFreeListSerialize, err)
} }
_, err = bin.WriteVarUint64(dst, uint64(len(serialized))) _, err = bin.WriteVarSize(dst, len(serialized))
if err != nil { if err != nil {
return err return err
} }
@@ -272,14 +248,32 @@ func freeListWriteTo(freeList *freelist.FreeList, dst io.Writer) error {
} }
func restoreFreeList(freeList *freelist.FreeList, src io.Reader) error { func restoreFreeList(freeList *freelist.FreeList, src io.Reader) error {
size, _, err := bin.ReadVarUint64(src) size, err := bin.ReadVarSize(src)
if err != nil { if err != nil {
return err return err
} }
serialized, err := bin.ReadN(src, int(size)) serialized, err := bin.ReadN(src, size)
if err != nil { if err != nil {
return err return err
} }
freeList.Restore(serialized) freeList.Restore(serialized)
return nil return nil
} }
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
}

View File

@@ -4,37 +4,139 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"os" "os"
"slices"
"sync"
"gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/util"
) )
const ( const (
pageSize = 1024
crcSize = 4 crcSize = 4
ptrSize = 4 ptrSize = 4
pointersOnPage = (pageSize - crcSize) / ptrSize
flushTreshold = 1000 //= int(float(pageSize/ptrSize) * 1.3) // кількість вільних сторінок коли вже треба писати на диск
) )
type FreeList struct { type FreeList struct {
mutex sync.Mutex
file *os.File file *os.File
pageSize int
pointersOnPage int
flushTreshold int // кількість вільних сторінок коли вже треба писати на диск
pages int pages int
//free *roaring.Bitmap
//reserved *roaring.Bitmap
free []uint32 free []uint32
reserved []uint32
} }
func New() *FreeList { type Options struct {
return &FreeList{ PageSize int
//free: roaring.New(), File *os.File
//reserved: roaring.New(),
}
} }
func New(opt Options) (*FreeList, error) {
s := &FreeList{
file: opt.File,
pageSize: opt.PageSize,
pointersOnPage: (opt.PageSize - crcSize) / ptrSize,
}
s.flushTreshold = int(float64(s.pointersOnPage) * 1.3)
info, err := s.file.Stat()
if err != nil {
return nil, err
}
fileSize := info.Size()
if fileSize > 0 {
if (fileSize % int64(s.pageSize)) > 0 {
return nil, fmt.Errorf("file size %d is not a multiple of page size %d", fileSize, s.pageSize)
}
s.pages = int(fileSize) / s.pageSize
}
return s, nil
}
func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
if len(pageNumbers) == 0 {
return
}
s.free = append(s.free, pageNumbers...)
for len(s.free) > s.flushTreshold {
err = s.save()
if err != nil {
return
}
// викидаю номери сторінок, які записав на диск
copy(s.free, s.free[s.pointersOnPage:])
s.free = s.free[:len(s.free)-s.pointersOnPage]
}
return
}
func (s *FreeList) save() error {
buf := make([]byte, s.pageSize)
i := crcSize
for _, pageNo := range s.free[:s.pointersOnPage] {
bin.PutUint32(buf[i:], pageNo)
i += ptrSize
}
bin.PutUint32(buf, util.CalcChecksum(buf[crcSize:]))
//
off := int64(s.pages * s.pageSize)
n, err := s.file.WriteAt(buf, off)
if err != nil {
return err
}
if n != s.pageSize {
return fmt.Errorf("written size %d bytes not equal page size %d", n, s.pageSize)
}
err = s.file.Sync()
if err != nil {
return err
}
s.pages++
return nil
}
func (s *FreeList) loadLastPage() error {
var (
buf = make([]byte, s.pageSize)
off = int64((s.pages - 1) * s.pageSize)
)
n, err := s.file.ReadAt(buf, off)
if err != nil {
return fmt.Errorf("file.Seek: %s", err)
}
if n != s.pageSize {
return fmt.Errorf("read size %d bytes not equal page size %d", n, s.pageSize)
}
// check crc
checksum := bin.GetUint32(buf[0:])
if util.CalcChecksum(buf[crcSize:]) != checksum {
return fmt.Errorf("page %d is corrupted", s.pages)
}
for i := crcSize; i < len(buf); i += ptrSize {
s.free = append(s.free, bin.GetUint32(buf[i:]))
}
s.pages--
err = s.file.Truncate(int64(s.pages * s.pageSize))
if err != nil {
return err
}
return nil
}
func (s *FreeList) GetPageNumber() (pageNo uint32, err error) {
if len(s.free) == 0 {
if s.pages == 0 {
return
}
err = s.loadLastPage()
if err != nil {
return
}
}
lastIdx := len(s.free) - 1
pageNo = s.free[lastIdx]
s.free = s.free[:lastIdx]
return
}
// для відновлення недозаповненнох сторінки із снапшота
func (s *FreeList) Restore(serialized []byte) error { func (s *FreeList) Restore(serialized []byte) error {
if (len(serialized) % ptrSize) != 0 { if (len(serialized) % ptrSize) != 0 {
return fmt.Errorf("wrong size") return fmt.Errorf("wrong size")
@@ -45,112 +147,11 @@ func (s *FreeList) Restore(serialized []byte) error {
return nil return nil
} }
func (s *FreeList) AddPages(pageNumbers []uint32) { // для запису в snapshot
if len(pageNumbers) == 0 {
return
}
s.mutex.Lock()
s.free = append(s.free, pageNumbers...)
if len(s.free) > flushTreshold {
err := s.save()
if err != nil {
// ABORT
}
}
s.mutex.Unlock()
}
func (s *FreeList) save() error {
buf := make([]byte, pageSize)
//w := bytes.NewBuffer(nil)
//s.mutex.Lock()
i := crcSize
for _, pageNo := range s.free[:pointersOnPage] {
bin.PutUint32(buf[i:], pageNo)
i += ptrSize
}
// fix checksum
fileOffset := int64(s.pages-1) * pageSize
n, err := s.file.WriteAt(buf, fileOffset)
if err != nil {
return err
}
if n != pageSize {
return fmt.Errorf("written size %d bytes not equal page size %d", n, pageSize)
}
s.free = s.free[pointersOnPage:]
s.pages++
return nil
}
func (s *FreeList) loadLastPage() error {
var (
buf = make([]byte, pageSize)
offset = int64(s.pages-1) * pageSize
)
n, err := s.file.ReadAt(buf, offset)
if err != nil {
return fmt.Errorf("file.Seek: %s", err)
}
if n != s.pages {
return fmt.Errorf("read size %d bytes not equal page size %d", n, pageSize)
}
// check crc
//var pageNumbers []uint32
for i := crcSize; i < len(buf); i += ptrSize {
s.free = append(s.free, bin.GetUint32(buf[i:]))
}
s.pages--
return nil
}
// ReserveDataPage - аллокатор резервирует страницу, но не удаляет до визова
// DeleteFromFree, ибо транзакция может не завершится, а между віделением страници
// и падением транзакции - будет создан init файл.
func (s *FreeList) ReservePage() (pageNo uint32) {
s.mutex.Lock()
defer s.mutex.Unlock()
if len(s.free) == 0 {
if s.pages == 0 {
return
}
if err := s.loadLastPage(); err != nil {
// ABORT
return
}
}
lastIdx := len(s.free)
pageNo = s.free[lastIdx]
s.free = s.free[:lastIdx]
s.reserved = append(s.reserved, pageNo)
return
}
// Удаляет ранее зарезервированные страницы
func (s *FreeList) DeleteReservedPages(pageNumbers []uint32) {
var cleared []uint32
s.mutex.Lock()
for _, reservedPageNo := range s.reserved {
if slices.Contains(pageNumbers, reservedPageNo) {
// delete
} else {
cleared = append(cleared, reservedPageNo)
}
}
s.reserved = cleared
s.mutex.Unlock()
}
func (s *FreeList) Serialize() ([]byte, error) { func (s *FreeList) Serialize() ([]byte, error) {
w := bytes.NewBuffer(nil) w := bytes.NewBuffer(nil)
s.mutex.Lock()
for _, pageNo := range s.free { for _, pageNo := range s.free {
bin.WriteUint32(w, pageNo) bin.WriteUint32(w, pageNo)
} }
for _, pageNo := range s.reserved {
bin.WriteUint32(w, pageNo)
}
s.mutex.Unlock()
return w.Bytes(), nil return w.Bytes(), nil
} }

44
freelist/freelist_test.go Normal file
View File

@@ -0,0 +1,44 @@
package freelist
import (
"fmt"
"os"
"testing"
)
func createFreeList() (_ *FreeList, err error) {
file, err := os.OpenFile("test.free", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return
}
return New(Options{
PageSize: 20,
File: file,
})
}
func TestAddToFreeList(t *testing.T) {
freeList, err := createFreeList()
if err != nil {
t.Fatal(err)
}
pageNumbers := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
err = freeList.AddPageNumbers(pageNumbers)
if err != nil {
t.Fatal(err)
}
}
func TestGetFromFreeList(t *testing.T) {
freeList, err := createFreeList()
if err != nil {
t.Fatal(err)
}
for range 4 {
pageNo, err := freeList.GetPageNumber()
fmt.Println(pageNo, err)
}
}

BIN
freelist/test.free Normal file

Binary file not shown.

238
txlog/helpers.go Normal file
View File

@@ -0,0 +1,238 @@
package txlog
import (
"bytes"
bin "gordenko.dev/dima/bin/little"
)
const recordSize = 8
const chunkSize = 24
/*
Format appended measures:
1b - tx type
4b - metricID
Nb - qty of filled data pages (varsize)
[
4b - pageNo
1b - reused
4b - prevPageNo
4b - page crc32
2b - timestamps size
Nb - timestamps payload
2b - values size
Nb - values payload
]
2b - timestamps offset (for the 1st data page only)
2b - values offset (for the 1st data page only)
Nb - timestamps size unfilled (varsize)
Nb - timestamps payload unfilled
Nb - values size unfilled (varsize)
Nb - values payload unfilled
Nb - qty of index levels (varsize)
[
Nb - qty of level filled pages (varsize)
[
4b - pageNo
1b - reused
4b - page crc32
Nb - records qty (varsize)
Nb - records payload
]
Nb - records qty unfilled (varsize)
Nb - records payload unfilled
]
*/
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
}

View File

@@ -113,26 +113,26 @@ func (s *Reader) parseRecords(body []byte) ([]any, error) {
} }
records = append(records, rec) records = append(records, rec)
case CodeAppendedMeasure: // case CodeAppendedMeasure:
rec := new(AppendedMeasure) // rec := new(AppendedMeasure)
if err = rec.Parse(src); err != nil { // if err = rec.Parse(src); err != nil {
return nil, err // return nil, err
} // }
records = append(records, rec) // records = append(records, rec)
case CodeAppendedMeasures: // case CodeAppendedMeasures:
rec := new(AppendedMeasures) // rec := new(AppendedMeasures)
if err = rec.Parse(src); err != nil { // if err = rec.Parse(src); err != nil {
return nil, err // return nil, err
} // }
records = append(records, rec) // records = append(records, rec)
case CodeAppendedPages: // case CodeAppendedPages:
rec := new(AppendedPages) // rec := new(AppendedPages)
if err = rec.Parse(src); err != nil { // if err = rec.Parse(src); err != nil {
return nil, err // return nil, err
} // }
records = append(records, rec) // records = append(records, rec)
case CodeDeletedMeasures: case CodeDeletedMeasures:
rec := new(DeletedMeasures) rec := new(DeletedMeasures)

View File

@@ -5,9 +5,7 @@ import (
bin "gordenko.dev/dima/bin/little" bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/conbuf" "gordenko.dev/dima/qb/conbuf"
"gordenko.dev/dima/qb/proto"
) )
type AddedMetric struct { type AddedMetric struct {
@@ -77,93 +75,93 @@ func (s *DeletedMetric) Parse(src io.Reader) (err error) {
return nil return nil
} }
type AppendedMeasure struct { // type AppendedMeasure struct {
MetricID uint32 // MetricID uint32
Timestamp uint32 // Timestamp uint32
Value float64 // Value float64
} // }
func (s AppendedMeasure) Pack(w io.Writer) { // func (s AppendedMeasure) Pack(w io.Writer) {
arr := []byte{ // arr := []byte{
CodeAppendedMeasure, // CodeAppendedMeasure,
0, 0, 0, 0, // metricID // 0, 0, 0, 0, // metricID
0, 0, 0, 0, // timestamp // 0, 0, 0, 0, // timestamp
0, 0, 0, 0, 0, 0, 0, 0, // value // 0, 0, 0, 0, 0, 0, 0, 0, // value
} // }
bin.PutUint32(arr[1:], s.MetricID) // bin.PutUint32(arr[1:], s.MetricID)
bin.PutUint32(arr[5:], s.Timestamp) // bin.PutUint32(arr[5:], s.Timestamp)
bin.PutFloat64(arr[9:], s.Value) // bin.PutFloat64(arr[9:], s.Value)
w.Write(arr) // w.Write(arr)
} // }
func (s *AppendedMeasure) Parse(src io.Reader) (err error) { // func (s *AppendedMeasure) Parse(src io.Reader) (err error) {
arr, err := bin.ReadN(src, 16) // arr, err := bin.ReadN(src, 16)
if err != nil { // if err != nil {
return // return
} // }
s.MetricID, _ = bin.GetUint32(arr[0:]) // s.MetricID, _ = bin.GetUint32(arr[0:])
s.Timestamp, _ = bin.GetUint32(arr[4:]) // s.Timestamp, _ = bin.GetUint32(arr[4:])
s.Value, _ = bin.GetFloat64(arr[8:]) // s.Value, _ = bin.GetFloat64(arr[8:])
return nil // return nil
} // }
type AppendedMeasures struct { // type AppendedMeasures struct {
MetricID uint32 // MetricID uint32
Measures []proto.Measure // Measures []proto.Measure
} // }
func (s AppendedMeasures) Pack(w io.Writer) { // func (s AppendedMeasures) Pack(w io.Writer) {
arr := []byte{ // arr := []byte{
CodeAppendedMeasures, // CodeAppendedMeasures,
0, 0, 0, 0, // metricID // 0, 0, 0, 0, // metricID
0, 0, // qty // 0, 0, // qty
} // }
bin.PutUint32(arr[1:], s.MetricID) // bin.PutUint32(arr[1:], s.MetricID)
bin.PutUint16(arr[5:], uint16(len(s.Measures))) // bin.PutUint16(arr[5:], uint16(len(s.Measures)))
w.Write(arr) // w.Write(arr)
for _, measure := range s.Measures { // for _, measure := range s.Measures {
bin.WriteUint32(w, measure.Timestamp) // bin.WriteUint32(w, measure.Timestamp)
bin.WriteFloat64(w, measure.Value) // bin.WriteFloat64(w, measure.Value)
} // }
} // }
func (s *AppendedMeasures) Parse(src io.Reader) (err error) { // func (s *AppendedMeasures) Parse(src io.Reader) (err error) {
s.MetricID, err = bin.ReadUint32(src) // s.MetricID, err = bin.ReadUint32(src)
if err != nil { // if err != nil {
return // return
} // }
qty, err := bin.ReadUint16(src) // qty, err := bin.ReadUint16(src)
if err != nil { // if err != nil {
return // return
} // }
for range qty { // for range qty {
var measure proto.Measure // var measure proto.Measure
measure.Timestamp, err = bin.ReadUint32(src) // measure.Timestamp, err = bin.ReadUint32(src)
if err != nil { // if err != nil {
return // return
} // }
measure.Value, err = bin.ReadFloat64(src) // measure.Value, err = bin.ReadFloat64(src)
if err != nil { // if err != nil {
return // return
} // }
s.Measures = append(s.Measures, measure) // s.Measures = append(s.Measures, measure)
} // }
return nil // return nil
} // }
type AppendedPagesReq struct { // type AppendedPagesReq struct {
Legs []atree.PathLeg // Legs []atree.PathLeg
MetricID uint32 // MetricID uint32
Timestamp uint32 // last measure // Timestamp uint32 // last measure
Value float64 // last measure // Value float64 // last measure
//RootPageNo uint32 // //RootPageNo uint32
LastPageNo uint32 // LastPageNo uint32
Pages []atree.NotLinkedDataPage // Pages []atree.NotLinkedDataPage
TimestampsChunks [][]byte // TimestampsChunks [][]byte
TimestampsSize uint16 // TimestampsSize uint16
ValuesChunks [][]byte // ValuesChunks [][]byte
ValuesSize uint16 // ValuesSize uint16
} // }
/* /*
4b metricID 4b metricID
@@ -184,96 +182,96 @@ Nb - timestamps payload
2b - values size (not filled page) 2b - values size (not filled page)
Nb - values payload Nb - values payload
*/ */
type AppendedPages struct { // type AppendedPages struct {
MetricID uint32 // MetricID uint32
Timestamp uint32 // last measure // Timestamp uint32 // last measure
Value float64 // last measure // Value float64 // last measure
NewRootPageNo uint32 // NewRootPageNo uint32
LastPageNo uint32 // LastPageNo uint32
Pages []atree.PageToWrite // Pages []atree.PageToWrite
TimestampsChunks [][]byte // TimestampsChunks [][]byte
TimestampsSize int // TimestampsSize int
ValuesChunks [][]byte // ValuesChunks [][]byte
ValuesSize int // ValuesSize int
} // }
func (s *AppendedPages) Pack(w io.Writer) { // func (s *AppendedPages) Pack(w io.Writer) {
w.Write([]byte{ // w.Write([]byte{
CodeAppendedPages, // CodeAppendedPages,
}) // })
bin.WriteUint32(w, s.MetricID) // bin.WriteUint32(w, s.MetricID)
bin.WriteUint32(w, s.Timestamp) // bin.WriteUint32(w, s.Timestamp)
bin.WriteFloat64(w, s.Value) // bin.WriteFloat64(w, s.Value)
bin.WriteUint32(w, s.LastPageNo) // bin.WriteUint32(w, s.LastPageNo)
bin.WriteUint32(w, s.NewRootPageNo) // bin.WriteUint32(w, s.NewRootPageNo)
// // //
bin.WriteVarSize(w, len(s.Pages)) // bin.WriteVarSize(w, len(s.Pages))
for _, p := range s.Pages { // for _, p := range s.Pages {
bin.WriteUint32(w, p.PageNo) // bin.WriteUint32(w, p.PageNo)
if p.IsReused { // if p.IsReused {
w.Write([]byte{1}) // w.Write([]byte{1})
} else { // } else {
w.Write([]byte{0}) // w.Write([]byte{0})
} // }
w.Write(p.Data) // w.Write(p.Data)
} // }
// timestamps // // timestamps
writeChunkedPayloadTo(s.TimestampsChunks, int(s.TimestampsSize), w) // writeChunkedPayloadTo(s.TimestampsChunks, int(s.TimestampsSize), w)
// values // // values
writeChunkedPayloadTo(s.ValuesChunks, int(s.ValuesSize), w) // writeChunkedPayloadTo(s.ValuesChunks, int(s.ValuesSize), w)
} // }
func (s *AppendedPages) Parse(src io.Reader) (err error) { // func (s *AppendedPages) Parse(src io.Reader) (err error) {
s.MetricID, err = bin.ReadUint32(src) // s.MetricID, err = bin.ReadUint32(src)
if err != nil { // if err != nil {
return // return
} // }
s.Timestamp, err = bin.ReadUint32(src) // s.Timestamp, err = bin.ReadUint32(src)
if err != nil { // if err != nil {
return // return
} // }
s.Value, err = bin.ReadFloat64(src) // s.Value, err = bin.ReadFloat64(src)
if err != nil { // if err != nil {
return // return
} // }
s.LastPageNo, err = bin.ReadUint32(src) // s.LastPageNo, err = bin.ReadUint32(src)
if err != nil { // if err != nil {
return // return
} // }
s.NewRootPageNo, err = bin.ReadUint32(src) // s.NewRootPageNo, err = bin.ReadUint32(src)
if err != nil { // if err != nil {
return // return
} // }
pagesQty, err := bin.ReadVarSize(src) // pagesQty, err := bin.ReadVarSize(src)
if err != nil { // if err != nil {
return // return
} // }
for range pagesQty { // for range pagesQty {
var page atree.PageToWrite // var page atree.PageToWrite
page.PageNo, err = bin.ReadUint32(src) // page.PageNo, err = bin.ReadUint32(src)
if err != nil { // if err != nil {
return // return
} // }
page.IsReused, err = bin.ReadBool(src) // page.IsReused, err = bin.ReadBool(src)
if err != nil { // if err != nil {
return // return
} // }
page.Data, err = bin.ReadN(src, atree.PageSize) // page.Data, err = bin.ReadN(src, atree.PageSize)
if err != nil { // if err != nil {
return // return
} // }
s.Pages = append(s.Pages, page) // s.Pages = append(s.Pages, page)
} // }
s.TimestampsChunks, s.TimestampsSize, err = readChunkedPayload(src) // s.TimestampsChunks, s.TimestampsSize, err = readChunkedPayload(src)
if err != nil { // if err != nil {
return // return
} // }
s.ValuesChunks, s.ValuesSize, err = readChunkedPayload(src) // s.ValuesChunks, s.ValuesSize, err = readChunkedPayload(src)
if err != nil { // if err != nil {
return // return
} // }
return nil // return nil
} // }
func writeChunkedPayloadTo(chunks [][]byte, size int, w io.Writer) { func writeChunkedPayloadTo(chunks [][]byte, size int, w io.Writer) {
bin.WriteVarSize(w, size) bin.WriteVarSize(w, size)

View File

@@ -5,12 +5,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash/crc32" "hash/crc32"
"log"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
bin "gordenko.dev/dima/bin/little" bin "gordenko.dev/dima/bin/little"
octopus "gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree" "gordenko.dev/dima/qb/atree"
) )
@@ -38,7 +39,7 @@ const (
type FreeList interface { type FreeList interface {
// використовується в allocPage // використовується в allocPage
ReservePage() uint32 GetPageNumber() (uint32, error)
} }
func JoinChangesFileName(dir string, logNumber int) string { func JoinChangesFileName(dir string, logNumber int) string {
@@ -59,15 +60,19 @@ type Writer struct {
atree *atree.Atree atree *atree.Atree
logNumber int logNumber int
dir string dir string
w *bytes.Buffer // wal буфер для упаковки даних перед записом на диск
wal *os.File
dataFile *os.File dataFile *os.File
file *os.File indexFile *os.File
dataPage []byte
indexPage []byte
input []any input []any
//buf *bytes.Buffer //buf *bytes.Buffer
pagesToWrite []atree.PageToWrite //pagesToWrite []atree.PageToWrite
workerReqs []any workerReqs []any
waitCh chan struct{} //waitCh chan struct{}
appendToWorkerQueue func(any) appendToWorkerQueue func(any)
lsn uint32 //lsn uint32
written int64 written int64
isExited bool isExited bool
exitCh chan struct{} exitCh chan struct{}
@@ -114,13 +119,12 @@ func NewWriter(opt WriterOptions) (*Writer, error) {
exitCh: opt.ExitCh, exitCh: opt.ExitCh,
waitGroup: opt.WaitGroup, waitGroup: opt.WaitGroup,
signalCh: make(chan struct{}, 1), signalCh: make(chan struct{}, 1),
waitCh: make(chan struct{}),
} }
var err error var err error
if opt.LogNumber > 0 { if opt.LogNumber > 0 {
s.file, err = os.OpenFile( s.wal, err = os.OpenFile(
JoinChangesFileName(opt.Dir, s.logNumber), JoinChangesFileName(opt.Dir, s.logNumber),
os.O_APPEND|os.O_WRONLY, os.O_APPEND|os.O_WRONLY,
filePerm, filePerm,
@@ -130,7 +134,7 @@ func NewWriter(opt WriterOptions) (*Writer, error) {
} }
} else { } else {
s.logNumber = 1 s.logNumber = 1
s.file, err = os.OpenFile( s.wal, err = os.OpenFile(
JoinChangesFileName(opt.Dir, s.logNumber), JoinChangesFileName(opt.Dir, s.logNumber),
os.O_CREATE|os.O_WRONLY, os.O_CREATE|os.O_WRONLY,
filePerm, filePerm,
@@ -147,7 +151,7 @@ func (s *Writer) Run() {
select { select {
case <-s.signalCh: case <-s.signalCh:
if err := s.packAndWrite(); err != nil { if err := s.packAndWrite(); err != nil {
octopus.Abort(octopus.FailedWriteToTxLog, err) qb.Abort(qb.FailedWriteToTxLog, err)
} }
case <-s.exitCh: case <-s.exitCh:
@@ -157,48 +161,67 @@ func (s *Writer) Run() {
} }
} }
func (s *Writer) packAndWrite() error { func (s *Writer) packAndWrite() (err error) {
s.mutex.Lock() s.mutex.Lock()
input := s.input input := s.input
waitCh := s.waitCh
s.input = nil s.input = nil
s.waitCh = make(chan struct{})
s.mutex.Unlock() s.mutex.Unlock()
w := bytes.NewBuffer(nil) w := bytes.NewBuffer(nil)
// 1. Пакую всі дані в WAL буфер запису
for _, untyped := range input { for _, untyped := range input {
switch x := untyped.(type) { switch x := untyped.(type) {
case AppendedPagesReq:
s.packWriteAppended(w, x)
case AddedMetric: case AddedMetric:
case DeletedMetric: x.Pack(w)
if len(x.FreePageNumbers) > 0 { // case DeletedMetric:
//s.freeList.AddPages(x.FreePageNumbers) // if len(x.FreePageNumbers) > 0 {
} // s.freeList.AddPageNumbers(x.FreePageNumbers)
case AppendedMeasure: // }
case AppendedMeasures: case AppendedMeasures:
case DeletedMeasures: //
s.packMeasuresIntoWALBuffer(x)
//case DeletedMeasures:
//case DeletedMeasuresSince: //case DeletedMeasuresSince:
} }
} }
// flush // 2. Додаю розмір пакету і чексуму
close(waitCh)
return s.flush(w) // 3. Пишу на диск WAL (append)
// 4. Пишу в atree сторінки
for range 1 {
err = s.writePagesToAtree(nil, nil)
if err != nil {
return
}
} }
func (s *Writer) Append(req any) chan struct{} { // 5. Пишу зміни в free list
// 6. відправляю input - worker-у
// flush
err = s.flush(w)
if err != nil {
return err
}
// FIX send to worker workerReqs
return nil
}
func (s *Writer) Append(req any) {
s.mutex.Lock() s.mutex.Lock()
s.input = append(s.input, req) s.input = append(s.input, req)
s.workerReqs = append(s.workerReqs, req) s.workerReqs = append(s.workerReqs, req)
waitCh := s.waitCh
s.mutex.Unlock() s.mutex.Unlock()
select { select {
case s.signalCh <- struct{}{}: case s.signalCh <- struct{}{}:
default: default:
} }
return waitCh
} }
// func (s *Writer) reset() { // func (s *Writer) reset() {
@@ -217,9 +240,8 @@ func (s *Writer) Append(req any) chan struct{} {
func (s *Writer) flush(w *bytes.Buffer) error { func (s *Writer) flush(w *bytes.Buffer) error {
s.mutex.Lock() s.mutex.Lock()
pagesToWrite := s.pagesToWrite //pagesToWrite := s.pagesToWrite
workerReqs := s.workerReqs workerReqs := s.workerReqs
waitCh := s.waitCh
isExited := s.isExited isExited := s.isExited
var exitWaitGroup *sync.WaitGroup var exitWaitGroup *sync.WaitGroup
@@ -228,8 +250,8 @@ func (s *Writer) flush(w *bytes.Buffer) error {
} }
if w.Len() > packetPrefixSize { if w.Len() > packetPrefixSize {
s.lsn++ //s.lsn++
lsn := s.lsn //lsn := s.lsn
packet := make([]byte, w.Len()) packet := make([]byte, w.Len())
copy(packet, w.Bytes()) copy(packet, w.Bytes())
//s.reset() fix //s.reset() fix
@@ -238,26 +260,25 @@ func (s *Writer) flush(w *bytes.Buffer) error {
s.mutex.Unlock() s.mutex.Unlock()
bin.PutUint32(packet[lengthIdx:], uint32(len(packet)-packetPrefixSize)) bin.PutUint32(packet[lengthIdx:], uint32(len(packet)-packetPrefixSize))
bin.PutUint32(packet[lsnIdx:], lsn) //bin.PutUint32(packet[lsnIdx:], lsn)
bin.PutUint32(packet[checksumIdx:], crc32.ChecksumIEEE(packet[8:])) bin.PutUint32(packet[checksumIdx:], crc32.ChecksumIEEE(packet[8:]))
n, err := s.file.Write(packet) n, err := s.wal.Write(packet)
if err != nil { if err != nil {
return fmt.Errorf("TxLog write: %s", err) return fmt.Errorf("TxLog write: %s", err)
} }
if n != len(packet) { if n != len(packet) {
return fmt.Errorf("TxLog written %d != packet size %d", n, len(packet)) return fmt.Errorf("TxLog written %d != packet size %d", n, len(packet))
} }
if err := s.file.Sync(); err != nil { if err := s.wal.Sync(); err != nil {
return fmt.Errorf("TxLog sync: %s", err) return fmt.Errorf("TxLog sync: %s", err)
} }
err = s.writePagesToAtree(pagesToWrite) // err = s.writePagesToAtree(pagesToWrite)
if err != nil { // if err != nil {
return fmt.Errorf("TxLog writePagesToAtree: %s", err) // return fmt.Errorf("TxLog writePagesToAtree: %s", err)
} // }
} else { } else {
s.waitCh = make(chan struct{})
s.mutex.Unlock() s.mutex.Unlock()
} }
@@ -272,12 +293,12 @@ func (s *Writer) flush(w *bytes.Buffer) error {
} }
if forceSnapshot { if forceSnapshot {
if err := s.file.Close(); err != nil { if err := s.wal.Close(); err != nil {
return fmt.Errorf("close changes file: %s", err) return fmt.Errorf("close changes file: %s", err)
} }
s.logNumber++ s.logNumber++
var err error var err error
s.file, err = os.OpenFile( s.wal, err = os.OpenFile(
JoinChangesFileName(s.dir, s.logNumber), JoinChangesFileName(s.dir, s.logNumber),
os.O_CREATE|os.O_WRONLY, os.O_CREATE|os.O_WRONLY,
filePerm, filePerm,
@@ -293,41 +314,146 @@ func (s *Writer) flush(w *bytes.Buffer) error {
Records: workerReqs, Records: workerReqs,
ForceSnapshot: forceSnapshot, ForceSnapshot: forceSnapshot,
LogNumber: s.logNumber, LogNumber: s.logNumber,
WaitCh: waitCh,
ExitWaitGroup: exitWaitGroup, ExitWaitGroup: exitWaitGroup,
}) })
return nil return nil
} }
func (s *Writer) writePagesToAtree(pages []atree.PageToWrite) error {
for _, p := range pages {
if len(p.Data) != atree.PageSize {
return fmt.Errorf("wrong page %d size: %d",
p.PageNo, len(p.Data))
}
off := (p.PageNo - 1) * atree.PageSize
n, err := s.dataFile.WriteAt(p.Data, int64(off))
if err != nil {
return err
}
if n != len(p.Data) {
return fmt.Errorf("write %d instead of %d", n, len(p.Data))
}
}
return nil
}
func (s *Writer) exit() { func (s *Writer) exit() {
s.mutex.Lock() s.mutex.Lock()
s.isExited = true s.isExited = true
s.mutex.Unlock() s.mutex.Unlock()
if err := s.packAndWrite(); err != nil { if err := s.packAndWrite(); err != nil {
octopus.Abort(octopus.FailedWriteToTxLog, err) qb.Abort(qb.FailedWriteToTxLog, err)
} }
} }
// writePagesToAtree - записує сторінки в .data та .index файли
func (s *Writer) writePagesToAtree(levels []*atree.IndexLevel, dataPages []*atree.DataPage) (err error) {
for _, p := range dataPages {
p.Checksum = atree.ChunksToDataPage(s.dataPage, atree.ChunksToDataPageReq{
PrevPageNo: p.PrevPageNo,
Timestamps: p.Timestamps,
TimestampsSize: p.TimestampsSize,
Values: p.Values,
ValuesSize: p.ValuesSize,
})
off := (p.PageNo - 1) * atree.DataPageSize
n, err := s.dataFile.WriteAt(s.dataPage, int64(off))
if err != nil {
return err
}
if n != atree.DataPageSize {
return fmt.Errorf("write %d instead of %d", n, atree.DataPageSize)
}
}
for levelIdx, level := range levels {
for _, p := range level.Filled {
// if len(p.Data) != atree.PageSize {
// return fmt.Errorf("wrong page %d size: %d",
// p.PageNo, len(p.Data))
// }
p.Checksum = atree.DataToIndexPage(s.indexPage, p.Data, levelIdx == 0)
off := (p.PageNo - 1) * atree.IndexPageSize
n, err := s.indexFile.WriteAt(s.indexPage, int64(off))
if err != nil {
return err
}
if n != atree.IndexPageSize {
return fmt.Errorf("write %d instead of %d", n, atree.IndexPageSize)
}
}
}
return nil
}
type AppendedMeasures struct {
MetricID uint32
TimestampsOffset int // (заповнені одразу)
ValuesOffset int // (заповнені одразу)
Timestamps [][]byte
TimestampsSize int
Values [][]byte
ValuesSize int
IndexLevels []*atree.IndexLevel
DataPages []*atree.DataPage // fix - prevPageNo for the 1st data page
}
func (s *Writer) packMeasuresIntoWALBuffer(req AppendedMeasures) (err error) {
// fix write code
bin.WriteUint32(s.w, req.MetricID)
bin.WriteVarSize(s.w, len(req.DataPages))
for idx, p := range req.DataPages {
bin.WriteUint32(s.w, p.PageNo)
var reused byte
if p.IsReused {
reused = 1
}
s.w.WriteByte(reused)
bin.WriteUint32(s.w, p.PrevPageNo)
bin.WriteUint32(s.w, p.Checksum)
var (
timestampsOffset int
valuesOffset int
)
if idx == 0 {
timestampsOffset = req.TimestampsOffset
valuesOffset = req.ValuesOffset
}
copyPayloadFromChunks(s.w, p.Timestamps, p.TimestampsSize, timestampsOffset)
copyPayloadFromChunks(s.w, p.Values, p.ValuesSize, valuesOffset)
}
bin.WriteUint16(s.w, uint16(req.TimestampsOffset))
bin.WriteUint16(s.w, uint16(req.ValuesOffset))
var (
timestampsOffset int
valuesOffset int
)
if len(req.DataPages) == 0 {
timestampsOffset = req.TimestampsOffset
valuesOffset = req.ValuesOffset
}
copyPayloadFromChunks(s.w, req.Timestamps, req.TimestampsSize, timestampsOffset)
copyPayloadFromChunks(s.w, req.Values, req.ValuesSize, valuesOffset)
if len(req.DataPages) == 0 {
return
}
// fix - levels reduced
bin.WriteVarSize(s.w, len(req.IndexLevels))
for _, level := range req.IndexLevels {
bin.WriteVarSize(s.w, len(level.Filled))
for idx, p := range level.Filled {
bin.WriteUint32(s.w, p.PageNo)
var reused byte
if p.IsReused {
reused = 1
}
s.w.WriteByte(reused)
bin.WriteUint32(s.w, p.Checksum)
records := p.Data
if idx == 0 {
records = p.Data[level.Offset:]
}
if (len(records) % recordSize) != 0 {
log.Fatalf("wrong records length: %d", len(records))
}
bin.WriteVarSize(s.w, len(records)/recordSize)
s.w.Write(records)
}
records := level.Data
if len(level.Filled) == 0 {
records = level.Data[level.Offset:]
}
if (len(records) % recordSize) != 0 {
log.Fatalf("wrong unfilled records length: %d", len(records))
}
bin.WriteVarSize(s.w, len(records)/recordSize)
s.w.Write(records)
}
return
}
// API // API
// FIX - add // FIX - add
@@ -351,28 +477,28 @@ func (s *Writer) sendSignal() {
// Якщо (Pages) > 0 - незаповнену сторінку кодую в стисненому вигляді. // Якщо (Pages) > 0 - незаповнену сторінку кодую в стисненому вигляді.
// Якщо (Pages) = 0 - кодую просто пари timestamp / value // Якщо (Pages) = 0 - кодую просто пари timestamp / value
func (s *Writer) packWriteAppended(w *bytes.Buffer, req AppendedPagesReq) { // func (s *Writer) packWriteAppended(w *bytes.Buffer, req AppendedPagesReq) {
// завантажений path. Отже просто додаємо в дерево data сторінки, створюємо нові індексні // // завантажений path. Отже просто додаємо в дерево data сторінки, створюємо нові індексні
// без звернення до диску. // // без звернення до диску.
report := s.atree.AppendDataPages(atree.AppendDataPagesReq{ // report := s.atree.AppendDataPages(atree.AppendDataPagesReq{
LastPageNo: req.LastPageNo, // LastPageNo: req.LastPageNo,
Legs: req.Legs, // Legs: req.Legs,
DataPages: req.Pages, // DataPages: req.Pages,
}) // })
m := AppendedPages{ // m := AppendedPages{
MetricID: req.MetricID, // MetricID: req.MetricID,
Timestamp: req.Timestamp, // Timestamp: req.Timestamp,
Value: req.Value, // Value: req.Value,
NewRootPageNo: report.NewRootPageNo, // NewRootPageNo: report.NewRootPageNo,
LastPageNo: report.LastPageNo, // LastPageNo: report.LastPageNo,
Pages: report.Pages, // Pages: report.Pages,
TimestampsChunks: req.TimestampsChunks, // TimestampsChunks: req.TimestampsChunks,
TimestampsSize: int(req.TimestampsSize), // TimestampsSize: int(req.TimestampsSize),
ValuesChunks: req.ValuesChunks, // ValuesChunks: req.ValuesChunks,
ValuesSize: int(req.ValuesSize), // ValuesSize: int(req.ValuesSize),
} // }
m.Pack(w) // m.Pack(w)
} // }
// helpers // helpers

View File

@@ -1,233 +0,0 @@
package txlog
import (
"bytes"
"fmt"
"log"
"os"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb/atree"
)
const recordSize = 8
type LogWriter struct {
w *bytes.Buffer
wal *os.File
dataFile *os.File
indexFile *os.File
dataPage []byte
indexPage []byte
}
func (s *LogWriter) writePagesToAtree(levels []*atree.IndexLevel, dataPages []*atree.DataPage) (err error) {
for _, p := range dataPages {
atree.ChunksToDataPage(s.dataPage, atree.ChunksToDataPageReq{
PrevPageNo: p.PrevPageNo,
Timestamps: p.Timestamps,
TimestampsSize: p.TimestampsSize,
Values: p.Values,
ValuesSize: p.ValuesSize,
})
off := (p.PageNo - 1) * atree.DataPageSize
n, err := s.dataFile.WriteAt(s.dataPage, int64(off))
if err != nil {
return err
}
if n != atree.DataPageSize {
return fmt.Errorf("write %d instead of %d", n, atree.DataPageSize)
}
}
for levelIdx, level := range levels {
for _, p := range level.Filled {
// if len(p.Data) != atree.PageSize {
// return fmt.Errorf("wrong page %d size: %d",
// p.PageNo, len(p.Data))
// }
atree.DataToIndexPage(s.indexPage, p.Data, levelIdx == 0)
off := (p.PageNo - 1) * atree.IndexPageSize
n, err := s.indexFile.WriteAt(s.indexPage, int64(off))
if err != nil {
return err
}
if n != atree.IndexPageSize {
return fmt.Errorf("write %d instead of %d", n, atree.IndexPageSize)
}
}
}
return nil
}
type ToWrite struct {
MetricID uint32
TimestampsOffset int // (заповнені одразу)
ValuesOffset int // (заповнені одразу)
Timestamps [][]byte
TimestampsSize int
Values [][]byte
ValuesSize int
IndexLevels []*atree.IndexLevel
DataPages []*atree.DataPage // fix - prevPageNo for the 1st data page
}
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
// offset потрібен тому що дані не додаються в кінець, а перезаписують кілька
// останніх байтів unfilled даних. Потрібно для коректного recovery.
// Якщо є Pages, то застосовуються для 1-ї Index сторінки, інакше - до records (unfilled)
RecordsOffset int
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 []DataPage
}
/*
Format appended measures:
1b - tx type
4b - metricID
Nb - qty of filled data pages (varsize)
[
4b - pageNo
1b - reused
4b - prevPageNo
4b - page crc32
Nb - timestamps size (varsize)
Nb - timestamps payload
Nb - values size (varsize)
Nb - values payload
]
2b - timestamps offset (for the 1st data page only)
2b - values offset (for the 1st data page only)
Nb - timestamps size unfilled (varsize)
Nb - timestamps payload unfilled
Nb - values size unfilled (varsize)
Nb - values payload unfilled
Nb - qty of index levels (varsize)
[
Nb - qty of level filled pages (varsize)
[
4b - pageNo
1b - reused
4b - page crc32
Nb - records qty (varsize)
Nb - records payload
]
Nb - records offset (for the 1st index page only)
Nb - records qty unfilled (varsize)
Nb - records payload unfilled
]
*/
func (s *LogWriter) writeToWAL(req ToWrite) (err error) {
// fix write code
bin.WriteUint32(s.w, req.MetricID)
bin.WriteVarSize(s.w, len(req.DataPages))
for idx, p := range req.DataPages {
bin.WriteUint32(s.w, p.PageNo)
var reused byte
if p.IsReused {
reused = 1
}
s.w.WriteByte(reused)
bin.WriteUint32(s.w, p.PrevPageNo)
//bin.WriteUint32(s.w, p.CRC32) fix
var (
timestampsOffset int
valuesOffset int
)
if idx == 0 {
timestampsOffset = req.TimestampsOffset
valuesOffset = req.ValuesOffset
}
copyPayloadFromChunks(s.w, p.Timestamps, p.TimestampsSize, timestampsOffset)
copyPayloadFromChunks(s.w, p.Values, p.ValuesSize, valuesOffset)
}
bin.WriteUint16(s.w, uint16(req.TimestampsOffset))
bin.WriteUint16(s.w, uint16(req.ValuesOffset))
// fix offset
copyPayloadFromChunks(s.w, req.Timestamps, req.TimestampsSize, req.TimestampsOffset)
copyPayloadFromChunks(s.w, req.Values, req.ValuesSize, req.ValuesOffset)
if len(req.DataPages) == 0 {
return
}
bin.WriteVarSize(s.w, len(req.IndexLevels))
for _, level := range req.IndexLevels {
bin.WriteVarSize(s.w, len(level.Filled))
for idx, p := range level.Filled {
bin.WriteUint32(s.w, p.PageNo)
var reused byte
if p.IsReused {
reused = 1
}
s.w.WriteByte(reused)
//bin.WriteUint32(s.w, p.CRC32) fix
records := p.Data
if idx == 0 {
records = p.Data[level.Offset:]
}
if (len(records) % recordSize) != 0 {
log.Fatalf("wrong records length: %d", len(records))
}
bin.WriteVarSize(s.w, len(records)/recordSize)
s.w.Write(records)
}
if len(level.Filled) > 0 {
s.w.Write(level.Data)
} else {
s.w.Write(level.Data[level.Offset:])
}
}
return
}
const chunkSize = 24
func copyPayloadFromChunks(w *bytes.Buffer, chunks [][]byte, size int, offset int) {
bin.WriteVarSize(w, 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
}
}
}

View File

@@ -1,4 +1,4 @@
package atree package util
import ( import (
"hash/crc32" "hash/crc32"

17
util/until.go Normal file
View File

@@ -0,0 +1,17 @@
package util
import (
"hash/crc32"
)
var (
castagnoliTable = crc32.MakeTable(crc32.Castagnoli)
)
func CalcChecksum(page []byte) uint32 {
return crc32.Checksum(page, castagnoliTable)
}
func IsValidChecksum(page []byte, checksum uint32) bool {
return crc32.Checksum(page, castagnoliTable) == checksum
}