vmalert: use stringified label keys for duplicates map in recroding rules (#1301)

duplicates map helps to determine wheter extra labels has overriden
labels which make time series unique. It was using a sorted hashed
labels sequence as a key. But hashing algorithm could have collisions,
so it is more convenient to not use hashing at all.

Log message for recording rules duplicates was improved as well.

https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1293
This commit is contained in:
Roman Khavronenko 2021-05-15 11:25:57 +01:00 committed by Aliaksandr Valialkin
parent 9c8a411e08
commit 3428df6f15

View file

@ -3,8 +3,8 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"hash/fnv"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
@ -103,33 +103,38 @@ func (rr *RecordingRule) Exec(ctx context.Context, series bool) ([]prompbmarshal
return nil, fmt.Errorf("failed to execute query %q: %w", rr.Expr, err) return nil, fmt.Errorf("failed to execute query %q: %w", rr.Expr, err)
} }
duplicates := make(map[uint64]prompbmarshal.TimeSeries, len(qMetrics)) duplicates := make(map[string]struct{}, len(qMetrics))
var tss []prompbmarshal.TimeSeries var tss []prompbmarshal.TimeSeries
for _, r := range qMetrics { for _, r := range qMetrics {
ts := rr.toTimeSeries(r, time.Unix(r.Timestamp, 0)) ts := rr.toTimeSeries(r, time.Unix(r.Timestamp, 0))
h := hashTimeSeries(ts) key := stringifyLabels(ts)
if _, ok := duplicates[h]; ok { if _, ok := duplicates[key]; ok {
rr.lastExecError = errDuplicate rr.lastExecError = errDuplicate
return nil, errDuplicate return nil, fmt.Errorf("original metric %v; resulting labels %q: %w", r, key, errDuplicate)
} }
duplicates[h] = ts duplicates[key] = struct{}{}
tss = append(tss, ts) tss = append(tss, ts)
} }
return tss, nil return tss, nil
} }
func hashTimeSeries(ts prompbmarshal.TimeSeries) uint64 { func stringifyLabels(ts prompbmarshal.TimeSeries) string {
hash := fnv.New64a()
labels := ts.Labels labels := ts.Labels
if len(labels) > 1 {
sort.Slice(labels, func(i, j int) bool { sort.Slice(labels, func(i, j int) bool {
return labels[i].Name < labels[j].Name return labels[i].Name < labels[j].Name
}) })
for _, l := range labels {
hash.Write([]byte(l.Name))
hash.Write([]byte(l.Value))
hash.Write([]byte("\xff"))
} }
return hash.Sum64() b := strings.Builder{}
for i, l := range labels {
b.WriteString(l.Name)
b.WriteString("=")
b.WriteString(l.Value)
if i != len(labels)-1 {
b.WriteString(",")
}
}
return b.String()
} }
func (rr *RecordingRule) toTimeSeries(m datasource.Metric, timestamp time.Time) prompbmarshal.TimeSeries { func (rr *RecordingRule) toTimeSeries(m datasource.Metric, timestamp time.Time) prompbmarshal.TimeSeries {