-
Notifications
You must be signed in to change notification settings - Fork 781
/
ring.go
747 lines (625 loc) · 23.7 KB
/
ring.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
package ring
// Based on https://raw.githubusercontent.com/stathat/consistent/master/consistent.go
import (
"context"
"errors"
"flag"
"fmt"
"math"
"math/rand"
"sync"
"time"
"github.com/go-kit/kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/cortexproject/cortex/pkg/ring/kv"
"github.com/cortexproject/cortex/pkg/util"
"github.com/cortexproject/cortex/pkg/util/services"
)
const (
unhealthy = "Unhealthy"
// IngesterRingKey is the key under which we store the ingesters ring in the KVStore.
IngesterRingKey = "ring"
// RulerRingKey is the key under which we store the rulers ring in the KVStore.
RulerRingKey = "ring"
// DistributorRingKey is the key under which we store the distributors ring in the KVStore.
DistributorRingKey = "distributor"
// CompactorRingKey is the key under which we store the compactors ring in the KVStore.
CompactorRingKey = "compactor"
)
// ReadRing represents the read interface to the ring.
type ReadRing interface {
prometheus.Collector
// Get returns n (or more) ingesters which form the replicas for the given key.
// buf is a slice to be overwritten for the return value
// to avoid memory allocation; can be nil.
Get(key uint32, op Operation, buf []IngesterDesc) (ReplicationSet, error)
// GetAllHealthy returns all healthy instances in the ring, for the given operation.
// This function doesn't check if the quorum is honored, so doesn't fail if the number
// of unhealthy ingesters is greater than the tolerated max unavailable.
GetAllHealthy(op Operation) (ReplicationSet, error)
// GetReplicationSetForOperation returns all instances where the input operation should be executed.
// The resulting ReplicationSet doesn't necessarily contains all healthy instances
// in the ring, but could contain the minimum set of instances required to execute
// the input operation.
GetReplicationSetForOperation(op Operation) (ReplicationSet, error)
ReplicationFactor() int
IngesterCount() int
// ShuffleShard returns a subring for the provided identifier (eg. a tenant ID)
// and size (number of instances).
ShuffleShard(identifier string, size int) ReadRing
// ShuffleShardWithLookback is like ShuffleShard() but the returned subring includes
// all instances that have been part of the identifier's shard since "now - lookbackPeriod".
ShuffleShardWithLookback(identifier string, size int, lookbackPeriod time.Duration, now time.Time) ReadRing
// HasInstance returns whether the ring contains an instance matching the provided instanceID.
HasInstance(instanceID string) bool
}
// Operation can be Read or Write
type Operation int
// Values for Operation
const (
Read Operation = iota
Write
Reporting // Special value for inquiring about health
// BlocksSync is the operation run by the store-gateway to sync blocks.
BlocksSync
// BlocksRead is the operation run by the querier to query blocks via the store-gateway.
BlocksRead
// Ruler is the operation used for distributing rule groups between rulers.
Ruler
// Compactor is the operation used for distributing tenants/blocks across compactors.
Compactor
)
var (
// ErrEmptyRing is the error returned when trying to get an element when nothing has been added to hash.
ErrEmptyRing = errors.New("empty ring")
// ErrInstanceNotFound is the error returned when trying to get information for an instance
// not registered within the ring.
ErrInstanceNotFound = errors.New("instance not found in the ring")
// ErrTooManyFailedIngesters is the error returned when there are too many failed ingesters for a
// specific operation.
ErrTooManyFailedIngesters = errors.New("too many failed ingesters")
)
// Config for a Ring
type Config struct {
KVStore kv.Config `yaml:"kvstore"`
HeartbeatTimeout time.Duration `yaml:"heartbeat_timeout"`
ReplicationFactor int `yaml:"replication_factor"`
ZoneAwarenessEnabled bool `yaml:"zone_awareness_enabled"`
ExtendWrites bool `yaml:"extend_writes"`
}
// RegisterFlags adds the flags required to config this to the given FlagSet with a specified prefix
func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
cfg.RegisterFlagsWithPrefix("", f)
}
// RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet with a specified prefix
func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
cfg.KVStore.RegisterFlagsWithPrefix(prefix, "collectors/", f)
f.DurationVar(&cfg.HeartbeatTimeout, prefix+"ring.heartbeat-timeout", time.Minute, "The heartbeat timeout after which ingesters are skipped for reads/writes.")
f.IntVar(&cfg.ReplicationFactor, prefix+"distributor.replication-factor", 3, "The number of ingesters to write to and read from.")
f.BoolVar(&cfg.ZoneAwarenessEnabled, prefix+"distributor.zone-awareness-enabled", false, "True to enable the zone-awareness and replicate ingested samples across different availability zones.")
f.BoolVar(&cfg.ExtendWrites, prefix+"distributor.extend-writes", true, "Try writing to an additional ingester in the presence of an ingester not in the ACTIVE state. It is useful to disable this along with -ingester.unregister-on-shutdown=false in order to not spread samples to extra ingesters during rolling restarts with consistent naming.")
}
// Ring holds the information about the members of the consistent hash ring.
type Ring struct {
services.Service
key string
cfg Config
KVClient kv.Client
strategy ReplicationStrategy
mtx sync.RWMutex
ringDesc *Desc
ringTokens []TokenDesc
ringTokensByZone map[string][]TokenDesc
// When did a set of instances change the last time (instance changing state or heartbeat is ignored for this timestamp).
lastTopologyChange time.Time
// List of zones for which there's at least 1 instance in the ring. This list is guaranteed
// to be sorted alphabetically.
ringZones []string
// Cache of shuffle-sharded subrings per identifier. Invalidated when topology changes.
// If set to nil, no caching is done (used by tests, and subrings).
shuffledSubringCache map[subringCacheKey]*Ring
memberOwnershipDesc *prometheus.Desc
numMembersDesc *prometheus.Desc
totalTokensDesc *prometheus.Desc
numTokensDesc *prometheus.Desc
oldestTimestampDesc *prometheus.Desc
}
type subringCacheKey struct {
identifier string
shardSize int
}
// New creates a new Ring. Being a service, Ring needs to be started to do anything.
func New(cfg Config, name, key string, reg prometheus.Registerer) (*Ring, error) {
codec := GetCodec()
// Suffix all client names with "-ring" to denote this kv client is used by the ring
store, err := kv.NewClient(
cfg.KVStore,
codec,
kv.RegistererWithKVName(reg, name+"-ring"),
)
if err != nil {
return nil, err
}
return NewWithStoreClientAndStrategy(cfg, name, key, store, NewDefaultReplicationStrategy(cfg.ExtendWrites))
}
func NewWithStoreClientAndStrategy(cfg Config, name, key string, store kv.Client, strategy ReplicationStrategy) (*Ring, error) {
if cfg.ReplicationFactor <= 0 {
return nil, fmt.Errorf("ReplicationFactor must be greater than zero: %d", cfg.ReplicationFactor)
}
r := &Ring{
key: key,
cfg: cfg,
KVClient: store,
strategy: strategy,
ringDesc: &Desc{},
shuffledSubringCache: map[subringCacheKey]*Ring{},
memberOwnershipDesc: prometheus.NewDesc(
"cortex_ring_member_ownership_percent",
"The percent ownership of the ring by member",
[]string{"member"},
map[string]string{"name": name},
),
numMembersDesc: prometheus.NewDesc(
"cortex_ring_members",
"Number of members in the ring",
[]string{"state"},
map[string]string{"name": name},
),
totalTokensDesc: prometheus.NewDesc(
"cortex_ring_tokens_total",
"Number of tokens in the ring",
nil,
map[string]string{"name": name},
),
numTokensDesc: prometheus.NewDesc(
"cortex_ring_tokens_owned",
"The number of tokens in the ring owned by the member",
[]string{"member"},
map[string]string{"name": name},
),
oldestTimestampDesc: prometheus.NewDesc(
"cortex_ring_oldest_member_timestamp",
"Timestamp of the oldest member in the ring.",
[]string{"state"},
map[string]string{"name": name},
),
}
r.Service = services.NewBasicService(nil, r.loop, nil)
return r, nil
}
func (r *Ring) loop(ctx context.Context) error {
r.KVClient.WatchKey(ctx, r.key, func(value interface{}) bool {
if value == nil {
level.Info(util.Logger).Log("msg", "ring doesn't exist in KV store yet")
return true
}
ringDesc := value.(*Desc)
r.mtx.RLock()
prevRing := r.ringDesc
r.mtx.RUnlock()
rc := prevRing.RingCompare(ringDesc)
if rc == Equal || rc == EqualButStatesAndTimestamps {
// No need to update tokens or zones. Only states and timestamps
// have changed. (If Equal, nothing has changed, but that doesn't happen
// when watching the ring for updates).
r.mtx.Lock()
r.ringDesc = ringDesc
r.mtx.Unlock()
return true
}
now := time.Now()
ringTokens := ringDesc.getTokens()
ringTokensByZone := ringDesc.getTokensByZone()
ringZones := getZones(ringTokensByZone)
r.mtx.Lock()
defer r.mtx.Unlock()
r.ringDesc = ringDesc
r.ringTokens = ringTokens
r.ringTokensByZone = ringTokensByZone
r.ringZones = ringZones
r.lastTopologyChange = now
if r.shuffledSubringCache != nil {
// Invalidate all cached subrings.
r.shuffledSubringCache = make(map[subringCacheKey]*Ring)
}
return true
})
return nil
}
// Get returns n (or more) ingesters which form the replicas for the given key.
func (r *Ring) Get(key uint32, op Operation, buf []IngesterDesc) (ReplicationSet, error) {
r.mtx.RLock()
defer r.mtx.RUnlock()
if r.ringDesc == nil || len(r.ringTokens) == 0 {
return ReplicationSet{}, ErrEmptyRing
}
var (
n = r.cfg.ReplicationFactor
ingesters = buf[:0]
distinctHosts = map[string]struct{}{}
distinctZones = map[string]struct{}{}
start = searchToken(r.ringTokens, key)
iterations = 0
)
for i := start; len(distinctHosts) < n && iterations < len(r.ringTokens); i++ {
iterations++
// Wrap i around in the ring.
i %= len(r.ringTokens)
// We want n *distinct* ingesters && distinct zones.
token := r.ringTokens[i]
if _, ok := distinctHosts[token.Ingester]; ok {
continue
}
// Ignore if the ingesters don't have a zone set.
if r.cfg.ZoneAwarenessEnabled && token.Zone != "" {
if _, ok := distinctZones[token.Zone]; ok {
continue
}
distinctZones[token.Zone] = struct{}{}
}
distinctHosts[token.Ingester] = struct{}{}
ingester := r.ringDesc.Ingesters[token.Ingester]
// Check whether the replica set should be extended given we're including
// this instance.
if r.strategy.ShouldExtendReplicaSet(ingester, op) {
n++
}
ingesters = append(ingesters, ingester)
}
liveIngesters, maxFailure, err := r.strategy.Filter(ingesters, op, r.cfg.ReplicationFactor, r.cfg.HeartbeatTimeout, r.cfg.ZoneAwarenessEnabled)
if err != nil {
return ReplicationSet{}, err
}
return ReplicationSet{
Ingesters: liveIngesters,
MaxErrors: maxFailure,
}, nil
}
// GetAllHealthy implements ReadRing.
func (r *Ring) GetAllHealthy(op Operation) (ReplicationSet, error) {
r.mtx.RLock()
defer r.mtx.RUnlock()
if r.ringDesc == nil || len(r.ringDesc.Ingesters) == 0 {
return ReplicationSet{}, ErrEmptyRing
}
ingesters := make([]IngesterDesc, 0, len(r.ringDesc.Ingesters))
for _, ingester := range r.ringDesc.Ingesters {
if r.IsHealthy(&ingester, op) {
ingesters = append(ingesters, ingester)
}
}
return ReplicationSet{
Ingesters: ingesters,
MaxErrors: 0,
}, nil
}
// GetReplicationSetForOperation implements ReadRing.
func (r *Ring) GetReplicationSetForOperation(op Operation) (ReplicationSet, error) {
r.mtx.RLock()
defer r.mtx.RUnlock()
if r.ringDesc == nil || len(r.ringTokens) == 0 {
return ReplicationSet{}, ErrEmptyRing
}
// Build the initial replication set, excluding unhealthy instances.
healthyInstances := make([]IngesterDesc, 0, len(r.ringDesc.Ingesters))
zoneFailures := make(map[string]struct{})
for _, ingester := range r.ringDesc.Ingesters {
if r.IsHealthy(&ingester, op) {
healthyInstances = append(healthyInstances, ingester)
} else {
zoneFailures[ingester.Zone] = struct{}{}
}
}
// Max errors and max unavailable zones are mutually exclusive. We initialise both
// to 0 and then we update them whether zone-awareness is enabled or not.
maxErrors := 0
maxUnavailableZones := 0
if r.cfg.ZoneAwarenessEnabled {
// Given data is replicated to RF different zones, we can tolerate a number of
// RF/2 failing zones. However, we need to protect from the case the ring currently
// contains instances in a number of zones < RF.
numReplicatedZones := util.Min(len(r.ringZones), r.cfg.ReplicationFactor)
minSuccessZones := (numReplicatedZones / 2) + 1
maxUnavailableZones = minSuccessZones - 1
if len(zoneFailures) > maxUnavailableZones {
return ReplicationSet{}, ErrTooManyFailedIngesters
}
if len(zoneFailures) > 0 {
// We remove all instances (even healthy ones) from zones with at least
// 1 failing ingester. Due to how replication works when zone-awareness is
// enabled (data is replicated to RF different zones), there's no benefit in
// querying healthy instances from "failing zones". A zone is considered
// failed if there is single error.
filteredInstances := make([]IngesterDesc, 0, len(r.ringDesc.Ingesters))
for _, ingester := range healthyInstances {
if _, ok := zoneFailures[ingester.Zone]; !ok {
filteredInstances = append(filteredInstances, ingester)
}
}
healthyInstances = filteredInstances
}
// Since we removed all instances from zones containing at least 1 failing
// instance, we have to decrease the max unavailable zones accordingly.
maxUnavailableZones -= len(zoneFailures)
} else {
// Calculate the number of required ingesters;
// ensure we always require at least RF-1 when RF=3.
numRequired := len(r.ringDesc.Ingesters)
if numRequired < r.cfg.ReplicationFactor {
numRequired = r.cfg.ReplicationFactor
}
// We can tolerate this many failures
numRequired -= r.cfg.ReplicationFactor / 2
if len(healthyInstances) < numRequired {
return ReplicationSet{}, ErrTooManyFailedIngesters
}
maxErrors = len(healthyInstances) - numRequired
}
return ReplicationSet{
Ingesters: healthyInstances,
MaxErrors: maxErrors,
MaxUnavailableZones: maxUnavailableZones,
}, nil
}
// Describe implements prometheus.Collector.
func (r *Ring) Describe(ch chan<- *prometheus.Desc) {
ch <- r.memberOwnershipDesc
ch <- r.numMembersDesc
ch <- r.totalTokensDesc
ch <- r.oldestTimestampDesc
ch <- r.numTokensDesc
}
func countTokens(ringDesc *Desc, tokens []TokenDesc) (map[string]uint32, map[string]uint32) {
owned := map[string]uint32{}
numTokens := map[string]uint32{}
for i, token := range tokens {
var diff uint32
if i+1 == len(tokens) {
diff = (math.MaxUint32 - token.Token) + tokens[0].Token
} else {
diff = tokens[i+1].Token - token.Token
}
numTokens[token.Ingester] = numTokens[token.Ingester] + 1
owned[token.Ingester] = owned[token.Ingester] + diff
}
for id := range ringDesc.Ingesters {
if _, ok := owned[id]; !ok {
owned[id] = 0
numTokens[id] = 0
}
}
return numTokens, owned
}
// Collect implements prometheus.Collector.
func (r *Ring) Collect(ch chan<- prometheus.Metric) {
r.mtx.RLock()
defer r.mtx.RUnlock()
numTokens, ownedRange := countTokens(r.ringDesc, r.ringTokens)
for id, totalOwned := range ownedRange {
ch <- prometheus.MustNewConstMetric(
r.memberOwnershipDesc,
prometheus.GaugeValue,
float64(totalOwned)/float64(math.MaxUint32),
id,
)
ch <- prometheus.MustNewConstMetric(
r.numTokensDesc,
prometheus.GaugeValue,
float64(numTokens[id]),
id,
)
}
numByState := map[string]int{}
oldestTimestampByState := map[string]int64{}
// Initialised to zero so we emit zero-metrics (instead of not emitting anything)
for _, s := range []string{unhealthy, ACTIVE.String(), LEAVING.String(), PENDING.String(), JOINING.String()} {
numByState[s] = 0
oldestTimestampByState[s] = 0
}
for _, ingester := range r.ringDesc.Ingesters {
s := ingester.State.String()
if !r.IsHealthy(&ingester, Reporting) {
s = unhealthy
}
numByState[s]++
if oldestTimestampByState[s] == 0 || ingester.Timestamp < oldestTimestampByState[s] {
oldestTimestampByState[s] = ingester.Timestamp
}
}
for state, count := range numByState {
ch <- prometheus.MustNewConstMetric(
r.numMembersDesc,
prometheus.GaugeValue,
float64(count),
state,
)
}
for state, timestamp := range oldestTimestampByState {
ch <- prometheus.MustNewConstMetric(
r.oldestTimestampDesc,
prometheus.GaugeValue,
float64(timestamp),
state,
)
}
ch <- prometheus.MustNewConstMetric(
r.totalTokensDesc,
prometheus.GaugeValue,
float64(len(r.ringTokens)),
)
}
// ShuffleShard returns a subring for the provided identifier (eg. a tenant ID)
// and size (number of instances). The size is expected to be a multiple of the
// number of zones and the returned subring will contain the same number of
// instances per zone as far as there are enough registered instances in the ring.
//
// The algorithm used to build the subring is a shuffle sharder based on probabilistic
// hashing. We treat each zone as a separate ring and pick N unique replicas from each
// zone, walking the ring starting from random but predictable numbers. The random
// generator is initialised with a seed based on the provided identifier.
//
// This implementation guarantees:
//
// - Stability: given the same ring, two invocations returns the same result.
//
// - Consistency: adding/removing 1 instance from the ring generates a resulting
// subring with no more then 1 difference.
//
// - Shuffling: probabilistically, for a large enough cluster each identifier gets a different
// set of instances, with a reduced number of overlapping instances between two identifiers.
func (r *Ring) ShuffleShard(identifier string, size int) ReadRing {
// Nothing to do if the shard size is not smaller then the actual ring.
if size <= 0 || r.IngesterCount() <= size {
return r
}
if cached := r.getCachedShuffledSubring(identifier, size); cached != nil {
return cached
}
result := r.shuffleShard(identifier, size, 0, time.Now())
r.setCachedShuffledSubring(identifier, size, result)
return result
}
// ShuffleShardWithLookback is like ShuffleShard() but the returned subring includes all instances
// that have been part of the identifier's shard since "now - lookbackPeriod".
//
// The returned subring may be unbalanced with regard to zones and should never be used for write
// operations (read only).
//
// This function doesn't support caching.
func (r *Ring) ShuffleShardWithLookback(identifier string, size int, lookbackPeriod time.Duration, now time.Time) ReadRing {
// Nothing to do if the shard size is not smaller then the actual ring.
if size <= 0 || r.IngesterCount() <= size {
return r
}
return r.shuffleShard(identifier, size, lookbackPeriod, now)
}
func (r *Ring) shuffleShard(identifier string, size int, lookbackPeriod time.Duration, now time.Time) *Ring {
lookbackUntil := now.Add(-lookbackPeriod).Unix()
r.mtx.RLock()
defer r.mtx.RUnlock()
var numInstancesPerZone int
var actualZones []string
if r.cfg.ZoneAwarenessEnabled {
numInstancesPerZone = util.ShuffleShardExpectedInstancesPerZone(size, len(r.ringZones))
actualZones = r.ringZones
} else {
numInstancesPerZone = size
actualZones = []string{""}
}
shard := make(map[string]IngesterDesc, size)
// We need to iterate zones always in the same order to guarantee stability.
for _, zone := range actualZones {
var tokens []TokenDesc
if r.cfg.ZoneAwarenessEnabled {
tokens = r.ringTokensByZone[zone]
} else {
// When zone-awareness is disabled, we just iterate over 1 single fake zone
// and use all tokens in the ring.
tokens = r.ringTokens
}
// Initialise the random generator used to select instances in the ring.
// Since we consider each zone like an independent ring, we have to use dedicated
// pseudo-random generator for each zone, in order to guarantee the "consistency"
// property when the shard size changes or a new zone is added.
random := rand.New(rand.NewSource(util.ShuffleShardSeed(identifier, zone)))
// To select one more instance while guaranteeing the "consistency" property,
// we do pick a random value from the generator and resolve uniqueness collisions
// (if any) continuing walking the ring.
for i := 0; i < numInstancesPerZone; i++ {
start := searchToken(tokens, random.Uint32())
iterations := 0
found := false
for p := start; iterations < len(tokens); p++ {
iterations++
// Wrap p around in the ring.
p %= len(tokens)
// Ensure we select an unique instance.
if _, ok := shard[tokens[p].Ingester]; ok {
continue
}
instance := r.ringDesc.Ingesters[tokens[p].Ingester]
// If the lookback is enabled and this instance has been registered within the lookback period
// then we should include it in the subring but continuing selecting instances.
if lookbackPeriod > 0 && instance.RegisteredTimestamp >= lookbackUntil {
shard[tokens[p].Ingester] = instance
continue
}
shard[tokens[p].Ingester] = instance
found = true
break
}
// If one more instance has not been found, we can stop looking for
// more instances in this zone, because it means the zone has no more
// instances which haven't been already selected.
if !found {
break
}
}
}
// Build a read-only ring for the shard.
shardDesc := &Desc{Ingesters: shard}
shardTokensByZone := shardDesc.getTokensByZone()
return &Ring{
cfg: r.cfg,
strategy: r.strategy,
ringDesc: shardDesc,
ringTokens: shardDesc.getTokens(),
ringTokensByZone: shardTokensByZone,
ringZones: getZones(shardTokensByZone),
// For caching to work, remember these values.
lastTopologyChange: r.lastTopologyChange,
}
}
// GetInstanceState returns the current state of an instance or an error if the
// instance does not exist in the ring.
func (r *Ring) GetInstanceState(instanceID string) (IngesterState, error) {
r.mtx.RLock()
defer r.mtx.RUnlock()
instances := r.ringDesc.GetIngesters()
instance, ok := instances[instanceID]
if !ok {
return PENDING, ErrInstanceNotFound
}
return instance.GetState(), nil
}
// HasInstance returns whether the ring contains an instance matching the provided instanceID.
func (r *Ring) HasInstance(instanceID string) bool {
r.mtx.RLock()
defer r.mtx.RUnlock()
instances := r.ringDesc.GetIngesters()
_, ok := instances[instanceID]
return ok
}
func (r *Ring) getCachedShuffledSubring(identifier string, size int) *Ring {
r.mtx.RLock()
defer r.mtx.RUnlock()
// if shuffledSubringCache map is nil, reading it returns default value (nil pointer).
cached := r.shuffledSubringCache[subringCacheKey{identifier: identifier, shardSize: size}]
if cached == nil {
return nil
}
cached.mtx.Lock()
defer cached.mtx.Unlock()
// Update instance states and timestamps. We know that the topology is the same,
// so zones and tokens are equal.
for name, cachedIng := range cached.ringDesc.Ingesters {
ing := r.ringDesc.Ingesters[name]
cachedIng.State = ing.State
cachedIng.Timestamp = ing.Timestamp
cached.ringDesc.Ingesters[name] = cachedIng
}
return cached
}
func (r *Ring) setCachedShuffledSubring(identifier string, size int, subring *Ring) {
if subring == nil {
return
}
r.mtx.Lock()
defer r.mtx.Unlock()
// Only cache if *this* ring hasn't changed since computing result
// (which can happen between releasing the read lock and getting read-write lock).
// Note that shuffledSubringCache can be only nil when set by test.
if r.shuffledSubringCache != nil && r.lastTopologyChange.Equal(subring.lastTopologyChange) {
r.shuffledSubringCache[subringCacheKey{identifier: identifier, shardSize: size}] = subring
}
}