mirror of https://github.com/grafana/loki
feat: improve performance of `first_over_time` and `last_over_time` queries by sharding them (#11605)
Signed-off-by: Callum Styan <callumstyan@gmail.com> Co-authored-by: Callum Styan <callumstyan@gmail.com>pull/12958/head
parent
35e10d4004
commit
f66172eed1
@ -0,0 +1,270 @@ |
||||
package logql |
||||
|
||||
import ( |
||||
"math" |
||||
"time" |
||||
|
||||
"github.com/prometheus/prometheus/model/labels" |
||||
"github.com/prometheus/prometheus/promql" |
||||
|
||||
"github.com/grafana/loki/v3/pkg/iter" |
||||
) |
||||
|
||||
// newFirstWithTimestampIterator returns an iterator the returns the first value
|
||||
// of a windowed aggregation.
|
||||
func newFirstWithTimestampIterator( |
||||
it iter.PeekingSampleIterator, |
||||
selRange, step, start, end, offset int64) RangeVectorIterator { |
||||
inner := &batchRangeVectorIterator{ |
||||
iter: it, |
||||
step: step, |
||||
end: end, |
||||
selRange: selRange, |
||||
metrics: map[string]labels.Labels{}, |
||||
window: map[string]*promql.Series{}, |
||||
agg: nil, |
||||
current: start - step, // first loop iteration will set it to start
|
||||
offset: offset, |
||||
} |
||||
return &firstWithTimestampBatchRangeVectorIterator{ |
||||
batchRangeVectorIterator: inner, |
||||
} |
||||
} |
||||
|
||||
type firstWithTimestampBatchRangeVectorIterator struct { |
||||
*batchRangeVectorIterator |
||||
at []promql.Sample |
||||
} |
||||
|
||||
// At aggregates the underlying window by picking the first sample with its
|
||||
// timestamp.
|
||||
func (r *firstWithTimestampBatchRangeVectorIterator) At() (int64, StepResult) { |
||||
if r.at == nil { |
||||
r.at = make([]promql.Sample, 0, len(r.window)) |
||||
} |
||||
r.at = r.at[:0] |
||||
// convert ts from nano to milli seconds as the iterator work with nanoseconds
|
||||
ts := r.current/1e+6 + r.offset/1e+6 |
||||
for _, series := range r.window { |
||||
s := r.agg(series.Floats) |
||||
r.at = append(r.at, promql.Sample{ |
||||
F: s.F, |
||||
T: s.T / int64(time.Millisecond), |
||||
Metric: series.Metric, |
||||
}) |
||||
} |
||||
return ts, SampleVector(r.at) |
||||
} |
||||
|
||||
// agg returns the first sample with its timestamp. The input is assumed to be
|
||||
// in order.
|
||||
func (r *firstWithTimestampBatchRangeVectorIterator) agg(samples []promql.FPoint) promql.FPoint { |
||||
if len(samples) == 0 { |
||||
return promql.FPoint{F: math.NaN(), T: 0} |
||||
} |
||||
return samples[0] |
||||
} |
||||
|
||||
func newLastWithTimestampIterator( |
||||
it iter.PeekingSampleIterator, |
||||
selRange, step, start, end, offset int64) RangeVectorIterator { |
||||
inner := &batchRangeVectorIterator{ |
||||
iter: it, |
||||
step: step, |
||||
end: end, |
||||
selRange: selRange, |
||||
metrics: map[string]labels.Labels{}, |
||||
window: map[string]*promql.Series{}, |
||||
agg: nil, |
||||
current: start - step, // first loop iteration will set it to start
|
||||
offset: offset, |
||||
} |
||||
return &lastWithTimestampBatchRangeVectorIterator{ |
||||
batchRangeVectorIterator: inner, |
||||
} |
||||
} |
||||
|
||||
// lastWithTimestampBatchRangeVectorIterator returns an iterator that returns the
|
||||
// last point in a windowed aggregation.
|
||||
type lastWithTimestampBatchRangeVectorIterator struct { |
||||
*batchRangeVectorIterator |
||||
at []promql.Sample |
||||
} |
||||
|
||||
// At aggregates the underlying window by picking the last sample with its
|
||||
// timestamp.
|
||||
func (r *lastWithTimestampBatchRangeVectorIterator) At() (int64, StepResult) { |
||||
if r.at == nil { |
||||
r.at = make([]promql.Sample, 0, len(r.window)) |
||||
} |
||||
r.at = r.at[:0] |
||||
// convert ts from nano to milli seconds as the iterator work with nanoseconds
|
||||
ts := r.current/1e+6 + r.offset/1e+6 |
||||
for _, series := range r.window { |
||||
s := r.agg(series.Floats) |
||||
r.at = append(r.at, promql.Sample{ |
||||
F: s.F, |
||||
T: s.T / int64(time.Millisecond), |
||||
Metric: series.Metric, |
||||
}) |
||||
} |
||||
return ts, SampleVector(r.at) |
||||
} |
||||
|
||||
// agg returns the last sample with its timestamp. The input is assumed to be
|
||||
// in order.
|
||||
func (r *lastWithTimestampBatchRangeVectorIterator) agg(samples []promql.FPoint) promql.FPoint { |
||||
if len(samples) == 0 { |
||||
return promql.FPoint{F: math.NaN(), T: 0} |
||||
} |
||||
return samples[len(samples)-1] |
||||
} |
||||
|
||||
type mergeOverTimeStepEvaluator struct { |
||||
start, end, ts time.Time |
||||
step time.Duration |
||||
matrices []promql.Matrix |
||||
merge func(promql.Vector, int, int, promql.Series) promql.Vector |
||||
} |
||||
|
||||
// Next returns the first or last element within one step of each matrix.
|
||||
func (e *mergeOverTimeStepEvaluator) Next() (bool, int64, StepResult) { |
||||
|
||||
var ( |
||||
vec promql.Vector |
||||
) |
||||
|
||||
e.ts = e.ts.Add(e.step) |
||||
if e.ts.After(e.end) { |
||||
return false, 0, nil |
||||
} |
||||
ts := e.ts.UnixNano() / int64(time.Millisecond) |
||||
|
||||
// Merge other results
|
||||
for i, m := range e.matrices { |
||||
for j, series := range m { |
||||
|
||||
if len(series.Floats) == 0 || !e.inRange(series.Floats[0].T, ts) { |
||||
continue |
||||
} |
||||
|
||||
vec = e.merge(vec, j, len(m), series) |
||||
e.pop(i, j) |
||||
} |
||||
} |
||||
|
||||
// Align vector timestamps with step
|
||||
for i := range vec { |
||||
vec[i].T = ts |
||||
} |
||||
|
||||
if len(vec) == 0 { |
||||
return e.hasNext(), ts, SampleVector(vec) |
||||
} |
||||
|
||||
return true, ts, SampleVector(vec) |
||||
} |
||||
|
||||
// pop drops the float of the s'th series in the r'th matrix.
|
||||
func (e *mergeOverTimeStepEvaluator) pop(r, s int) { |
||||
if len(e.matrices[r][s].Floats) <= 1 { |
||||
e.matrices[r][s].Floats = nil |
||||
return |
||||
} |
||||
e.matrices[r][s].Floats = e.matrices[r][s].Floats[1:] |
||||
} |
||||
|
||||
// inRange returns true if t is in step range of ts.
|
||||
func (e *mergeOverTimeStepEvaluator) inRange(t, ts int64) bool { |
||||
return (ts-e.step.Milliseconds()) <= t && t < ts |
||||
} |
||||
|
||||
func (e *mergeOverTimeStepEvaluator) hasNext() bool { |
||||
for _, m := range e.matrices { |
||||
for _, s := range m { |
||||
if len(s.Floats) != 0 { |
||||
return true |
||||
} |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
func (*mergeOverTimeStepEvaluator) Close() error { return nil } |
||||
|
||||
func (*mergeOverTimeStepEvaluator) Error() error { return nil } |
||||
|
||||
func NewMergeFirstOverTimeStepEvaluator(params Params, m []promql.Matrix) StepEvaluator { |
||||
if len(m) == 0 { |
||||
return EmptyEvaluator[SampleVector]{} |
||||
} |
||||
|
||||
var ( |
||||
start = params.Start() |
||||
end = params.End() |
||||
step = params.Step() |
||||
) |
||||
|
||||
return &mergeOverTimeStepEvaluator{ |
||||
start: start, |
||||
end: end, |
||||
ts: start.Add(-step), // will be corrected on first Next() call
|
||||
step: step, |
||||
matrices: m, |
||||
merge: mergeFirstOverTime, |
||||
} |
||||
} |
||||
|
||||
// mergeFirstOverTime selects the first sample by timestamp of each series.
|
||||
func mergeFirstOverTime(vec promql.Vector, pos int, nSeries int, series promql.Series) promql.Vector { |
||||
if len(vec) < nSeries { |
||||
return append(vec, promql.Sample{ |
||||
Metric: series.Metric, |
||||
T: series.Floats[0].T, |
||||
F: series.Floats[0].F, |
||||
}) |
||||
} else if vec[pos].T > series.Floats[0].T { |
||||
vec[pos].F = series.Floats[0].F |
||||
vec[pos].T = series.Floats[0].T |
||||
} |
||||
|
||||
return vec |
||||
} |
||||
|
||||
func NewMergeLastOverTimeStepEvaluator(params Params, m []promql.Matrix) StepEvaluator { |
||||
if len(m) == 0 { |
||||
return EmptyEvaluator[SampleVector]{} |
||||
} |
||||
|
||||
var ( |
||||
start = params.Start() |
||||
end = params.End() |
||||
step = params.Step() |
||||
) |
||||
|
||||
return &mergeOverTimeStepEvaluator{ |
||||
start: start, |
||||
end: end, |
||||
ts: start.Add(-step), // will be corrected on first Next() call
|
||||
step: step, |
||||
matrices: m, |
||||
merge: mergeLastOverTime, |
||||
} |
||||
} |
||||
|
||||
// mergeLastOverTime selects the last sample by timestamp of each series.
|
||||
func mergeLastOverTime(vec promql.Vector, pos int, nSeries int, series promql.Series) promql.Vector { |
||||
if len(vec) < nSeries { |
||||
return append(vec, promql.Sample{ |
||||
Metric: series.Metric, |
||||
T: series.Floats[0].T, |
||||
F: series.Floats[0].F, |
||||
}) |
||||
} else if vec[pos].T < series.Floats[0].T { |
||||
vec[pos].F = series.Floats[0].F |
||||
vec[pos].T = series.Floats[0].T |
||||
} |
||||
|
||||
return vec |
||||
} |
Loading…
Reference in new issue