This commit is contained in:
2026-06-05 19:43:01 +00:00
parent db5ecd3dfd
commit 05eea3b279
16 changed files with 1187 additions and 724 deletions

View File

@@ -1,163 +0,0 @@
package atree
import (
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/util"
)
const (
payloadSize = 24
recordSize = 4 + 4 // timestamp + pageNo
)
type DataPage struct {
PrevPageNo uint32
LowerTimestamp uint32
Timestamps [][]byte // chunks
TimestampsSize int
Values [][]byte // chunks
ValuesSize int
PageNo uint32
IsReused bool
//Checksum uint32
}
type IndexPage struct {
LowerTimestamp uint32
Data []byte
PageNo uint32
IsReused bool
}
type IndexLevel struct {
// вже записані дані (лише для першої в списку індексної сторінки, беремо із _metric)
// дані потрібні, якщо сторінка буде заповнена і піде на запис в .index файл
Offset int // (заповнені одразу)
LowerTimestamp uint32 // (заповнені одразу)
Data []byte // поточні дані index сторінки (заповнені одразу)
Filled []*IndexPage // пусто
}
func appendIndexRecord(data []byte, timestamp uint32, pageNo uint32) []byte {
var tmp = []byte{
0, 0, 0, 0, // timestamp
0, 0, 0, 0, // pageNo
}
bin.PutUint32(tmp, timestamp)
bin.PutUint32(tmp[4:], pageNo)
return append(data, tmp...)
}
type ChunksToDataPageReq struct {
PrevPageNo uint32
Timestamps [][]byte // chunks
TimestampsSize int
Values [][]byte // chunks
ValuesSize int
}
// return checksum
func ChunksToDataPage(buf []byte, req ChunksToDataPageReq) {
var (
remainingSize = int(req.TimestampsSize)
pos = 0
)
for _, chunk := range req.Timestamps {
if remainingSize >= len(chunk) {
copy(buf[pos:], chunk)
remainingSize -= len(chunk)
pos += len(chunk)
} else {
copy(buf[pos:], chunk[:remainingSize])
break
}
}
remainingSize = int(req.ValuesSize)
pos = int(req.TimestampsSize)
for _, chunk := range req.Values {
if remainingSize >= len(chunk) {
copy(buf[pos:], chunk)
remainingSize -= len(chunk)
pos += len(chunk)
} else {
copy(buf[pos:], chunk[:remainingSize])
break
}
}
bin.PutUint16(buf[timestampsSizeIdx:], uint16(req.TimestampsSize))
bin.PutUint16(buf[valuesSizeIdx:], uint16(req.ValuesSize))
bin.PutUint32(buf[prevPageIdx:], req.PrevPageNo)
checksum := util.CalcChecksum(buf[:dataCRC32Idx])
bin.PutUint32(buf[dataCRC32Idx:], checksum)
}
func DataToIndexPage(buf []byte, data []byte, lastLevel bool) {
copy(buf, data)
bin.PutUint16(buf[indexRecordsQtyIdx:], uint16(len(data)/indexRecordSize))
if lastLevel {
buf[isLastLevelIdx] = 1
}
checksum := util.CalcChecksum(buf[:indexCRC32Idx])
bin.PutUint32(buf[indexCRC32Idx:], checksum)
}
// fix - reduce leves
func AppendDataPagesToTree(getDataPageNumber func() (uint32, bool, error), getIndexPageNumber func() (uint32, bool, error), levels []*IndexLevel, dataPages []*DataPage) {
for i, d := range dataPages[1:] {
pageNo, isReused, err := getDataPageNumber()
if err != nil {
qb.Abort(qb.FailedGetPageNumber, err)
}
d.PageNo = pageNo
d.IsReused = isReused
d.PrevPageNo = dataPages[i-1].PageNo
}
for _, d := range dataPages {
var (
upTimestamp = d.LowerTimestamp
upPageNo = d.PageNo
levelIdx = 0
)
for {
if levelIdx == len(levels) {
// encode prev page record + up*
levels = append(levels, &IndexLevel{
LowerTimestamp: upTimestamp,
Data: appendIndexRecord(nil, upTimestamp, upPageNo),
})
break
}
level := levels[levelIdx]
if len(level.Data)+recordSize <= payloadSize {
level.Data = appendIndexRecord(level.Data, upTimestamp, upPageNo)
break
}
// в буфері немає місця для додавання нової пари, отже це
// заповнена сторінка
filled := &IndexPage{
LowerTimestamp: level.LowerTimestamp,
Data: level.Data,
}
pageNo, isReused, err := getIndexPageNumber()
if err != nil {
qb.Abort(qb.FailedGetPageNumber, err)
}
filled.PageNo = pageNo
filled.IsReused = isReused
level.Filled = append(level.Filled, filled)
//
level.LowerTimestamp = upTimestamp
level.Data = appendIndexRecord(nil, upTimestamp, upPageNo)
//
upPageNo = filled.PageNo
upTimestamp = filled.LowerTimestamp
levelIdx++
}
}
}

View File

@@ -2,4 +2,7 @@ package chunkenc
const eps = 0.000001 const eps = 0.000001
const flagLiteral = 128 const (
flagLiteral = 128
minBufferSize = 1024
)

View File

