github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/tsdb/index.go (about)

     1  // originally from https://github.com/cortexproject/cortex/blob/868898a2921c662dcd4f90683e8b95c927a8edd8/pkg/ingester/index/index.go
     2  // but modified to support sharding queries.
     3  package tsdb
     4  
     5  import (
     6  	"bytes"
     7  	"crypto/sha256"
     8  	"encoding/base64"
     9  	"encoding/binary"
    10  	"errors"
    11  	"fmt"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"unicode/utf8"
    17  	"unsafe"
    18  
    19  	"github.com/prometheus/common/model"
    20  	"github.com/prometheus/prometheus/model/labels"
    21  
    22  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    23  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    24  	"github.com/grafana/pyroscope/pkg/phlaredb/tsdb/shard"
    25  )
    26  
    27  var (
    28  	// Bitmap used by func isRegexMetaCharacter to check whether a character needs to be escaped.
    29  	regexMetaCharacterBytes [16]byte
    30  	ErrInvalidShardQuery    = errors.New("incompatible index shard query")
    31  )
    32  
    33  // isRegexMetaCharacter reports whether byte b needs to be escaped.
    34  func isRegexMetaCharacter(b byte) bool {
    35  	return b < utf8.RuneSelf && regexMetaCharacterBytes[b%16]&(1<<(b/16)) != 0
    36  }
    37  
    38  func init() {
    39  	for _, b := range []byte(`.+*?()|[]{}^$`) {
    40  		regexMetaCharacterBytes[b%16] |= 1 << (b / 16)
    41  	}
    42  }
    43  
    44  const DefaultIndexShards = 32
    45  
    46  type Interface interface {
    47  	Add(labels []*typesv1.LabelPair, fp model.Fingerprint) labels.Labels
    48  	Lookup(matchers []*labels.Matcher, shard *shard.Annotation) ([]model.Fingerprint, error)
    49  	LabelNames(shard *shard.Annotation) ([]string, error)
    50  	LabelValues(name string, shard *shard.Annotation) ([]string, error)
    51  	Delete(labels labels.Labels, fp model.Fingerprint)
    52  }
    53  
    54  // InvertedIndex implements a in-memory inverted index from label pairs to fingerprints.
    55  // It is sharded to reduce lock contention on writes.
    56  type InvertedIndex struct {
    57  	totalShards uint32
    58  	shards      []*indexShard
    59  }
    60  
    61  func NewWithShards(totalShards uint32) *InvertedIndex {
    62  	shards := make([]*indexShard, totalShards)
    63  	for i := uint32(0); i < totalShards; i++ {
    64  		shards[i] = &indexShard{
    65  			idx:   map[string]indexEntry{},
    66  			shard: i,
    67  		}
    68  	}
    69  	return &InvertedIndex{
    70  		totalShards: totalShards,
    71  		shards:      shards,
    72  	}
    73  }
    74  
    75  func (ii *InvertedIndex) getShards(shard *shard.Annotation) []*indexShard {
    76  	if shard == nil {
    77  		return ii.shards
    78  	}
    79  
    80  	totalRequested := int(ii.totalShards) / shard.Of
    81  	result := make([]*indexShard, totalRequested)
    82  	var j int
    83  	for i := 0; i < totalRequested; i++ {
    84  		subShard := ((shard.Shard) + (i * shard.Of))
    85  		result[j] = ii.shards[subShard]
    86  		j++
    87  	}
    88  	return result
    89  }
    90  
    91  func (ii *InvertedIndex) validateShard(shard *shard.Annotation) error {
    92  	if shard == nil {
    93  		return nil
    94  	}
    95  	if int(ii.totalShards)%shard.Of != 0 || uint32(shard.Of) > ii.totalShards {
    96  		return fmt.Errorf("%w index_shard:%d query_shard:%v", ErrInvalidShardQuery, ii.totalShards, shard)
    97  	}
    98  	return nil
    99  }
   100  
   101  // Add a fingerprint under the specified labels.
   102  // NOTE: memory for `labels` is unsafe; anything retained beyond the
   103  // life of this function must be copied
   104  func (ii *InvertedIndex) Add(labels phlaremodel.Labels, fp model.Fingerprint) phlaremodel.Labels {
   105  	shardIndex := labelsSeriesIDHash(labels)
   106  	shard := ii.shards[shardIndex%ii.totalShards]
   107  	return shard.add(labels, fp) // add() returns 'interned' values so the original labels are not retained
   108  }
   109  
   110  var (
   111  	bufferPool = sync.Pool{
   112  		New: func() interface{} {
   113  			return bytes.NewBuffer(make([]byte, 0, 1000))
   114  		},
   115  	}
   116  	base64Pool = sync.Pool{
   117  		New: func() interface{} {
   118  			return bytes.NewBuffer(make([]byte, 0, base64.RawStdEncoding.EncodedLen(sha256.Size)))
   119  		},
   120  	}
   121  )
   122  
   123  func labelsSeriesIDHash(ls []*typesv1.LabelPair) uint32 {
   124  	b64 := base64Pool.Get().(*bytes.Buffer)
   125  	defer func() {
   126  		base64Pool.Put(b64)
   127  	}()
   128  	buf := b64.Bytes()[:b64.Cap()]
   129  	labelsSeriesID(ls, buf)
   130  	return binary.BigEndian.Uint32(buf)
   131  }
   132  
   133  func labelsSeriesID(ls []*typesv1.LabelPair, dest []byte) {
   134  	buf := bufferPool.Get().(*bytes.Buffer)
   135  	defer func() {
   136  		buf.Reset()
   137  		bufferPool.Put(buf)
   138  	}()
   139  	labelsString(buf, ls)
   140  	h := sha256.Sum256(buf.Bytes())
   141  	dest = dest[:base64.RawStdEncoding.EncodedLen(len(h))]
   142  	base64.RawStdEncoding.Encode(dest, h[:])
   143  }
   144  
   145  // Backwards-compatible with model.Metric.String()
   146  func labelsString(b *bytes.Buffer, ls []*typesv1.LabelPair) {
   147  	// metrics name is used in the store for computing shards.
   148  	// see chunk/schema_util.go for more details. `labelsString()`
   149  	for _, l := range ls {
   150  		if l.Name == labels.MetricName {
   151  			b.WriteString(l.Value)
   152  			break
   153  		}
   154  	}
   155  	b.WriteByte('{')
   156  	i := 0
   157  	for _, l := range ls {
   158  		if l.Name == labels.MetricName {
   159  			continue
   160  		}
   161  		if i > 0 {
   162  			b.WriteByte(',')
   163  			b.WriteByte(' ')
   164  		}
   165  		b.WriteString(l.Name)
   166  		b.WriteByte('=')
   167  		var buf [1000]byte
   168  		b.Write(strconv.AppendQuote(buf[:0], l.Value))
   169  		i++
   170  	}
   171  	b.WriteByte('}')
   172  }
   173  
   174  // Lookup all fingerprints for the provided matchers.
   175  func (ii *InvertedIndex) Lookup(matchers []*labels.Matcher, shard *shard.Annotation) ([]model.Fingerprint, error) {
   176  	if err := ii.validateShard(shard); err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	var result []model.Fingerprint
   181  	shards := ii.getShards(shard)
   182  
   183  	// if no matcher is specified, all fingerprints would be returned
   184  	if len(matchers) == 0 {
   185  		for i := range shards {
   186  			fps := shards[i].allFPs()
   187  			result = append(result, fps...)
   188  		}
   189  		return result, nil
   190  	}
   191  
   192  	for i := range shards {
   193  		fps := shards[i].lookup(matchers)
   194  		result = append(result, fps...)
   195  	}
   196  	return result, nil
   197  }
   198  
   199  // LabelNames returns all label names.
   200  func (ii *InvertedIndex) LabelNames(shard *shard.Annotation) ([]string, error) {
   201  	if err := ii.validateShard(shard); err != nil {
   202  		return nil, err
   203  	}
   204  	shards := ii.getShards(shard)
   205  	results := make([][]string, 0, len(shards))
   206  	for i := range shards {
   207  		shardResult := shards[i].labelNames(nil)
   208  		results = append(results, shardResult)
   209  	}
   210  
   211  	return mergeStringSlices(results), nil
   212  }
   213  
   214  // LabelValues returns the values for the given label.
   215  func (ii *InvertedIndex) LabelValues(name string, shard *shard.Annotation) ([]string, error) {
   216  	if err := ii.validateShard(shard); err != nil {
   217  		return nil, err
   218  	}
   219  	shards := ii.getShards(shard)
   220  	results := make([][]string, 0, len(shards))
   221  
   222  	for i := range shards {
   223  		shardResult := shards[i].labelValues(name, nil)
   224  		results = append(results, shardResult)
   225  	}
   226  
   227  	return mergeStringSlices(results), nil
   228  }
   229  
   230  // Delete a fingerprint with the given label pairs.
   231  func (ii *InvertedIndex) Delete(labels []*typesv1.LabelPair, fp model.Fingerprint) {
   232  	shard := ii.shards[labelsSeriesIDHash(labels)%ii.totalShards]
   233  	shard.delete(labels, fp)
   234  }
   235  
   236  // NB slice entries are sorted in fp order.
   237  type indexEntry struct {
   238  	name string
   239  	fps  map[string]indexValueEntry
   240  }
   241  
   242  type indexValueEntry struct {
   243  	value string
   244  	fps   []model.Fingerprint
   245  }
   246  
   247  type unlockIndex map[string]indexEntry
   248  
   249  // This is the prevalent value for Intel and AMD CPUs as-at 2018.
   250  // nolint unused
   251  const cacheLineSize = 64
   252  
   253  // Roughly
   254  // map[labelName] => map[labelValue] => []fingerprint
   255  type indexShard struct {
   256  	shard uint32
   257  	mtx   sync.RWMutex
   258  	idx   unlockIndex
   259  	//nolint structcheck,unused
   260  	pad [cacheLineSize - unsafe.Sizeof(sync.Mutex{}) - unsafe.Sizeof(unlockIndex{})]byte
   261  }
   262  
   263  func copyString(s string) string {
   264  	return string([]byte(s))
   265  }
   266  
   267  // add metric to the index; return all the name/value pairs as a fresh
   268  // sorted slice, referencing 'interned' strings from the index so that
   269  // no references are retained to the memory of `metric`.
   270  func (shard *indexShard) add(metric []*typesv1.LabelPair, fp model.Fingerprint) phlaremodel.Labels {
   271  	shard.mtx.Lock()
   272  	defer shard.mtx.Unlock()
   273  
   274  	internedLabels := make(phlaremodel.Labels, len(metric))
   275  
   276  	for i, pair := range metric {
   277  		values, ok := shard.idx[pair.Name]
   278  		if !ok {
   279  			values = indexEntry{
   280  				name: copyString(pair.Name),
   281  				fps:  map[string]indexValueEntry{},
   282  			}
   283  			shard.idx[values.name] = values
   284  		}
   285  		fingerprints, ok := values.fps[pair.Value]
   286  		if !ok {
   287  			fingerprints = indexValueEntry{
   288  				value: copyString(pair.Value),
   289  			}
   290  		}
   291  		// Insert into the right position to keep fingerprints sorted
   292  		j := sort.Search(len(fingerprints.fps), func(i int) bool {
   293  			return fingerprints.fps[i] >= fp
   294  		})
   295  		fingerprints.fps = append(fingerprints.fps, 0)
   296  		copy(fingerprints.fps[j+1:], fingerprints.fps[j:])
   297  		fingerprints.fps[j] = fp
   298  		values.fps[fingerprints.value] = fingerprints
   299  		internedLabels[i] = &typesv1.LabelPair{Name: values.name, Value: fingerprints.value}
   300  	}
   301  	return internedLabels
   302  }
   303  
   304  func (shard *indexShard) lookup(matchers []*labels.Matcher) []model.Fingerprint {
   305  	// index slice values must only be accessed under lock, so all
   306  	// code paths must take a copy before returning
   307  	shard.mtx.RLock()
   308  	defer shard.mtx.RUnlock()
   309  
   310  	// per-shard intersection is initially nil, which is a special case
   311  	// meaning "everything" when passed to intersect()
   312  	// loop invariant: result is sorted
   313  	var result []model.Fingerprint
   314  	for _, matcher := range matchers {
   315  		values, ok := shard.idx[matcher.Name]
   316  		if !ok {
   317  			return nil
   318  		}
   319  		var toIntersect model.Fingerprints
   320  		if matcher.Type == labels.MatchEqual {
   321  			fps := values.fps[matcher.Value]
   322  			toIntersect = append(toIntersect, fps.fps...) // deliberate copy
   323  		} else if matcher.Type == labels.MatchRegexp && len(FindSetMatches(matcher.Value)) > 0 {
   324  			// The lookup is of the form `=~"a|b|c|d"`
   325  			set := FindSetMatches(matcher.Value)
   326  			for _, value := range set {
   327  				toIntersect = append(toIntersect, values.fps[value].fps...)
   328  			}
   329  			sort.Sort(toIntersect)
   330  		} else {
   331  			// accumulate the matching fingerprints (which are all distinct)
   332  			// then sort to maintain the invariant
   333  			for value, fps := range values.fps {
   334  				if matcher.Matches(value) {
   335  					toIntersect = append(toIntersect, fps.fps...)
   336  				}
   337  			}
   338  			sort.Sort(toIntersect)
   339  		}
   340  		result = intersect(result, toIntersect)
   341  		if len(result) == 0 {
   342  			return nil
   343  		}
   344  	}
   345  
   346  	return result
   347  }
   348  
   349  func (shard *indexShard) allFPs() model.Fingerprints {
   350  	shard.mtx.RLock()
   351  	defer shard.mtx.RUnlock()
   352  
   353  	var fps model.Fingerprints
   354  	for _, ie := range shard.idx {
   355  		for _, ive := range ie.fps {
   356  			fps = append(fps, ive.fps...)
   357  		}
   358  	}
   359  	if len(fps) == 0 {
   360  		return nil
   361  	}
   362  
   363  	var result model.Fingerprints
   364  	m := map[model.Fingerprint]struct{}{}
   365  	for _, fp := range fps {
   366  		if _, ok := m[fp]; !ok {
   367  			m[fp] = struct{}{}
   368  			result = append(result, fp)
   369  		}
   370  	}
   371  	return result
   372  }
   373  
   374  func (shard *indexShard) labelNames(extractor func(unlockIndex) []string) []string {
   375  	shard.mtx.RLock()
   376  	defer shard.mtx.RUnlock()
   377  
   378  	results := make([]string, 0, len(shard.idx))
   379  	if extractor != nil {
   380  		results = append(results, extractor(shard.idx)...)
   381  	} else {
   382  		for name := range shard.idx {
   383  			results = append(results, name)
   384  		}
   385  	}
   386  
   387  	sort.Strings(results)
   388  	return results
   389  }
   390  
   391  func (shard *indexShard) labelValues(
   392  	name string,
   393  	extractor func(indexEntry) []string,
   394  ) []string {
   395  	shard.mtx.RLock()
   396  	defer shard.mtx.RUnlock()
   397  
   398  	values, ok := shard.idx[name]
   399  	if !ok {
   400  		return nil
   401  	}
   402  
   403  	if extractor == nil {
   404  		results := make([]string, 0, len(values.fps))
   405  		for val := range values.fps {
   406  			results = append(results, val)
   407  		}
   408  		sort.Strings(results)
   409  		return results
   410  	}
   411  
   412  	return extractor(values)
   413  }
   414  
   415  func (shard *indexShard) delete(labels []*typesv1.LabelPair, fp model.Fingerprint) {
   416  	shard.mtx.Lock()
   417  	defer shard.mtx.Unlock()
   418  
   419  	for _, pair := range labels {
   420  		name, value := pair.Name, pair.Value
   421  		values, ok := shard.idx[name]
   422  		if !ok {
   423  			continue
   424  		}
   425  		fingerprints, ok := values.fps[value]
   426  		if !ok {
   427  			continue
   428  		}
   429  
   430  		j := sort.Search(len(fingerprints.fps), func(i int) bool {
   431  			return fingerprints.fps[i] >= fp
   432  		})
   433  
   434  		// see if search didn't find fp which matches the condition which means we don't have to do anything.
   435  		if j >= len(fingerprints.fps) || fingerprints.fps[j] != fp {
   436  			continue
   437  		}
   438  		fingerprints.fps = fingerprints.fps[:j+copy(fingerprints.fps[j:], fingerprints.fps[j+1:])]
   439  
   440  		if len(fingerprints.fps) == 0 {
   441  			delete(values.fps, value)
   442  		} else {
   443  			values.fps[value] = fingerprints
   444  		}
   445  
   446  		if len(values.fps) == 0 {
   447  			delete(shard.idx, name)
   448  		} else {
   449  			shard.idx[name] = values
   450  		}
   451  	}
   452  }
   453  
   454  // intersect two sorted lists of fingerprints.  Assumes there are no duplicate
   455  // fingerprints within the input lists.
   456  func intersect(a, b []model.Fingerprint) []model.Fingerprint {
   457  	if a == nil {
   458  		return b
   459  	}
   460  	result := []model.Fingerprint{}
   461  	for i, j := 0, 0; i < len(a) && j < len(b); {
   462  		if a[i] == b[j] {
   463  			result = append(result, a[i])
   464  		}
   465  		if a[i] < b[j] {
   466  			i++
   467  		} else {
   468  			j++
   469  		}
   470  	}
   471  	return result
   472  }
   473  
   474  func mergeStringSlices(ss [][]string) []string {
   475  	switch len(ss) {
   476  	case 0:
   477  		return nil
   478  	case 1:
   479  		return ss[0]
   480  	case 2:
   481  		return mergeTwoStringSlices(ss[0], ss[1])
   482  	default:
   483  		halfway := len(ss) / 2
   484  		return mergeTwoStringSlices(
   485  			mergeStringSlices(ss[:halfway]),
   486  			mergeStringSlices(ss[halfway:]),
   487  		)
   488  	}
   489  }
   490  
   491  func mergeTwoStringSlices(a, b []string) []string {
   492  	result := make([]string, 0, len(a)+len(b))
   493  	i, j := 0, 0
   494  	for i < len(a) && j < len(b) {
   495  		if a[i] < b[j] {
   496  			result = append(result, a[i])
   497  			i++
   498  		} else if a[i] > b[j] {
   499  			result = append(result, b[j])
   500  			j++
   501  		} else {
   502  			result = append(result, a[i])
   503  			i++
   504  			j++
   505  		}
   506  	}
   507  	result = append(result, a[i:]...)
   508  	result = append(result, b[j:]...)
   509  	return result
   510  }
   511  
   512  func FindSetMatches(pattern string) []string {
   513  	// Return empty matches if the wrapper from Prometheus is missing.
   514  	if len(pattern) < 6 || pattern[:4] != "^(?:" || pattern[len(pattern)-2:] != ")$" {
   515  		return nil
   516  	}
   517  	escaped := false
   518  	sets := []*strings.Builder{{}}
   519  	for i := 4; i < len(pattern)-2; i++ {
   520  		if escaped {
   521  			switch {
   522  			case isRegexMetaCharacter(pattern[i]):
   523  				sets[len(sets)-1].WriteByte(pattern[i])
   524  			case pattern[i] == '\\':
   525  				sets[len(sets)-1].WriteByte('\\')
   526  			default:
   527  				return nil
   528  			}
   529  			escaped = false
   530  		} else {
   531  			switch {
   532  			case isRegexMetaCharacter(pattern[i]):
   533  				if pattern[i] == '|' {
   534  					sets = append(sets, &strings.Builder{})
   535  				} else {
   536  					return nil
   537  				}
   538  			case pattern[i] == '\\':
   539  				escaped = true
   540  			default:
   541  				sets[len(sets)-1].WriteByte(pattern[i])
   542  			}
   543  		}
   544  	}
   545  	matches := make([]string, 0, len(sets))
   546  	for _, s := range sets {
   547  		if s.Len() > 0 {
   548  			matches = append(matches, s.String())
   549  		}
   550  	}
   551  	return matches
   552  }