package database import ( "io" bin "gordenko.dev/dima/bin/little" "gordenko.dev/dima/qb" "gordenko.dev/dima/qb/atree" "gordenko.dev/dima/qb/txlog" ) // 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 []txlog.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(txlog.AppendedMeasures)) { func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage func(any)) { var ( // 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 // } timestamps = s.timestamps values = s.values dataPages []txlog.DataPayload //written int //resultCode byte ) 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.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 timestampCompressionWay, timestampRequiredSpace := timestamps.Evaluate(measure.Timestamp) valueCompressionWay, valueRequiredSpace := values.Evaluate(measure.Value) totalRequiredSpace := timestampRequiredSpace + valueRequiredSpace 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 { // сторінка заповнена since := s.timestamps.ReplaceSinceWithUntil() dataPages = append(dataPages, txlog.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 timestampCompressionWay, _ = timestamps.Evaluate(measure.Timestamp) valueCompressionWay, _ = values.Evaluate(measure.Value) timestamps.Compress(timestampCompressionWay, measure.Timestamp) values.Compress(valueCompressionWay, measure.Value) } // s.lastValue = measure.Value } // виділити змінені байти. // скопіювати. Причому можна скопіювати зрізи chunks if len(dataPages) > 0 { // пишу в txlog довгим шляхом через redo файл і запис в data файл 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: s.indexLevelTails, DataPages: dataPages, Timestamps: nil, Values: nil, //ResultCode: resultCode, //WrittenCount: wri, ResultCh: nil, }) } else { // короткий шлях - запис лише в txlog } } func (s *_metric) FinAppendMeasures(rec txlog.AppendMeasuresSummary) { // Видаляю state. Оригінальні Timestamps і Values вже мають останню версію s.values.ForgetCapturedState() s.timestamps.ForgetCapturedState() if rec.LastPageNo > 0 { s.lastPageNo = rec.LastPageNo } // В txlog я передав повний індекс. У нього додали елементи (можливо нові рівні). // Тому проста заміна 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 // 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 } err = bin.WriteUint32(w, s.lastPageNo) if err != nil { return } // 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