@@ -3,14 +3,12 @@ package chunkenc
import ( import (
"fmt" "fmt"
"testing" "testing"
"gordenko.dev/dima/qb/conbuf"
) )
func TestCumdelta(t *testing.T) { func TestCumdelta(t *testing.T) {
var ( var (
fracDigits byte = 0 fracDigits byte = 0
buf = conbuf.New(nil) buf = make([]byte, minBufferSize)
value float64 value float64
done bool done bool
) )
@@ -41,7 +39,7 @@ func TestCumdelta(t *testing.T) {
func TestInsdelta(t *testing.T) { func TestInsdelta(t *testing.T) {
var ( var (
fracDigits byte = 2 fracDigits byte = 2
buf = conbuf.New(nil) buf = make([]byte, minBufferSize)
value float64 value float64
done bool done bool
) )
@@ -70,7 +68,7 @@ func TestInsdelta(t *testing.T) {
func TestTimeDelta(t *testing.T) { func TestTimeDelta(t *testing.T) {
var ( var (
buf = conbuf.New(nil) buf = make([]byte, minBufferSize)
value uint32 value uint32
done bool done bool
) )
@@ -120,7 +118,7 @@ func TestTimeDelta(t *testing.T) {
func TestCumdeltaBound(t *testing.T) { func TestCumdeltaBound(t *testing.T) {
var ( var (
fracDigits byte = 0 fracDigits byte = 0
buf = conbuf.New(nil) buf = make([]byte, minBufferSize)
value float64 value float64
done bool done bool
) )
@@ -181,7 +179,7 @@ func TestCumdeltaBound(t *testing.T) {
func TestInsdeltaBound(t *testing.T) { func TestInsdeltaBound(t *testing.T) {
var ( var (
fracDigits byte = 0 fracDigits byte = 0
buf = conbuf.New(nil) buf = make([]byte, minBufferSize)
value float64 value float64
done bool done bool
) )
@@ -241,7 +239,7 @@ func TestInsdeltaBound(t *testing.T) {
func TestInsdeltaBound2(t *testing.T) { func TestInsdeltaBound2(t *testing.T) {
var ( var (
fracDigits byte = 0 fracDigits byte = 0
buf = conbuf.New(nil) buf = make([]byte, minBufferSize)
value float64 value float64
done bool done bool
) )
@@ -297,3 +295,27 @@ func TestInsdeltaBound2(t *testing.T) {
fmt.Println(value, done) fmt.Println(value, done)
} }
} }
func TestCap(t *testing.T) {
a := make([]byte, 0, 10)
fmt.Println("len:", len(a))
fmt.Println("cap:", cap(a))
a = append(a, 1)
fmt.Println("after append:")
fmt.Println("len:", len(a))
fmt.Println("cap:", cap(a))
fmt.Println("a:", a)
a[1] = 55
fmt.Println("after []:")
fmt.Println("len:", len(a))
fmt.Println("cap:", cap(a))
fmt.Println("a:", a)
a = append(a, 2)
fmt.Println("after 2nd append:")
fmt.Println("len:", len(a))
fmt.Println("cap:", cap(a))
fmt.Println("a:", a)
}

View File

@@ -5,6 +5,7 @@ import (
"log" "log"
"math" "math"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/conbuf" "gordenko.dev/dima/qb/conbuf"
) )
@@ -38,7 +39,7 @@ v1 v2 h-byte(literal, 2) v3 h-byte(run, 2)
*/ */
type ReverseCumulativeDeltaCompressor struct { type ReverseCumulativeDeltaCompressor struct {
buf *conbuf.ContinuousBuffer buf []byte
coef float64 coef float64
pos int pos int
baseValue float64 baseValue float64
@@ -48,7 +49,7 @@ type ReverseCumulativeDeltaCompressor struct {
state *CumulativeDeltaBound state *CumulativeDeltaBound
} }
func NewReverseCumulativeDeltaCompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseCumulativeDeltaCompressor { func NewReverseCumulativeDeltaCompressor(buf []byte, size int, fracDigits byte) *ReverseCumulativeDeltaCompressor {
var coef float64 = 1 var coef float64 = 1
if fracDigits > 0 { if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits)) coef = math.Pow(10, float64(fracDigits))
@@ -59,13 +60,13 @@ func NewReverseCumulativeDeltaCompressor(buf *conbuf.ContinuousBuffer, size int,
coef: coef, coef: coef,
} }
if size > 0 { if size > 0 {
u64, _, err := s.buf.GetVarUint64(0) u64, _, err := bin.GetVarUint64(s.buf)
if err != nil { if err != nil {
log.Fatalf("bug: get base value: %s", err) log.Fatalf("bug: get base value: %s", err)
} }
s.baseValue = float64(u64) / s.coef s.baseValue = float64(u64) / s.coef
s.h = s.buf.GetByte(s.pos - 1) s.h = s.buf[s.pos-1]
s.lastDelta, s.lastDeltaSize, err = s.buf.ReverseGetVarUint64(s.pos - 2) s.lastDelta, s.lastDeltaSize, err = bin.ReverseGetVarUint64(s.buf[:s.pos-2])
if err != nil { if err != nil {
log.Fatalf("bug: get last delta: %s", err) log.Fatalf("bug: get last delta: %s", err)
} }
@@ -84,7 +85,8 @@ func (s *ReverseCumulativeDeltaCompressor) CalcRequiredSpace(value float64) int
func (s *ReverseCumulativeDeltaCompressor) Append(value float64) { func (s *ReverseCumulativeDeltaCompressor) Append(value float64) {
if s.pos == 0 { if s.pos == 0 {
// base value // base value
s.pos += s.buf.PutVarUint64(s.pos, uint64(value*s.coef)) n, _ := bin.PutVarUint64(s.buf[s.pos:], uint64(value*s.coef))
s.pos += n
s.baseValue = value s.baseValue = value
s.appendNewLiteral(0) s.appendNewLiteral(0)
} else { } else {
@@ -95,7 +97,7 @@ func (s *ReverseCumulativeDeltaCompressor) Append(value float64) {
if s.h < 127 { if s.h < 127 {
// increase counter // increase counter
s.h++ s.h++
s.buf.SetByte(s.pos-1, s.h) s.buf[s.pos-1] = s.h
} else { } else {
// не можу збільшити - буде переповнення. Додаю новий literal блок // не можу збільшити - буде переповнення. Додаю новий literal блок
// counter overflow // counter overflow
@@ -134,37 +136,37 @@ func (s *ReverseCumulativeDeltaCompressor) convertLastFromLiteralToRun() {
// Зменшую кількість елементів в literal блоці // Зменшую кількість елементів в literal блоці
s.h-- s.h--
s.pos -= 1 + s.lastDeltaSize s.pos -= 1 + s.lastDeltaSize
s.buf.SetByte(s.pos, s.h) // закриваю literal блок s.buf[s.pos] = s.h // закриваю literal блок
s.pos++ s.pos++
s.lastDeltaSize = s.buf.ReversePutVarUint64(s.pos, s.lastDelta) s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], s.lastDelta)
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.h = 0 // run блок, довжини 2 s.h = 0 // run блок, довжини 2
s.buf.SetByte(s.pos, s.h) s.buf[s.pos] = s.h
s.pos++ s.pos++
} }
func (s *ReverseCumulativeDeltaCompressor) convertLiteralToRun() { func (s *ReverseCumulativeDeltaCompressor) convertLiteralToRun() {
// Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії. // Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії.
s.h = 0 s.h = 0
s.buf.SetByte(s.pos-1, s.h) s.buf[s.pos-1] = s.h
} }
func (s *ReverseCumulativeDeltaCompressor) appendDeltaToLiteral(delta uint64) { func (s *ReverseCumulativeDeltaCompressor) appendDeltaToLiteral(delta uint64) {
s.h++ // збільшую к-сть дельт s.h++ // збільшую к-сть дельт
s.lastDelta = delta s.lastDelta = delta
s.pos-- s.pos--
s.lastDeltaSize = s.buf.ReversePutVarUint64(s.pos, delta) s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], delta)
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.buf.SetByte(s.pos, s.h) s.buf[s.pos] = s.h
s.pos++ s.pos++
} }
func (s *ReverseCumulativeDeltaCompressor) appendNewLiteral(delta uint64) { func (s *ReverseCumulativeDeltaCompressor) appendNewLiteral(delta uint64) {
s.h = flagLiteral s.h = flagLiteral
s.lastDelta = delta s.lastDelta = delta
s.lastDeltaSize = s.buf.ReversePutVarUint64(s.pos, delta) s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], delta)
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.buf.SetByte(s.pos, flagLiteral) // literal, length = 1 s.buf[s.pos] = flagLiteral // literal, length = 1
s.pos++ s.pos++
} }
@@ -176,7 +178,7 @@ type CumulativeDeltaBound struct {
Pos int Pos int
H byte H byte
LastDelta uint64 LastDelta uint64
Chunks [][]byte Chunks []byte
} }
// delta h // delta h
@@ -186,15 +188,11 @@ func (s *ReverseCumulativeDeltaCompressor) Lock() {
} }
// позиція посувається вліво, отже може перескочити на попередній chunk // позиція посувається вліво, отже може перескочити на попередній chunk
pos := s.pos - 1 - s.lastDeltaSize pos := s.pos - 1 - s.lastDeltaSize
chunksQty := pos / conbuf.ChunkSize
if (pos % conbuf.ChunkSize) > 0 {
chunksQty++
}
s.state = &CumulativeDeltaBound{ s.state = &CumulativeDeltaBound{
Pos: pos, Pos: pos,
H: s.h, H: s.h,
LastDelta: s.lastDelta, LastDelta: s.lastDelta,
Chunks: s.buf.Chunks()[:chunksQty], Chunks: s.buf[:s.pos], // fix check ?
} }
} }
@@ -210,9 +208,9 @@ func (s *ReverseCumulativeDeltaCompressor) Offset() int {
return 0 return 0
} }
func (s *ReverseCumulativeDeltaCompressor) Snapshot() ([][]byte, int) { func (s *ReverseCumulativeDeltaCompressor) Snapshot() ([]byte, int) {
if s.state == nil { if s.state == nil {
return s.buf.Chunks(), s.Size() return s.buf, s.Size()
} }
// ВАЖЛИВО! // ВАЖЛИВО!
// Треба відтворити стан останнього чанка // Треба відтворити стан останнього чанка
@@ -246,7 +244,7 @@ func (s *ReverseCumulativeDeltaCompressor) CreateDecompressor(fracDigits byte) q
func (s *ReverseCumulativeDeltaCompressor) Renew() { func (s *ReverseCumulativeDeltaCompressor) Renew() {
// УВАГА! // УВАГА!
// state не чіпаємо // state не чіпаємо
s.buf = conbuf.New(nil) s.buf = make([]byte, minBufferSize)
s.pos = 0 s.pos = 0
// //
s.baseValue = 0 s.baseValue = 0
@@ -255,14 +253,14 @@ func (s *ReverseCumulativeDeltaCompressor) Renew() {
s.h = 0 s.h = 0
} }
func (s *ReverseCumulativeDeltaCompressor) Chunks() [][]byte { func (s *ReverseCumulativeDeltaCompressor) Chunks() []byte {
return s.buf.Chunks() return s.buf
} }
// DECOMPRESSOR // DECOMPRESSOR
type ReverseCumulativeDeltaDecompressor struct { type ReverseCumulativeDeltaDecompressor struct {
buf *conbuf.ContinuousBuffer buf []byte
coef float64 coef float64
pos int pos int
bound int bound int
@@ -273,12 +271,12 @@ type ReverseCumulativeDeltaDecompressor struct {
done bool done bool
} }
func NewReverseCumulativeDeltaDecompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseCumulativeDeltaDecompressor { func NewReverseCumulativeDeltaDecompressor(buf []byte, size int, fracDigits byte) *ReverseCumulativeDeltaDecompressor {
var coef float64 = 1 var coef float64 = 1
if fracDigits > 0 { if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits)) coef = math.Pow(10, float64(fracDigits))
} }
u64, n, err := buf.GetVarUint64(0) u64, n, err := bin.GetVarUint64(buf)
if err != nil { if err != nil {
log.Fatalf("bug: get base value: %s", err) log.Fatalf("bug: get base value: %s", err)
} }
@@ -335,7 +333,7 @@ func (s *ReverseCumulativeDeltaDecompressor) NextValue() (value float64, done bo
} }
func (s *ReverseCumulativeDeltaDecompressor) readHeader() { func (s *ReverseCumulativeDeltaDecompressor) readHeader() {
h := s.buf.GetByte(s.pos) h := s.buf[s.pos]
s.pos-- s.pos--
s.decodeHeaderByte(h) s.decodeHeaderByte(h)
// fmt.Println("h:", h) // fmt.Println("h:", h)
@@ -343,7 +341,7 @@ func (s *ReverseCumulativeDeltaDecompressor) readHeader() {
// fmt.Println("pending:", s.pending) // fmt.Println("pending:", s.pending)
} }
func (s *ReverseCumulativeDeltaDecompressor) readValue() { func (s *ReverseCumulativeDeltaDecompressor) readValue() {
u64, n, err := s.buf.ReverseGetVarUint64(s.pos) u64, n, err := bin.ReverseGetVarUint64(s.buf[:s.pos])
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@@ -6,11 +6,12 @@ import (
"math" "math"
"gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/conbuf" "gordenko.dev/dima/qb/conbuf"
) )
type ReverseInstantDeltaCompressor struct { type ReverseInstantDeltaCompressor struct {
buf *conbuf.ContinuousBuffer buf []byte
coef float64 coef float64
pos int pos int
baseValue float64 baseValue float64
@@ -20,7 +21,7 @@ type ReverseInstantDeltaCompressor struct {
state *InstantDeltaBound state *InstantDeltaBound
} }
func NewReverseInstantDeltaCompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseInstantDeltaCompressor { func NewReverseInstantDeltaCompressor(buf []byte, size int, fracDigits byte) *ReverseInstantDeltaCompressor {
var coef float64 = 1 var coef float64 = 1
if fracDigits > 0 { if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits)) coef = math.Pow(10, float64(fracDigits))
@@ -31,13 +32,13 @@ func NewReverseInstantDeltaCompressor(buf *conbuf.ContinuousBuffer, size int, fr
coef: coef, coef: coef,
} }
if size > 0 { if size > 0 {
i64, _, err := s.buf.GetVarInt64(0) i64, _, err := bin.GetVarInt64(s.buf)
if err != nil { if err != nil {
log.Fatalf("bug: get base value: %s", err) log.Fatalf("bug: get base value: %s", err)
} }
s.baseValue = float64(i64) / s.coef s.baseValue = float64(i64) / s.coef
s.h = s.buf.GetByte(s.pos - 1) s.h = s.buf[s.pos-1]
s.lastDelta, s.lastDeltaSize, err = s.buf.ReverseGetVarInt64(s.pos - 2) s.lastDelta, s.lastDeltaSize, err = bin.ReverseGetVarInt64(s.buf[:s.pos-2])
if err != nil { if err != nil {
log.Fatalf("bug: get last delta: %s", err) log.Fatalf("bug: get last delta: %s", err)
} }
@@ -52,7 +53,7 @@ func (s *ReverseInstantDeltaCompressor) Size() int {
func (s *ReverseInstantDeltaCompressor) Append(value float64) { func (s *ReverseInstantDeltaCompressor) Append(value float64) {
if s.pos == 0 { if s.pos == 0 {
// base value // base value
n := s.buf.PutVarInt64(s.pos, int64(value*s.coef)) n, _ := bin.PutVarInt64(s.buf[s.pos:], int64(value*s.coef))
s.pos += n s.pos += n
s.baseValue = value s.baseValue = value
s.appendNewLiteral(0) s.appendNewLiteral(0)
@@ -70,7 +71,7 @@ func (s *ReverseInstantDeltaCompressor) Append(value float64) {
if s.h < 127 { if s.h < 127 {
// increase counter // increase counter
s.h++ s.h++
s.buf.SetByte(s.pos-1, s.h) s.buf[s.pos-1] = s.h
} else { } else {
// не можу збільшити - буде переповнення. Додаю новий literal блок // не можу збільшити - буде переповнення. Додаю новий literal блок
// counter overflow // counter overflow
@@ -109,37 +110,37 @@ func (s *ReverseInstantDeltaCompressor) convertLastFromLiteralToRun() {
// Зменшую кількість елементів в literal блоці // Зменшую кількість елементів в literal блоці
s.h-- s.h--
s.pos -= 1 + s.lastDeltaSize s.pos -= 1 + s.lastDeltaSize
s.buf.SetByte(s.pos, s.h) // закриваю literal блок s.buf[s.pos] = s.h // закриваю literal блок
s.pos++ s.pos++
s.lastDeltaSize = s.buf.ReversePutVarInt64(s.pos, s.lastDelta) s.lastDeltaSize, _ = bin.ReversePutVarInt64(s.buf[:s.pos], s.lastDelta)
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.h = 0 // run блок, довжини 2 s.h = 0 // run блок, довжини 2
s.buf.SetByte(s.pos, s.h) s.buf[s.pos] = s.h
s.pos++ s.pos++
} }
func (s *ReverseInstantDeltaCompressor) convertLiteralToRun() { func (s *ReverseInstantDeltaCompressor) convertLiteralToRun() {
// Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії. // Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії.
s.h = 0 s.h = 0
s.buf.SetByte(s.pos-1, s.h) s.buf[s.pos-1] = s.h
} }
func (s *ReverseInstantDeltaCompressor) appendDeltaToLiteral(delta int64) { func (s *ReverseInstantDeltaCompressor) appendDeltaToLiteral(delta int64) {
s.h++ // збільшую к-сть дельт s.h++ // збільшую к-сть дельт
s.lastDelta = delta s.lastDelta = delta
s.pos-- s.pos--
s.lastDeltaSize = s.buf.ReversePutVarInt64(s.pos, delta) s.lastDeltaSize, _ = bin.ReversePutVarInt64(s.buf[s.pos:], delta)
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.buf.SetByte(s.pos, s.h) s.buf[s.pos] = s.h
s.pos++ s.pos++
} }
func (s *ReverseInstantDeltaCompressor) appendNewLiteral(delta int64) { func (s *ReverseInstantDeltaCompressor) appendNewLiteral(delta int64) {
s.h = flagLiteral s.h = flagLiteral
s.lastDelta = delta s.lastDelta = delta
s.lastDeltaSize = s.buf.ReversePutVarInt64(s.pos, delta) s.lastDeltaSize, _ = bin.ReversePutVarInt64(s.buf[s.pos:], delta)
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.buf.SetByte(s.pos, flagLiteral) // literal, length = 1 s.buf[s.pos] = flagLiteral // literal, length = 1
s.pos++ s.pos++
} }
@@ -149,7 +150,7 @@ type InstantDeltaBound struct {
Pos int Pos int
H byte H byte
LastDelta int64 LastDelta int64
Chunks [][]byte Chunks []byte
} }
// delta h // delta h
@@ -159,15 +160,11 @@ func (s *ReverseInstantDeltaCompressor) Lock() {
} }
// позиція посувається вліво, отже може перескочити на попередній chunk // позиція посувається вліво, отже може перескочити на попередній chunk
pos := s.pos - 1 - s.lastDeltaSize pos := s.pos - 1 - s.lastDeltaSize
chunksQty := pos / conbuf.ChunkSize
if (pos % conbuf.ChunkSize) > 0 {
chunksQty++
}
s.state = &InstantDeltaBound{ s.state = &InstantDeltaBound{
Pos: pos, Pos: pos,
H: s.h, H: s.h,
LastDelta: s.lastDelta, LastDelta: s.lastDelta,
Chunks: s.buf.Chunks()[:chunksQty], Chunks: s.buf[:s.pos], // fix check ?
} }
} }
@@ -183,9 +180,9 @@ func (s *ReverseInstantDeltaCompressor) Offset() int {
return 0 return 0
} }
func (s *ReverseInstantDeltaCompressor) Snapshot() ([][]byte, int) { func (s *ReverseInstantDeltaCompressor) Snapshot() ([]byte, int) {
if s.state == nil { if s.state == nil {
return s.buf.Chunks(), s.Size() return s.buf, s.Size()
} }
// ВАЖЛИВО! // ВАЖЛИВО!
// Треба відтворити стан останнього чанка // Треба відтворити стан останнього чанка
@@ -219,7 +216,7 @@ func (s *ReverseInstantDeltaCompressor) CreateDecompressor(fracDigits byte) qb.V
func (s *ReverseInstantDeltaCompressor) Renew() { func (s *ReverseInstantDeltaCompressor) Renew() {
// УВАГА! // УВАГА!
// state не чіпаємо // state не чіпаємо
s.buf = conbuf.New(nil) s.buf = make([]byte, minBufferSize)
s.pos = 0 s.pos = 0
// //
s.baseValue = 0 s.baseValue = 0
@@ -232,14 +229,14 @@ func (s *ReverseInstantDeltaCompressor) CalcRequiredSpace(value float64) int {
return 0 return 0
} }
func (s *ReverseInstantDeltaCompressor) Chunks() [][]byte { func (s *ReverseInstantDeltaCompressor) Chunks() []byte {
return s.buf.Chunks() return s.buf
} }
// DECOMPRESSOR // DECOMPRESSOR
type ReverseInstantDeltaDecompressor struct { type ReverseInstantDeltaDecompressor struct {
buf *conbuf.ContinuousBuffer buf []byte
coef float64 coef float64
pos int pos int
bound int bound int
@@ -250,12 +247,12 @@ type ReverseInstantDeltaDecompressor struct {
done bool done bool
} }
func NewReverseInstantDeltaDecompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseInstantDeltaDecompressor { func NewReverseInstantDeltaDecompressor(buf []byte, size int, fracDigits byte) *ReverseInstantDeltaDecompressor {
var coef float64 = 1 var coef float64 = 1
if fracDigits > 0 { if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits)) coef = math.Pow(10, float64(fracDigits))
} }
i64, n, err := buf.GetVarInt64(0) i64, n, err := bin.GetVarInt64(buf)
if err != nil { if err != nil {
log.Fatalf("bug: get base value: %s", err) log.Fatalf("bug: get base value: %s", err)
} }
@@ -312,7 +309,7 @@ func (s *ReverseInstantDeltaDecompressor) NextValue() (value float64, done bool)
} }
func (s *ReverseInstantDeltaDecompressor) readHeader() { func (s *ReverseInstantDeltaDecompressor) readHeader() {
h := s.buf.GetByte(s.pos) h := s.buf[s.pos]
s.pos-- s.pos--
s.decodeHeaderByte(h) s.decodeHeaderByte(h)
// fmt.Println("h:", h) // fmt.Println("h:", h)
@@ -320,7 +317,7 @@ func (s *ReverseInstantDeltaDecompressor) readHeader() {
// fmt.Println("pending:", s.pending) // fmt.Println("pending:", s.pending)
} }
func (s *ReverseInstantDeltaDecompressor) readValue() { func (s *ReverseInstantDeltaDecompressor) readValue() {
i64, n, err := s.buf.ReverseGetVarInt64(s.pos) i64, n, err := bin.ReverseGetVarInt64(s.buf[:s.pos])
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@@ -4,13 +4,24 @@ import (
"fmt" "fmt"
"log" "log"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/pretty" "gordenko.dev/dima/pretty"
"gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/conbuf" "gordenko.dev/dima/qb/conbuf"
) )
/*
Payload data файла має таку структуру:
vvvvvvvv-> <-ttttttt
де v - це values, які додаються зліва направо, а читаються справа наліво;
t - це timestamps, які додаються справа наліво, а читаються зліва направо.
Timestamps читаються звичайними методами Get*, а писатись мають типу ByRightBound
*/
type ReverseTimeDeltaCompressor struct { type ReverseTimeDeltaCompressor struct {
buf *conbuf.ContinuousBuffer buf []byte
pos int pos int
lastUnixtime uint32 lastUnixtime uint32
lastDelta uint32 lastDelta uint32
@@ -19,21 +30,21 @@ type ReverseTimeDeltaCompressor struct {
state *TimeDeltaBound state *TimeDeltaBound
} }
func NewReverseTimeDeltaCompressor(buf *conbuf.ContinuousBuffer, size int) *ReverseTimeDeltaCompressor { func NewReverseTimeDeltaCompressor(buf []byte, size int) *ReverseTimeDeltaCompressor {
s := &ReverseTimeDeltaCompressor{ s := &ReverseTimeDeltaCompressor{
buf: buf, buf: buf,
pos: size, // перший вільний байт pos: size, // перший вільний байт
} }
if size > 0 { if size > 0 {
u64, n, err := s.buf.ReverseGetVarUint64(s.pos - 1) u64, n, err := bin.ReverseGetVarUint64(s.buf[:s.pos-1])
if err != nil { if err != nil {
log.Fatalf("bug: get last unixtime: %s", err) log.Fatalf("bug: get last unixtime: %s", err)
} }
s.lastUnixtime = uint32(u64) s.lastUnixtime = uint32(u64)
s.pos -= n s.pos -= n
if s.pos > 0 { if s.pos > 0 {
s.h = s.buf.GetByte(s.pos - 1) s.h = s.buf[s.pos-1]
u64, s.lastDeltaSize, err = s.buf.ReverseGetVarUint64(s.pos - 2) u64, s.lastDeltaSize, err = bin.ReverseGetVarUint64(s.buf[:s.pos-2])
if err != nil { if err != nil {
log.Fatalf("bug: get last delta: %s", err) log.Fatalf("bug: get last delta: %s", err)
} }
@@ -66,7 +77,7 @@ func (s *ReverseTimeDeltaCompressor) Append(unixtime uint32) {
if s.h < 127 { if s.h < 127 {
// increase counter // increase counter
s.h++ s.h++
s.buf.SetByte(s.pos-1, s.h) s.buf[s.pos-1] = s.h
} else { } else {
// не можу збільшити - буде переповнення. Додаю новий literal блок // не можу збільшити - буде переповнення. Додаю новий literal блок
// counter overflow // counter overflow
@@ -106,28 +117,28 @@ func (s *ReverseTimeDeltaCompressor) convertLastFromLiteralToRun() {
// Зменшую кількість елементів в literal блоці // Зменшую кількість елементів в literal блоці
s.h-- s.h--
s.pos -= 1 + s.lastDeltaSize s.pos -= 1 + s.lastDeltaSize
s.buf.SetByte(s.pos, s.h) // закриваю literal блок s.buf[s.pos] = s.h // закриваю literal блок
s.pos++ s.pos++
s.lastDeltaSize = s.buf.ReversePutVarUint64(s.pos, uint64(s.lastDelta)) s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], uint64(s.lastDelta))
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.h = 0 // run блок, довжини 2 s.h = 0 // run блок, довжини 2
s.buf.SetByte(s.pos, s.h) s.buf[s.pos] = s.h
s.pos++ s.pos++
} }
func (s *ReverseTimeDeltaCompressor) convertLiteralToRun() { func (s *ReverseTimeDeltaCompressor) convertLiteralToRun() {
// Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії. // Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії.
s.h = 0 s.h = 0
s.buf.SetByte(s.pos-1, s.h) s.buf[s.pos-1] = s.h
} }
func (s *ReverseTimeDeltaCompressor) appendDeltaToLiteral(delta uint32) { func (s *ReverseTimeDeltaCompressor) appendDeltaToLiteral(delta uint32) {
s.h++ // збільшую к-сть дельт s.h++ // збільшую к-сть дельт
s.lastDelta = delta s.lastDelta = delta
s.pos-- s.pos--
s.lastDeltaSize = s.buf.ReversePutVarUint64(s.pos, uint64(delta)) s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], uint64(delta))
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.buf.SetByte(s.pos, s.h) s.buf[s.pos] = s.h
s.pos++ s.pos++
} }
@@ -135,9 +146,9 @@ func (s *ReverseTimeDeltaCompressor) appendNewLiteral(delta uint32) {
//fmt.Println("appendNewLiteral", delta) //fmt.Println("appendNewLiteral", delta)
s.h = flagLiteral s.h = flagLiteral
s.lastDelta = delta s.lastDelta = delta
s.lastDeltaSize = s.buf.ReversePutVarUint64(s.pos, uint64(delta)) s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], uint64(delta))
s.pos += s.lastDeltaSize s.pos += s.lastDeltaSize
s.buf.SetByte(s.pos, flagLiteral) // literal, length = 1 s.buf[s.pos] = flagLiteral // literal, length = 1
s.pos++ s.pos++
} }
@@ -146,7 +157,8 @@ func (s *ReverseTimeDeltaCompressor) DeleteLast() {
} }
func (s *ReverseTimeDeltaCompressor) Sync() { func (s *ReverseTimeDeltaCompressor) Sync() {
s.pos += s.buf.ReversePutVarUint64(s.pos, uint64(s.lastUnixtime)) n, _ := bin.ReversePutVarUint64(s.buf[s.pos:], uint64(s.lastUnixtime))
s.pos += n
} }
// FIX - check methods // FIX - check methods
@@ -156,7 +168,7 @@ type TimeDeltaBound struct {
H byte H byte
LastUnixtime uint32 LastUnixtime uint32
LastDelta uint32 LastDelta uint32
Chunks [][]byte Chunks []byte
} }
// delta h // delta h
@@ -179,16 +191,12 @@ func (s *ReverseTimeDeltaCompressor) Lock() {
} }
// позиція посувається вліво, отже може перескочити на попередній chunk // позиція посувається вліво, отже може перескочити на попередній chunk
pos := s.pos - 1 - s.lastDeltaSize pos := s.pos - 1 - s.lastDeltaSize
chunksQty := pos / conbuf.ChunkSize
if (pos % conbuf.ChunkSize) > 0 {
chunksQty++
}
s.state = &TimeDeltaBound{ s.state = &TimeDeltaBound{
Pos: pos, Pos: pos,
H: s.h, H: s.h,
LastUnixtime: s.lastUnixtime, // fix LastUnixtime: s.lastUnixtime, // fix
LastDelta: s.lastDelta, LastDelta: s.lastDelta,
Chunks: s.buf.Chunks()[:chunksQty], Chunks: s.buf[:s.pos], // fix check pos?
} }
} }
@@ -205,9 +213,9 @@ func (s *ReverseTimeDeltaCompressor) Offset() int {
} }
// fix - // fix -
func (s *ReverseTimeDeltaCompressor) Snapshot() ([][]byte, int) { func (s *ReverseTimeDeltaCompressor) Snapshot() ([]byte, int) {
if s.state == nil { if s.state == nil {
return s.buf.Chunks(), s.Size() return s.buf, s.Size()
} }
// ВАЖЛИВО! // ВАЖЛИВО!
// Треба відтворити стан останнього чанка // Треба відтворити стан останнього чанка
@@ -241,7 +249,7 @@ func (s *ReverseTimeDeltaCompressor) CreateDecompressor() qb.TimestampDecompress
func (s *ReverseTimeDeltaCompressor) Renew() { func (s *ReverseTimeDeltaCompressor) Renew() {
// УВАГА! // УВАГА!
// state не чіпаємо // state не чіпаємо
s.buf = conbuf.New(nil) s.buf = make([]byte, minBufferSize)
s.pos = 0 s.pos = 0
// //
//s.baseValue = 0 //s.baseValue = 0
@@ -250,14 +258,14 @@ func (s *ReverseTimeDeltaCompressor) Renew() {
s.h = 0 s.h = 0
} }
func (s *ReverseTimeDeltaCompressor) Chunks() [][]byte { func (s *ReverseTimeDeltaCompressor) Chunks() []byte {
return s.buf.Chunks() return s.buf
} }
// DECOMPRESSOR // DECOMPRESSOR
type ReverseTimeDeltaDecompressor struct { type ReverseTimeDeltaDecompressor struct {
buf *conbuf.ContinuousBuffer buf []byte
pos int pos int
lastDelta uint32 lastDelta uint32
lastUnixtime uint32 lastUnixtime uint32
@@ -267,7 +275,7 @@ type ReverseTimeDeltaDecompressor struct {
done bool done bool
} }
func NewReverseTimeDeltaDecompressor(buf *conbuf.ContinuousBuffer, size int) *ReverseTimeDeltaDecompressor { func NewReverseTimeDeltaDecompressor(buf []byte, size int) *ReverseTimeDeltaDecompressor {
return &ReverseTimeDeltaDecompressor{ return &ReverseTimeDeltaDecompressor{
buf: buf, buf: buf,
pos: size, pos: size,
@@ -278,7 +286,7 @@ func (s *ReverseTimeDeltaDecompressor) RestoreFromEnd() {
fmt.Println("RestoreFromEnd", s.pos) fmt.Println("RestoreFromEnd", s.pos)
if s.pos > 0 { if s.pos > 0 {
s.pos-- // перший байт даних s.pos-- // перший байт даних
u64, n, err := s.buf.ReverseGetVarUint64(s.pos) u64, n, err := bin.ReverseGetVarUint64(s.buf[:s.pos])
if err != nil { if err != nil {
log.Fatalf("bug: get last unixtime: %s", err) log.Fatalf("bug: get last unixtime: %s", err)
} }
@@ -344,7 +352,7 @@ func (s *ReverseTimeDeltaDecompressor) NextValue() (value uint32, done bool) {
} }
func (s *ReverseTimeDeltaDecompressor) readHeader() { func (s *ReverseTimeDeltaDecompressor) readHeader() {
h := s.buf.GetByte(s.pos) h := s.buf[s.pos]
s.pos-- s.pos--
s.decodeHeaderByte(h) s.decodeHeaderByte(h)
// fmt.Println("h:", h) // fmt.Println("h:", h)
@@ -362,7 +370,7 @@ func (s *ReverseTimeDeltaDecompressor) decodeHeaderByte(h byte) {
} }
func (s *ReverseTimeDeltaDecompressor) readDelta() { func (s *ReverseTimeDeltaDecompressor) readDelta() {
u64, n, err := s.buf.ReverseGetVarUint64(s.pos) u64, n, err := bin.ReverseGetVarUint64(s.buf[:s.pos])
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@@ -3,35 +3,34 @@ package database
import ( import (
"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/txlog" "gordenko.dev/dima/qb/txlog"
) )
// METRIC // METRIC
type IndexRec struct { type IndexLevelTail struct {
Since uint32 Payload []byte
PageNo uint32 RecordsCount int
} }
type _metric struct { type _metric struct {
MetricType qb.MetricType MetricType qb.MetricType
FracDigits byte FracDigits byte
LastPageNo uint32 LastPageNo uint32
SinceValue float64 SinceValue float64
Since uint32 Since uint32
UntilValue float64 UntilValue float64
Until uint32 Until uint32
Timestamps qb.TimestampCompressor Timestamps qb.TimestampCompressor
Values qb.ValueCompressor Values qb.ValueCompressor
// fix - add index levels XLock bool
XLock bool RLocks int
RLocks int WaitQueue []any
WaitQueue []any IndexLevelTails []IndexLevelTail // root - last element
//IndexLevels [][]IndexRec // root - last element
IndexLevels [][]byte // root - last element
} }
//IndexLevels [][]IndexRec // root - last element
func (s *_metric) ReinitBy(timestamp uint32, value float64) { func (s *_metric) ReinitBy(timestamp uint32, value float64) {
s.Timestamps.Renew() s.Timestamps.Renew()
s.Values.Renew() s.Values.Renew()
@@ -74,8 +73,8 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
timestamps = s.Timestamps timestamps = s.Timestamps
values = s.Values values = s.Values
indexLevels []*atree.IndexLevel indexLevels []atree.IndexLevelTail
dataPages []*atree.DataPage dataPages []atree.DataPayload
//written int //written int
//resultCode byte //resultCode byte
@@ -135,12 +134,10 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
} else { } else {
// сторінка заповнена // сторінка заповнена
// prevPageNo - виставляю в txlog, коли забираю номер сторінки із freeList або генерую новий // prevPageNo - виставляю в txlog, коли забираю номер сторінки із freeList або генерую новий
dataPages = append(dataPages, &atree.DataPage{ dataPages = append(dataPages, atree.DataPayload{
PrevPageNo: 0, // ? Since: s.Since,
LowerTimestamp: s.Since, Content: nil,
Timestamps: timestamps.Chunks(),
TimestampsSize: timestamps.Size(), TimestampsSize: timestamps.Size(),
Values: values.Chunks(),
ValuesSize: values.Size(), ValuesSize: values.Size(),
}) })
@@ -163,28 +160,31 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
if len(dataPages) > 0 { if len(dataPages) > 0 {
// пишу в txlog довгим шляхом через redo файл і запис в data файл // пишу в txlog довгим шляхом через redo файл і запис в data файл
for _, unfilled := range s.IndexLevels { for _, tail := range s.IndexLevelTails {
indexLevels = append(indexLevels, &atree.IndexLevel{ indexLevels = append(indexLevels, atree.IndexLevelTail{
Offset: len(unfilled), Payload: tail.Payload,
LowerTimestamp: bin.GetUint32(unfilled), RecordsCount: tail.RecordsCount,
Data: unfilled,
}) })
} }
sendToStorage(txlog.AppendedMeasures{
MetricID: req.MetricID,
LastPageNo: s.LastPageNo,
TimestampsOffset: timestamps.Offset(), // state.Pos() з якої позиції дописувати дані на сторінку 0 (при відновленні)
ValuesOffset: values.Offset(), // state.Pos()
//Payload: s.payload, // fix - timestamps + values ? or timestamps and values (for WAL)
IndexLevelTails: indexLevels,
DataPages: dataPages,
Timestamps: nil,
Values: nil,
//ResultCode: resultCode,
//WrittenCount: wri,
ResultCh: nil,
})
} else { } else {
// короткий шлях - запис лише в txlog // короткий шлях - запис лише в txlog
} }
sendToStorage(txlog.AppendedMeasures{
MetricID: req.MetricID,
TimestampsOffset: timestamps.Offset(), // state.Pos() з якої позиції дописувати дані на сторінку 0 (при відновленні)
ValuesOffset: values.Offset(), // state.Pos()
Timestamps: timestamps.Chunks(),
TimestampsSize: timestamps.Size(),
Values: values.Chunks(),
ValuesSize: values.Size(),
IndexLevels: indexLevels,
DataPages: dataPages,
})
} }
func (s *_metric) FinAppendMeasures(rec txlog.AppendMeasuresSummary) { func (s *_metric) FinAppendMeasures(rec txlog.AppendMeasuresSummary) {

2
go.mod
View File

@@ -4,6 +4,6 @@ go 1.24.2
require ( require (
gopkg.in/ini.v1 v1.67.1 gopkg.in/ini.v1 v1.67.1
gordenko.dev/dima/bin v0.0.0-20260528204801-12b890585248 gordenko.dev/dima/bin v0.0.0-20260604235618-bff16d774d98
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69 gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69
) )

2
go.sum
View File

@@ -20,5 +20,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gordenko.dev/dima/bin v0.0.0-20260528204801-12b890585248 h1:APYOkErjDz3hykfcddvmABsexoyKUgdRPU3XiM16F34= gordenko.dev/dima/bin v0.0.0-20260528204801-12b890585248 h1:APYOkErjDz3hykfcddvmABsexoyKUgdRPU3XiM16F34=
gordenko.dev/dima/bin v0.0.0-20260528204801-12b890585248/go.mod h1:/I+9fvRUzXHgXSwGEwOK1mBBM4BJ3AtCmoCZ2upDiwk= gordenko.dev/dima/bin v0.0.0-20260528204801-12b890585248/go.mod h1:/I+9fvRUzXHgXSwGEwOK1mBBM4BJ3AtCmoCZ2upDiwk=
gordenko.dev/dima/bin v0.0.0-20260604235618-bff16d774d98 h1:pQzJ4wnSrFXn9Hu+v6KvWZ4GQVBmuTljJGZ20Pb+BJA=
gordenko.dev/dima/bin v0.0.0-20260604235618-bff16d774d98/go.mod h1:/I+9fvRUzXHgXSwGEwOK1mBBM4BJ3AtCmoCZ2upDiwk=
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69 h1:nyJ3mzTQ46yUeMZCdLyYcs7B5JCS54c67v84miyhq2E= gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69 h1:nyJ3mzTQ46yUeMZCdLyYcs7B5JCS54c67v84miyhq2E=
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69/go.mod h1:AxgKDktpqBVyIOhIcP+nlCpK+EsJyjN5kPdqyd8euVU= gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69/go.mod h1:AxgKDktpqBVyIOhIcP+nlCpK+EsJyjN5kPdqyd8euVU=

1
qb.go
View File

@@ -86,6 +86,7 @@ const (
UnknownWorkerQueueItemBug AbortCode = 20 UnknownWorkerQueueItemBug AbortCode = 20
UnknownMetricWaitQueueItemBug AbortCode = 21 UnknownMetricWaitQueueItemBug AbortCode = 21
RepeatableLock AbortCode = 22 RepeatableLock AbortCode = 22
NoSpaceOnIndexPage AbortCode = 23
// //
GetRecoveryRecipeFailed AbortCode = 26 GetRecoveryRecipeFailed AbortCode = 26
LoadSnapshotFailed AbortCode = 27 LoadSnapshotFailed AbortCode = 27

207
txlog/data_preparer.go Normal file
View File

@@ -0,0 +1,207 @@
package txlog
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 AddedMetric:
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 *CompletedIndexPage
if len(level.IndexPages) > 0 {
first := level.IndexPages[0]
skipSize := level.SkipRecords * indexRecordSize
completedIndexPage = &CompletedIndexPage{
PageNo: first.PageNo,
Reused: first.Reused,
Checksum: first.Checksum,
Records: first.Content[skipSize:],
}
}
changedIndexPages = append(changedIndexPages, ChangedIndexLevel{
CompletedIndexPage: completedIndexPage,
IndexPages: level.IndexPages[1:],
TailRecords: level.TailRecords[:level.TailRecordsCount*indexRecordSize],
})
indexLevelTails = append(indexLevelTails, IndexLevelTail{
Records: level.TailRecords,
RecordsCount: level.TailRecordsCount,
})
}
first := sealResult.DataPages[0]
rec := WALRecordAppendMeasures{
MetricID: x.MetricID,
CompletedDataPage: CompletedDataPage{
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
Timestamps []byte // offset on 1st page
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{}
}

View File

@@ -9,42 +9,6 @@ import (
const recordSize = 8 const recordSize = 8
const chunkSize = 24 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) { func copyPayloadFromChunks(w *bytes.Buffer, chunks [][]byte, size int, offset int) {
bin.WriteUint16(w, uint16(size)) bin.WriteUint16(w, uint16(size))
var ( var (
@@ -69,170 +33,170 @@ func copyPayloadFromChunks(w *bytes.Buffer, chunks [][]byte, size int, offset in
///////////////////////////////////////// /////////////////////////////////////////
type DataPage struct { // type DataPage struct {
PageNo uint32 // PageNo uint32
Reused bool // Reused bool
PrevPageNo uint32 // PrevPageNo uint32
Checksum uint32 // Checksum uint32
Timestamps []byte // Timestamps []byte
Values []byte // Values []byte
} // }
type IndexPage struct { // type IndexPage struct {
PageNo uint32 // PageNo uint32
Reused bool // Reused bool
Checksum uint32 // Checksum uint32
Records []byte // Records []byte
} // }
type IndexLevel struct { // type IndexLevel struct {
Pages []IndexPage // Pages []IndexPage
Records []byte // unfilled // Records []byte // unfilled
} // }
// Для декодінга // Для декодінга
type TxAppendedMeasures struct { // type TxAppendedMeasures struct {
MetricID uint32 // MetricID uint32
DataPages []DataPage // DataPages []DataPage
// offsets потрібні тому що дані не додаються в кінець, а перезаписують кілька // // offsets потрібні тому що дані не додаються в кінець, а перезаписують кілька
// останніх байтів unfilled даних. Потрібно для коректного recovery. // // останніх байтів unfilled даних. Потрібно для коректного recovery.
// Якщо є DataPages, то застосовуються для 1-ї Data сторінки, інакше - до timestamps і values (unfilled) // // Якщо є DataPages, то застосовуються для 1-ї Data сторінки, інакше - до timestamps і values (unfilled)
TimestampsOffset int // TimestampsOffset int
ValuesOffset int // ValuesOffset int
Timestamps []byte // unfilled // Timestamps []byte // unfilled
Values []byte // unfilled // Values []byte // unfilled
IndexLevels []IndexLevel // IndexLevels []IndexLevel
} // }
func (s *TxAppendedMeasures) Read(r *bytes.Buffer) (err error) { // func (s *TxAppendedMeasures) Read(r *bytes.Buffer) (err error) {
s.MetricID, err = bin.ReadUint32(r) // s.MetricID, err = bin.ReadUint32(r)
if err != nil { // if err != nil {
return // return
} // }
dataPagesQty, err := bin.ReadVarSize(r) // dataPagesQty, err := bin.ReadVarSize(r)
if err != nil { // if err != nil {
return // return
} // }
for range dataPagesQty { // for range dataPagesQty {
var ( // var (
p DataPage // p DataPage
timestampsSize, valuesSize int // timestampsSize, valuesSize int
//b byte // //b byte
) // )
p.PageNo, err = bin.ReadUint32(r) // p.PageNo, err = bin.ReadUint32(r)
if err != nil { // if err != nil {
return // return
} // }
p.Reused, err = bin.ReadBool(r) // p.Reused, err = bin.ReadBool(r)
if err != nil { // if err != nil {
return // return
} // }
p.PrevPageNo, err = bin.ReadUint32(r) // p.PrevPageNo, err = bin.ReadUint32(r)
if err != nil { // if err != nil {
return // return
} // }
p.Checksum, err = bin.ReadUint32(r) // p.Checksum, err = bin.ReadUint32(r)
if err != nil { // if err != nil {
return // return
} // }
timestampsSize, err = bin.ReadUint16AsInt(r) // timestampsSize, err = bin.ReadUint16AsInt(r)
if err != nil { // if err != nil {
return // return
} // }
p.Timestamps, err = bin.ReadN(r, timestampsSize) // p.Timestamps, err = bin.ReadN(r, timestampsSize)
if err != nil { // if err != nil {
return // return
} // }
valuesSize, err = bin.ReadUint16AsInt(r) // valuesSize, err = bin.ReadUint16AsInt(r)
if err != nil { // if err != nil {
return // return
} // }
p.Values, err = bin.ReadN(r, valuesSize) // p.Values, err = bin.ReadN(r, valuesSize)
if err != nil { // if err != nil {
return // return
} // }
s.DataPages = append(s.DataPages, p) // s.DataPages = append(s.DataPages, p)
} // }
s.TimestampsOffset, err = bin.ReadUint16AsInt(r) // s.TimestampsOffset, err = bin.ReadUint16AsInt(r)
if err != nil { // if err != nil {
return // return
} // }
s.ValuesOffset, err = bin.ReadUint16AsInt(r) // s.ValuesOffset, err = bin.ReadUint16AsInt(r)
if err != nil { // if err != nil {
return // return
} // }
var ( // var (
timestampsSize, valuesSize int // timestampsSize, valuesSize int
) // )
timestampsSize, err = bin.ReadVarSize(r) // timestampsSize, err = bin.ReadVarSize(r)
if err != nil { // if err != nil {
return // return
} // }
s.Timestamps, err = bin.ReadN(r, timestampsSize) // s.Timestamps, err = bin.ReadN(r, timestampsSize)
if err != nil { // if err != nil {
return // return
} // }
valuesSize, err = bin.ReadVarSize(r) // valuesSize, err = bin.ReadVarSize(r)
if err != nil { // if err != nil {
return // return
} // }
s.Values, err = bin.ReadN(r, valuesSize) // s.Values, err = bin.ReadN(r, valuesSize)
if err != nil { // if err != nil {
return // return
} // }
if len(s.DataPages) == 0 { // if len(s.DataPages) == 0 {
return // return
} // }
indexLevelsQty, err := bin.ReadVarSize(r) // indexLevelsQty, err := bin.ReadVarSize(r)
if err != nil { // if err != nil {
return // return
} // }
for range indexLevelsQty { // for range indexLevelsQty {
var ( // var (
indexPagesQty int // indexPagesQty int
level IndexLevel // level IndexLevel
recordsQty int // recordsQty int
) // )
indexPagesQty, err = bin.ReadVarSize(r) // indexPagesQty, err = bin.ReadVarSize(r)
if err != nil { // if err != nil {
return // return
} // }
for range indexPagesQty { // for range indexPagesQty {
var ( // var (
p IndexPage // p IndexPage
recordsQty int // recordsQty int
) // )
p.PageNo, err = bin.ReadUint32(r) // p.PageNo, err = bin.ReadUint32(r)
if err != nil { // if err != nil {
return // return
} // }
p.Reused, err = bin.ReadBool(r) // p.Reused, err = bin.ReadBool(r)
if err != nil { // if err != nil {
return // return
} // }
p.Checksum, err = bin.ReadUint32(r) // p.Checksum, err = bin.ReadUint32(r)
if err != nil { // if err != nil {
return // return
} // }
recordsQty, err = bin.ReadVarSize(r) // recordsQty, err = bin.ReadVarSize(r)
if err != nil { // if err != nil {
return // return
} // }
p.Records, err = bin.ReadN(r, recordsQty*recordSize) // p.Records, err = bin.ReadN(r, recordsQty*recordSize)
if err != nil { // if err != nil {
return // return
} // }
level.Pages = append(level.Pages, p) // level.Pages = append(level.Pages, p)
} // }
recordsQty, err = bin.ReadVarSize(r) // recordsQty, err = bin.ReadVarSize(r)
if err != nil { // if err != nil {
return // return
} // }
level.Records, err = bin.ReadN(r, recordsQty*recordSize) // level.Records, err = bin.ReadN(r, recordsQty*recordSize)
if err != nil { // if err != nil {
return // return
} // }
s.IndexLevels = append(s.IndexLevels, level) // s.IndexLevels = append(s.IndexLevels, level)
} // }
return // return
} // }

279
txlog/page_preparer.go Normal file
View File

@@ -0,0 +1,279 @@
package txlog
import (
"errors"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/bin"
"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 {
Records []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.Records,
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,
}
}

View File

@@ -1,13 +1,15 @@
package txlog package txlog
import ( import (
"bytes"
"io" "io"
bin "gordenko.dev/dima/bin/little" bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb" "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/conbuf" "gordenko.dev/dima/qb/conbuf"
"gordenko.dev/dima/qb/util" )
var (
indexRecordSize = 8
) )
type AddedMetric struct { type AddedMetric struct {
@@ -359,62 +361,3 @@ Nb - values payload
// } // }
// PACK // PACK
type PreparedData struct {
Packet []byte // пакет для запису в WAL
WriteToIndex []IndexPayloadToWrite
WriteToData []DataPayloadToWrite
ToWorker []any
}
func prepareData(input []any) PreparedData {
buffer := bytes.NewBuffer(nil)
buffer.Write([]byte{
0, 0, 0, 0, 0, 0, 0, 0, // size (max 8 byte)
0, 0, 0, 0, // crc32
})
hasher := util.NewHasher()
w := io.MultiWriter(buffer, hasher)
var (
writeToIndex []IndexPayloadToWrite
writeToData []DataPayloadToWrite
)
// 1. Пакую всі дані в WAL буфер запису
for _, untyped := range input {
switch x := untyped.(type) {
case AddedMetric:
x.Pack(w)
// case DeletedMetric:
// if len(x.FreePageNumbers) > 0 {
// s.freeList.AddPageNumbers(x.FreePageNumbers)
// }
case AppendedMeasures:
//s.packMeasuresIntoWALBuffer(x)
//case DeletedMeasures:
//case DeletedMeasuresSince:
}
}
// 2. Додаю розмір пакету і чексуму
//s.written += int64(len(packet)) + 12
packet := buffer.Bytes()
// write size
payloadSize := len(packet) - 12
start := 8 - bin.CountVarSize(payloadSize)
bin.PutVarSize(packet[start:], payloadSize)
// checksum
bin.PutUint32(packet[8:], hasher.Sum32())
//
//totalSize := (12 - start) + payloadSize
return PreparedData{
Packet: packet[start:],
WriteToIndex: writeToIndex,
WriteToData: writeToData,
}
}

306
txlog/wal_records.go Normal file
View File

@@ -0,0 +1,306 @@
package txlog
import (
"bytes"
bin "gordenko.dev/dima/bin/little"
)
type CompletedDataPage 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 CompletedIndexPage struct {
PageNo uint32
Reused bool
Checksum uint32
Records []byte // offset не потрібен, оскільки Records додаються в кінець
}
type ChangedIndexLevel struct {
CompletedIndexPage *CompletedIndexPage
IndexPages []SealedIndexPage
TailRecords []byte // payload only
}
type WALRecordAppendMeasures struct {
MetricID uint32
CompletedDataPage CompletedDataPage
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 WALRecordAppendMeasures) WriteTo(w *bytes.Buffer) {
w.WriteByte(CodeAppendedMeasures)
bin.WriteUint32(w, s.MetricID)
// completed data page
completed := s.CompletedDataPage
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.CompletedIndexPage
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)
}
return
}
func (s *WALRecordAppendMeasures) Parse(r *bytes.Buffer) (err error) {
var (
size int
recordsCount int
)
s.MetricID, err = bin.ReadUint32(r)
if err != nil {
return
}
s.CompletedDataPage.PageNo, err = bin.ReadUint32(r)
if err != nil {
return
}
s.CompletedDataPage.Reused, err = bin.ReadBool(r)
if err != nil {
return
}
s.CompletedDataPage.PrevPageNo, err = bin.ReadUint32(r)
if err != nil {
return
}
s.CompletedDataPage.Checksum, err = bin.ReadUint32(r)
if err != nil {
return
}
s.CompletedDataPage.TimestampsOffset, err = bin.ReadVarSize(r)
if err != nil {
return
}
size, err = bin.ReadVarSize(r)
if err != nil {
return
}
s.CompletedDataPage.Timestamps, err = bin.ReadN(r, size)
if err != nil {
return
}
size, err = bin.ReadVarSize(r)
if err != nil {
return
}
s.CompletedDataPage.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.CompletedIndexPage.PageNo, err = bin.ReadUint32(r)
if err != nil {
return
}
level.CompletedIndexPage.Reused, err = bin.ReadBool(r)
if err != nil {
return
}
level.CompletedIndexPage.Checksum, err = bin.ReadUint32(r)
if err != nil {
return
}
recordsCount, err = bin.ReadVarSize(r)
if err != nil {
return
}
level.CompletedIndexPage.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
}

View File

@@ -6,7 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
@@ -63,21 +62,22 @@ type Changes struct {
} }
type Writer struct { type Writer struct {
mutex sync.Mutex mutex sync.Mutex
dataPagesCount uint32 dataPagesCount uint32
indexPagesCount uint32 indexPagesCount uint32
dataFreeList *freelist.FreeList dataFreeList *freelist.FreeList
indexFreeList *freelist.FreeList indexFreeList *freelist.FreeList
atree *atree.Atree atree *atree.Atree
logNumber int logNumber int
dir string dir string
w *bytes.Buffer // wal буфер для упаковки даних перед записом на диск w *bytes.Buffer // wal буфер для упаковки даних перед записом на диск
wal *os.File wal *os.File
dataFile *os.File dataFile *os.File
indexFile *os.File indexFile *os.File
// буфер для складання payload на сторінку, розрахунку CRC32 та інше indexPageSize int
pageBuffer []byte dataPageSize int
input []any input []any
dataPreparer *DataPreparer
appendToWorkerQueue func(any) appendToWorkerQueue func(any)
//lsn uint32 //lsn uint32
written int64 written int64
@@ -88,6 +88,8 @@ type Writer struct {
} }
type WriterOptions struct { type WriterOptions struct {
IndexPageSize int
DataPageSize int
Dir string Dir string
LogNumber int // номер журнала LogNumber int // номер журнала
AppendToWorkerQueue func(any) AppendToWorkerQueue func(any)
@@ -99,6 +101,12 @@ type WriterOptions struct {
} }
func NewWriter(opt WriterOptions) (*Writer, error) { 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 == "" { if opt.Dir == "" {
return nil, errors.New("Dir option is required") return nil, errors.New("Dir option is required")
} }
@@ -122,6 +130,8 @@ func NewWriter(opt WriterOptions) (*Writer, error) {
} }
s := &Writer{ s := &Writer{
indexPageSize: opt.IndexPageSize,
dataPageSize: opt.DataPageSize,
dir: opt.Dir, dir: opt.Dir,
appendToWorkerQueue: opt.AppendToWorkerQueue, appendToWorkerQueue: opt.AppendToWorkerQueue,
dataFreeList: opt.DataFreeList, dataFreeList: opt.DataFreeList,
@@ -215,7 +225,7 @@ func (s *Writer) packAndWrite() (err error) {
// } // }
s.mutex.Unlock() s.mutex.Unlock()
prepared := prepareData(input) prepared := s.dataPreparer.Prepare(input)
// 3. Пишу на диск WAL (append) // 3. Пишу на диск WAL (append)
n, err := s.wal.Write(prepared.Packet) n, err := s.wal.Write(prepared.Packet)
@@ -253,7 +263,7 @@ func (s *Writer) packAndWrite() (err error) {
metricsStateCh := make(chan MetricsState) metricsStateCh := make(chan MetricsState)
s.appendToWorkerQueue(Changes{ s.appendToWorkerQueue(Changes{
Records: prepared.ToWorker, Records: prepared.WriteResults,
MetricsCh: metricsStateCh, MetricsCh: metricsStateCh,
}) })
@@ -293,7 +303,7 @@ func (s *Writer) packAndWrite() (err error) {
close(state.WaitCh) close(state.WaitCh)
} else { } else {
s.appendToWorkerQueue(Changes{ s.appendToWorkerQueue(Changes{
Records: prepared.ToWorker, Records: prepared.WriteResults,
}) })
} }
@@ -350,168 +360,54 @@ func (s *Writer) exit() {
} }
} }
type IndexPayloadToWrite struct { // type IndexPayloadToWrite struct {
PageNo uint32 // PageNo uint32
Payload []byte // Payload []byte
ZeroLevel bool // ZeroLevel bool
} // }
type DataPayloadToWrite struct { type PageToWrite struct {
PrevPageNo uint32 PageNo uint32
Timestamps [][]byte // chunks Content []byte
TimestampsSize int
Values [][]byte // chunks
ValuesSize int
PageNo uint32
} }
// writePagesToAtree - записує сторінки в .data та .index файли // writePagesToAtree - записує сторінки в .data та .index файли
func (s *Writer) writePagesToAtree(indexItems []IndexPayloadToWrite, dataItems []DataPayloadToWrite) (err error) { func (s *Writer) writePagesToAtree(indexPages []PageToWrite, dataPages []PageToWrite) (err error) {
for _, p := range dataItems { for _, p := range dataPages {
atree.ChunksToDataPage(s.pageBuffer, atree.ChunksToDataPageReq{ if len(p.Content) != s.dataPageSize {
PrevPageNo: p.PrevPageNo, return fmt.Errorf("wrong data page size: %d", len(p.Content))
Timestamps: p.Timestamps, }
TimestampsSize: p.TimestampsSize,
Values: p.Values,
ValuesSize: p.ValuesSize,
})
var ( var (
off = (p.PageNo - 1) * atree.DataPageSize off = int(p.PageNo-1) * s.dataPageSize
n int n int
) )
n, err = s.dataFile.WriteAt(s.pageBuffer, int64(off)) n, err = s.dataFile.WriteAt(p.Content, int64(off))
if err != nil { if err != nil {
return return
} }
if n != atree.DataPageSize { if n != s.dataPageSize {
return fmt.Errorf("write %d instead of %d", n, atree.DataPageSize) return fmt.Errorf("write %d instead of %d", n, s.dataPageSize)
} }
} }
for _, p := range indexItems { for _, p := range indexPages {
// if len(p.Payload) > atree.MaxIndexPayload { if len(p.Content) != s.indexPageSize {
// return fmt.Errorf("wrong index payload size: %d", len(p.Payload)) return fmt.Errorf("wrong index page size: %d", len(p.Content))
// } }
indexPageBuffer := s.pageBuffer[:atree.IndexPageSize]
atree.DataToIndexPage(indexPageBuffer, p.Payload, p.ZeroLevel)
var ( var (
off = (p.PageNo - 1) * atree.IndexPageSize off = int(p.PageNo-1) * s.indexPageSize
n int n int
) )
n, err = s.indexFile.WriteAt(indexPageBuffer, int64(off)) n, err = s.indexFile.WriteAt(p.Content, int64(off))
if err != nil { if err != nil {
return return
} }
if n != atree.IndexPageSize { if n != s.indexPageSize {
return fmt.Errorf("write %d instead of %d", n, atree.IndexPageSize) return fmt.Errorf("write %d instead of %d", n, s.indexPageSize)
} }
} }
return nil return nil
} }
type AppendMeasuresSummary struct {
MetricID uint32
//TimestampsOffset int // (заповнені одразу)
//ValuesOffset int // (заповнені одразу)
// Timestamps [][]byte
// TimestampsSize int
// Values [][]byte
// ValuesSize int
LastPageNo uint32
Index [][]byte
ResultCode int
WrittenCount int
ResultCh chan struct{}
}
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
ResultCode int
WrittenCount int
ResultCh chan struct{}
}
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