Skip to content

MQE improve memory consumption estimate with Label information #11203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
34 changes: 19 additions & 15 deletions pkg/streamingpromql/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1505,7 +1505,7 @@ func TestMemoryConsumptionLimit_SingleQueries(t *testing.T) {

// At peak, we'll hold all the output samples plus one series, which has one sample.
// The output contains five samples, which will be rounded up to 8 (the nearest power of two).
instantQueryExpectedPeak: types.FPointSize + 8*types.VectorSampleSize,
instantQueryExpectedPeak: types.FPointSize + 8*(types.VectorSampleSize+types.LabelPairEstimatedSize),
instantQueryLimit: 0,
},
"limit enabled, but query does not exceed limit": {
Expand All @@ -1518,8 +1518,8 @@ func TestMemoryConsumptionLimit_SingleQueries(t *testing.T) {

// At peak, we'll hold all the output samples plus one series, which has one sample.
// The output contains five samples, which will be rounded up to 8 (the nearest power of two).
instantQueryExpectedPeak: types.FPointSize + 8*types.VectorSampleSize,
instantQueryLimit: 1000,
instantQueryExpectedPeak: types.FPointSize + 8*(types.VectorSampleSize+types.LabelPairEstimatedSize),
instantQueryLimit: types.FPointSize + 8*(types.VectorSampleSize+types.LabelPairEstimatedSize),
},
"limit enabled, and query exceeds limit": {
expr: "some_metric",
Expand Down Expand Up @@ -1548,9 +1548,10 @@ func TestMemoryConsumptionLimit_SingleQueries(t *testing.T) {
// At peak we'll hold in memory:
// - the running total for the sum() (two floats and a bool),
// - the next series from the selector,
// - and the output sample.
instantQueryExpectedPeak: 2*types.Float64Size + types.BoolSize + types.FPointSize + types.VectorSampleSize,
instantQueryLimit: 2*types.Float64Size + types.BoolSize + types.FPointSize + types.VectorSampleSize,
// - the output sample,
// - and the sample labels.
instantQueryExpectedPeak: 2*types.Float64Size + types.BoolSize + types.FPointSize + types.VectorSampleSize + types.LabelPairEstimatedSize,
instantQueryLimit: 2*types.Float64Size + types.BoolSize + types.FPointSize + types.VectorSampleSize + types.LabelPairEstimatedSize,
},
"limit enabled, query selects more samples than limit but should not load all of them into memory at once, and peak consumption is over limit": {
expr: "sum(some_metric)",
Expand All @@ -1568,10 +1569,11 @@ func TestMemoryConsumptionLimit_SingleQueries(t *testing.T) {
// At peak we'll hold in memory:
// - the running total for the sum() (two floats and a bool),
// - the next series from the selector,
// - and the output sample.
// - the output sample,
// - and the sample labels.
// The last thing to be allocated is the bool slice for the running total, so that won't contribute to the peak before the query is aborted.
instantQueryExpectedPeak: 2*types.Float64Size + types.FPointSize + types.VectorSampleSize,
instantQueryLimit: 2*types.Float64Size + types.BoolSize + types.FPointSize + types.VectorSampleSize - 1,
instantQueryExpectedPeak: 2*types.Float64Size + types.FPointSize + types.VectorSampleSize + types.LabelPairEstimatedSize,
instantQueryLimit: 2*types.Float64Size + types.BoolSize + types.FPointSize + types.VectorSampleSize + types.LabelPairEstimatedSize - 1,
},
"histogram: limit enabled, but query does not exceed limit": {
expr: "sum(some_histogram)",
Expand All @@ -1587,9 +1589,10 @@ func TestMemoryConsumptionLimit_SingleQueries(t *testing.T) {
// At peak we'll hold in memory:
// - the running total for the sum() (a histogram pointer),
// - the next series from the selector,
// - and the output sample.
instantQueryExpectedPeak: types.HistogramPointerSize + types.HPointSize + types.VectorSampleSize,
instantQueryLimit: types.HistogramPointerSize + types.HPointSize + types.VectorSampleSize,
// - the output sample,
// - and the sample labels.
instantQueryExpectedPeak: types.HistogramPointerSize + types.HPointSize + types.VectorSampleSize + types.LabelPairEstimatedSize,
instantQueryLimit: types.HistogramPointerSize + types.HPointSize + types.VectorSampleSize + types.LabelPairEstimatedSize,
},
"histogram: limit enabled, and query exceeds limit": {
expr: "sum(some_histogram)",
Expand All @@ -1606,10 +1609,11 @@ func TestMemoryConsumptionLimit_SingleQueries(t *testing.T) {
// At peak we'll hold in memory:
// - the running total for the sum() (a histogram pointer),
// - the next series from the selector,
// - and the output sample.
// - the output sample,
// - and the sample labels.
// The last thing to be allocated is the HistogramPointerSize slice for the running total, so that won't contribute to the peak before the query is aborted.
instantQueryExpectedPeak: types.HPointSize + types.VectorSampleSize,
instantQueryLimit: types.HistogramPointerSize + types.HPointSize + types.VectorSampleSize - 1,
instantQueryExpectedPeak: types.HPointSize + types.VectorSampleSize + types.LabelPairEstimatedSize,
instantQueryLimit: types.HistogramPointerSize + types.HPointSize + types.VectorSampleSize + types.LabelPairEstimatedSize - 1,
},
}

Expand Down
24 changes: 23 additions & 1 deletion pkg/streamingpromql/types/limiting_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"unsafe"

"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"

"github.com/grafana/mimir/pkg/streamingpromql/limiting"
Expand All @@ -24,6 +25,15 @@ const (
// (5 * 8 bytes). Some FloatHistograms will be bigger than this and some will be smaller.
nativeHistogramEstimatedSize = 288

// Estimated size of a label pair (name + value) in bytes.
// This is a conservative estimate that includes:
// - The size of Label struct
// - Average name length (8 bytes)
// - Average value length (8 bytes)
// - Average total of labels per series (10)
// The last 3 averages value are taken based on guesstimate.
LabelPairEstimatedSize = (uint64(unsafe.Sizeof(labels.Label{})) + 8 + 8) * 10

FPointSize = uint64(unsafe.Sizeof(promql.FPoint{}))
HPointSize = uint64(unsafe.Sizeof(promql.HPoint{}) + nativeHistogramEstimatedSize)
VectorSampleSize = uint64(unsafe.Sizeof(promql.Sample{})) // This assumes each sample is a float sample, not a histogram.
Expand Down Expand Up @@ -176,6 +186,11 @@ func (p *LimitingBucketedPool[S, E]) Get(size int, tracker *limiting.MemoryConsu
// - there's no guarantee the slice will have size 'size' when it's returned to us in putWithElementSize, so using 'size' would make the accounting below impossible
estimatedBytes := uint64(cap(s)) * p.elementSize

// Series labels must be estimated only for VectorPool.
if _, isVector := any(s).(promql.Vector); isVector {
estimatedBytes += uint64(cap(s)) * LabelPairEstimatedSize
}

if err := tracker.IncreaseMemoryConsumption(estimatedBytes); err != nil {
p.inner.Put(s)
return nil, err
Expand All @@ -200,7 +215,14 @@ func (p *LimitingBucketedPool[S, E]) Put(s S, tracker *limiting.MemoryConsumptio
}
}

tracker.DecreaseMemoryConsumption(uint64(cap(s)) * p.elementSize)
estimatedBytes := uint64(cap(s)) * p.elementSize

// Series labels must be estimated only for VectorPool.
if _, isVector := any(s).(promql.Vector); isVector {
estimatedBytes += uint64(cap(s)) * LabelPairEstimatedSize
}

tracker.DecreaseMemoryConsumption(estimatedBytes)
p.inner.Put(s)
}

Expand Down
Loading