lib/storage: further tuning for time series search

This commit is contained in:
Aliaksandr Valialkin 2021-03-16 18:46:22 +02:00
parent f0a4157f89
commit e36fbfae5b
2 changed files with 165 additions and 96 deletions

View file

@ -2058,7 +2058,7 @@ func (is *indexSearch) getTagFilterWithMinMetricIDsCount(tfs *TagFilters, maxMet
continue continue
} }
metricIDs, _, err := is.getMetricIDsForTagFilter(tf, nil, maxMetrics) metricIDs, _, err := is.getMetricIDsForTagFilter(tf, nil, maxMetrics, int64Max)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("cannot find MetricIDs for tagFilter %s: %w", tf, err) return nil, nil, fmt.Errorf("cannot find MetricIDs for tagFilter %s: %w", tf, err)
} }
@ -2309,7 +2309,7 @@ func (is *indexSearch) updateMetricIDsForTagFilters(metricIDs *uint64set.Set, tf
// Fast path: found metricIDs by date range. // Fast path: found metricIDs by date range.
return nil return nil
} }
if err != errFallbackToGlobalSearch { if !errors.Is(err, errFallbackToGlobalSearch) {
return err return err
} }
@ -2361,19 +2361,19 @@ const (
var uselessTagFilterCacheValue = []byte("1") var uselessTagFilterCacheValue = []byte("1")
func (is *indexSearch) getMetricIDsForTagFilter(tf *tagFilter, filter *uint64set.Set, maxMetrics int) (*uint64set.Set, uint64, error) { func (is *indexSearch) getMetricIDsForTagFilter(tf *tagFilter, filter *uint64set.Set, maxMetrics int, maxLoopsCount int64) (*uint64set.Set, int64, error) {
if tf.isNegative { if tf.isNegative {
logger.Panicf("BUG: isNegative must be false") logger.Panicf("BUG: isNegative must be false")
} }
metricIDs := &uint64set.Set{} metricIDs := &uint64set.Set{}
if len(tf.orSuffixes) > 0 { if len(tf.orSuffixes) > 0 {
// Fast path for orSuffixes - seek for rows for each value from orSuffixes. // Fast path for orSuffixes - seek for rows for each value from orSuffixes.
var loopsCount uint64 var loopsCount int64
var err error var err error
if filter != nil { if filter != nil {
loopsCount, err = is.updateMetricIDsForOrSuffixesWithFilter(tf, metricIDs, filter) loopsCount, err = is.updateMetricIDsForOrSuffixesWithFilter(tf, metricIDs, filter, maxLoopsCount)
} else { } else {
loopsCount, err = is.updateMetricIDsForOrSuffixesNoFilter(tf, maxMetrics, metricIDs) loopsCount, err = is.updateMetricIDsForOrSuffixesNoFilter(tf, metricIDs, maxMetrics, maxLoopsCount)
} }
if err != nil { if err != nil {
return nil, loopsCount, fmt.Errorf("error when searching for metricIDs for tagFilter in fast path: %w; tagFilter=%s", err, tf) return nil, loopsCount, fmt.Errorf("error when searching for metricIDs for tagFilter in fast path: %w; tagFilter=%s", err, tf)
@ -2382,14 +2382,16 @@ func (is *indexSearch) getMetricIDsForTagFilter(tf *tagFilter, filter *uint64set
} }
// Slow path - scan for all the rows with the given prefix. // Slow path - scan for all the rows with the given prefix.
loopsCount, err := is.getMetricIDsForTagFilterSlow(tf, filter, metricIDs.Add) loopsCount, err := is.getMetricIDsForTagFilterSlow(tf, filter, metricIDs.Add, maxLoopsCount)
if err != nil { if err != nil {
return nil, loopsCount, fmt.Errorf("error when searching for metricIDs for tagFilter in slow path: %w; tagFilter=%s", err, tf) return nil, loopsCount, fmt.Errorf("error when searching for metricIDs for tagFilter in slow path: %w; tagFilter=%s", err, tf)
} }
return metricIDs, loopsCount, nil return metricIDs, loopsCount, nil
} }
func (is *indexSearch) getMetricIDsForTagFilterSlow(tf *tagFilter, filter *uint64set.Set, f func(metricID uint64)) (uint64, error) { var errTooManyLoops = fmt.Errorf("too many loops is needed for applying this filter")
func (is *indexSearch) getMetricIDsForTagFilterSlow(tf *tagFilter, filter *uint64set.Set, f func(metricID uint64), maxLoopsCount int64) (int64, error) {
if len(tf.orSuffixes) > 0 { if len(tf.orSuffixes) > 0 {
logger.Panicf("BUG: the getMetricIDsForTagFilterSlow must be called only for empty tf.orSuffixes; got %s", tf.orSuffixes) logger.Panicf("BUG: the getMetricIDsForTagFilterSlow must be called only for empty tf.orSuffixes; got %s", tf.orSuffixes)
} }
@ -2401,7 +2403,7 @@ func (is *indexSearch) getMetricIDsForTagFilterSlow(tf *tagFilter, filter *uint6
mp.Reset() mp.Reset()
var prevMatchingSuffix []byte var prevMatchingSuffix []byte
var prevMatch bool var prevMatch bool
var loopsCount uint64 var loopsCount int64
loopsPaceLimiter := 0 loopsPaceLimiter := 0
prefix := tf.prefix prefix := tf.prefix
ts.Seek(prefix) ts.Seek(prefix)
@ -2427,7 +2429,10 @@ func (is *indexSearch) getMetricIDsForTagFilterSlow(tf *tagFilter, filter *uint6
return loopsCount, err return loopsCount, err
} }
mp.ParseMetricIDs() mp.ParseMetricIDs()
loopsCount += uint64(mp.MetricIDsLen()) loopsCount += int64(mp.MetricIDsLen())
if loopsCount > maxLoopsCount {
return loopsCount, errTooManyLoops
}
if prevMatch && string(suffix) == string(prevMatchingSuffix) { if prevMatch && string(suffix) == string(prevMatchingSuffix) {
// Fast path: the same tag value found. // Fast path: the same tag value found.
// There is no need in checking it again with potentially // There is no need in checking it again with potentially
@ -2448,7 +2453,7 @@ func (is *indexSearch) getMetricIDsForTagFilterSlow(tf *tagFilter, filter *uint6
// Slow path: need tf.matchSuffix call. // Slow path: need tf.matchSuffix call.
ok, err := tf.matchSuffix(suffix) ok, err := tf.matchSuffix(suffix)
// Assume that tf.matchSuffix call needs 10x more time than a single metric scan iteration. // Assume that tf.matchSuffix call needs 10x more time than a single metric scan iteration.
loopsCount += 10 * tf.matchCost loopsCount += 10 * int64(tf.matchCost)
if err != nil { if err != nil {
return loopsCount, fmt.Errorf("error when matching %s against suffix %q: %w", tf, suffix, err) return loopsCount, fmt.Errorf("error when matching %s against suffix %q: %w", tf, suffix, err)
} }
@ -2488,18 +2493,18 @@ func (is *indexSearch) getMetricIDsForTagFilterSlow(tf *tagFilter, filter *uint6
return loopsCount, nil return loopsCount, nil
} }
func (is *indexSearch) updateMetricIDsForOrSuffixesNoFilter(tf *tagFilter, maxMetrics int, metricIDs *uint64set.Set) (uint64, error) { func (is *indexSearch) updateMetricIDsForOrSuffixesNoFilter(tf *tagFilter, metricIDs *uint64set.Set, maxMetrics int, maxLoopsCount int64) (int64, error) {
if tf.isNegative { if tf.isNegative {
logger.Panicf("BUG: isNegative must be false") logger.Panicf("BUG: isNegative must be false")
} }
kb := kbPool.Get() kb := kbPool.Get()
defer kbPool.Put(kb) defer kbPool.Put(kb)
var loopsCount uint64 var loopsCount int64
for _, orSuffix := range tf.orSuffixes { for _, orSuffix := range tf.orSuffixes {
kb.B = append(kb.B[:0], tf.prefix...) kb.B = append(kb.B[:0], tf.prefix...)
kb.B = append(kb.B, orSuffix...) kb.B = append(kb.B, orSuffix...)
kb.B = append(kb.B, tagSeparatorChar) kb.B = append(kb.B, tagSeparatorChar)
lc, err := is.updateMetricIDsForOrSuffixNoFilter(kb.B, maxMetrics, metricIDs) lc, err := is.updateMetricIDsForOrSuffixNoFilter(kb.B, metricIDs, maxMetrics, maxLoopsCount-loopsCount)
if err != nil { if err != nil {
return loopsCount, err return loopsCount, err
} }
@ -2511,16 +2516,16 @@ func (is *indexSearch) updateMetricIDsForOrSuffixesNoFilter(tf *tagFilter, maxMe
return loopsCount, nil return loopsCount, nil
} }
func (is *indexSearch) updateMetricIDsForOrSuffixesWithFilter(tf *tagFilter, metricIDs, filter *uint64set.Set) (uint64, error) { func (is *indexSearch) updateMetricIDsForOrSuffixesWithFilter(tf *tagFilter, metricIDs, filter *uint64set.Set, maxLoopsCount int64) (int64, error) {
sortedFilter := filter.AppendTo(nil) sortedFilter := filter.AppendTo(nil)
kb := kbPool.Get() kb := kbPool.Get()
defer kbPool.Put(kb) defer kbPool.Put(kb)
var loopsCount uint64 var loopsCount int64
for _, orSuffix := range tf.orSuffixes { for _, orSuffix := range tf.orSuffixes {
kb.B = append(kb.B[:0], tf.prefix...) kb.B = append(kb.B[:0], tf.prefix...)
kb.B = append(kb.B, orSuffix...) kb.B = append(kb.B, orSuffix...)
kb.B = append(kb.B, tagSeparatorChar) kb.B = append(kb.B, tagSeparatorChar)
lc, err := is.updateMetricIDsForOrSuffixWithFilter(kb.B, metricIDs, sortedFilter, tf.isNegative) lc, err := is.updateMetricIDsForOrSuffixWithFilter(kb.B, metricIDs, sortedFilter, tf.isNegative, maxLoopsCount-loopsCount)
if err != nil { if err != nil {
return loopsCount, err return loopsCount, err
} }
@ -2529,11 +2534,11 @@ func (is *indexSearch) updateMetricIDsForOrSuffixesWithFilter(tf *tagFilter, met
return loopsCount, nil return loopsCount, nil
} }
func (is *indexSearch) updateMetricIDsForOrSuffixNoFilter(prefix []byte, maxMetrics int, metricIDs *uint64set.Set) (uint64, error) { func (is *indexSearch) updateMetricIDsForOrSuffixNoFilter(prefix []byte, metricIDs *uint64set.Set, maxMetrics int, maxLoopsCount int64) (int64, error) {
ts := &is.ts ts := &is.ts
mp := &is.mp mp := &is.mp
mp.Reset() mp.Reset()
var loopsCount uint64 var loopsCount int64
loopsPaceLimiter := 0 loopsPaceLimiter := 0
ts.Seek(prefix) ts.Seek(prefix)
for metricIDs.Len() < maxMetrics && ts.NextItem() { for metricIDs.Len() < maxMetrics && ts.NextItem() {
@ -2550,7 +2555,10 @@ func (is *indexSearch) updateMetricIDsForOrSuffixNoFilter(prefix []byte, maxMetr
if err := mp.InitOnlyTail(item, item[len(prefix):]); err != nil { if err := mp.InitOnlyTail(item, item[len(prefix):]); err != nil {
return loopsCount, err return loopsCount, err
} }
loopsCount += uint64(mp.MetricIDsLen()) loopsCount += int64(mp.MetricIDsLen())
if loopsCount > maxLoopsCount {
return loopsCount, errTooManyLoops
}
mp.ParseMetricIDs() mp.ParseMetricIDs()
metricIDs.AddMulti(mp.MetricIDs) metricIDs.AddMulti(mp.MetricIDs)
} }
@ -2560,7 +2568,7 @@ func (is *indexSearch) updateMetricIDsForOrSuffixNoFilter(prefix []byte, maxMetr
return loopsCount, nil return loopsCount, nil
} }
func (is *indexSearch) updateMetricIDsForOrSuffixWithFilter(prefix []byte, metricIDs *uint64set.Set, sortedFilter []uint64, isNegative bool) (uint64, error) { func (is *indexSearch) updateMetricIDsForOrSuffixWithFilter(prefix []byte, metricIDs *uint64set.Set, sortedFilter []uint64, isNegative bool, maxLoopsCount int64) (int64, error) {
if len(sortedFilter) == 0 { if len(sortedFilter) == 0 {
return 0, nil return 0, nil
} }
@ -2569,7 +2577,7 @@ func (is *indexSearch) updateMetricIDsForOrSuffixWithFilter(prefix []byte, metri
ts := &is.ts ts := &is.ts
mp := &is.mp mp := &is.mp
mp.Reset() mp.Reset()
var loopsCount uint64 var loopsCount int64
loopsPaceLimiter := 0 loopsPaceLimiter := 0
ts.Seek(prefix) ts.Seek(prefix)
var sf []uint64 var sf []uint64
@ -2588,7 +2596,10 @@ func (is *indexSearch) updateMetricIDsForOrSuffixWithFilter(prefix []byte, metri
if err := mp.InitOnlyTail(item, item[len(prefix):]); err != nil { if err := mp.InitOnlyTail(item, item[len(prefix):]); err != nil {
return loopsCount, err return loopsCount, err
} }
loopsCount += uint64(mp.MetricIDsLen()) loopsCount += int64(mp.MetricIDsLen())
if loopsCount > maxLoopsCount {
return loopsCount, errTooManyLoops
}
firstMetricID, lastMetricID := mp.FirstAndLastMetricIDs() firstMetricID, lastMetricID := mp.FirstAndLastMetricIDs()
if lastMetricID < firstFilterMetricID { if lastMetricID < firstFilterMetricID {
// Skip the item, since it contains metricIDs lower // Skip the item, since it contains metricIDs lower
@ -2749,12 +2760,6 @@ func (is *indexSearch) tryUpdatingMetricIDsForDateRange(metricIDs *uint64set.Set
return return
} }
if err != nil { if err != nil {
if err == errFallbackToGlobalSearch {
// The per-date search is too expensive. Probably it is faster to perform global search
// using metric name match.
errGlobal = err
return
}
dateStr := time.Unix(int64(date*24*3600), 0) dateStr := time.Unix(int64(date*24*3600), 0)
errGlobal = fmt.Errorf("cannot search for metricIDs at %s: %w", dateStr, err) errGlobal = fmt.Errorf("cannot search for metricIDs at %s: %w", dateStr, err)
return return
@ -2778,26 +2783,26 @@ func (is *indexSearch) getMetricIDsForDateAndFilters(date uint64, tfs *TagFilter
// This stats is usually collected from the previous queries. // This stats is usually collected from the previous queries.
// This way we limit the amount of work below by applying fast filters at first. // This way we limit the amount of work below by applying fast filters at first.
type tagFilterWithWeight struct { type tagFilterWithWeight struct {
tf *tagFilter tf *tagFilter
loopsCount uint64 loopsCount int64
filterLoopsCount int64
} }
tfws := make([]tagFilterWithWeight, len(tfs.tfs)) tfws := make([]tagFilterWithWeight, len(tfs.tfs))
currentTime := fasttime.UnixTimestamp() currentTime := fasttime.UnixTimestamp()
for i := range tfs.tfs { for i := range tfs.tfs {
tf := &tfs.tfs[i] tf := &tfs.tfs[i]
loopsCount, lastQueryTimestamp := is.getLoopsCountAndTimestampForDateFilter(date, tf) loopsCount, filterLoopsCount, timestamp := is.getLoopsCountAndTimestampForDateFilter(date, tf)
origLoopsCount := loopsCount origLoopsCount := loopsCount
if loopsCount == 0 && tf.looksLikeHeavy() { origFilterLoopsCount := filterLoopsCount
// Set high loopsCount for heavy tag filters instead of spending CPU time on their execution. if currentTime > timestamp+3600 {
loopsCount = 11e6
is.storeLoopsCountForDateFilter(date, tf, loopsCount)
}
if currentTime > lastQueryTimestamp+3600 {
// Update stats once per hour for relatively fast tag filters. // Update stats once per hour for relatively fast tag filters.
// There is no need in spending CPU resources on updating stats for heavy tag filters. // There is no need in spending CPU resources on updating stats for heavy tag filters.
if loopsCount <= 10e6 { if loopsCount <= 10e6 {
loopsCount = 0 loopsCount = 0
} }
if filterLoopsCount <= 10e6 {
filterLoopsCount = 0
}
} }
if loopsCount == 0 { if loopsCount == 0 {
// Prevent from possible thundering herd issue when potentially heavy tf is executed from multiple concurrent queries // Prevent from possible thundering herd issue when potentially heavy tf is executed from multiple concurrent queries
@ -2805,11 +2810,15 @@ func (is *indexSearch) getMetricIDsForDateAndFilters(date uint64, tfs *TagFilter
if origLoopsCount == 0 { if origLoopsCount == 0 {
origLoopsCount = 9e6 origLoopsCount = 9e6
} }
is.storeLoopsCountForDateFilter(date, tf, origLoopsCount) if origFilterLoopsCount == 0 {
origFilterLoopsCount = 9e6
}
is.storeLoopsCountForDateFilter(date, tf, origLoopsCount, origFilterLoopsCount)
} }
tfws[i] = tagFilterWithWeight{ tfws[i] = tagFilterWithWeight{
tf: tf, tf: tf,
loopsCount: loopsCount, loopsCount: loopsCount,
filterLoopsCount: filterLoopsCount,
} }
} }
sort.Slice(tfws, func(i, j int) bool { sort.Slice(tfws, func(i, j int) bool {
@ -2819,45 +2828,84 @@ func (is *indexSearch) getMetricIDsForDateAndFilters(date uint64, tfs *TagFilter
} }
return a.tf.Less(b.tf) return a.tf.Less(b.tf)
}) })
getFirstPositiveLoopsCount := func(tfws []tagFilterWithWeight) int64 {
for i := range tfws {
if n := tfws[i].loopsCount; n > 0 {
return n
}
}
return int64Max
}
storeLoopsCount := func(tfw *tagFilterWithWeight, loopsCount int64) {
needStore := false
if loopsCount != tfw.loopsCount {
tfw.loopsCount = loopsCount
needStore = true
}
if loopsCount > tfw.filterLoopsCount {
tfw.filterLoopsCount = loopsCount
needStore = true
}
if needStore {
is.storeLoopsCountForDateFilter(date, tfw.tf, tfw.loopsCount, tfw.filterLoopsCount)
}
}
storeZeroLoopsCounts := func(tfws []tagFilterWithWeight) {
for _, tfw := range tfws {
if tfw.loopsCount == 0 || tfw.filterLoopsCount == 0 {
is.storeLoopsCountForDateFilter(date, tfw.tf, tfw.loopsCount, tfw.filterLoopsCount)
}
}
}
// Populate metricIDs for the first non-negative filter. // Populate metricIDs for the first non-negative filter with the cost smaller than maxLoopsCount.
var metricIDs *uint64set.Set var metricIDs *uint64set.Set
tfwsRemaining := tfws[:0] tfwsRemaining := tfws[:0]
maxDateMetrics := maxMetrics * 50 maxDateMetrics := intMax
for i := range tfws { if maxMetrics < intMax/50 {
tfw := tfws[i] maxDateMetrics = maxMetrics * 50
}
for i, tfw := range tfws {
tf := tfw.tf tf := tfw.tf
if tf.isNegative { if tf.isNegative {
tfwsRemaining = append(tfwsRemaining, tfw) tfwsRemaining = append(tfwsRemaining, tfw)
continue continue
} }
m, loopsCount, err := is.getMetricIDsForDateTagFilter(tf, date, nil, tfs.commonPrefix, maxDateMetrics) maxLoopsCount := getFirstPositiveLoopsCount(tfws[i+1:])
if loopsCount > tfw.loopsCount { m, loopsCount, err := is.getMetricIDsForDateTagFilter(tf, date, nil, tfs.commonPrefix, maxDateMetrics, maxLoopsCount)
is.storeLoopsCountForDateFilter(date, tf, loopsCount)
}
if err != nil { if err != nil {
if errors.Is(err, errTooManyLoops) {
// The tf took too many loops compared to the next filter. Postpone applying this filter.
storeLoopsCount(&tfw, loopsCount+1)
tfwsRemaining = append(tfwsRemaining, tfw)
continue
}
// Move failing filter to the end of filter list.
storeLoopsCount(&tfw, int64Max)
storeZeroLoopsCounts(tfws[i+1:])
storeZeroLoopsCounts(tfwsRemaining)
return nil, err return nil, err
} }
if m.Len() >= maxDateMetrics { if m.Len() >= maxDateMetrics {
// Too many time series found by a single tag filter. Postpone applying this filter. // Too many time series found by a single tag filter. Move the filter to the end of list.
storeLoopsCount(&tfw, int64Max-1)
tfwsRemaining = append(tfwsRemaining, tfw) tfwsRemaining = append(tfwsRemaining, tfw)
tfw.loopsCount = loopsCount
continue continue
} }
storeLoopsCount(&tfw, loopsCount)
metricIDs = m metricIDs = m
i++ tfwsRemaining = append(tfwsRemaining, tfws[i+1:]...)
for i < len(tfws) {
tfwsRemaining = append(tfwsRemaining, tfws[i])
i++
}
break break
} }
tfws = tfwsRemaining
if metricIDs == nil { if metricIDs == nil {
// All the filters in tfs are negative or match too many time series. // All the filters in tfs are negative or match too many time series.
// Populate all the metricIDs for the given (date), // Populate all the metricIDs for the given (date),
// so later they can be filtered out with negative filters. // so later they can be filtered out with negative filters.
m, err := is.getMetricIDsForDate(date, maxDateMetrics) m, err := is.getMetricIDsForDate(date, maxDateMetrics)
if err != nil { if err != nil {
storeZeroLoopsCounts(tfws)
if err == errMissingMetricIDsForDate { if err == errMissingMetricIDsForDate {
// Zero time series were written on the given date. // Zero time series were written on the given date.
return nil, nil return nil, nil
@ -2866,11 +2914,33 @@ func (is *indexSearch) getMetricIDsForDateAndFilters(date uint64, tfs *TagFilter
} }
if m.Len() >= maxDateMetrics { if m.Len() >= maxDateMetrics {
// Too many time series found for the given (date). Fall back to global search. // Too many time series found for the given (date). Fall back to global search.
storeZeroLoopsCounts(tfws)
return nil, errFallbackToGlobalSearch return nil, errFallbackToGlobalSearch
} }
metricIDs = m metricIDs = m
} }
sort.Slice(tfws, func(i, j int) bool {
a, b := &tfws[i], &tfws[j]
if a.filterLoopsCount != b.filterLoopsCount {
return a.filterLoopsCount < b.filterLoopsCount
}
return a.tf.Less(b.tf)
})
getFirstPositiveFilterLoopsCount := func(tfws []tagFilterWithWeight) int64 {
for i := range tfws {
if n := tfws[i].filterLoopsCount; n > 0 {
return n
}
}
return int64Max
}
storeFilterLoopsCount := func(tfw *tagFilterWithWeight, filterLoopsCount int64) {
if filterLoopsCount != tfw.filterLoopsCount {
is.storeLoopsCountForDateFilter(date, tfw.tf, tfw.loopsCount, filterLoopsCount)
}
}
// Intersect metricIDs with the rest of filters. // Intersect metricIDs with the rest of filters.
// //
// Do not run these tag filters in parallel, since this may result in CPU and RAM waste // Do not run these tag filters in parallel, since this may result in CPU and RAM waste
@ -2878,34 +2948,38 @@ func (is *indexSearch) getMetricIDsForDateAndFilters(date uint64, tfs *TagFilter
// so the remaining filters could be performed via much faster metricName matching instead // so the remaining filters could be performed via much faster metricName matching instead
// of slow selecting of matching metricIDs. // of slow selecting of matching metricIDs.
var tfsPostponed []*tagFilter var tfsPostponed []*tagFilter
for i := range tfwsRemaining { for i, tfw := range tfws {
tfw := tfwsRemaining[i]
tf := tfw.tf tf := tfw.tf
metricIDsLen := metricIDs.Len() metricIDsLen := metricIDs.Len()
if metricIDsLen == 0 { if metricIDsLen == 0 {
// Short circuit - there is no need in applying the remaining filters to an empty set. // There is no need in applying the remaining filters to an empty set.
storeZeroLoopsCounts(tfws[i:])
break break
} }
if tfw.loopsCount > uint64(metricIDsLen)*loopsCountPerMetricNameMatch { if tfw.filterLoopsCount > int64(metricIDsLen)*loopsCountPerMetricNameMatch {
// It should be faster performing metricName match on the remaining filters // It should be faster performing metricName match on the remaining filters
// instead of scanning big number of entries in the inverted index for these filters. // instead of scanning big number of entries in the inverted index for these filters.
for i < len(tfwsRemaining) { for _, tfw := range tfws[i:] {
tfw := tfwsRemaining[i] tfsPostponed = append(tfsPostponed, tfw.tf)
tf := tfw.tf
tfsPostponed = append(tfsPostponed, tf)
// Store stats for non-executed tf, since it could be updated during protection from thundered herd.
is.storeLoopsCountForDateFilter(date, tf, tfw.loopsCount)
i++
} }
storeZeroLoopsCounts(tfws[i:])
break break
} }
m, loopsCount, err := is.getMetricIDsForDateTagFilter(tf, date, metricIDs, tfs.commonPrefix, 0) maxLoopsCount := getFirstPositiveFilterLoopsCount(tfws[i+1:])
if loopsCount > tfw.loopsCount { m, filterLoopsCount, err := is.getMetricIDsForDateTagFilter(tf, date, metricIDs, tfs.commonPrefix, intMax, maxLoopsCount)
is.storeLoopsCountForDateFilter(date, tf, loopsCount)
}
if err != nil { if err != nil {
if errors.Is(err, errTooManyLoops) {
// Postpone tf, since it took more loops than the next filter may need.
storeFilterLoopsCount(&tfw, filterLoopsCount+1)
tfsPostponed = append(tfsPostponed, tf)
continue
}
// Move failing tf to the end of filter list
storeFilterLoopsCount(&tfw, int64Max)
storeZeroLoopsCounts(tfws[i:])
return nil, err return nil, err
} }
storeFilterLoopsCount(&tfw, filterLoopsCount)
if tf.isNegative { if tf.isNegative {
metricIDs.Subtract(m) metricIDs.Subtract(m)
} else { } else {
@ -2927,6 +3001,11 @@ func (is *indexSearch) getMetricIDsForDateAndFilters(date uint64, tfs *TagFilter
return metricIDs, nil return metricIDs, nil
} }
const (
intMax = int((^uint(0)) >> 1)
int64Max = int64((1 << 63) - 1)
)
func (is *indexSearch) storeDateMetricID(date, metricID uint64) error { func (is *indexSearch) storeDateMetricID(date, metricID uint64) error {
ii := getIndexItems() ii := getIndexItems()
defer putIndexItems(ii) defer putIndexItems(ii)
@ -3073,7 +3152,8 @@ func (is *indexSearch) hasDateMetricID(date, metricID uint64) (bool, error) {
return true, nil return true, nil
} }
func (is *indexSearch) getMetricIDsForDateTagFilter(tf *tagFilter, date uint64, filter *uint64set.Set, commonPrefix []byte, maxMetrics int) (*uint64set.Set, uint64, error) { func (is *indexSearch) getMetricIDsForDateTagFilter(tf *tagFilter, date uint64, filter *uint64set.Set, commonPrefix []byte,
maxMetrics int, maxLoopsCount int64) (*uint64set.Set, int64, error) {
// Augument tag filter prefix for per-date search instead of global search. // Augument tag filter prefix for per-date search instead of global search.
if !bytes.HasPrefix(tf.prefix, commonPrefix) { if !bytes.HasPrefix(tf.prefix, commonPrefix) {
logger.Panicf("BUG: unexpected tf.prefix %q; must start with commonPrefix %q", tf.prefix, commonPrefix) logger.Panicf("BUG: unexpected tf.prefix %q; must start with commonPrefix %q", tf.prefix, commonPrefix)
@ -3085,38 +3165,31 @@ func (is *indexSearch) getMetricIDsForDateTagFilter(tf *tagFilter, date uint64,
tfNew := *tf tfNew := *tf
tfNew.isNegative = false // isNegative for the original tf is handled by the caller. tfNew.isNegative = false // isNegative for the original tf is handled by the caller.
tfNew.prefix = kb.B tfNew.prefix = kb.B
metricIDs, loopsCount, err := is.getMetricIDsForTagFilter(&tfNew, filter, maxMetrics) metricIDs, loopsCount, err := is.getMetricIDsForTagFilter(&tfNew, filter, maxMetrics, maxLoopsCount)
kbPool.Put(kb) kbPool.Put(kb)
if err != nil {
// Set high loopsCount for failing filter, so it is moved to the end of filter list.
loopsCount = 20e9
}
if filter == nil && metricIDs.Len() >= maxMetrics {
// Increase loopsCount for tag filter matching too many metrics,
// So next time it is moved to the end of filter list.
loopsCount *= 2
}
return metricIDs, loopsCount, err return metricIDs, loopsCount, err
} }
func (is *indexSearch) getLoopsCountAndTimestampForDateFilter(date uint64, tf *tagFilter) (uint64, uint64) { func (is *indexSearch) getLoopsCountAndTimestampForDateFilter(date uint64, tf *tagFilter) (int64, int64, uint64) {
is.kb.B = appendDateTagFilterCacheKey(is.kb.B[:0], date, tf) is.kb.B = appendDateTagFilterCacheKey(is.kb.B[:0], date, tf)
kb := kbPool.Get() kb := kbPool.Get()
defer kbPool.Put(kb) defer kbPool.Put(kb)
kb.B = is.db.loopsPerDateTagFilterCache.Get(kb.B[:0], is.kb.B) kb.B = is.db.loopsPerDateTagFilterCache.Get(kb.B[:0], is.kb.B)
if len(kb.B) != 16 { if len(kb.B) != 3*8 {
return 0, 0 return 0, 0, 0
} }
loopsCount := encoding.UnmarshalUint64(kb.B) loopsCount := encoding.UnmarshalInt64(kb.B)
timestamp := encoding.UnmarshalUint64(kb.B[8:]) filterLoopsCount := encoding.UnmarshalInt64(kb.B[8:])
return loopsCount, timestamp timestamp := encoding.UnmarshalUint64(kb.B[16:])
return loopsCount, filterLoopsCount, timestamp
} }
func (is *indexSearch) storeLoopsCountForDateFilter(date uint64, tf *tagFilter, loopsCount uint64) { func (is *indexSearch) storeLoopsCountForDateFilter(date uint64, tf *tagFilter, loopsCount, filterLoopsCount int64) {
currentTimestamp := fasttime.UnixTimestamp() currentTimestamp := fasttime.UnixTimestamp()
is.kb.B = appendDateTagFilterCacheKey(is.kb.B[:0], date, tf) is.kb.B = appendDateTagFilterCacheKey(is.kb.B[:0], date, tf)
kb := kbPool.Get() kb := kbPool.Get()
kb.B = encoding.MarshalUint64(kb.B[:0], loopsCount) kb.B = encoding.MarshalInt64(kb.B[:0], loopsCount)
kb.B = encoding.MarshalInt64(kb.B, filterLoopsCount)
kb.B = encoding.MarshalUint64(kb.B, currentTimestamp) kb.B = encoding.MarshalUint64(kb.B, currentTimestamp)
is.db.loopsPerDateTagFilterCache.Set(is.kb.B, kb.B) is.db.loopsPerDateTagFilterCache.Set(is.kb.B, kb.B)
kbPool.Put(kb) kbPool.Put(kb)
@ -3201,7 +3274,7 @@ func (is *indexSearch) intersectMetricIDsWithTagFilter(tf *tagFilter, filter *ui
} }
if len(tf.orSuffixes) > 0 { if len(tf.orSuffixes) > 0 {
// Fast path for orSuffixes - seek for rows for each value from orSuffixes. // Fast path for orSuffixes - seek for rows for each value from orSuffixes.
_, err := is.updateMetricIDsForOrSuffixesWithFilter(tf, metricIDs, filter) _, err := is.updateMetricIDsForOrSuffixesWithFilter(tf, metricIDs, filter, int64Max)
if err != nil { if err != nil {
return nil, fmt.Errorf("error when intersecting metricIDs for tagFilter in fast path: %w; tagFilter=%s", err, tf) return nil, fmt.Errorf("error when intersecting metricIDs for tagFilter in fast path: %w; tagFilter=%s", err, tf)
} }
@ -3216,7 +3289,7 @@ func (is *indexSearch) intersectMetricIDsWithTagFilter(tf *tagFilter, filter *ui
} else { } else {
metricIDs.Add(metricID) metricIDs.Add(metricID)
} }
}) }, int64Max)
if err != nil { if err != nil {
return nil, fmt.Errorf("error when intersecting metricIDs for tagFilter in slow path: %w; tagFilter=%s", err, tf) return nil, fmt.Errorf("error when intersecting metricIDs for tagFilter in slow path: %w; tagFilter=%s", err, tf)
} }

View file

@ -248,10 +248,6 @@ type tagFilter struct {
graphiteReverseSuffix []byte graphiteReverseSuffix []byte
} }
func (tf *tagFilter) looksLikeHeavy() bool {
return tf.isRegexp && len(tf.orSuffixes) == 0
}
func (tf *tagFilter) isComposite() bool { func (tf *tagFilter) isComposite() bool {
k := tf.key k := tf.key
return len(k) > 0 && k[0] == compositeTagKeyPrefix return len(k) > 0 && k[0] == compositeTagKeyPrefix