optimization: fast path float on AppenderV2

Signed-off-by: bwplotka <bwplotka@gmail.com>
This commit is contained in:
bwplotka 2026-01-28 11:52:18 +00:00
parent 5ec87d59a3
commit f0ca5f38cf
2 changed files with 47 additions and 21 deletions

View file

@ -357,6 +357,18 @@ const (
stCustomBucketFloatHistogram // Native float histograms with custom bucket boundaries. Goes to `floatHistograms`.
)
// TypeLabel returns a name to use for instrumentation to reflect histogram vs float operations.
func (s sampleType) TypeLabel() string {
switch s {
default:
return ""
case stFloat:
return sampleMetricTypeFloat
case stHistogram, stCustomBucketHistogram, stFloatHistogram, stCustomBucketFloatHistogram:
return sampleMetricTypeHistogram
}
}
// appendBatch is used to partition all the appended data into batches that are
// "type clean", i.e. every series receives only samples of one type within the
// batch. Types in this regard are defined by the sampleType enum above.

View file

@ -103,31 +103,43 @@ type headAppenderV2 struct {
headAppenderBase
}
// toBasicSampleType detects sample type based on a typical multi sample Append API.
// Basic because this does not detect some details like custom bucket histogram or native.
// TODO: Improve with https://github.com/prometheus/prometheus/issues/17925
func toBasicSampleType(v float64, h *histogram.Histogram, fh *histogram.FloatHistogram) sampleType {
if v != 0 || (h == nil && fh == nil) {
return stFloat // Fast path.
}
if fh != nil {
return stFloatHistogram
}
return stHistogram
}
func (a *headAppenderV2) Append(ref storage.SeriesRef, ls labels.Labels, st, t int64, v float64, h *histogram.Histogram, fh *histogram.FloatHistogram, opts storage.AOptions) (storage.SeriesRef, error) {
var (
sTyp = toBasicSampleType(v, h, fh)
isStale bool
// Avoid shadowing err variables for reliability.
valErr, appErr, partialErr error
sampleMetricType = sampleMetricTypeFloat
isStale bool
appErr, partialErr error
)
// Fail fast on incorrect histograms.
switch {
case fh != nil:
sampleMetricType = sampleMetricTypeHistogram
valErr = fh.Validate()
case h != nil:
sampleMetricType = sampleMetricTypeHistogram
valErr = h.Validate()
}
if valErr != nil {
return 0, valErr
switch sTyp {
default:
case stFloatHistogram:
if err := fh.Validate(); err != nil {
return 0, err
}
case stHistogram:
if err := h.Validate(); err != nil {
return 0, err
}
}
// Fail fast if OOO is disabled and the sample is out of bounds.
// Otherwise, a full check will be done later to decide if the sample is in-order or out-of-order.
if a.oooTimeWindow == 0 && t < a.minValidTime {
a.head.metrics.outOfBoundSamples.WithLabelValues(sampleMetricType).Inc()
a.head.metrics.outOfBoundSamples.WithLabelValues(sTyp.TypeLabel()).Inc()
return 0, storage.ErrOutOfBounds
}
@ -141,15 +153,15 @@ func (a *headAppenderV2) Append(ref storage.SeriesRef, ls labels.Labels, st, t i
}
// TODO(bwplotka): Handle ST natively (as per PROM-60).
if a.head.opts.EnableSTAsZeroSample && st != 0 {
if st != 0 && a.head.opts.EnableSTAsZeroSample {
a.bestEffortAppendSTZeroSample(s, ls, st, t, h, fh)
}
switch {
case fh != nil:
switch sTyp {
case stFloatHistogram:
isStale = value.IsStaleNaN(fh.Sum)
appErr = a.appendFloatHistogram(s, t, fh, opts.RejectOutOfOrder)
case h != nil:
case stHistogram:
isStale = value.IsStaleNaN(h.Sum)
appErr = a.appendHistogram(s, t, h, opts.RejectOutOfOrder)
default:
@ -171,6 +183,7 @@ func (a *headAppenderV2) Append(ref storage.SeriesRef, ls labels.Labels, st, t i
return a.Append(storage.SeriesRef(s.ref), ls, st, t, 0, nil, &histogram.FloatHistogram{Sum: v}, storage.AOptions{
RejectOutOfOrder: opts.RejectOutOfOrder,
})
default:
}
// Note that a series reference not yet in the map will come out
// as stNone, but since we do not handle that case separately,
@ -179,13 +192,14 @@ func (a *headAppenderV2) Append(ref storage.SeriesRef, ls labels.Labels, st, t i
}
appErr = a.appendFloat(s, t, v, opts.RejectOutOfOrder)
}
// Handle append error, if any.
if appErr != nil {
switch {
case errors.Is(appErr, storage.ErrOutOfOrderSample):
a.head.metrics.outOfOrderSamples.WithLabelValues(sampleMetricType).Inc()
a.head.metrics.outOfOrderSamples.WithLabelValues(sTyp.TypeLabel()).Inc()
case errors.Is(appErr, storage.ErrTooOldSample):
a.head.metrics.tooOldSamples.WithLabelValues(sampleMetricType).Inc()
a.head.metrics.tooOldSamples.WithLabelValues(sTyp.TypeLabel()).Inc()
}
return 0, appErr
}