package database import ( "gordenko.dev/dima/qb" "gordenko.dev/dima/qb/atree" "gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb/txlog" ) // METRIC type IndexRec struct { Since uint32 PageNo uint32 } type _metric struct { MetricType qb.MetricType FracDigits byte LastPageNo uint32 SinceValue float64 Since uint32 UntilValue float64 Until uint32 Timestamps qb.TimestampCompressor Values qb.ValueCompressor // fix - add index levels XLock bool RLocks int WaitQueue []any //IndexLevels [][]IndexRec // root - last element IndexLevels [][]byte // 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.IndexLevel dataPages []*atree.DataPage //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.DataPage{ PrevPageNo: 0, // ? LowerTimestamp: s.Since, Timestamps: timestamps.Chunks(), TimestampsSize: timestamps.Size(), Values: values.Chunks(), 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 _, unfilled := range s.IndexLevels { indexLevels = append(indexLevels, &atree.IndexLevel{ Offset: len(unfilled), LowerTimestamp: bin.GetUint32(unfilled), Data: unfilled, }) } } else { // короткий шлях - запис лише в txlog } sendToStorage(txlog.AppendedMeasures{ MetricID: req.MetricID, TimestampsOffset: timestamps.Offset(), // state.Pos() з якої позиції дописувати дані на сторінку 0 (при відновленні) ValuesOffset: values.Offset(), // state.Pos() Timestamps: timestamps.Chunks(), TimestampsSize: timestamps.Size(), Values: values.Chunks(), ValuesSize: values.Size(), IndexLevels: indexLevels, DataPages: dataPages, }) } 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, } } }