@@ -127,8 +127,6 @@ func (ic internedConstraint) less(b internedConstraint) bool {
127
127
// constraints are in increasing order using internedConstraint.less.
128
128
type constraintsConj []internedConstraint
129
129
130
- type conjunctionRelationship int
131
-
132
130
func (nconf * normalizedSpanConfig ) uninternedConfig () roachpb.SpanConfig {
133
131
var conf roachpb.SpanConfig
134
132
conf .NumReplicas = nconf .numReplicas
@@ -158,6 +156,8 @@ func (nconf *normalizedSpanConfig) uninternedConfig() roachpb.SpanConfig {
158
156
return conf
159
157
}
160
158
159
+ type conjunctionRelationship int
160
+
161
161
// Relationship between conjunctions used for structural normalization. This
162
162
// relationship is solely defined based on the conjunctions, and not based on
163
163
// what stores actually match. It simply assumes that if two conjuncts are not
@@ -211,13 +211,13 @@ func (cc constraintsConj) relationship(b constraintsConj) conjunctionRelationshi
211
211
if extraInB > 0 && extraInCC == 0 {
212
212
return conjStrictSuperset
213
213
}
214
- // (extraInCC == 0 || extraInBB > 0) && (extraInB == 0 || extraInCC > 0)
214
+ // (extraInCC == 0 || extraInB > 0) && (extraInB == 0 || extraInCC > 0)
215
215
// =>
216
- // (extraInCC == 0 && extraInB == 0) || (extraInBB > 0 && extraInCC > 0)
216
+ // (extraInCC == 0 && extraInB == 0) || (extraInB > 0 && extraInCC > 0)
217
217
if extraInCC == 0 && extraInB == 0 {
218
218
return conjEqualSet
219
219
}
220
- // (extraInBB > 0 && extraInCC > 0)
220
+ // (extraInB > 0 && extraInCC > 0)
221
221
if inBoth > 0 {
222
222
return conjIntersecting
223
223
}
@@ -576,6 +576,118 @@ func doStructuralNormalization(conf *normalizedSpanConfig) (*normalizedSpanConfi
576
576
}
577
577
}
578
578
conf .voterConstraints = vc
579
+
580
+ // We are done with normalizing voter constraints. We also do some basic
581
+ // normalization for constraints: we have seen examples where the
582
+ // constraints are under-specified and give freedom in the choice of
583
+ // constraintsForAddingNonVoter() that isn't actually there. Note that when
584
+ // all the voter constraints are satisfied (which we try to do before
585
+ // satisfying non-voter constraints), then the under-specification does not
586
+ // hurt the choice made in constraintsForAddingNonVoter(). However, we could
587
+ // have situations where an outage of some kind is preventing all the voter
588
+ // constraints from being satisfied, and now we need to place a non-voter --
589
+ // placing that non-voter correctly will avoid the need to move it later
590
+ // when the voter constraint does get satisfied.
591
+ //
592
+ // For example, see the config from #106559 in testdata/normalize_config.
593
+ // The constraints for us-west-1 and us-east-1 are under-specified in
594
+ // needing only 1 replica, while voter constraints specify we need 2
595
+ // replicas in each. Consider if it were left under-specified, and we had
596
+ // only 3 voters, 2 in us-west-1 and 1 in us-east-1 and we were temporarily
597
+ // unable to add a voter in us-east-1. Say we lose the non-voter too, and
598
+ // need to add one. With the under-specified constraint we could add the
599
+ // non-voter anywhere, since we think we are allowed 2 replicas with the
600
+ // empty constraint conjunction. This is technically true, but once we have
601
+ // the required second voter in us-east-1, we will need to move that
602
+ // non-voter to us-central-1, which is wasteful.
603
+ if emptyConstraintIndex >= 0 {
604
+ // Recompute the relationship since voterConstraints have changed.
605
+ emptyVoterConstraintIndex = - 1
606
+ rels = rels [:0 ]
607
+ for i := range conf .voterConstraints {
608
+ if len (conf .voterConstraints [i ].constraints ) == 0 {
609
+ // We don't actually use emptyVoterConstraintIndex later, but it is
610
+ // harmless to recompute, and will avoid subtle bugs if we change the
611
+ // logic below to start using it.
612
+ emptyVoterConstraintIndex = i
613
+ }
614
+ for j := range conf .constraints {
615
+ rels = append (rels , relationshipVoterAndAll {
616
+ voterIndex : i ,
617
+ allIndex : j ,
618
+ voterAndAllRel : conf .voterConstraints [i ].constraints .relationship (
619
+ conf .constraints [j ].constraints ),
620
+ })
621
+ }
622
+ }
623
+ // Sort these relationships in the order we want to examine them.
624
+ sort .Slice (rels , func (i , j int ) bool {
625
+ return rels [i ].voterAndAllRel < rels [j ].voterAndAllRel
626
+ })
627
+ // Ignore conjIntersecting.
628
+ index = 0
629
+ for rels [index ].voterAndAllRel == conjIntersecting {
630
+ index ++
631
+ }
632
+ voterConstraintHasEqualityWithConstraint := make ([]bool , len (conf .voterConstraints ))
633
+ // For conjEqualSet, if we can grab from the emptyConstraintIndex, do so.
634
+ for ; index < len (rels ) && rels [index ].voterAndAllRel <= conjEqualSet ; index ++ {
635
+ rel := rels [index ]
636
+ voterConstraintHasEqualityWithConstraint [rel .voterIndex ] = true
637
+ if rel .allIndex == emptyConstraintIndex {
638
+ // rel.voterIndex must be emptyVoterConstraintIndex.
639
+ continue
640
+ }
641
+ if conf .constraints [rel .allIndex ].numReplicas < conf .voterConstraints [rel .voterIndex ].numReplicas {
642
+ toAddCount := conf .voterConstraints [rel .voterIndex ].numReplicas -
643
+ conf .constraints [rel .allIndex ].numReplicas
644
+ availableCount := conf .constraints [emptyConstraintIndex ].numReplicas
645
+ if availableCount < toAddCount {
646
+ toAddCount = availableCount
647
+ }
648
+ conf .constraints [emptyConstraintIndex ].numReplicas -= toAddCount
649
+ conf .constraints [rel .allIndex ].numReplicas += toAddCount
650
+ }
651
+ }
652
+ // For conjStrictSubset, if the subset relationship is with
653
+ // emptyConstraintIndex, grab from there.
654
+ for ; index < len (rels ) && rels [index ].voterAndAllRel <= conjStrictSubset ; index ++ {
655
+ rel := rels [index ]
656
+ if rel .allIndex != emptyConstraintIndex {
657
+ continue
658
+ }
659
+ if voterConstraintHasEqualityWithConstraint [rel .voterIndex ] {
660
+ // Already has a corresponding constraint, that we have considered in
661
+ // the previous loop.
662
+ continue
663
+ }
664
+ availableCount := conf .constraints [emptyConstraintIndex ].numReplicas
665
+ if availableCount > 0 {
666
+ toAddCount := conf .voterConstraints [rel .voterIndex ].numReplicas
667
+ if toAddCount > availableCount {
668
+ toAddCount = availableCount
669
+ }
670
+ conf .constraints [emptyConstraintIndex ].numReplicas -= toAddCount
671
+ conf .constraints = append (conf .constraints , internedConstraintsConjunction {
672
+ numReplicas : toAddCount ,
673
+ constraints : conf .voterConstraints [rel .voterIndex ].constraints ,
674
+ })
675
+ }
676
+ }
677
+ // We may have appended to conf.constraints in the previous loop. We like
678
+ // to keep the empty constraint as the last one, so do a swap if needed.
679
+ n := len (conf .constraints ) - 1
680
+ if n != emptyConstraintIndex {
681
+ conf .constraints [n ], conf .constraints [emptyConstraintIndex ] =
682
+ conf .constraints [emptyConstraintIndex ], conf .constraints [n ]
683
+ }
684
+ // If the empty constraint does not have any replicas due to the
685
+ // normalization, discard it.
686
+ if conf .constraints [n ].numReplicas == 0 {
687
+ conf .constraints = conf .constraints [:n ]
688
+ }
689
+ }
690
+
579
691
return conf , err
580
692
}
581
693
0 commit comments