Files
qb/database/metric.go
2026-06-11 14:27:38 +00:00

401 lines
9.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package database
import (
"io"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/storage"
)
// METRIC
const minBufferSize = 1024
var (
indexRecordSize = 8
)
type _metric struct {
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 []storage.IndexLevelTail // root - last element
}
//IndexLevels [][]IndexRec // root - last element
// func (s *_metric) ReinitBy(timestamp uint32, value float64) {
// s.Timestamps.Renew()
// s.Values.Renew()
// s.Timestamps.Append(timestamp)
// s.Values.Append(value)
// s.Since = timestamp
// s.SinceValue = value
// s.Until = timestamp
// s.UntilValue = value
// }
func (s *_metric) DeleteMeasures() {
// s.Timestamps.Renew()
// s.Values.Renew()
// s.LastPageNo = 0
// s.Since = 0
// s.SinceValue = 0
// s.Until = 0
// s.UntilValue = 0
}
// func (s *_metric) startAppendMeasures(req tryAppendMeasuresReq, sendToStorage func(storage.AppendedMeasures)) {
func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, tmp []byte, sendToStorage func(any)) {
var (
timestamps = s.timestamps
values = s.values
dataPages []storage.DataPayload
// офсети на head сторінці
timestampsOffset int
valuesOffset int
//written int
//resultCode byte
)
s.values.CaptureState()
s.timestamps.CaptureState()
for idx, measure := range req.Measures {
// FIX - у випадку помилки треба в транзакції зберегти що помилка, але також зафіксувати скільки елементів збережено.
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в storage
if measure.Timestamp <= s.timestamps.LastTimestamp() {
if idx == 0 {
s.values.ForgetCapturedState()
s.timestamps.ForgetCapturedState()
req.ResultCh <- tryAppendMeasuresResult{
ResultCode: ExpiredMeasure,
}
return
}
//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
tReport := timestamps.Evaluate(tmp, measure.Timestamp)
vReport := values.Evaluate(tmp, measure.Value)
totalRequiredSpace := tReport.TotalSpace + vReport.TotalSpace
if totalRequiredSpace <= len(s.buffer) {
// якщо на сторінці є місце
if idx == 0 {
timestampsOffset = tReport.Offset
valuesOffset = vReport.Offset
}
} else {
if len(s.buffer) < storage.DataPageSize {
// allocate bigger buffer
buf, databuf := growBuffers(growBuffersIn{
databuf: s.buffer,
tSize: timestamps.Size(),
vSize: values.Size(),
requiredSpace: totalRequiredSpace,
})
s.buffer = buf
// replace buffer in timestamps and values
timestamps.Rotate(databuf) // fix pos
values.Rotate(databuf) // fix pos
} else {
// сторінка заповнена
since := s.timestamps.ReplaceSinceWithUntil()
if len(dataPages) == 0 {
// head page FIX
// timestampsPayload =
// valuesPayload =
}
dataPages = append(dataPages, storage.DataPayload{
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
}
}
timestamps.Append(tReport.Offset, tmp[:tReport.ChangeSize], measure.Timestamp)
values.Append(vReport.Offset, tmp[7:7+vReport.ChangeSize], measure.Value, vReport.Delta)
//
s.lastValue = measure.Value
}
// виділити змінені байти.
// скопіювати. Причому можна скопіювати зрізи chunks
if len(dataPages) > 0 {
// пишу в storage довгим шляхом через redo файл і запис в data файл
sendToStorage(storage.AppendedMeasures{
MetricID: req.MetricID,
LastPageNo: s.lastPageNo,
TimestampsOffset: timestampsOffset,
ValuesOffset: valuesOffset,
//Payload: s.payload, // fix - timestamps + values ? or timestamps and values (for WAL)
IndexLevelTails: s.indexLevelTails,
DataPages: dataPages,
Timestamps: nil,
Values: nil,
//ResultCode: resultCode,
//WrittenCount: wri,
ResultCh: nil,
})
} else {
// короткий шлях - запис лише в storage
sendToStorage(storage.AppendedMeasures{
MetricID: req.MetricID,
TimestampsOffset: timestampsOffset,
ValuesOffset: valuesOffset,
Timestamps: nil, // pos - offset
Values: nil,
//ResultCode: resultCode,
//WrittenCount: wri,
ResultCh: nil,
})
}
}
func (s *_metric) FinAppendMeasures(rec storage.AppendMeasuresSummary) {
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
s.values.ForgetCapturedState()
s.timestamps.ForgetCapturedState()
if rec.LastPageNo > 0 {
s.lastPageNo = rec.LastPageNo
}
// В storage я передав повний індекс. У нього додали елементи (можливо нові рівні).
// Тому проста заміна
s.indexLevelTails = rec.Index
}
// READ
func (s *_metric) StartRangeScan(req tryRangeScanReq) {
// if s.Since == 0 {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// if req.Since > s.Until {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// if req.Until < s.Since {
// if s.RootPageNo > 0 {
// req.ResultCh <- rangeScanResult{
// ResultCode: UntilNotFound,
// RootPageNo: s.RootPageNo,
// FracDigits: s.FracDigits,
// }
// s.RLocks++
// return
// } else {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// 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)
// }
// if timestamp <= req.Until {
// req.ResponseWriter.FeedNoSend(timestamp, value)
// if timestamp < req.Since {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// }
// }
// if s.LastPageNo > 0 {
// req.ResultCh <- rangeScanResult{
// ResultCode: UntilFound,
// LastPageNo: s.LastPageNo,
// FracDigits: s.FracDigits,
// }
// s.RLocks++
// } else {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// }
}
func (s *_metric) StartFullScan(req tryFullScanReq) {
// 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
// timestamps size - 2b
// values size - 2b
// timestams payload - Nb
// 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
}
err = bin.WriteUint32(w, s.lastPageNo)
if err != nil {
return
}
err = bin.WriteUint16(w, uint16(s.timestamps.Size()))
if err != nil {
return
}
err = bin.WriteUint16(w, uint16(s.values.Size()))
if err != nil {
return
}
// timestamps payload
err = s.timestamps.WritePayloadTo(w)
if err != nil {
return
}
// 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
}
// since - 4b
// sinceValue - 8b -
// untilValue - 8b