@@ -70,6 +70,10 @@ type ReadRing interface {
70
70
// and size (number of instances).
71
71
ShuffleShard (identifier string , size int ) ReadRing
72
72
73
+ // ShuffleShardWithOperation returns a subring for the provided identifier (eg. a tenant ID)
74
+ // and size (number of instances) filtered for a given operation.
75
+ ShuffleShardWithOperation (identifier string , size int , op Operation ) ReadRing
76
+
73
77
// ShuffleShardWithZoneStability does the same as ShuffleShard but using a different shuffle sharding algorithm.
74
78
// It doesn't round up shard size to be divisible to number of zones and make sure when scaling up/down one
75
79
// shard size at a time, at most 1 instance can be changed.
@@ -112,6 +116,8 @@ var (
112
116
return s == READONLY
113
117
})
114
118
119
+ WriteShard = NewOp ([]InstanceState {ACTIVE , PENDING , LEAVING , JOINING }, func (s InstanceState ) bool { return false })
120
+
115
121
// Read operation that extends the replica set if an instance is not ACTIVE, PENDING, LEAVING, JOINING OR READONLY
116
122
Read = NewOp ([]InstanceState {ACTIVE , PENDING , LEAVING , JOINING , READONLY }, func (s InstanceState ) bool {
117
123
// To match Write with extended replica set we have to also increase the
@@ -222,6 +228,7 @@ type subringCacheKey struct {
222
228
shardSize int
223
229
224
230
zoneStableSharding bool
231
+ operation Operation
225
232
}
226
233
227
234
// New creates a new Ring. Being a service, Ring needs to be started to do anything.
@@ -333,12 +340,16 @@ func (r *Ring) updateRingState(ringDesc *Desc) {
333
340
}
334
341
335
342
rc := prevRing .RingCompare (ringDesc )
336
- if rc == Equal || rc == EqualButStatesAndTimestamps {
343
+ if rc == Equal || rc == EqualButStatesAndTimestamps || rc == EqualButReadOnly {
337
344
// No need to update tokens or zones. Only states and timestamps
338
345
// have changed. (If Equal, nothing has changed, but that doesn't happen
339
346
// when watching the ring for updates).
340
347
r .mtx .Lock ()
341
348
r .ringDesc = ringDesc
349
+ if rc == EqualButReadOnly && r .shuffledSubringCache != nil {
350
+ // Invalidate all cached subrings.
351
+ r .shuffledSubringCache = make (map [subringCacheKey ]* Ring )
352
+ }
342
353
r .updateRingMetrics (rc )
343
354
r .mtx .Unlock ()
344
355
return
@@ -732,11 +743,15 @@ func (r *Ring) updateRingMetrics(compareResult CompareResult) {
732
743
// - Shuffling: probabilistically, for a large enough cluster each identifier gets a different
733
744
// set of instances, with a reduced number of overlapping instances between two identifiers.
734
745
func (r * Ring ) ShuffleShard (identifier string , size int ) ReadRing {
735
- return r .shuffleShardWithCache (identifier , size , false )
746
+ return r .shuffleShardWithCache (identifier , size , false , Reporting )
747
+ }
748
+
749
+ func (r * Ring ) ShuffleShardWithOperation (identifier string , size int , op Operation ) ReadRing {
750
+ return r .shuffleShardWithCache (identifier , size , false , op )
736
751
}
737
752
738
753
func (r * Ring ) ShuffleShardWithZoneStability (identifier string , size int ) ReadRing {
739
- return r .shuffleShardWithCache (identifier , size , true )
754
+ return r .shuffleShardWithCache (identifier , size , true , Reporting )
740
755
}
741
756
742
757
// ShuffleShardWithLookback is like ShuffleShard() but the returned subring includes all instances
@@ -752,26 +767,26 @@ func (r *Ring) ShuffleShardWithLookback(identifier string, size int, lookbackPer
752
767
return r
753
768
}
754
769
755
- return r .shuffleShard (identifier , size , lookbackPeriod , now , false )
770
+ return r .shuffleShard (identifier , size , lookbackPeriod , now , false , Reporting )
756
771
}
757
772
758
- func (r * Ring ) shuffleShardWithCache (identifier string , size int , zoneStableSharding bool ) ReadRing {
773
+ func (r * Ring ) shuffleShardWithCache (identifier string , size int , zoneStableSharding bool , op Operation ) ReadRing {
759
774
// Nothing to do if the shard size is not smaller than the actual ring.
760
- if size <= 0 || r .InstancesCount () <= size {
775
+ if size <= 0 || ( op == Reporting && r .InstancesCount () <= size ) {
761
776
return r
762
777
}
763
778
764
- if cached := r .getCachedShuffledSubring (identifier , size , zoneStableSharding ); cached != nil {
779
+ if cached := r .getCachedShuffledSubring (identifier , size , zoneStableSharding , op ); cached != nil {
765
780
return cached
766
781
}
767
782
768
- result := r .shuffleShard (identifier , size , 0 , time .Now (), zoneStableSharding )
783
+ result := r .shuffleShard (identifier , size , 0 , time .Now (), zoneStableSharding , op )
769
784
770
- r .setCachedShuffledSubring (identifier , size , zoneStableSharding , result )
785
+ r .setCachedShuffledSubring (identifier , size , zoneStableSharding , op , result )
771
786
return result
772
787
}
773
788
774
- func (r * Ring ) shuffleShard (identifier string , size int , lookbackPeriod time.Duration , now time.Time , zoneStableSharding bool ) * Ring {
789
+ func (r * Ring ) shuffleShard (identifier string , size int , lookbackPeriod time.Duration , now time.Time , zoneStableSharding bool , op Operation ) * Ring {
775
790
lookbackUntil := now .Add (- lookbackPeriod ).Unix ()
776
791
777
792
r .mtx .RLock ()
@@ -783,14 +798,16 @@ func (r *Ring) shuffleShard(identifier string, size int, lookbackPeriod time.Dur
783
798
zonesWithExtraInstance int
784
799
)
785
800
801
+ ro := r .getRingForOperation (op )
802
+
786
803
if r .cfg .ZoneAwarenessEnabled {
787
804
if zoneStableSharding {
788
- numInstancesPerZone = size / len (r .ringZones )
789
- zonesWithExtraInstance = size % len (r .ringZones )
805
+ numInstancesPerZone = size / len (ro .ringZones )
806
+ zonesWithExtraInstance = size % len (ro .ringZones )
790
807
} else {
791
- numInstancesPerZone = shardUtil .ShuffleShardExpectedInstancesPerZone (size , len (r .ringZones ))
808
+ numInstancesPerZone = shardUtil .ShuffleShardExpectedInstancesPerZone (size , len (ro .ringZones ))
792
809
}
793
- actualZones = r .ringZones
810
+ actualZones = ro .ringZones
794
811
} else {
795
812
numInstancesPerZone = size
796
813
actualZones = []string {"" }
@@ -802,12 +819,12 @@ func (r *Ring) shuffleShard(identifier string, size int, lookbackPeriod time.Dur
802
819
for _ , zone := range actualZones {
803
820
var tokens []uint32
804
821
805
- if r .cfg .ZoneAwarenessEnabled {
806
- tokens = r .ringTokensByZone [zone ]
822
+ if ro .cfg .ZoneAwarenessEnabled {
823
+ tokens = ro .ringTokensByZone [zone ]
807
824
} else {
808
825
// When zone-awareness is disabled, we just iterate over 1 single fake zone
809
826
// and use all tokens in the ring.
810
- tokens = r .ringTokens
827
+ tokens = ro .ringTokens
811
828
}
812
829
813
830
// Initialise the random generator used to select instances in the ring.
@@ -835,7 +852,7 @@ func (r *Ring) shuffleShard(identifier string, size int, lookbackPeriod time.Dur
835
852
// Wrap p around in the ring.
836
853
p %= len (tokens )
837
854
838
- info , ok := r .ringInstanceByToken [tokens [p ]]
855
+ info , ok := ro .ringInstanceByToken [tokens [p ]]
839
856
if ! ok {
840
857
// This should never happen unless a bug in the ring code.
841
858
panic (ErrInconsistentTokensInfo )
@@ -847,7 +864,7 @@ func (r *Ring) shuffleShard(identifier string, size int, lookbackPeriod time.Dur
847
864
}
848
865
849
866
instanceID := info .InstanceID
850
- instance := r .ringDesc .Ingesters [instanceID ]
867
+ instance := ro .ringDesc .Ingesters [instanceID ]
851
868
shard [instanceID ] = instance
852
869
853
870
// If the lookback is enabled and this instance has been registered within the lookback period
@@ -869,27 +886,7 @@ func (r *Ring) shuffleShard(identifier string, size int, lookbackPeriod time.Dur
869
886
}
870
887
}
871
888
872
- // Build a read-only ring for the shard.
873
- shardDesc := & Desc {Ingesters : shard }
874
- shardTokensByZone := shardDesc .getTokensByZone ()
875
-
876
- return & Ring {
877
- cfg : r .cfg ,
878
- strategy : r .strategy ,
879
- ringDesc : shardDesc ,
880
- ringTokens : shardDesc .GetTokens (),
881
- ringTokensByZone : shardTokensByZone ,
882
- ringZones : getZones (shardTokensByZone ),
883
- KVClient : r .KVClient ,
884
-
885
- // We reference the original map as is in order to avoid copying. It's safe to do
886
- // because this map is immutable by design and it's a superset of the actual instances
887
- // with the subring.
888
- ringInstanceByToken : r .ringInstanceByToken ,
889
-
890
- // For caching to work, remember these values.
891
- lastTopologyChange : r .lastTopologyChange ,
892
- }
889
+ return r .copyWithNewDesc (shard )
893
890
}
894
891
895
892
// GetInstanceState returns the current state of an instance or an error if the
@@ -926,7 +923,7 @@ func (r *Ring) HasInstance(instanceID string) bool {
926
923
return ok
927
924
}
928
925
929
- func (r * Ring ) getCachedShuffledSubring (identifier string , size int , zoneStableSharding bool ) * Ring {
926
+ func (r * Ring ) getCachedShuffledSubring (identifier string , size int , zoneStableSharding bool , op Operation ) * Ring {
930
927
if r .cfg .SubringCacheDisabled {
931
928
return nil
932
929
}
@@ -935,7 +932,7 @@ func (r *Ring) getCachedShuffledSubring(identifier string, size int, zoneStableS
935
932
defer r .mtx .RUnlock ()
936
933
937
934
// if shuffledSubringCache map is nil, reading it returns default value (nil pointer).
938
- cached := r .shuffledSubringCache [subringCacheKey {identifier : identifier , shardSize : size , zoneStableSharding : zoneStableSharding }]
935
+ cached := r .shuffledSubringCache [subringCacheKey {identifier : identifier , shardSize : size , zoneStableSharding : zoneStableSharding , operation : op }]
939
936
if cached == nil {
940
937
return nil
941
938
}
@@ -954,7 +951,7 @@ func (r *Ring) getCachedShuffledSubring(identifier string, size int, zoneStableS
954
951
return cached
955
952
}
956
953
957
- func (r * Ring ) setCachedShuffledSubring (identifier string , size int , zoneStableSharding bool , subring * Ring ) {
954
+ func (r * Ring ) setCachedShuffledSubring (identifier string , size int , zoneStableSharding bool , op Operation , subring * Ring ) {
958
955
if subring == nil || r .cfg .SubringCacheDisabled {
959
956
return
960
957
}
@@ -966,7 +963,49 @@ func (r *Ring) setCachedShuffledSubring(identifier string, size int, zoneStableS
966
963
// (which can happen between releasing the read lock and getting read-write lock).
967
964
// Note that shuffledSubringCache can be only nil when set by test.
968
965
if r .shuffledSubringCache != nil && r .lastTopologyChange .Equal (subring .lastTopologyChange ) {
969
- r .shuffledSubringCache [subringCacheKey {identifier : identifier , shardSize : size , zoneStableSharding : zoneStableSharding }] = subring
966
+ r .shuffledSubringCache [subringCacheKey {identifier : identifier , shardSize : size , zoneStableSharding : zoneStableSharding , operation : op }] = subring
967
+ }
968
+ }
969
+
970
+ // getRingForOperation Returns a new ring filtered for operation.
971
+ // The ring read lock must be already taken when calling this function.
972
+ func (r * Ring ) getRingForOperation (op Operation ) * Ring {
973
+ //Avoid filtering if we are receiving default operation or empty ring
974
+ if r .ringDesc == nil || len (r .ringDesc .Ingesters ) == 0 || op == Reporting {
975
+ return r
976
+ }
977
+
978
+ instanceDescs := make (map [string ]InstanceDesc )
979
+ for id , instance := range r .ringDesc .Ingesters {
980
+ if op .IsInstanceInStateHealthy (instance .State ) {
981
+ instanceDescs [id ] = instance
982
+ }
983
+ }
984
+
985
+ return r .copyWithNewDesc (instanceDescs )
986
+ }
987
+
988
+ // copyWithNewDesc Return a new ring with updated data for different InstanceDesc
989
+ func (r * Ring ) copyWithNewDesc (desc map [string ]InstanceDesc ) * Ring {
990
+ shardDesc := & Desc {Ingesters : desc }
991
+ shardTokensByZone := shardDesc .getTokensByZone ()
992
+
993
+ return & Ring {
994
+ cfg : r .cfg ,
995
+ strategy : r .strategy ,
996
+ ringDesc : shardDesc ,
997
+ ringTokens : shardDesc .GetTokens (),
998
+ ringTokensByZone : shardTokensByZone ,
999
+ ringZones : getZones (shardTokensByZone ),
1000
+ KVClient : r .KVClient ,
1001
+
1002
+ // We reference the original map as is in order to avoid copying. It's safe to do
1003
+ // because this map is immutable by design and it's a superset of the actual instances
1004
+ // with the subring.
1005
+ ringInstanceByToken : r .ringInstanceByToken ,
1006
+
1007
+ // For caching to work, remember these values.
1008
+ lastTopologyChange : r .lastTopologyChange ,
970
1009
}
971
1010
}
972
1011
0 commit comments