github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/index/bitprefix.go (about) 1 package index 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/prometheus/common/model" 8 "github.com/prometheus/prometheus/model/labels" 9 10 "github.com/grafana/loki/pkg/logproto" 11 "github.com/grafana/loki/pkg/querier/astmapper" 12 "github.com/grafana/loki/pkg/storage/stores/tsdb/index" 13 ) 14 15 // BitPrefixInvertedIndex is another inverted index implementation 16 // that uses the bit prefix sharding algorithm in tsdb/index/shard.go 17 // instead of a modulo approach. 18 // This is the standard for TSDB compatibility because 19 // the same series must resolve to the same shard (for each period config), 20 // whether it's resolved on the ingester or via the store. 21 type BitPrefixInvertedIndex struct { 22 totalShards uint32 23 shards []*indexShard 24 } 25 26 func ValidateBitPrefixShardFactor(factor uint32) error { 27 if requiredBits := index.NewShard(0, factor).RequiredBits(); 1<<requiredBits != factor { 28 return fmt.Errorf("Incompatible inverted index shard factor on ingester: It must be a power of two, got %d", factor) 29 } 30 return nil 31 } 32 33 func NewBitPrefixWithShards(totalShards uint32) (*BitPrefixInvertedIndex, error) { 34 if err := ValidateBitPrefixShardFactor(totalShards); err != nil { 35 return nil, err 36 } 37 38 shards := make([]*indexShard, totalShards) 39 for i := uint32(0); i < totalShards; i++ { 40 shards[i] = &indexShard{ 41 idx: map[string]indexEntry{}, 42 shard: i, 43 } 44 } 45 return &BitPrefixInvertedIndex{ 46 totalShards: totalShards, 47 shards: shards, 48 }, nil 49 } 50 51 func (ii *BitPrefixInvertedIndex) getShards(shard *astmapper.ShardAnnotation) ([]*indexShard, bool) { 52 if shard == nil { 53 return ii.shards, false 54 } 55 56 // When comparing a higher shard factor to a lower inverted index shard factor 57 // we must filter resulting fingerprints as the the lower shard factor in the 58 // inverted index is a superset of the requested factor. 59 // 60 // For instance, the 3_of_4 shard factor maps to the bit prefix 0b11. 61 // If the inverted index only has a factor of 2, we'll need to check the 0b1 62 // prefixed shard (which contains the 0b10 and 0b11 prefixes). 63 // Conversely, if the requested shard is 1_of_2, but the index has a factor of 4, 64 // we can _exactly_ match ob1 => (ob10, ob11) and know all fingerprints in those 65 // resulting shards have the requested ob1 prefix (don't need to filter). 66 var filter bool 67 if shard.Of > len(ii.shards) { 68 filter = true 69 } 70 71 requestedShard := shard.TSDB() 72 minFp, maxFp := requestedShard.Bounds() 73 74 // Determine how many bits we need to take from 75 // the requested shard's min/max fingerprint values 76 // in order to calculate the indices for the inverted index's 77 // shard factor. 78 requiredBits := index.NewShard(0, uint32(len(ii.shards))).RequiredBits() 79 lowerIdx := int(minFp >> (64 - requiredBits)) 80 upperIdx := int(maxFp >> (64 - requiredBits)) 81 82 // If the upper bound's shard doesn't align exactly 83 // with the maximum fingerprint, we must also 84 // check the subsequent shard. 85 // This happens in two cases: 86 // 1) When requesting the last shard of any factor. 87 // This accounts for zero indexing in our sharding logic 88 // to successfully request `shards[start:len(shards)]` 89 // 2) When requesting the _first_ shard of a larger factor 90 // than the index uses. In this case, the required_bits are not 91 // enough and the requested end prefix gets trimmed. 92 // If confused, comment out this line and see which tests fail. 93 if (upperIdx << (64 - requiredBits)) != int(maxFp) { 94 upperIdx++ 95 } 96 97 return ii.shards[lowerIdx:upperIdx], filter 98 } 99 100 func (ii *BitPrefixInvertedIndex) shardForFP(fp model.Fingerprint) int { 101 localShard := index.NewShard(0, uint32(len(ii.shards))) 102 return int(fp >> (64 - localShard.RequiredBits())) 103 } 104 105 func (ii *BitPrefixInvertedIndex) validateShard(shard *astmapper.ShardAnnotation) error { 106 if shard == nil { 107 return nil 108 } 109 110 return shard.TSDB().Validate() 111 } 112 113 // Add a fingerprint under the specified labels. 114 // NOTE: memory for `labels` is unsafe; anything retained beyond the 115 // life of this function must be copied 116 func (ii *BitPrefixInvertedIndex) Add(labels []logproto.LabelAdapter, fp model.Fingerprint) labels.Labels { 117 // add() returns 'interned' values so the original labels are not retained 118 return ii.shards[ii.shardForFP(fp)].add(labels, fp) 119 } 120 121 // Lookup all fingerprints for the provided matchers. 122 func (ii *BitPrefixInvertedIndex) Lookup(matchers []*labels.Matcher, shard *astmapper.ShardAnnotation) ([]model.Fingerprint, error) { 123 if err := ii.validateShard(shard); err != nil { 124 return nil, err 125 } 126 127 var result []model.Fingerprint 128 shards, filter := ii.getShards(shard) 129 130 // if no matcher is specified, all fingerprints would be returned 131 if len(matchers) == 0 { 132 for i := range shards { 133 fps := shards[i].allFPs() 134 result = append(result, fps...) 135 } 136 } else { 137 for i := range shards { 138 fps := shards[i].lookup(matchers) 139 result = append(result, fps...) 140 } 141 } 142 143 // Because bit prefix order is also ascending order, 144 // the merged fingerprints from ascending shards are also in order. 145 if filter { 146 minFP, maxFP := shard.TSDB().Bounds() 147 minIdx := sort.Search(len(result), func(i int) bool { 148 return result[i] >= minFP 149 }) 150 151 maxIdx := sort.Search(len(result), func(i int) bool { 152 return result[i] >= maxFP 153 }) 154 155 result = result[minIdx:maxIdx] 156 } 157 158 return result, nil 159 } 160 161 // LabelNames returns all label names. 162 func (ii *BitPrefixInvertedIndex) LabelNames(shard *astmapper.ShardAnnotation) ([]string, error) { 163 if err := ii.validateShard(shard); err != nil { 164 return nil, err 165 } 166 167 var extractor func(unlockIndex) []string 168 shards, filter := ii.getShards(shard) 169 170 // If we need to check shard inclusion, we have to do it the expensive way :( 171 // Therefore it's more performant to request shard factors lower or equal to the 172 // inverted index factor 173 if filter { 174 s := shard.TSDB() 175 176 extractor = func(x unlockIndex) (results []string) { 177 178 outer: 179 for name, entry := range x { 180 for _, valEntry := range entry.fps { 181 for _, fp := range valEntry.fps { 182 if s.Match(fp) { 183 results = append(results, name) 184 continue outer 185 } 186 } 187 } 188 } 189 190 return results 191 } 192 } 193 194 results := make([][]string, 0, len(shards)) 195 for i := range shards { 196 shardResult := shards[i].labelNames(extractor) 197 results = append(results, shardResult) 198 } 199 200 return mergeStringSlices(results), nil 201 } 202 203 // LabelValues returns the values for the given label. 204 func (ii *BitPrefixInvertedIndex) LabelValues(name string, shard *astmapper.ShardAnnotation) ([]string, error) { 205 if err := ii.validateShard(shard); err != nil { 206 return nil, err 207 } 208 209 var extractor func(indexEntry) []string 210 shards, filter := ii.getShards(shard) 211 if filter { 212 s := shard.TSDB() 213 214 extractor = func(x indexEntry) []string { 215 results := make([]string, 0, len(x.fps)) 216 217 outer: 218 for val, valEntry := range x.fps { 219 for _, fp := range valEntry.fps { 220 if s.Match(fp) { 221 results = append(results, val) 222 continue outer 223 } 224 } 225 } 226 return results 227 } 228 } 229 results := make([][]string, 0, len(shards)) 230 231 for i := range shards { 232 shardResult := shards[i].labelValues(name, extractor) 233 results = append(results, shardResult) 234 } 235 236 return mergeStringSlices(results), nil 237 } 238 239 // Delete a fingerprint with the given label pairs. 240 func (ii *BitPrefixInvertedIndex) Delete(labels labels.Labels, fp model.Fingerprint) { 241 localShard := index.NewShard(0, uint32(len(ii.shards))) 242 idx := int(fp >> (64 - localShard.RequiredBits())) 243 ii.shards[idx].delete(labels, fp) 244 }