@@ -19,7 +19,9 @@ package discovery
19
19
import (
20
20
"context"
21
21
"fmt"
22
+ "slices"
22
23
"sync"
24
+ "time"
23
25
24
26
"google.golang.org/protobuf/proto"
25
27
@@ -36,6 +38,11 @@ import (
36
38
vschemapb "vitess.io/vitess/go/vt/proto/vschema"
37
39
)
38
40
41
+ var (
42
+ // waitConsistentKeyspacesCheck is the amount of time to wait for between checks to verify the keyspace is consistent.
43
+ waitConsistentKeyspacesCheck = 100 * time .Millisecond
44
+ )
45
+
39
46
// KeyspaceEventWatcher is an auxiliary watcher that watches all availability incidents
40
47
// for all keyspaces in a Vitess cell and notifies listeners when the events have been resolved.
41
48
// Right now this is capable of detecting the end of failovers, both planned and unplanned,
@@ -643,28 +650,53 @@ func (kew *KeyspaceEventWatcher) TargetIsBeingResharded(ctx context.Context, tar
643
650
return ks .beingResharded (target .Shard )
644
651
}
645
652
646
- // PrimaryIsNotServing checks if the reason why the given target is not accessible right now is
647
- // that the primary tablet for that shard is not serving. This is possible during a Planned Reparent Shard
648
- // operation. Just as the operation completes, a new primary will be elected, and it will send its own healthcheck
649
- // stating that it is serving. We should buffer requests until that point.
650
- // There are use cases where people do not run with a Primary server at all, so we must verify that
651
- // we only start buffering when a primary was present, and it went not serving.
652
- // The shard state keeps track of the current primary and the last externally reparented time, which we can use
653
- // to determine that there was a serving primary which now became non serving. This is only possible in a DemotePrimary
654
- // RPC which are only called from ERS and PRS. So buffering will stop when these operations succeed.
655
- // We return the tablet alias of the primary if it is serving.
656
- func (kew * KeyspaceEventWatcher ) PrimaryIsNotServing (ctx context.Context , target * querypb.Target ) (* topodatapb.TabletAlias , bool ) {
653
+ // ShouldStartBufferingForTarget checks if we should be starting buffering for the given target.
654
+ // We check the following things before we start buffering -
655
+ // 1. The shard must have a primary.
656
+ // 2. The primary must be non-serving.
657
+ // 3. The keyspace must be marked inconsistent.
658
+ //
659
+ // This buffering is meant to kick in during a Planned Reparent Shard operation.
660
+ // As part of that operation the old primary will become non-serving. At that point
661
+ // this code should return true to start buffering requests.
662
+ // Just as the PRS operation completes, a new primary will be elected, and
663
+ // it will send its own healthcheck stating that it is serving. We should buffer requests until
664
+ // that point.
665
+ //
666
+ // There are use cases where people do not run with a Primary server at all, so we must
667
+ // verify that we only start buffering when a primary was present, and it went not serving.
668
+ // The shard state keeps track of the current primary and the last externally reparented time, which
669
+ // we can use to determine that there was a serving primary which now became non serving. This is
670
+ // only possible in a DemotePrimary RPC which are only called from ERS and PRS. So buffering will
671
+ // stop when these operations succeed. We also return the tablet alias of the primary if it is serving.
672
+ func (kew * KeyspaceEventWatcher ) ShouldStartBufferingForTarget (ctx context.Context , target * querypb.Target ) (* topodatapb.TabletAlias , bool ) {
657
673
if target .TabletType != topodatapb .TabletType_PRIMARY {
674
+ // We don't support buffering for any target tablet type other than the primary.
658
675
return nil , false
659
676
}
660
677
ks := kew .getKeyspaceStatus (ctx , target .Keyspace )
661
678
if ks == nil {
679
+ // If the keyspace status is nil, then the keyspace must be deleted.
680
+ // The user query is trying to access a keyspace that has been deleted.
681
+ // There is no reason to buffer this query.
662
682
return nil , false
663
683
}
664
684
ks .mu .Lock ()
665
685
defer ks .mu .Unlock ()
666
686
if state , ok := ks .shards [target .Shard ]; ok {
667
- // If the primary tablet was present then externallyReparented will be non-zero and currentPrimary will be not nil
687
+ // As described in the function comment, we only want to start buffering when all the following conditions are met -
688
+ // 1. The shard must have a primary. We check this by checking the currentPrimary and externallyReparented fields being non-empty.
689
+ // They are set the first time the shard registers an update from a serving primary and are never cleared out after that.
690
+ // If the user has configured vtgates to wait for the primary tablet healthchecks before starting query service, this condition
691
+ // will always be true.
692
+ // 2. The primary must be non-serving. We check this by checking the serving field in the shard state.
693
+ // When a primary becomes non-serving, it also marks the keyspace inconsistent. So the next check is only added
694
+ // for being defensive against any bugs.
695
+ // 3. The keyspace must be marked inconsistent. We check this by checking the consistent field in the keyspace state.
696
+ //
697
+ // The reason we need all the three checks is that we want to be very defensive in when we start buffering.
698
+ // We don't want to start buffering when we don't know for sure if the primary
699
+ // is not serving and we will receive an update that stops buffering soon.
668
700
return state .currentPrimary , ! state .serving && ! ks .consistent && state .externallyReparented != 0 && state .currentPrimary != nil
669
701
}
670
702
return nil , false
@@ -716,3 +748,46 @@ func (kew *KeyspaceEventWatcher) MarkShardNotServing(ctx context.Context, keyspa
716
748
}
717
749
return true
718
750
}
751
+
752
+ // WaitForConsistentKeyspaces waits for the given set of keyspaces to be marked consistent.
753
+ func (kew * KeyspaceEventWatcher ) WaitForConsistentKeyspaces (ctx context.Context , ksList []string ) error {
754
+ // We don't want to change the original keyspace list that we receive so we clone it
755
+ // before we empty it elements down below.
756
+ keyspaces := slices .Clone (ksList )
757
+ for {
758
+ // We empty keyspaces as we find them to be consistent.
759
+ allConsistent := true
760
+ for i , ks := range keyspaces {
761
+ if ks == "" {
762
+ continue
763
+ }
764
+
765
+ // Get the keyspace status and see it is consistent yet or not.
766
+ kss := kew .getKeyspaceStatus (ctx , ks )
767
+ // If kss is nil, then it must be deleted. In that case too it is fine for us to consider
768
+ // it consistent since the keyspace has been deleted.
769
+ if kss == nil || kss .consistent {
770
+ keyspaces [i ] = ""
771
+ } else {
772
+ allConsistent = false
773
+ }
774
+ }
775
+
776
+ if allConsistent {
777
+ // all the keyspaces are consistent.
778
+ return nil
779
+ }
780
+
781
+ // Unblock after the sleep or when the context has expired.
782
+ select {
783
+ case <- ctx .Done ():
784
+ for _ , ks := range keyspaces {
785
+ if ks != "" {
786
+ log .Infof ("keyspace %v didn't become consistent" , ks )
787
+ }
788
+ }
789
+ return ctx .Err ()
790
+ case <- time .After (waitConsistentKeyspacesCheck ):
791
+ }
792
+ }
793
+ }
0 commit comments