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