github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/series/index/schema.go (about) 1 package index 2 3 import ( 4 "encoding/binary" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "strconv" 9 "strings" 10 11 "github.com/go-kit/log/level" 12 jsoniter "github.com/json-iterator/go" 13 "github.com/prometheus/common/model" 14 "github.com/prometheus/prometheus/model/labels" 15 16 "github.com/grafana/loki/pkg/querier/astmapper" 17 util_log "github.com/grafana/loki/pkg/util/log" 18 ) 19 20 const ( 21 chunkTimeRangeKeyV1a = 1 22 chunkTimeRangeKeyV1 = '1' 23 chunkTimeRangeKeyV2 = '2' 24 chunkTimeRangeKeyV3 = '3' 25 chunkTimeRangeKeyV4 = '4' 26 chunkTimeRangeKeyV5 = '5' 27 metricNameRangeKeyV1 = '6' 28 29 // For v9 schema 30 seriesRangeKeyV1 = '7' 31 labelSeriesRangeKeyV1 = '8' 32 // For v11 schema 33 labelNamesRangeKeyV1 = '9' 34 ) 35 36 var ( 37 // ErrNotSupported when a schema doesn't support that particular lookup. 38 ErrNotSupported = errors.New("not supported") 39 ErrMetricNameLabelMissing = errors.New("metric name label missing") 40 empty = []byte("-") 41 ) 42 43 type hasChunksForIntervalFunc func(userID, seriesID string, from, through model.Time) (bool, error) 44 45 // Schema interfaces define methods to calculate the hash and range keys needed 46 // to write or read chunks from the external index. 47 48 // SeriesStoreSchema is a schema used by seriesStore 49 type SeriesStoreSchema interface { 50 // When doing a read, use these methods to return the list of entries you should query 51 GetReadQueriesForMetric(from, through model.Time, userID string, metricName string) ([]Query, error) 52 GetReadQueriesForMetricLabel(from, through model.Time, userID string, metricName string, labelName string) ([]Query, error) 53 GetReadQueriesForMetricLabelValue(from, through model.Time, userID string, metricName string, labelName string, labelValue string) ([]Query, error) 54 FilterReadQueries(queries []Query, shard *astmapper.ShardAnnotation) []Query 55 56 // returns cache key string and []IndexEntry per bucket, matched in order 57 GetCacheKeysAndLabelWriteEntries(from, through model.Time, userID string, metricName string, labels labels.Labels, chunkID string) ([]string, [][]Entry, error) 58 GetChunkWriteEntries(from, through model.Time, userID string, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) 59 60 // If the query resulted in series IDs, use this method to find chunks. 61 GetChunksForSeries(from, through model.Time, userID string, seriesID []byte) ([]Query, error) 62 // Returns queries to retrieve all label names of multiple series by id. 63 GetLabelNamesForSeries(from, through model.Time, userID string, seriesID []byte) ([]Query, error) 64 } 65 66 type schemaBucketsFunc func(from, through model.Time, userID string) []Bucket 67 68 // baseSchema implements BaseSchema given a bucketing function and and set of range key callbacks 69 type baseSchema struct { 70 buckets schemaBucketsFunc 71 entries baseEntries 72 } 73 74 // seriesStoreSchema implements SeriesStoreSchema given a bucketing function and and set of range key callbacks 75 type seriesStoreSchema struct { 76 baseSchema 77 entries seriesStoreEntries 78 } 79 80 func newSeriesStoreSchema(buckets schemaBucketsFunc, entries seriesStoreEntries) seriesStoreSchema { 81 return seriesStoreSchema{ 82 baseSchema: baseSchema{buckets: buckets, entries: entries}, 83 entries: entries, 84 } 85 } 86 87 // returns cache key string and []IndexEntry per bucket, matched in order 88 func (s seriesStoreSchema) GetCacheKeysAndLabelWriteEntries(from, through model.Time, userID string, metricName string, labels labels.Labels, chunkID string) ([]string, [][]Entry, error) { 89 var keys []string 90 var indexEntries [][]Entry 91 92 for _, bucket := range s.buckets(from, through, userID) { 93 key := strings.Join([]string{ 94 bucket.tableName, 95 bucket.hashKey, 96 string(labelsSeriesID(labels)), 97 }, 98 "-", 99 ) 100 // This is just encoding to remove invalid characters so that we can put them in memcache. 101 // We're not hashing them as the length of the key is well within memcache bounds. tableName + userid + day + 32Byte(seriesID) 102 key = hex.EncodeToString([]byte(key)) 103 keys = append(keys, key) 104 105 entries, err := s.entries.GetLabelWriteEntries(bucket, metricName, labels, chunkID) 106 if err != nil { 107 return nil, nil, err 108 } 109 indexEntries = append(indexEntries, entries) 110 } 111 return keys, indexEntries, nil 112 } 113 114 func (s seriesStoreSchema) GetChunkWriteEntries(from, through model.Time, userID string, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) { 115 var result []Entry 116 117 for _, bucket := range s.buckets(from, through, userID) { 118 entries, err := s.entries.GetChunkWriteEntries(bucket, metricName, labels, chunkID) 119 if err != nil { 120 return nil, err 121 } 122 result = append(result, entries...) 123 } 124 return result, nil 125 } 126 127 func (s baseSchema) GetReadQueriesForMetric(from, through model.Time, userID string, metricName string) ([]Query, error) { 128 var result []Query 129 130 buckets := s.buckets(from, through, userID) 131 for _, bucket := range buckets { 132 entries, err := s.entries.GetReadMetricQueries(bucket, metricName) 133 if err != nil { 134 return nil, err 135 } 136 result = append(result, entries...) 137 } 138 return result, nil 139 } 140 141 func (s baseSchema) GetReadQueriesForMetricLabel(from, through model.Time, userID string, metricName string, labelName string) ([]Query, error) { 142 var result []Query 143 144 buckets := s.buckets(from, through, userID) 145 for _, bucket := range buckets { 146 entries, err := s.entries.GetReadMetricLabelQueries(bucket, metricName, labelName) 147 if err != nil { 148 return nil, err 149 } 150 result = append(result, entries...) 151 } 152 return result, nil 153 } 154 155 func (s baseSchema) GetReadQueriesForMetricLabelValue(from, through model.Time, userID string, metricName string, labelName string, labelValue string) ([]Query, error) { 156 var result []Query 157 158 buckets := s.buckets(from, through, userID) 159 for _, bucket := range buckets { 160 entries, err := s.entries.GetReadMetricLabelValueQueries(bucket, metricName, labelName, labelValue) 161 if err != nil { 162 return nil, err 163 } 164 result = append(result, entries...) 165 } 166 return result, nil 167 } 168 169 func (s seriesStoreSchema) GetChunksForSeries(from, through model.Time, userID string, seriesID []byte) ([]Query, error) { 170 var result []Query 171 172 buckets := s.buckets(from, through, userID) 173 for _, bucket := range buckets { 174 entries, err := s.entries.GetChunksForSeries(bucket, seriesID) 175 if err != nil { 176 return nil, err 177 } 178 result = append(result, entries...) 179 } 180 return result, nil 181 } 182 183 // GetSeriesDeleteEntries returns IndexEntry's for deleting SeriesIDs from SeriesStore. 184 // Since SeriesIDs are created per bucket, it makes sure that we don't include series entries which are in use by verifying using hasChunksForIntervalFunc i.e 185 // It checks first and last buckets covered by the time interval to see if a SeriesID still has chunks in the store, 186 // if yes then it doesn't include IndexEntry's for that bucket for deletion. 187 func (s seriesStoreSchema) GetSeriesDeleteEntries(from, through model.Time, userID string, metric labels.Labels, hasChunksForIntervalFunc hasChunksForIntervalFunc) ([]Entry, error) { 188 metricName := metric.Get(model.MetricNameLabel) 189 if metricName == "" { 190 return nil, ErrMetricNameLabelMissing 191 } 192 193 buckets := s.buckets(from, through, userID) 194 if len(buckets) == 0 { 195 return nil, nil 196 } 197 198 seriesID := string(labelsSeriesID(metric)) 199 200 // Only first and last buckets needs to be checked for in-use series ids. 201 // Only partially deleted first/last deleted bucket needs to be checked otherwise 202 // not since whole bucket is anyways considered for deletion. 203 204 // Bucket times are relative to the bucket i.e for a per-day bucket 205 // bucket.from would be the number of milliseconds elapsed since the start of that day. 206 // If bucket.from is not 0, it means the from param doesn't align with the start of the bucket. 207 if buckets[0].from != 0 { 208 bucketStartTime := from - model.Time(buckets[0].from) 209 hasChunks, err := hasChunksForIntervalFunc(userID, seriesID, bucketStartTime, bucketStartTime+model.Time(buckets[0].bucketSize)-1) 210 if err != nil { 211 return nil, err 212 } 213 214 if hasChunks { 215 buckets = buckets[1:] 216 if len(buckets) == 0 { 217 return nil, nil 218 } 219 } 220 } 221 222 lastBucket := buckets[len(buckets)-1] 223 224 // Similar to bucket.from, bucket.through here is also relative i.e for a per-day bucket 225 // through would be the number of milliseconds elapsed since the start of that day 226 // If bucket.through is not equal to max size of bucket, it means the through param doesn't align with the end of the bucket. 227 if lastBucket.through != lastBucket.bucketSize { 228 bucketStartTime := through - model.Time(lastBucket.through) 229 hasChunks, err := hasChunksForIntervalFunc(userID, seriesID, bucketStartTime, bucketStartTime+model.Time(lastBucket.bucketSize)-1) 230 if err != nil { 231 return nil, err 232 } 233 234 if hasChunks { 235 buckets = buckets[:len(buckets)-1] 236 if len(buckets) == 0 { 237 return nil, nil 238 } 239 } 240 } 241 242 var result []Entry 243 244 for _, bucket := range buckets { 245 entries, err := s.entries.GetLabelWriteEntries(bucket, metricName, metric, "") 246 if err != nil { 247 return nil, err 248 } 249 result = append(result, entries...) 250 } 251 252 return result, nil 253 } 254 255 func (s seriesStoreSchema) GetLabelNamesForSeries(from, through model.Time, userID string, seriesID []byte) ([]Query, error) { 256 var result []Query 257 258 buckets := s.buckets(from, through, userID) 259 for _, bucket := range buckets { 260 entries, err := s.entries.GetLabelNamesForSeries(bucket, seriesID) 261 if err != nil { 262 return nil, err 263 } 264 result = append(result, entries...) 265 } 266 return result, nil 267 } 268 269 func (s baseSchema) FilterReadQueries(queries []Query, shard *astmapper.ShardAnnotation) []Query { 270 return s.entries.FilterReadQueries(queries, shard) 271 } 272 273 type baseEntries interface { 274 GetReadMetricQueries(bucket Bucket, metricName string) ([]Query, error) 275 GetReadMetricLabelQueries(bucket Bucket, metricName string, labelName string) ([]Query, error) 276 GetReadMetricLabelValueQueries(bucket Bucket, metricName string, labelName string, labelValue string) ([]Query, error) 277 FilterReadQueries(queries []Query, shard *astmapper.ShardAnnotation) []Query 278 } 279 280 // used by seriesStoreSchema 281 type seriesStoreEntries interface { 282 baseEntries 283 284 GetLabelWriteEntries(bucket Bucket, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) 285 GetChunkWriteEntries(bucket Bucket, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) 286 287 GetChunksForSeries(bucket Bucket, seriesID []byte) ([]Query, error) 288 GetLabelNamesForSeries(bucket Bucket, seriesID []byte) ([]Query, error) 289 } 290 291 // v9Entries adds a layer of indirection between labels -> series -> chunks. 292 type v9Entries struct{} 293 294 func (v9Entries) GetLabelWriteEntries(bucket Bucket, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) { 295 seriesID := labelsSeriesID(labels) 296 297 entries := []Entry{ 298 // Entry for metricName -> seriesID 299 { 300 TableName: bucket.tableName, 301 HashValue: bucket.hashKey + ":" + metricName, 302 RangeValue: encodeRangeKey(seriesRangeKeyV1, seriesID, nil, nil), 303 Value: empty, 304 }, 305 } 306 307 // Entries for metricName:labelName -> hash(value):seriesID 308 // We use a hash of the value to limit its length. 309 for _, v := range labels { 310 if v.Name == model.MetricNameLabel { 311 continue 312 } 313 valueHash := sha256bytes(v.Value) 314 entries = append(entries, Entry{ 315 TableName: bucket.tableName, 316 HashValue: fmt.Sprintf("%s:%s:%s", bucket.hashKey, metricName, v.Name), 317 RangeValue: encodeRangeKey(labelSeriesRangeKeyV1, valueHash, seriesID, nil), 318 Value: []byte(v.Value), 319 }) 320 } 321 322 return entries, nil 323 } 324 325 func (v9Entries) GetChunkWriteEntries(bucket Bucket, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) { 326 seriesID := labelsSeriesID(labels) 327 encodedThroughBytes := encodeTime(bucket.through) 328 329 entries := []Entry{ 330 // Entry for seriesID -> chunkID 331 { 332 TableName: bucket.tableName, 333 HashValue: bucket.hashKey + ":" + string(seriesID), 334 RangeValue: encodeRangeKey(chunkTimeRangeKeyV3, encodedThroughBytes, nil, []byte(chunkID)), 335 }, 336 } 337 338 return entries, nil 339 } 340 341 func (v9Entries) GetReadMetricQueries(bucket Bucket, metricName string) ([]Query, error) { 342 return []Query{ 343 { 344 TableName: bucket.tableName, 345 HashValue: bucket.hashKey + ":" + metricName, 346 }, 347 }, nil 348 } 349 350 func (v9Entries) GetReadMetricLabelQueries(bucket Bucket, metricName string, labelName string) ([]Query, error) { 351 return []Query{ 352 { 353 TableName: bucket.tableName, 354 HashValue: fmt.Sprintf("%s:%s:%s", bucket.hashKey, metricName, labelName), 355 }, 356 }, nil 357 } 358 359 func (v9Entries) GetReadMetricLabelValueQueries(bucket Bucket, metricName string, labelName string, labelValue string) ([]Query, error) { 360 valueHash := sha256bytes(labelValue) 361 return []Query{ 362 { 363 TableName: bucket.tableName, 364 HashValue: fmt.Sprintf("%s:%s:%s", bucket.hashKey, metricName, labelName), 365 RangeValuePrefix: rangeValuePrefix(valueHash), 366 ValueEqual: []byte(labelValue), 367 }, 368 }, nil 369 } 370 371 func (v9Entries) GetChunksForSeries(bucket Bucket, seriesID []byte) ([]Query, error) { 372 encodedFromBytes := encodeTime(bucket.from) 373 return []Query{ 374 { 375 TableName: bucket.tableName, 376 HashValue: bucket.hashKey + ":" + string(seriesID), 377 RangeValueStart: rangeValuePrefix(encodedFromBytes), 378 }, 379 }, nil 380 } 381 382 func (v9Entries) GetLabelNamesForSeries(_ Bucket, _ []byte) ([]Query, error) { 383 return nil, ErrNotSupported 384 } 385 386 func (v9Entries) FilterReadQueries(queries []Query, shard *astmapper.ShardAnnotation) []Query { 387 return queries 388 } 389 390 // v10Entries builds on v9 by sharding index rows to reduce their size. 391 type v10Entries struct { 392 rowShards uint32 393 } 394 395 func (s v10Entries) GetLabelWriteEntries(bucket Bucket, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) { 396 seriesID := labelsSeriesID(labels) 397 398 // read first 32 bits of the hash and use this to calculate the shard 399 shard := binary.BigEndian.Uint32(seriesID) % s.rowShards 400 401 entries := []Entry{ 402 // Entry for metricName -> seriesID 403 { 404 TableName: bucket.tableName, 405 HashValue: fmt.Sprintf("%02d:%s:%s", shard, bucket.hashKey, metricName), 406 RangeValue: encodeRangeKey(seriesRangeKeyV1, seriesID, nil, nil), 407 Value: empty, 408 }, 409 } 410 411 // Entries for metricName:labelName -> hash(value):seriesID 412 // We use a hash of the value to limit its length. 413 for _, v := range labels { 414 if v.Name == model.MetricNameLabel { 415 continue 416 } 417 valueHash := sha256bytes(v.Value) 418 entries = append(entries, Entry{ 419 TableName: bucket.tableName, 420 HashValue: fmt.Sprintf("%02d:%s:%s:%s", shard, bucket.hashKey, metricName, v.Name), 421 RangeValue: encodeRangeKey(labelSeriesRangeKeyV1, valueHash, seriesID, nil), 422 Value: []byte(v.Value), 423 }) 424 } 425 426 return entries, nil 427 } 428 429 func (v10Entries) GetChunkWriteEntries(bucket Bucket, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) { 430 seriesID := labelsSeriesID(labels) 431 encodedThroughBytes := encodeTime(bucket.through) 432 433 entries := []Entry{ 434 // Entry for seriesID -> chunkID 435 { 436 TableName: bucket.tableName, 437 HashValue: bucket.hashKey + ":" + string(seriesID), 438 RangeValue: encodeRangeKey(chunkTimeRangeKeyV3, encodedThroughBytes, nil, []byte(chunkID)), 439 Value: empty, 440 }, 441 } 442 443 return entries, nil 444 } 445 446 func (s v10Entries) GetReadMetricQueries(bucket Bucket, metricName string) ([]Query, error) { 447 result := make([]Query, 0, s.rowShards) 448 for i := uint32(0); i < s.rowShards; i++ { 449 result = append(result, Query{ 450 TableName: bucket.tableName, 451 HashValue: fmt.Sprintf("%02d:%s:%s", i, bucket.hashKey, metricName), 452 }) 453 } 454 return result, nil 455 } 456 457 func (s v10Entries) GetReadMetricLabelQueries(bucket Bucket, metricName string, labelName string) ([]Query, error) { 458 result := make([]Query, 0, s.rowShards) 459 for i := uint32(0); i < s.rowShards; i++ { 460 result = append(result, Query{ 461 TableName: bucket.tableName, 462 HashValue: fmt.Sprintf("%02d:%s:%s:%s", i, bucket.hashKey, metricName, labelName), 463 }) 464 } 465 return result, nil 466 } 467 468 func (s v10Entries) GetReadMetricLabelValueQueries(bucket Bucket, metricName string, labelName string, labelValue string) ([]Query, error) { 469 valueHash := sha256bytes(labelValue) 470 result := make([]Query, 0, s.rowShards) 471 for i := uint32(0); i < s.rowShards; i++ { 472 result = append(result, Query{ 473 TableName: bucket.tableName, 474 HashValue: fmt.Sprintf("%02d:%s:%s:%s", i, bucket.hashKey, metricName, labelName), 475 RangeValuePrefix: rangeValuePrefix(valueHash), 476 ValueEqual: []byte(labelValue), 477 }) 478 } 479 return result, nil 480 } 481 482 func (v10Entries) GetChunksForSeries(bucket Bucket, seriesID []byte) ([]Query, error) { 483 encodedFromBytes := encodeTime(bucket.from) 484 return []Query{ 485 { 486 TableName: bucket.tableName, 487 HashValue: bucket.hashKey + ":" + string(seriesID), 488 RangeValueStart: rangeValuePrefix(encodedFromBytes), 489 }, 490 }, nil 491 } 492 493 func (v10Entries) GetLabelNamesForSeries(_ Bucket, _ []byte) ([]Query, error) { 494 return nil, ErrNotSupported 495 } 496 497 // FilterReadQueries will return only queries that match a certain shard 498 func (v10Entries) FilterReadQueries(queries []Query, shard *astmapper.ShardAnnotation) (matches []Query) { 499 if shard == nil { 500 return queries 501 } 502 503 for _, query := range queries { 504 s := strings.Split(query.HashValue, ":")[0] 505 n, err := strconv.Atoi(s) 506 if err != nil { 507 level.Error(util_log.Logger).Log( 508 "msg", 509 "Unable to determine shard from IndexQuery", 510 "HashValue", 511 query.HashValue, 512 "schema", 513 "v10", 514 ) 515 } 516 517 if err == nil && n == shard.Shard { 518 matches = append(matches, query) 519 } 520 } 521 return matches 522 } 523 524 // v11Entries builds on v10 but adds index entries for each series to store respective labels. 525 type v11Entries struct { 526 v10Entries 527 } 528 529 func (s v11Entries) GetLabelWriteEntries(bucket Bucket, metricName string, labels labels.Labels, chunkID string) ([]Entry, error) { 530 seriesID := labelsSeriesID(labels) 531 532 // read first 32 bits of the hash and use this to calculate the shard 533 shard := binary.BigEndian.Uint32(seriesID) % s.rowShards 534 535 labelNames := make([]string, 0, len(labels)) 536 for _, l := range labels { 537 if l.Name == model.MetricNameLabel { 538 continue 539 } 540 labelNames = append(labelNames, l.Name) 541 } 542 data, err := jsoniter.ConfigFastest.Marshal(labelNames) 543 if err != nil { 544 return nil, err 545 } 546 entries := []Entry{ 547 // Entry for metricName -> seriesID 548 { 549 TableName: bucket.tableName, 550 HashValue: fmt.Sprintf("%02d:%s:%s", shard, bucket.hashKey, metricName), 551 RangeValue: encodeRangeKey(seriesRangeKeyV1, seriesID, nil, nil), 552 Value: empty, 553 }, 554 // Entry for seriesID -> label names 555 { 556 TableName: bucket.tableName, 557 HashValue: string(seriesID), 558 RangeValue: encodeRangeKey(labelNamesRangeKeyV1, nil, nil, nil), 559 Value: data, 560 }, 561 } 562 563 // Entries for metricName:labelName -> hash(value):seriesID 564 // We use a hash of the value to limit its length. 565 for _, v := range labels { 566 if v.Name == model.MetricNameLabel { 567 continue 568 } 569 valueHash := sha256bytes(v.Value) 570 entries = append(entries, Entry{ 571 TableName: bucket.tableName, 572 HashValue: fmt.Sprintf("%02d:%s:%s:%s", shard, bucket.hashKey, metricName, v.Name), 573 RangeValue: encodeRangeKey(labelSeriesRangeKeyV1, valueHash, seriesID, nil), 574 Value: []byte(v.Value), 575 }) 576 } 577 578 return entries, nil 579 } 580 581 func (v11Entries) GetLabelNamesForSeries(bucket Bucket, seriesID []byte) ([]Query, error) { 582 return []Query{ 583 { 584 TableName: bucket.tableName, 585 HashValue: string(seriesID), 586 }, 587 }, nil 588 } 589 590 type v12Entries struct { 591 v11Entries 592 }