// UpdateBatch implements HighWatermarker. func (w *LevelDBHighWatermarker) UpdateBatch(m FingerprintHighWatermarkMapping) error { batch := leveldb.NewBatch() defer batch.Close() for fp, t := range m { existing, present, err := w.Get(&fp) if err != nil { return err } k := &dto.Fingerprint{} dumpFingerprint(k, &fp) v := &dto.MetricHighWatermark{} if !present { v.Timestamp = proto.Int64(t.Unix()) batch.Put(k, v) continue } // BUG(matt): Replace this with watermark management. if t.After(existing) { v.Timestamp = proto.Int64(t.Unix()) batch.Put(k, v) } } return w.LevelDBPersistence.Commit(batch) }
// IndexBatch implements MetricMembershipIndex. func (i *LevelDBMetricMembershipIndex) IndexBatch(b FingerprintMetricMapping) error { batch := leveldb.NewBatch() defer batch.Close() for _, m := range b { k := &dto.Metric{} dumpMetric(k, m) batch.Put(k, existenceIdentity) } return i.LevelDBPersistence.Commit(batch) }
func (l *LevelDBMetricPersistence) refreshHighWatermarks(groups map[model.Fingerprint]model.Samples) (err error) { begin := time.Now() defer func() { duration := time.Since(begin) recordOutcome(duration, err, map[string]string{operation: refreshHighWatermarks, result: success}, map[string]string{operation: refreshHighWatermarks, result: failure}) }() batch := leveldb.NewBatch() defer batch.Close() var ( mutationCount = 0 ) for fingerprint, samples := range groups { var ( key = &dto.Fingerprint{} value = &dto.MetricHighWatermark{} raw []byte newestSampleTimestamp = samples[len(samples)-1].Timestamp keyEncoded = coding.NewProtocolBuffer(key) ) key.Signature = proto.String(fingerprint.ToRowKey()) raw, err = l.metricHighWatermarks.Get(keyEncoded) if err != nil { panic(err) return } if raw != nil { err = proto.Unmarshal(raw, value) if err != nil { panic(err) continue } if newestSampleTimestamp.Before(time.Unix(*value.Timestamp, 0)) { continue } } value.Timestamp = proto.Int64(newestSampleTimestamp.Unix()) batch.Put(keyEncoded, coding.NewProtocolBuffer(value)) mutationCount++ } err = l.metricHighWatermarks.Commit(batch) if err != nil { panic(err) } return }
// IndexBatch implements FingerprintMetricIndex. func (i *LevelDBFingerprintMetricIndex) IndexBatch(mapping FingerprintMetricMapping) error { b := leveldb.NewBatch() defer b.Close() for f, m := range mapping { k := &dto.Fingerprint{} dumpFingerprint(k, &f) v := &dto.Metric{} dumpMetric(v, m) b.Put(k, v) } return i.LevelDBPersistence.Commit(b) }
// IndexBatch implements LabelNameLabelValuesIndex. func (i *LevelDBLabelNameLabelValuesIndex) IndexBatch(b LabelNameLabelValuesMapping) error { batch := leveldb.NewBatch() defer batch.Close() for labelName, labelValues := range b { sort.Sort(labelValues) key := &dto.LabelName{ Name: proto.String(string(labelName)), } value := &dto.LabelValueCollection{} value.Member = make([]string, 0, len(labelValues)) for _, labelValue := range labelValues { value.Member = append(value.Member, string(labelValue)) } batch.Put(key, value) } return i.LevelDBPersistence.Commit(batch) }
// IndexBatch implements LabelPairFingerprintMapping. func (i *LevelDBLabelPairFingerprintIndex) IndexBatch(m LabelPairFingerprintMapping) error { batch := leveldb.NewBatch() defer batch.Close() for pair, fps := range m { sort.Sort(fps) key := &dto.LabelPair{ Name: proto.String(string(pair.Name)), Value: proto.String(string(pair.Value)), } value := &dto.FingerprintCollection{} for _, fp := range fps { f := &dto.Fingerprint{} dumpFingerprint(f, fp) value.Member = append(value.Member, f) } batch.Put(key, value) } return i.LevelDBPersistence.Commit(batch) }
// indexFingerprints updates all of the Fingerprint to Metric reverse lookups // in the index and then bulk updates. // // This operation is idempotent. func (l *LevelDBMetricPersistence) indexFingerprints(metrics map[model.Fingerprint]model.Metric) (err error) { begin := time.Now() defer func() { duration := time.Since(begin) recordOutcome(duration, err, map[string]string{operation: indexFingerprints, result: success}, map[string]string{operation: indexFingerprints, result: failure}) }() batch := leveldb.NewBatch() defer batch.Close() for fingerprint, metric := range metrics { key := coding.NewProtocolBuffer(fingerprint.ToDTO()) value := coding.NewProtocolBuffer(model.MetricToDTO(metric)) batch.Put(key, value) } err = l.fingerprintToMetrics.Commit(batch) if err != nil { panic(err) } 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 (l *LevelDBMetricPersistence) AppendSamples(samples model.Samples) (err error) { begin := time.Now() defer func() { duration := time.Since(begin) recordOutcome(duration, err, map[string]string{operation: appendSamples, result: success}, map[string]string{operation: appendSamples, result: failure}) }() var ( fingerprintToSamples = groupByFingerprint(samples) indexErrChan = make(chan error) watermarkErrChan = make(chan error) ) go func(groups map[model.Fingerprint]model.Samples) { var ( metrics = map[model.Fingerprint]model.Metric{} ) for fingerprint, samples := range groups { metrics[fingerprint] = samples[0].Metric } indexErrChan <- l.indexMetrics(metrics) }(fingerprintToSamples) go func(groups map[model.Fingerprint]model.Samples) { watermarkErrChan <- l.refreshHighWatermarks(groups) }(fingerprintToSamples) samplesBatch := leveldb.NewBatch() defer samplesBatch.Close() for fingerprint, group := range fingerprintToSamples { for { lengthOfGroup := len(group) if lengthOfGroup == 0 { break } take := *leveldbChunkSize if lengthOfGroup < take { take = lengthOfGroup } chunk := group[0:take] group = group[take:lengthOfGroup] key := &dto.SampleKey{ Fingerprint: fingerprint.ToDTO(), Timestamp: indexable.EncodeTime(chunk[0].Timestamp), LastTimestamp: proto.Int64(chunk[take-1].Timestamp.Unix()), SampleCount: proto.Uint32(uint32(take)), } value := &dto.SampleValueSeries{} for _, sample := range chunk { value.Value = append(value.Value, &dto.SampleValueSeries_Value{ Timestamp: proto.Int64(sample.Timestamp.Unix()), Value: proto.Float32(float32(sample.Value)), }) } samplesBatch.Put(coding.NewProtocolBuffer(key), coding.NewProtocolBuffer(value)) } } err = l.metricSamples.Commit(samplesBatch) if err != nil { panic(err) } err = <-indexErrChan if err != nil { panic(err) } err = <-watermarkErrChan if err != nil { panic(err) } return }
// indexMetrics takes groups of samples, determines which ones contain metrics // that are unknown to the storage stack, and then proceeds to update all // affected indices. func (l *LevelDBMetricPersistence) indexMetrics(fingerprints map[model.Fingerprint]model.Metric) (err error) { begin := time.Now() defer func() { duration := time.Since(begin) recordOutcome(duration, err, map[string]string{operation: indexMetrics, result: success}, map[string]string{operation: indexMetrics, result: failure}) }() var ( absentMetrics map[model.Fingerprint]model.Metric ) absentMetrics, err = l.findUnindexedMetrics(fingerprints) if err != nil { panic(err) } if len(absentMetrics) == 0 { return } // TODO: For the missing fingerprints, determine what label names and pairs // are absent and act accordingly and append fingerprints. var ( doneBuildingLabelNameIndex = make(chan error) doneBuildingLabelPairIndex = make(chan error) doneBuildingFingerprintIndex = make(chan error) ) go func() { doneBuildingLabelNameIndex <- l.indexLabelNames(absentMetrics) }() go func() { doneBuildingLabelPairIndex <- l.indexLabelPairs(absentMetrics) }() go func() { doneBuildingFingerprintIndex <- l.indexFingerprints(absentMetrics) }() makeTopLevelIndex := true err = <-doneBuildingLabelNameIndex if err != nil { panic(err) makeTopLevelIndex = false } err = <-doneBuildingLabelPairIndex if err != nil { panic(err) makeTopLevelIndex = false } err = <-doneBuildingFingerprintIndex if err != nil { panic(err) makeTopLevelIndex = false } // If any of the preceding operations failed, we will have inconsistent // indices. Thusly, the Metric membership index should NOT be updated, as // its state is used to determine whether to bulk update the other indices. // Given that those operations are idempotent, it is OK to repeat them; // however, it will consume considerable amounts of time. if makeTopLevelIndex { batch := leveldb.NewBatch() defer batch.Close() // WART: We should probably encode simple fingerprints. for _, metric := range absentMetrics { key := coding.NewProtocolBuffer(model.MetricToDTO(metric)) batch.Put(key, key) } err := l.metricMembershipIndex.Commit(batch) if err != nil { panic(err) // Not critical. log.Println(err) } } return }
// indexLabelPairs accumulates all label pair to fingerprint index entries for // the dirty metrics, appends the new dirtied metrics, sorts, and bulk updates // the index to reflect the new state. // // This operation is idempotent. func (l *LevelDBMetricPersistence) indexLabelPairs(metrics map[model.Fingerprint]model.Metric) (err error) { begin := time.Now() defer func() { duration := time.Since(begin) recordOutcome(duration, err, map[string]string{operation: indexLabelPairs, result: success}, map[string]string{operation: indexLabelPairs, result: failure}) }() labelPairFingerprints := map[model.LabelPair]utility.Set{} for fingerprint, metric := range metrics { for labelName, labelValue := range metric { labelPair := model.LabelPair{ Name: labelName, Value: labelValue, } fingerprintSet, ok := labelPairFingerprints[labelPair] if !ok { fingerprintSet = utility.Set{} fingerprints, err := l.GetFingerprintsForLabelSet(model.LabelSet{ labelName: labelValue, }) if err != nil { panic(err) return err } for _, fingerprint := range fingerprints { fingerprintSet.Add(fingerprint) } } fingerprintSet.Add(fingerprint) labelPairFingerprints[labelPair] = fingerprintSet } } batch := leveldb.NewBatch() defer batch.Close() for labelPair, fingerprintSet := range labelPairFingerprints { fingerprints := model.Fingerprints{} for fingerprint := range fingerprintSet { fingerprints = append(fingerprints, fingerprint.(model.Fingerprint)) } sort.Sort(fingerprints) key := &dto.LabelPair{ Name: proto.String(string(labelPair.Name)), Value: proto.String(string(labelPair.Value)), } value := &dto.FingerprintCollection{} for _, fingerprint := range fingerprints { value.Member = append(value.Member, fingerprint.ToDTO()) } batch.Put(coding.NewProtocolBuffer(key), coding.NewProtocolBuffer(value)) } err = l.labelSetToFingerprints.Commit(batch) if err != nil { panic(err) return } return }