mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-18 14:40:26 +01:00
78121642df
The main change is getting rid of interning of sample key. It was discovered that for cases with many unique time series aggregated by vmagent interned keys could grow up to hundreds of millions of objects. This has negative impact on the following aspects: 1. It slows down garbage collection cycles, as GC has to scan all inuse objects periodically. The higher is the number of inuse objects, the longer it takes/the more CPU it takes. 2. It slows down the hot path of samples aggregation where each key needs to be looked up in the map first. The change makes code more fragile, but suppose to provide performance optimization for heavy-loaded vmagents with stream aggregation enabled. --------- Signed-off-by: hagen1778 <roman@victoriametrics.com> Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
98 lines
2.3 KiB
Go
98 lines
2.3 KiB
Go
package streamaggr
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
|
"github.com/valyala/histogram"
|
|
)
|
|
|
|
// quantilesAggrState calculates output=quantiles, e.g. the the given quantiles over the input samples.
|
|
type quantilesAggrState struct {
|
|
m sync.Map
|
|
|
|
phis []float64
|
|
}
|
|
|
|
type quantilesStateValue struct {
|
|
mu sync.Mutex
|
|
h *histogram.Fast
|
|
deleted bool
|
|
}
|
|
|
|
func newQuantilesAggrState(phis []float64) *quantilesAggrState {
|
|
return &quantilesAggrState{
|
|
phis: phis,
|
|
}
|
|
}
|
|
|
|
func (as *quantilesAggrState) pushSamples(samples []pushSample) {
|
|
for i := range samples {
|
|
s := &samples[i]
|
|
outputKey := getOutputKey(s.key)
|
|
|
|
again:
|
|
v, ok := as.m.Load(outputKey)
|
|
if !ok {
|
|
// The entry is missing in the map. Try creating it.
|
|
h := histogram.GetFast()
|
|
v = &quantilesStateValue{
|
|
h: h,
|
|
}
|
|
vNew, loaded := as.m.LoadOrStore(strings.Clone(outputKey), v)
|
|
if loaded {
|
|
// Use the entry created by a concurrent goroutine.
|
|
histogram.PutFast(h)
|
|
v = vNew
|
|
}
|
|
}
|
|
sv := v.(*quantilesStateValue)
|
|
sv.mu.Lock()
|
|
deleted := sv.deleted
|
|
if !deleted {
|
|
sv.h.Update(s.value)
|
|
}
|
|
sv.mu.Unlock()
|
|
if deleted {
|
|
// The entry has been deleted by the concurrent call to flushState
|
|
// Try obtaining and updating the entry again.
|
|
goto again
|
|
}
|
|
}
|
|
}
|
|
|
|
func (as *quantilesAggrState) flushState(ctx *flushCtx, resetState bool) {
|
|
currentTimeMsec := int64(fasttime.UnixTimestamp()) * 1000
|
|
m := &as.m
|
|
phis := as.phis
|
|
var quantiles []float64
|
|
var b []byte
|
|
m.Range(func(k, v interface{}) bool {
|
|
if resetState {
|
|
// Atomically delete the entry from the map, so new entry is created for the next flush.
|
|
m.Delete(k)
|
|
}
|
|
|
|
sv := v.(*quantilesStateValue)
|
|
sv.mu.Lock()
|
|
quantiles = sv.h.Quantiles(quantiles[:0], phis)
|
|
histogram.PutFast(sv.h)
|
|
if resetState {
|
|
// Mark the entry as deleted, so it won't be updated anymore by concurrent pushSample() calls.
|
|
sv.deleted = true
|
|
}
|
|
sv.mu.Unlock()
|
|
|
|
key := k.(string)
|
|
for i, quantile := range quantiles {
|
|
b = strconv.AppendFloat(b[:0], phis[i], 'g', -1, 64)
|
|
phiStr := bytesutil.InternBytes(b)
|
|
ctx.appendSeriesWithExtraLabel(key, "quantiles", currentTimeMsec, quantile, "quantile", phiStr)
|
|
}
|
|
return true
|
|
})
|
|
}
|