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 }