package database import ( "gordenko.dev/dima/qb" "gordenko.dev/dima/qb/atree" "gordenko.dev/dima/qb/txlog" ) // METRIC const minBufferSize = 1024 type IndexLevelTail struct { Payload []byte RecordsCount int } 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 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 indexLevels []txlog.IndexLevelTail 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.Until { 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.UntilValue { 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 if totalSpace <= atree.DataPagePayloadSize { // накопичую timestamps.Compress(timestampCompressionWay, measure.Timestamp) values.Compress(valueCompressionWay, measure.Value) } else { // сторінка заповнена buffer := make([]byte, minBufferSize) 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, }) // 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.Since = measure.Timestamp } s.Until = measure.Timestamp s.UntilValue = measure.Value } // виділити змінені байти. // скопіювати. Причому можна скопіювати зрізи chunks 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, 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 { // короткий шлях - запис лише в txlog } } 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 // } 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(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) } if s.LastPageNo > 0 { req.ResultCh <- fullScanResult{ ResultCode: UntilFound, LastPageNo: s.LastPageNo, FracDigits: s.FracDigits, } s.RLocks++ } else { req.ResultCh <- fullScanResult{ ResultCode: QueryDone, } } }