package database import ( "gordenko.dev/dima/qb" "gordenko.dev/dima/qb/atree" "gordenko.dev/dima/qb/txlog" ) // METRIC 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 Timestamps qb.TimestampCompressor Values qb.ValueCompressor XLock bool RLocks int WaitQueue []any IndexLevelTails []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 []atree.IndexLevelTail dataPages []atree.DataPayload //written int //resultCode byte ) s.Values.Lock() s.Timestamps.Lock() 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.Unlock() s.Timestamps.Unlock() 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.Unlock() s.Timestamps.Unlock() req.ResultCh <- tryAppendMeasuresResult{ ResultCode: NonMonotonicValue, } return } //resultCode = NonMonotonicValue //written = idx break } } // fix - 1 + 8 bytes extraSpace := timestamps.CalcRequiredSpace(measure.Timestamp) + values.CalcRequiredSpace(measure.Value) totalSpace := timestamps.Size() + values.Size() + extraSpace if totalSpace <= atree.DataPagePayloadSize { // накопичую timestamps.Append(measure.Timestamp) values.Append(measure.Value) } else { // сторінка заповнена // prevPageNo - виставляю в txlog, коли забираю номер сторінки із freeList або генерую новий dataPages = append(dataPages, atree.DataPayload{ Since: s.Since, Content: nil, TimestampsSize: timestamps.Size(), ValuesSize: values.Size(), }) // обнуляються буфери, але state незмінний timestamps.Renew() values.Renew() timestamps.Append(measure.Timestamp) values.Append(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, atree.IndexLevelTail{ Payload: tail.Payload, 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.Unlock() s.Timestamps.Unlock() // 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.IndexLevels = 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, } } }