This commit is contained in:
2026-06-09 08:16:16 +03:00
parent 964fe4b4a8
commit 7007b1b6fd
18 changed files with 1334 additions and 1026 deletions

View File

@@ -1,6 +1,9 @@
package database
import (
"io"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/atree"
"gordenko.dev/dima/qb/txlog"
@@ -10,26 +13,25 @@ import (
const minBufferSize = 1024
type IndexLevelTail struct {
Payload []byte
RecordsCount int
}
var (
indexRecordSize = 8
)
type _metric struct {
MetricType qb.MetricType
FracDigits byte
LastPageNo uint32
SinceValue float64
Since uint32
UntilValue float64
Until uint32
Buffer []byte
Timestamps qb.TimestampCompressor
Values qb.ValueCompressor
metricType qb.MetricType
fracDigits byte
lastPageNo uint32
//SinceValue float64
//Since uint32
lastValue float64
//Until uint32
buffer []byte
timestamps qb.TimestampCompressor
values qb.ValueCompressor
XLock bool
RLocks int
WaitQueue []any
IndexLevelTails []txlog.IndexLevelTail // root - last element
indexLevelTails []txlog.IndexLevelTail // root - last element
}
//IndexLevels [][]IndexRec // root - last element
@@ -73,96 +75,105 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
// DataPages []*atree.DataPage // fix - prevPageNo for the 1st data page
// }
timestamps = s.Timestamps
values = s.Values
timestamps = s.timestamps
values = s.values
indexLevels []txlog.IndexLevelTail
dataPages []txlog.DataPayload
dataPages []txlog.DataPayload
//written int
//resultCode byte
)
s.Values.CaptureState()
s.Timestamps.CaptureState()
s.values.CaptureState()
s.timestamps.CaptureState()
for idx, measure := range req.Measures {
if s.Since == 0 {
s.Since = measure.Timestamp
} else {
// FIX - у випадку помилки треба в транзакції зберегти що помилка, але також зафіксувати скільки елементів збережено.
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в txlog
if measure.Timestamp <= s.Until {
if idx == 0 {
s.Values.ForgetCapturedState()
s.Timestamps.ForgetCapturedState()
// if s.Since == 0 {
// s.Since = measure.Timestamp
// } else {
// FIX - у випадку помилки треба в транзакції зберегти що помилка, але також зафіксувати скільки елементів збережено.
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в txlog
if measure.Timestamp <= s.timestamps.LastTimestamp() {
if idx == 0 {
s.values.ForgetCapturedState()
s.timestamps.ForgetCapturedState()
req.ResultCh <- tryAppendMeasuresResult{
ResultCode: ExpiredMeasure,
}
return
req.ResultCh <- tryAppendMeasuresResult{
ResultCode: ExpiredMeasure,
}
//resultCode = ExpiredMeasure
//written = idx
break
return
}
if s.MetricType == qb.Cumulative && measure.Value < s.UntilValue {
if idx == 0 {
s.Values.ForgetCapturedState()
s.Timestamps.ForgetCapturedState()
req.ResultCh <- tryAppendMeasuresResult{
ResultCode: NonMonotonicValue,
}
return
}
//resultCode = NonMonotonicValue
//written = idx
break
}
//resultCode = ExpiredMeasure
//written = idx
break
}
if s.metricType == qb.Cumulative && measure.Value < s.lastValue {
if idx == 0 {
s.values.ForgetCapturedState()
s.timestamps.ForgetCapturedState()
req.ResultCh <- tryAppendMeasuresResult{
ResultCode: NonMonotonicValue,
}
return
}
//resultCode = NonMonotonicValue
//written = idx
break
}
//}
// fix - 1 + 8 bytes
timestampCompressionWay, timestampRequiredSpace := timestamps.Evaluate(measure.Timestamp)
valueCompressionWay, valueRequiredSpace := values.Evaluate(measure.Value)
totalSpace := timestampRequiredSpace + valueRequiredSpace
totalRequiredSpace := timestampRequiredSpace + valueRequiredSpace
if totalSpace <= atree.DataPagePayloadSize {
if totalRequiredSpace <= len(s.buffer) {
// накопичую
timestamps.Compress(timestampCompressionWay, measure.Timestamp)
values.Compress(valueCompressionWay, measure.Value)
} else if len(s.buffer) < atree.DataPagePayloadSize {
// allocate bigger buffer
buffer := make([]byte, len(s.buffer)*2)
// copy timestamps
// copy values
// replace buffer in timestamps and values
s.buffer = buffer
timestamps.Compress(timestampCompressionWay, measure.Timestamp)
values.Compress(valueCompressionWay, measure.Value)
} else {
// сторінка заповнена
buffer := make([]byte, minBufferSize)
since := s.timestamps.ReplaceSinceWithUntil()
timestampsSize := timestamps.Rotate(buffer)
valuesSize := values.Rotate(buffer)
// prevPageNo - виставляю в txlog, коли забираю номер сторінки із freeList або генерую новий
dataPages = append(dataPages, txlog.DataPayload{
Since: s.Since,
Content: s.Buffer,
TimestampsSize: timestampsSize,
ValuesSize: valuesSize,
Since: since,
Content: s.buffer,
TimestampsSize: timestamps.Size(),
ValuesSize: values.Size(),
})
buffer := make([]byte, minBufferSize)
timestamps.Rotate(buffer)
values.Rotate(buffer)
// renew
s.Buffer = buffer
s.buffer = buffer
timestampCompressionWay, _ = timestamps.Evaluate(measure.Timestamp)
valueCompressionWay, _ = values.Evaluate(measure.Value)
timestamps.Compress(timestampCompressionWay, measure.Timestamp)
values.Compress(valueCompressionWay, measure.Value)
s.Since = measure.Timestamp
}
s.Until = measure.Timestamp
s.UntilValue = measure.Value
//
s.lastValue = measure.Value
}
// виділити змінені байти.
@@ -170,20 +181,13 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
if len(dataPages) > 0 {
// пишу в txlog довгим шляхом через redo файл і запис в data файл
for _, tail := range s.IndexLevelTails {
indexLevels = append(indexLevels, txlog.IndexLevelTail{
Records: tail.Records,
RecordsCount: tail.RecordsCount,
})
}
sendToStorage(txlog.AppendedMeasures{
MetricID: req.MetricID,
LastPageNo: s.LastPageNo,
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,
IndexLevelTails: s.indexLevelTails,
DataPages: dataPages,
Timestamps: nil,
Values: nil,
@@ -199,19 +203,15 @@ func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage fu
func (s *_metric) FinAppendMeasures(rec txlog.AppendMeasuresSummary) {
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
s.Values.ForgetCapturedState()
s.Timestamps.ForgetCapturedState()
// fix write index levels
// update prev pageNo
// if len(rec.DataPages) > 0 {
// s.LastPageNo = rec.DataPages[len(rec.DataPages)-1].PageNo
// }
s.values.ForgetCapturedState()
s.timestamps.ForgetCapturedState()
if rec.LastPageNo > 0 {
s.LastPageNo = rec.LastPageNo
s.lastPageNo = rec.LastPageNo
}
// В txlog я передав повний індекс. У нього додали елементи (можливо нові рівні).
// Тому проста заміна
s.IndexLevelTails = rec.Index
s.indexLevelTails = rec.Index
}
// READ
@@ -288,38 +288,131 @@ func (s *_metric) StartRangeScan(req tryRangeScanReq) {
}
func (s *_metric) StartFullScan(req tryFullScanReq) {
if s.Since == 0 {
req.ResultCh <- fullScanResult{
ResultCode: QueryDone,
}
// if s.Since == 0 {
// req.ResultCh <- fullScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// timestampDecompressor := s.Timestamps.CreateDecompressor()
// valueDecompressor := s.Values.CreateDecompressor()
// for {
// timestamp, done := timestampDecompressor.NextValue()
// if done {
// break
// }
// value, done := valueDecompressor.NextValue()
// if done {
// qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
// }
// req.ResponseWriter.FeedNoSend(timestamp, value)
// }
// if s.LastPageNo > 0 {
// req.ResultCh <- fullScanResult{
// ResultCode: UntilFound,
// LastPageNo: s.LastPageNo,
// FracDigits: s.FracDigits,
// }
// s.RLocks++
// } else {
// req.ResultCh <- fullScanResult{
// ResultCode: QueryDone,
// }
// }
}
// індекси
// Metric encode format:
// metricID - 4b
// metricType - 1b
// fracDigits - 1b
// lastPageNo - 4b
// until - 4b
// timestamps size - 2b
// timestams payload - Nb
// values size - 2b
// values payload - Nb
// index levels count - varsize
// [
// records qty - varsize
// records - Nb
// ]
func (s *_metric) WriteTo(w io.Writer) (err error) {
_, err = w.Write([]byte{
byte(s.metricType),
s.fracDigits,
})
if err != nil {
return
}
timestampDecompressor := s.Timestamps.CreateDecompressor()
valueDecompressor := s.Values.CreateDecompressor(s.FracDigits)
for {
timestamp, done := timestampDecompressor.NextValue()
if done {
break
}
value, done := valueDecompressor.NextValue()
if done {
qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
}
req.ResponseWriter.FeedNoSend(timestamp, value)
err = bin.WriteUint32(w, s.lastPageNo)
if err != nil {
return
}
if s.LastPageNo > 0 {
req.ResultCh <- fullScanResult{
ResultCode: UntilFound,
LastPageNo: s.LastPageNo,
FracDigits: s.FracDigits,
}
s.RLocks++
} else {
req.ResultCh <- fullScanResult{
ResultCode: QueryDone,
}
// err = bin.WriteUint32(w, s.Since) // fix unixtime in timestamps
// if err != nil {
// return
// }
// err = bin.WriteFloat64(w, s.SinceValue)
// if err != nil {
// return
// }
err = bin.WriteUint32(w, s.timestamps.LastTimestamp())
if err != nil {
return
}
// err = bin.WriteFloat64(w, s.UntilValue)
// if err != nil {
// return
// }
// FIX - write sizes, then payloads
// copy timestamps payload
err = s.timestamps.WritePayloadTo(w)
if err != nil {
return
}
// copy values payload
err = s.values.WritePayloadTo(w)
if err != nil {
return
}
// indexes
_, err = bin.WriteVarSize(w, len(s.indexLevelTails))
if err != nil {
return
}
for _, level := range s.indexLevelTails {
_, err = bin.WriteVarSize(w, level.RecordsCount)
if err != nil {
return
}
_, err = w.Write(level.Buffer)
if err != nil {
return
}
}
return
}
// var (
// values qb.ValueCompressor
// )
// if s.metricType == qb.Cumulative {
// values = enc.NewCumulativeDeltaCompressor(s.buffer, s.fracDigits)
// } else {
// values = enc.NewInstantDeltaCompressor(s.buffer, s.fracDigits)
// }
// values.RestoreState(valuesSize)
// s.timestamps = enc.NewTimeDeltaCompressor(s.buffer)
// s.timestamps.RestoreState(timestampsSize, until)
// s.values = values
// s.lastValue = s.values.LastValue()
// since - 4b
// sinceValue - 8b -
// untilValue - 8b