Skip to content

Commit d4ba385

Browse files
authored
Merge pull request #1319 from jcechace/PBM-1668-ixq-member-bound
PBM-1668 add index quorum limit handling
2 parents 06ddd04 + 23249c7 commit d4ba385

6 files changed

Lines changed: 82 additions & 10 deletions

File tree

cmd/pbm/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ func (app *pbmApp) buildRestoreCmd() *cobra.Command {
835835
)
836836
restoreCmd.Flags().StringVar(
837837
&restoreOptions.indexCommitQuorum, "index-commit-quorum", "",
838-
"Index commit quorum for logical restore index builds: majority, votingMembers, or a positive integer.",
838+
"Index commit quorum for logical restore index builds: majority, votingMembers, or an integer from 1 to 50.",
839839
)
840840
restoreCmd.Flags().StringVar(
841841
&restoreOptions.ns, "ns", "",

packaging/conf/pbm-conf-reference.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@
253253
# numParallelCollections:
254254

255255
## Set the commit quorum for index creation during logical restore.
256-
## Supported values are majority, votingMembers, or a positive integer.
256+
## Supported values are majority, votingMembers, or an integer from 1 to 50.
257257
## Default value is votingMembers.
258258
# indexCommitQuorum: votingMembers
259259

pbm/config/ixquorum.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ const (
1414
IndexCommitQuorumVotingMembers IndexCommitQuorum = "votingMembers"
1515
)
1616

17-
const DefaultRestoreIndexCommitQuorum = IndexCommitQuorumVotingMembers
17+
const (
18+
DefaultRestoreIndexCommitQuorum = IndexCommitQuorumVotingMembers
19+
MaxIndexCommitQuorum = 50
20+
)
1821

1922
// CommandValue returns the value shape expected by MongoDB's createIndexes
2023
// commitQuorum field. MongoDB accepts symbolic string values or an integer
@@ -40,7 +43,7 @@ func ValidateIndexCommitQuorum(q IndexCommitQuorum) error {
4043
}
4144

4245
n, err := strconv.ParseInt(val, 10, 32)
43-
if err != nil || n <= 0 {
46+
if err != nil || n <= 0 || n > MaxIndexCommitQuorum {
4447
return errors.Errorf("invalid restore.indexCommitQuorum %q", val)
4548
}
4649

pbm/config/ixquorum_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ func TestValidateIndexCommitQuorum(t *testing.T) {
3535
{name: "majority", value: IndexCommitQuorumMajority},
3636
{name: "votingMembers", value: IndexCommitQuorumVotingMembers},
3737
{name: "positive integer", value: "3"},
38+
{name: "max integer", value: "50"},
3839
{name: "zero", value: "0", wantErr: true},
40+
{name: "above max integer", value: "51", wantErr: true},
3941
{name: "negative integer", value: "-1", wantErr: true},
4042
{name: "unknown string", value: "whatever", wantErr: true},
4143
{name: "leading whitespace", value: " majority", wantErr: true},

pbm/restore/logical.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import (
3636
"github.com/percona/percona-backup-mongodb/pbm/version"
3737
)
3838

39+
const mongoErrUnsatisfiableCommitQuorum = 278
40+
3941
// mDBCl represents mDB client iterface for the DB related ops.
4042
type mDBCl interface {
4143
runCmdShardsvrDropDatabase(ctx context.Context, db string, configDBDoc *configDatabasesDoc) error
@@ -1316,16 +1318,22 @@ func (r *Restore) restoreIndexes(ctx context.Context, nss []string) error {
13161318
delete(index.Options, "v")
13171319
}
13181320

1319-
rawCommand := bson.D{
1320-
{"createIndexes", ns.Collection},
1321-
{"indexes", indexes},
1322-
{"ignoreUnknownIndexOptions", true},
1323-
{"commitQuorum", r.indexCommitQuorum.CommandValue()},
1324-
}
1321+
commitQuorum := r.indexCommitQuorum.CommandValue()
1322+
rawCommand := createIndexesCommand(ns.Collection, indexes, commitQuorum)
13251323

13261324
r.log.Info("restoring indexes for %s.%s: %s",
13271325
ns.DB, ns.Collection, strings.Join(indexNames, ", "))
13281326
err := r.nodeConn.Database(ns.DB).RunCommand(ctx, rawCommand).Err()
1327+
if shouldRetryWithDefaultIndexCommitQuorum(err, commitQuorum) {
1328+
commitQuorum = config.DefaultRestoreIndexCommitQuorum.CommandValue()
1329+
r.log.Debug("createIndexes for %s.%s failed with MongoDB error: %v", ns.DB, ns.Collection, err)
1330+
r.log.Warning(
1331+
"index commit quorum cannot be satisfied for %s.%s, retrying with %s",
1332+
ns.DB, ns.Collection, commitQuorum,
1333+
)
1334+
rawCommand = createIndexesCommand(ns.Collection, indexes, commitQuorum)
1335+
err = r.nodeConn.Database(ns.DB).RunCommand(ctx, rawCommand).Err()
1336+
}
13291337
if err != nil {
13301338
return errors.Wrapf(err, "createIndexes for %s.%s", ns.DB, ns.Collection)
13311339
}
@@ -1334,6 +1342,27 @@ func (r *Restore) restoreIndexes(ctx context.Context, nss []string) error {
13341342
return nil
13351343
}
13361344

1345+
func createIndexesCommand(collection string, indexes []*idx.IndexDocument, commitQuorum any) bson.D {
1346+
return bson.D{
1347+
{"createIndexes", collection},
1348+
{"indexes", indexes},
1349+
{"ignoreUnknownIndexOptions", true},
1350+
{"commitQuorum", commitQuorum},
1351+
}
1352+
}
1353+
1354+
func shouldRetryWithDefaultIndexCommitQuorum(err error, commitQuorum any) bool {
1355+
if err == nil {
1356+
return false
1357+
}
1358+
if _, ok := commitQuorum.(int32); !ok {
1359+
return false
1360+
}
1361+
1362+
var cmdErr mongo.CommandError
1363+
return errors.As(err, &cmdErr) && cmdErr.HasErrorCode(mongoErrUnsatisfiableCommitQuorum)
1364+
}
1365+
13371366
func (r *Restore) updateRouterConfig(ctx context.Context) error {
13381367
if len(r.sMap) == 0 || !r.nodeInfo.IsSharded() {
13391368
return nil

pbm/restore/logical_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"strings"
99
"testing"
1010

11+
"github.com/stretchr/testify/require"
1112
"go.mongodb.org/mongo-driver/v2/bson"
13+
"go.mongodb.org/mongo-driver/v2/mongo"
1214

1315
"github.com/percona/percona-backup-mongodb/pbm/backup"
1416
"github.com/percona/percona-backup-mongodb/pbm/config"
@@ -216,6 +218,42 @@ func TestResolveNamespace(t *testing.T) {
216218
})
217219
}
218220

221+
func TestShouldRetryWithDefaultIndexCommitQuorum(t *testing.T) {
222+
tests := []struct {
223+
name string
224+
err error
225+
commitQuorum any
226+
want bool
227+
}{
228+
{name: "nil error", err: nil, commitQuorum: int32(3), want: false},
229+
{
230+
name: "numeric unsatisfiable quorum",
231+
err: mongo.CommandError{Code: mongoErrUnsatisfiableCommitQuorum},
232+
commitQuorum: int32(3),
233+
want: true,
234+
},
235+
{
236+
name: "string unsatisfiable quorum",
237+
err: mongo.CommandError{Code: mongoErrUnsatisfiableCommitQuorum},
238+
commitQuorum: string(config.IndexCommitQuorumVotingMembers),
239+
want: false,
240+
},
241+
{
242+
name: "numeric other command error",
243+
err: mongo.CommandError{Code: 9},
244+
commitQuorum: int32(3),
245+
want: false,
246+
},
247+
}
248+
249+
for _, tt := range tests {
250+
t.Run(tt.name, func(t *testing.T) {
251+
got := shouldRetryWithDefaultIndexCommitQuorum(tt.err, tt.commitQuorum)
252+
require.Equal(t, tt.want, got)
253+
})
254+
}
255+
}
256+
219257
func TestShouldRestoreUsersAndRoles(t *testing.T) {
220258
checkOpt := func(t testing.TB, got, want restoreUsersAndRolesOption) {
221259
t.Helper()

0 commit comments

Comments
 (0)