func newDiskFrontier(i leveldb.Iterator) (d *diskFrontier, err error) { if !i.SeekToLast() || i.Key() == nil { return } lastKey, err := extractSampleKey(i) if err != nil { panic(err) } if !i.SeekToFirst() || i.Key() == nil { return } firstKey, err := extractSampleKey(i) if i.Key() == nil { return } if err != nil { panic(err) } d = &diskFrontier{} d.firstFingerprint = model.NewFingerprintFromRowKey(*firstKey.Fingerprint.Signature) d.firstSupertime = indexable.DecodeTime(firstKey.Timestamp) d.lastFingerprint = model.NewFingerprintFromRowKey(*lastKey.Fingerprint.Signature) d.lastSupertime = indexable.DecodeTime(lastKey.Timestamp) return }
func extractSampleValues(i leveldb.Iterator) (v *dto.SampleValueSeries, err error) { if i == nil { panic("nil iterator") } v = &dto.SampleValueSeries{} err = proto.Unmarshal(i.Value(), v) return }
func extractSampleKey(i leveldb.Iterator) (k *dto.SampleKey, err error) { if i == nil { panic("nil iterator") } k = &dto.SampleKey{} rawKey := i.Key() if rawKey == nil { panic("illegal condition; got nil key...") } err = proto.Unmarshal(rawKey, k) return }
// Apply implements the Processor interface. func (p *CompactionProcessor) Apply(sampleIterator leveldb.Iterator, samplesPersistence raw.Persistence, stopAt clientmodel.Timestamp, fingerprint *clientmodel.Fingerprint) (lastCurated clientmodel.Timestamp, err error) { var pendingBatch raw.Batch defer func() { if pendingBatch != nil { pendingBatch.Close() } }() var pendingMutations = 0 var pendingSamples metric.Values var unactedSamples metric.Values var lastTouchedTime clientmodel.Timestamp var keyDropped bool sampleKey, _ := p.sampleKeys.Get() defer p.sampleKeys.Give(sampleKey) sampleKeyDto, _ := p.dtoSampleKeys.Get() defer p.dtoSampleKeys.Give(sampleKeyDto) if err = sampleIterator.Key(sampleKeyDto); err != nil { return } sampleKey.Load(sampleKeyDto) unactedSamples = unmarshalValues(sampleIterator.RawValue(), nil) for lastCurated.Before(stopAt) && lastTouchedTime.Before(stopAt) && sampleKey.Fingerprint.Equal(fingerprint) { switch { // Furnish a new pending batch operation if none is available. case pendingBatch == nil: pendingBatch = leveldb.NewBatch() // If there are no sample values to extract from the datastore, let's // continue extracting more values to use. We know that the time.Before() // block would prevent us from going into unsafe territory. case len(unactedSamples) == 0: if !sampleIterator.Next() { return lastCurated, fmt.Errorf("illegal condition: invalid iterator on continuation") } keyDropped = false if err = sampleIterator.Key(sampleKeyDto); err != nil { return } sampleKey.Load(sampleKeyDto) if !sampleKey.Fingerprint.Equal(fingerprint) { break } unactedSamples = unmarshalValues(sampleIterator.RawValue(), nil) // If the number of pending mutations exceeds the allowed batch amount, // commit to disk and delete the batch. A new one will be recreated if // necessary. case pendingMutations >= p.maximumMutationPoolBatch: err = samplesPersistence.Commit(pendingBatch) if err != nil { return } pendingMutations = 0 pendingBatch.Close() pendingBatch = nil case len(pendingSamples) == 0 && len(unactedSamples) >= p.minimumGroupSize: lastTouchedTime = unactedSamples[len(unactedSamples)-1].Timestamp unactedSamples = metric.Values{} case len(pendingSamples)+len(unactedSamples) < p.minimumGroupSize: if !keyDropped { k := &dto.SampleKey{} sampleKey.Dump(k) pendingBatch.Drop(k) keyDropped = true } pendingSamples = append(pendingSamples, unactedSamples...) lastTouchedTime = unactedSamples[len(unactedSamples)-1].Timestamp unactedSamples = metric.Values{} pendingMutations++ // If the number of pending writes equals the target group size case len(pendingSamples) == p.minimumGroupSize: k := &dto.SampleKey{} newSampleKey := buildSampleKey(fingerprint, pendingSamples) newSampleKey.Dump(k) b := marshalValues(pendingSamples, nil) pendingBatch.PutRaw(k, b) pendingMutations++ lastCurated = newSampleKey.FirstTimestamp if len(unactedSamples) > 0 { if !keyDropped { sampleKey.Dump(k) pendingBatch.Drop(k) keyDropped = true } if len(unactedSamples) > p.minimumGroupSize { pendingSamples = unactedSamples[:p.minimumGroupSize] unactedSamples = unactedSamples[p.minimumGroupSize:] lastTouchedTime = unactedSamples[len(unactedSamples)-1].Timestamp } else { pendingSamples = unactedSamples lastTouchedTime = pendingSamples[len(pendingSamples)-1].Timestamp unactedSamples = metric.Values{} } } case len(pendingSamples)+len(unactedSamples) >= p.minimumGroupSize: if !keyDropped { k := &dto.SampleKey{} sampleKey.Dump(k) pendingBatch.Drop(k) keyDropped = true } remainder := p.minimumGroupSize - len(pendingSamples) pendingSamples = append(pendingSamples, unactedSamples[:remainder]...) unactedSamples = unactedSamples[remainder:] if len(unactedSamples) == 0 { lastTouchedTime = pendingSamples[len(pendingSamples)-1].Timestamp } else { lastTouchedTime = unactedSamples[len(unactedSamples)-1].Timestamp } pendingMutations++ default: err = fmt.Errorf("unhandled processing case") } } if len(unactedSamples) > 0 || len(pendingSamples) > 0 { pendingSamples = append(pendingSamples, unactedSamples...) k := &dto.SampleKey{} newSampleKey := buildSampleKey(fingerprint, pendingSamples) newSampleKey.Dump(k) b := marshalValues(pendingSamples, nil) pendingBatch.PutRaw(k, b) pendingSamples = metric.Values{} pendingMutations++ lastCurated = newSampleKey.FirstTimestamp } // This is not deferred due to the off-chance that a pre-existing commit // failed. if pendingBatch != nil && pendingMutations > 0 { err = samplesPersistence.Commit(pendingBatch) if err != nil { return } } return }
// Apply implements the Processor interface. func (p *DeletionProcessor) Apply(sampleIterator leveldb.Iterator, samplesPersistence raw.Persistence, stopAt clientmodel.Timestamp, fingerprint *clientmodel.Fingerprint) (lastCurated clientmodel.Timestamp, err error) { var pendingBatch raw.Batch defer func() { if pendingBatch != nil { pendingBatch.Close() } }() sampleKeyDto, _ := p.dtoSampleKeys.Get() defer p.dtoSampleKeys.Give(sampleKeyDto) sampleKey, _ := p.sampleKeys.Get() defer p.sampleKeys.Give(sampleKey) if err = sampleIterator.Key(sampleKeyDto); err != nil { return } sampleKey.Load(sampleKeyDto) sampleValues := unmarshalValues(sampleIterator.RawValue(), nil) pendingMutations := 0 for lastCurated.Before(stopAt) && sampleKey.Fingerprint.Equal(fingerprint) { switch { // Furnish a new pending batch operation if none is available. case pendingBatch == nil: pendingBatch = leveldb.NewBatch() // If there are no sample values to extract from the datastore, // let's continue extracting more values to use. We know that // the time.Before() block would prevent us from going into // unsafe territory. case len(sampleValues) == 0: if !sampleIterator.Next() { return lastCurated, fmt.Errorf("illegal condition: invalid iterator on continuation") } if err = sampleIterator.Key(sampleKeyDto); err != nil { return } sampleKey.Load(sampleKeyDto) sampleValues = unmarshalValues(sampleIterator.RawValue(), nil) // If the number of pending mutations exceeds the allowed batch // amount, commit to disk and delete the batch. A new one will // be recreated if necessary. case pendingMutations >= p.maximumMutationPoolBatch: err = samplesPersistence.Commit(pendingBatch) if err != nil { return } pendingMutations = 0 pendingBatch.Close() pendingBatch = nil case !sampleKey.MayContain(stopAt): k := &dto.SampleKey{} sampleKey.Dump(k) pendingBatch.Drop(k) lastCurated = sampleKey.LastTimestamp sampleValues = metric.Values{} pendingMutations++ case sampleKey.MayContain(stopAt): k := &dto.SampleKey{} sampleKey.Dump(k) pendingBatch.Drop(k) pendingMutations++ sampleValues = sampleValues.TruncateBefore(stopAt) if len(sampleValues) > 0 { k := &dto.SampleKey{} sampleKey = buildSampleKey(fingerprint, sampleValues) sampleKey.Dump(k) lastCurated = sampleKey.FirstTimestamp v := marshalValues(sampleValues, nil) pendingBatch.PutRaw(k, v) pendingMutations++ } else { lastCurated = sampleKey.LastTimestamp } default: err = fmt.Errorf("unhandled processing case") } } // This is not deferred due to the off-chance that a pre-existing commit // failed. if pendingBatch != nil && pendingMutations > 0 { err = samplesPersistence.Commit(pendingBatch) if err != nil { return } } return }
func (t *TieredStorage) loadChunkAroundTime( iterator leveldb.Iterator, fingerprint *clientmodel.Fingerprint, ts clientmodel.Timestamp, firstBlock, lastBlock *SampleKey, ) (chunk metric.Values, expired bool) { if fingerprint.Less(firstBlock.Fingerprint) { return nil, false } if lastBlock.Fingerprint.Less(fingerprint) { return nil, true } seekingKey, _ := t.sampleKeys.Get() defer t.sampleKeys.Give(seekingKey) seekingKey.Fingerprint = fingerprint if fingerprint.Equal(firstBlock.Fingerprint) && ts.Before(firstBlock.FirstTimestamp) { seekingKey.FirstTimestamp = firstBlock.FirstTimestamp } else if fingerprint.Equal(lastBlock.Fingerprint) && ts.After(lastBlock.FirstTimestamp) { seekingKey.FirstTimestamp = lastBlock.FirstTimestamp } else { seekingKey.FirstTimestamp = ts } dto, _ := t.dtoSampleKeys.Get() defer t.dtoSampleKeys.Give(dto) seekingKey.Dump(dto) if !iterator.Seek(dto) { return chunk, true } var foundValues metric.Values if err := iterator.Key(dto); err != nil { panic(err) } seekingKey.Load(dto) if seekingKey.Fingerprint.Equal(fingerprint) { // Figure out if we need to rewind by one block. // Imagine the following supertime blocks with time ranges: // // Block 1: ft 1000 - lt 1009 <data> // Block 1: ft 1010 - lt 1019 <data> // // If we are aiming to find time 1005, we would first seek to the block with // supertime 1010, then need to rewind by one block by virtue of LevelDB // iterator seek behavior. // // Only do the rewind if there is another chunk before this one. if !seekingKey.MayContain(ts) { postValues := unmarshalValues(iterator.RawValue(), nil) if !seekingKey.Equal(firstBlock) { if !iterator.Previous() { panic("This should never return false.") } if err := iterator.Key(dto); err != nil { panic(err) } seekingKey.Load(dto) if !seekingKey.Fingerprint.Equal(fingerprint) { return postValues, false } foundValues = unmarshalValues(iterator.RawValue(), nil) foundValues = append(foundValues, postValues...) return foundValues, false } } foundValues = unmarshalValues(iterator.RawValue(), nil) return foundValues, false } if fingerprint.Less(seekingKey.Fingerprint) { if !seekingKey.Equal(firstBlock) { if !iterator.Previous() { panic("This should never return false.") } if err := iterator.Key(dto); err != nil { panic(err) } seekingKey.Load(dto) if !seekingKey.Fingerprint.Equal(fingerprint) { return nil, false } foundValues = unmarshalValues(iterator.RawValue(), nil) return foundValues, false } } panic("illegal state: violated sort invariant") }
func (t *TieredStorage) renderView(viewJob viewJob) { // Telemetry. var err error begin := time.Now() defer func() { t.memorySemaphore <- true duration := time.Since(begin) recordOutcome( duration, err, map[string]string{operation: renderView, result: success}, map[string]string{operation: renderView, result: failure}, ) }() view := newView() var iterator leveldb.Iterator diskPresent := true firstBlock, _ := t.sampleKeys.Get() defer t.sampleKeys.Give(firstBlock) lastBlock, _ := t.sampleKeys.Get() defer t.sampleKeys.Give(lastBlock) sampleKeyDto, _ := t.dtoSampleKeys.Get() defer t.dtoSampleKeys.Give(sampleKeyDto) defer func() { // Give back all ops not yet popped. for viewJob.builder.HasOp() { giveBackOp(viewJob.builder.PopOp()) } }() extractionTimer := viewJob.stats.GetTimer(stats.ViewDataExtractionTime).Start() for viewJob.builder.HasOp() { op := viewJob.builder.PopOp() defer giveBackOp(op) fp := op.Fingerprint() old, err := t.seriesTooOld(fp, op.CurrentTime()) if err != nil { glog.Errorf("Error getting watermark from cache for %s: %s", fp, err) continue } if old { continue } memValues := t.memoryArena.CloneSamples(fp) for !op.Consumed() { // Abort the view rendering if the caller (makeView) has timed out. if len(viewJob.abort) > 0 { return } // Load data value chunk(s) around the current time. targetTime := op.CurrentTime() currentChunk := chunk{} // If we aimed before the oldest value in memory, load more data from disk. if (len(memValues) == 0 || memValues.FirstTimeAfter(targetTime)) && diskPresent { if iterator == nil { // Get a single iterator that will be used for all data extraction // below. iterator, _ = t.DiskStorage.MetricSamples.NewIterator(true) defer iterator.Close() if diskPresent = iterator.SeekToLast(); diskPresent { if err := iterator.Key(sampleKeyDto); err != nil { panic(err) } lastBlock.Load(sampleKeyDto) if !iterator.SeekToFirst() { diskPresent = false } else { if err := iterator.Key(sampleKeyDto); err != nil { panic(err) } firstBlock.Load(sampleKeyDto) } } } if diskPresent { diskTimer := viewJob.stats.GetTimer(stats.ViewDiskExtractionTime).Start() diskValues, expired := t.loadChunkAroundTime( iterator, fp, targetTime, firstBlock, lastBlock, ) if expired { diskPresent = false } diskTimer.Stop() // If we aimed past the newest value on disk, // combine it with the next value from memory. if len(diskValues) == 0 { currentChunk = chunk(memValues) } else { if len(memValues) > 0 && diskValues.LastTimeBefore(targetTime) { latestDiskValue := diskValues[len(diskValues)-1:] currentChunk = append(chunk(latestDiskValue), chunk(memValues)...) } else { currentChunk = chunk(diskValues) } } } else { currentChunk = chunk(memValues) } } else { currentChunk = chunk(memValues) } // There's no data at all for this fingerprint, so stop processing. if len(currentChunk) == 0 { break } currentChunk = currentChunk.TruncateBefore(targetTime) lastChunkTime := currentChunk[len(currentChunk)-1].Timestamp if lastChunkTime.After(targetTime) { targetTime = lastChunkTime } if op.CurrentTime().After(targetTime) { break } // Extract all needed data from the current chunk and append the // extracted samples to the materialized view. for !op.Consumed() && !op.CurrentTime().After(targetTime) { view.appendSamples(fp, op.ExtractSamples(metric.Values(currentChunk))) } } } extractionTimer.Stop() viewJob.output <- view return }
// newSeriesFrontier furnishes a populated diskFrontier for a given // fingerprint. A nil diskFrontier will be returned if the series cannot // be found in the store. func newSeriesFrontier(f model.Fingerprint, d diskFrontier, i leveldb.Iterator) (s *seriesFrontier, err error) { var ( lowerSeek = firstSupertime upperSeek = lastSupertime ) // If the diskFrontier for this iterator says that the candidate fingerprint // is outside of its seeking domain, there is no way that a seriesFrontier // could be materialized. Simply bail. if !d.ContainsFingerprint(f) { return } // If we are either the first or the last key in the database, we need to use // pessimistic boundary frontiers. if f.Equal(d.firstFingerprint) { lowerSeek = indexable.EncodeTime(d.firstSupertime) } if f.Equal(d.lastFingerprint) { upperSeek = indexable.EncodeTime(d.lastSupertime) } key := &dto.SampleKey{ Fingerprint: f.ToDTO(), Timestamp: upperSeek, } raw, err := coding.NewProtocolBuffer(key).Encode() if err != nil { panic(err) } i.Seek(raw) if i.Key() == nil { return } retrievedKey, err := extractSampleKey(i) if err != nil { panic(err) } retrievedFingerprint := model.NewFingerprintFromRowKey(*retrievedKey.Fingerprint.Signature) // The returned fingerprint may not match if the original seek key lives // outside of a metric's frontier. This is probable, for we are seeking to // to the maximum allowed time, which could advance us to the next // fingerprint. // // if !retrievedFingerprint.Equal(f) { i.Previous() retrievedKey, err = extractSampleKey(i) if err != nil { panic(err) } retrievedFingerprint := model.NewFingerprintFromRowKey(*retrievedKey.Fingerprint.Signature) // If the previous key does not match, we know that the requested // fingerprint does not live in the database. if !retrievedFingerprint.Equal(f) { return } } s = &seriesFrontier{ lastSupertime: indexable.DecodeTime(retrievedKey.Timestamp), lastTime: time.Unix(*retrievedKey.LastTimestamp, 0), } key.Timestamp = lowerSeek raw, err = coding.NewProtocolBuffer(key).Encode() if err != nil { panic(err) } i.Seek(raw) retrievedKey, err = extractSampleKey(i) if err != nil { panic(err) } retrievedFingerprint = model.NewFingerprintFromRowKey(*retrievedKey.Fingerprint.Signature) s.firstSupertime = indexable.DecodeTime(retrievedKey.Timestamp) return }
func (t *tieredStorage) loadChunkAroundTime(iterator leveldb.Iterator, frontier *seriesFrontier, fingerprint model.Fingerprint, ts time.Time) (chunk []model.SamplePair) { var ( targetKey = &dto.SampleKey{ Fingerprint: fingerprint.ToDTO(), } foundKey = &dto.SampleKey{} foundValue *dto.SampleValueSeries ) // Limit the target key to be within the series' keyspace. if ts.After(frontier.lastSupertime) { targetKey.Timestamp = indexable.EncodeTime(frontier.lastSupertime) } else { targetKey.Timestamp = indexable.EncodeTime(ts) } // Try seeking to target key. rawKey, _ := coding.NewProtocolBuffer(targetKey).Encode() iterator.Seek(rawKey) foundKey, err := extractSampleKey(iterator) if err != nil { panic(err) } // Figure out if we need to rewind by one block. // Imagine the following supertime blocks with time ranges: // // Block 1: ft 1000 - lt 1009 <data> // Block 1: ft 1010 - lt 1019 <data> // // If we are aiming to find time 1005, we would first seek to the block with // supertime 1010, then need to rewind by one block by virtue of LevelDB // iterator seek behavior. // // Only do the rewind if there is another chunk before this one. rewound := false firstTime := indexable.DecodeTime(foundKey.Timestamp) if ts.Before(firstTime) && !frontier.firstSupertime.After(ts) { iterator.Previous() rewound = true } foundValue, err = extractSampleValues(iterator) if err != nil { panic(err) } // If we rewound, but the target time is still past the current block, return // the last value of the current (rewound) block and the entire next block. if rewound { foundKey, err = extractSampleKey(iterator) if err != nil { panic(err) } currentChunkLastTime := time.Unix(*foundKey.LastTimestamp, 0) if ts.After(currentChunkLastTime) { sampleCount := len(foundValue.Value) chunk = append(chunk, model.SamplePair{ Timestamp: time.Unix(*foundValue.Value[sampleCount-1].Timestamp, 0), Value: model.SampleValue(*foundValue.Value[sampleCount-1].Value), }) // We know there's a next block since we have rewound from it. iterator.Next() foundValue, err = extractSampleValues(iterator) if err != nil { panic(err) } } } // Now append all the samples of the currently seeked block to the output. for _, sample := range foundValue.Value { chunk = append(chunk, model.SamplePair{ Timestamp: time.Unix(*sample.Timestamp, 0), Value: model.SampleValue(*sample.Value), }) } return }