diff options
author | Siyuan Zhou <siyuan.zhou@mongodb.com> | 2020-04-24 17:07:25 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-05-13 18:40:45 +0000 |
commit | f8ca54b5fc64397be653e379fd1bc2b222620a40 (patch) | |
tree | 173df70430540c8890b42ab328f30dab09c53e6e | |
parent | 72432502fba43c6e10e54e94a0d69c751b21ddb7 (diff) | |
download | mongo-f8ca54b5fc64397be653e379fd1bc2b222620a40.tar.gz |
SERVER-47648 Simplify single node replset stepup on initiate and reconfig
(cherry picked from commit 49cc63fbb37f8a51f845c8d35bdb2f9c11219dab)
10 files changed, 126 insertions, 122 deletions
diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp index 9aaa120dcde..9fee5cc1f05 100644 --- a/src/mongo/db/repl/replication_coordinator_impl.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl.cpp @@ -3835,12 +3835,6 @@ ReplicationCoordinatorImpl::_updateMemberStateFromTopologyCoordinator(WithLock l const MemberState newState = _topCoord->getMemberState(); if (newState == _memberState) { - if (_topCoord->getRole() == TopologyCoordinator::Role::kCandidate) { - invariant(_rsConfig.getNumMembers() == 1 && _selfIndex == 0 && - _rsConfig.getMemberAt(0).isElectable()); - // Start election in protocol version 1 - return kActionStartSingleNodeElection; - } return kActionNone; } @@ -3890,13 +3884,10 @@ ReplicationCoordinatorImpl::_updateMemberStateFromTopologyCoordinator(WithLock l _readWriteAbility->setCanServeNonLocalReads_UNSAFE(1U); } - if (newState.secondary() && _topCoord->getRole() == TopologyCoordinator::Role::kCandidate) { - // When transitioning to SECONDARY, the only way for _topCoord to report the candidate - // role is if the configuration represents a single-node replica set. In that case, the - // overriding requirement is to elect this singleton node primary. - invariant(_rsConfig.getNumMembers() == 1 && _selfIndex == 0 && - _rsConfig.getMemberAt(0).isElectable()); - // Start election in protocol version 1 + if (newState.secondary() && result != kActionSteppedDown && + _topCoord->isElectableNodeInSingleNodeReplicaSet()) { + // When transitioning from other follower states to SECONDARY, run for election on a + // single-node replica set. result = kActionStartSingleNodeElection; } @@ -3982,9 +3973,7 @@ void ReplicationCoordinatorImpl::_performPostMemberStateUpdateAction( _externalState->stopAsyncUpdatesOfAndClearOplogTruncateAfterPoint(); break; case kActionStartSingleNodeElection: - // In protocol version 1, single node replset will run an election instead of - // kActionWinElection as in protocol version 0. - _startElectSelfV1(StartElectionReasonEnum::kElectionTimeout); + _startElectSelfIfEligibleV1(StartElectionReasonEnum::kElectionTimeout); break; default: LOGV2_FATAL(26010, @@ -4338,7 +4327,13 @@ ReplicationCoordinatorImpl::_setCurrentRSConfig(WithLock lk, _cancelPriorityTakeover_inlock(); _cancelAndRescheduleElectionTimeout_inlock(); - const PostMemberStateUpdateAction action = _updateMemberStateFromTopologyCoordinator(lk); + PostMemberStateUpdateAction action = _updateMemberStateFromTopologyCoordinator(lk); + if (_topCoord->isElectableNodeInSingleNodeReplicaSet()) { + // If the new config describes an electable one-node replica set, we need to start an + // election. + action = PostMemberStateUpdateAction::kActionStartSingleNodeElection; + } + if (_selfIndex >= 0) { // Don't send heartbeats if we're not in the config, if we get re-added one of the // nodes in the set will contact us. diff --git a/src/mongo/db/repl/replication_coordinator_impl.h b/src/mongo/db/repl/replication_coordinator_impl.h index ac5d76919bd..392d8bd31f6 100644 --- a/src/mongo/db/repl/replication_coordinator_impl.h +++ b/src/mongo/db/repl/replication_coordinator_impl.h @@ -450,13 +450,13 @@ public: long long term, TopologyCoordinator::UpdateTermResult* updateResult); /** - * If called after _startElectSelfV1(), blocks until all asynchronous + * If called after _startElectSelfV1_inlock(), blocks until all asynchronous * activities associated with election complete. */ void waitForElectionFinish_forTest(); /** - * If called after _startElectSelfV1(), blocks until all asynchronous + * If called after _startElectSelfV1_inlock(), blocks until all asynchronous * activities associated with election dry run complete, including writing * last vote and scheduling the real election. */ @@ -1069,7 +1069,7 @@ private: * For proper concurrency, start methods must be called while holding _mutex. * * For V1 (raft) style elections the election path is: - * _startElectSelfV1() or _startElectSelfV1_inlock() + * _startElectSelfIfEligibleV1() * _processDryRunResult() (may skip) * _startRealElection_inlock() * _writeLastVoteForMyElection() @@ -1077,7 +1077,6 @@ private: * _onVoteRequestComplete() */ void _startElectSelfV1_inlock(StartElectionReasonEnum reason); - void _startElectSelfV1(StartElectionReasonEnum reason); /** * Callback called when the dryRun VoteRequester has completed; checks the results and diff --git a/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp b/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp index 849d83e4084..671563ea058 100644 --- a/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp @@ -91,11 +91,6 @@ public: } }; -void ReplicationCoordinatorImpl::_startElectSelfV1(StartElectionReasonEnum reason) { - stdx::lock_guard<Latch> lk(_mutex); - _startElectSelfV1_inlock(reason); -} - void ReplicationCoordinatorImpl::_startElectSelfV1_inlock(StartElectionReasonEnum reason) { invariant(!_voteRequester); diff --git a/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp b/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp index ef8a7332b66..038c2568290 100644 --- a/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp @@ -160,7 +160,7 @@ TEST_F(ReplCoordTest, ElectionSucceedsWhenNodeIsTheOnlyElectableNode) { } net->exitNetwork(); - // _startElectSelfV1 is called when election timeout expires, so election + // _startElectSelfV1_inlock is called when election timeout expires, so election // finished event has been set. getReplCoord()->waitForElectionFinish_forTest(); @@ -904,6 +904,47 @@ TEST_F(ReplCoordTest, ElectionFailsWhenTermChangesDuringActualElection) { countTextFormatLogLinesContaining("Not becoming primary, we have been superseded already")); } +TEST_F(ReplCoordTest, StartElectionOnSingleNodeInitiate) { + init("mySet"); + start(HostAndPort("node1", 12345)); + auto opCtx = makeOperationContext(); + BSONObj configObj = configWithMembers(1, 0, BSON_ARRAY(member(1, "node1:12345"))); + + // Initialize my last applied, so that the node is electable. + replCoordSetMyLastAppliedAndDurableOpTime({Timestamp(100, 1), 0}); + + BSONObjBuilder result; + ASSERT_OK(getReplCoord()->processReplSetInitiate(opCtx.get(), configObj, &result)); + ASSERT_EQUALS(MemberState::RS_RECOVERING, getReplCoord()->getMemberState().s); + // Oplog applier attempts to transition the state to Secondary, which triggers + // election on single node. + getReplCoord()->finishRecoveryIfEligible(opCtx.get()); + // Run pending election operations on executor. + getNet()->enterNetwork(); + getNet()->runReadyNetworkOperations(); + getNet()->exitNetwork(); + ASSERT_EQUALS(MemberState::RS_PRIMARY, getReplCoord()->getMemberState().s); +} + +TEST_F(ReplCoordTest, StartElectionOnSingleNodeStartup) { + BSONObj configObj = configWithMembers(1, 0, BSON_ARRAY(member(1, "node1:12345"))); + assertStartSuccess(configObj, HostAndPort("node1", 12345)); + + // Initialize my last applied, so that the node is electable. + replCoordSetMyLastAppliedAndDurableOpTime({Timestamp(100, 1), 0}); + + ASSERT_OK(getReplCoord()->setFollowerMode(MemberState::RS_RECOVERING)); + // Oplog applier attempts to transition the state to Secondary, which triggers + // election on single node. + auto opCtx = makeOperationContext(); + getReplCoord()->finishRecoveryIfEligible(opCtx.get()); + // Run pending election operations on executor. + getNet()->enterNetwork(); + getNet()->runReadyNetworkOperations(); + getNet()->exitNetwork(); + ASSERT_EQUALS(MemberState::RS_PRIMARY, getReplCoord()->getMemberState().s); +} + class TakeoverTest : public ReplCoordTest { public: /* diff --git a/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp b/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp index 0a9fd580a06..5521ded0cf8 100644 --- a/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp @@ -1000,7 +1000,9 @@ void ReplicationCoordinatorImpl::_startElectSelfIfEligibleV1(WithLock, StartElectionReasonEnum reason) { // If it is not a single node replica set, no need to start an election after stepdown timeout. if (reason == StartElectionReasonEnum::kSingleNodePromptElection && - _rsConfig.getNumMembers() != 1) { + !_topCoord->isElectableNodeInSingleNodeReplicaSet()) { + LOGV2_FOR_ELECTION( + 4764800, 0, "Not starting an election, since we are not an electable single node"); return; } diff --git a/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp b/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp index 0e7fee0157f..b110f93b5f6 100644 --- a/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp @@ -779,17 +779,6 @@ public: unittest::MinimumLoggedSeverityGuard severityGuard{logv2::LogComponent::kDefault, logv2::LogSeverity::Debug(3)}; - BSONObj member(int id, std::string host) { - return BSON("_id" << id << "host" << host); - } - - BSONObj configWithMembers(int version, long long term, BSONArray members) { - return BSON("_id" - << "mySet" - << "protocolVersion" << 1 << "version" << version << "term" << term << "members" - << members); - } - void respondToHeartbeat() { auto net = getNet(); auto noi = net->getNextReadyRequest(); @@ -1386,6 +1375,34 @@ TEST_F(ReplCoordReconfigTest, StepdownShouldInterruptConfigWrite) { ASSERT_EQ(status.reason(), "Stepped down when persisting new config"); } +TEST_F(ReplCoordReconfigTest, StartElectionOnReconfigToSingleNode) { + // Start up as a secondary. + init(); + assertStartSuccess( + configWithMembers( + 1, 0, BSON_ARRAY(member(1, "n1:1") << member(2, "n2:1") << member(3, "n3:1"))), + HostAndPort("n1", 1)); + ASSERT_OK(getReplCoord()->setFollowerMode(MemberState::RS_SECONDARY)); + + // Simulate application of one oplog entry. + replCoordSetMyLastAppliedAndDurableOpTime(OpTime(Timestamp(1, 1), 0)); + + // Construct the new config of single node replset. + auto configVersion = 2; + ReplSetReconfigArgs args; + args.force = true; + args.newConfigObj = configWithMembers(configVersion, 1, BSON_ARRAY(member(1, "n1:1"))); + + BSONObjBuilder result; + const auto opCtx = makeOperationContext(); + ASSERT_OK(getReplCoord()->processReplSetReconfig(opCtx.get(), args, &result)); + getNet()->enterNetwork(); + getNet()->runReadyNetworkOperations(); + getNet()->exitNetwork(); + + ASSERT_EQUALS(MemberState::RS_PRIMARY, getReplCoord()->getMemberState().s); +} + } // anonymous namespace } // namespace repl } // namespace mongo diff --git a/src/mongo/db/repl/replication_coordinator_test_fixture.h b/src/mongo/db/repl/replication_coordinator_test_fixture.h index cef52cd81e9..e74e89bc6ee 100644 --- a/src/mongo/db/repl/replication_coordinator_test_fixture.h +++ b/src/mongo/db/repl/replication_coordinator_test_fixture.h @@ -76,6 +76,20 @@ public: */ static BSONObj addProtocolVersion(const BSONObj& configDoc, int protocolVersion); + /** + * Helpers to construct a config. + */ + static BSONObj member(int id, std::string host) { + return BSON("_id" << id << "host" << host); + } + + static BSONObj configWithMembers(int version, long long term, BSONArray members) { + return BSON("_id" + << "mySet" + << "protocolVersion" << 1 << "version" << version << "term" << term << "members" + << members); + } + protected: ReplCoordTest(); virtual ~ReplCoordTest(); diff --git a/src/mongo/db/repl/topology_coordinator.cpp b/src/mongo/db/repl/topology_coordinator.cpp index 96f02005bfb..ad7373215da 100644 --- a/src/mongo/db/repl/topology_coordinator.cpp +++ b/src/mongo/db/repl/topology_coordinator.cpp @@ -2196,13 +2196,6 @@ void TopologyCoordinator::updateConfig(const ReplSetConfig& newConfig, int selfI // By this point we know we are in Role::kFollower _currentPrimaryIndex = -1; // force secondaries to re-detect who the primary is - - if (_isElectableNodeInSingleNodeReplicaSet()) { - // If the new config describes a one-node replica set, we're the one member, - // we're electable, we're not in maintenance mode and we are currently in followerMode - // SECONDARY, we must transition to candidate, in leiu of heartbeats. - _role = Role::kCandidate; - } } std::string TopologyCoordinator::_getHbmsg(Date_t now) const { // ignore messages over 2 minutes old @@ -2646,24 +2639,13 @@ void TopologyCoordinator::setFollowerMode(MemberState::MS newMode) { default: MONGO_UNREACHABLE; } - - if (_followerMode != MemberState::RS_SECONDARY) { - return; - } - - // When a single node replica set transitions to SECONDARY, we must check if we should - // be a candidate here. This is necessary because a single node replica set has no - // heartbeats that would normally change the role to candidate. - - if (_isElectableNodeInSingleNodeReplicaSet()) { - _role = Role::kCandidate; - } } -bool TopologyCoordinator::_isElectableNodeInSingleNodeReplicaSet() const { - return _followerMode == MemberState::RS_SECONDARY && _rsConfig.getNumMembers() == 1 && - _selfIndex == 0 && _rsConfig.getMemberAt(_selfIndex).isElectable() && - _maintenanceModeCalls == 0; +bool TopologyCoordinator::isElectableNodeInSingleNodeReplicaSet() const { + auto isSingleNode = _rsConfig.getNumMembers() == 1 && _selfIndex == 0; + // Single node replset must be electable. + invariant(!isSingleNode || _rsConfig.getMemberAt(_selfIndex).isElectable()); + return (getMemberState() == MemberState::RS_SECONDARY) && isSingleNode; } void TopologyCoordinator::finishUnconditionalStepDown() { diff --git a/src/mongo/db/repl/topology_coordinator.h b/src/mongo/db/repl/topology_coordinator.h index 0e9f90aef21..752b9b52b2f 100644 --- a/src/mongo/db/repl/topology_coordinator.h +++ b/src/mongo/db/repl/topology_coordinator.h @@ -171,6 +171,16 @@ public: enum class UpdateTermResult { kAlreadyUpToDate, kTriggerStepDown, kUpdatedTerm }; + /** + * Returns true if we are a one-node replica set, we're the one member, + * we're electable, we're not in maintenance mode, and we are currently in followerMode + * SECONDARY. + * + * This is used to decide if we should start an election in a one-node replica set. + */ + bool isElectableNodeInSingleNodeReplicaSet() const; + + //////////////////////////////////////////////////////////// // // Basic state manipulation methods. @@ -945,15 +955,6 @@ private: **/ bool _memberIsBlacklisted(const MemberConfig& memberConfig, Date_t now) const; - /** - * Returns true if we are a one-node replica set, we're the one member, - * we're electable, we're not in maintenance mode, and we are currently in followerMode - * SECONDARY. - * - * This is used to decide if we should transition to Role::candidate in a one-node replica set. - */ - bool _isElectableNodeInSingleNodeReplicaSet() const; - // Returns a string representation of the current replica set status for logging purposes. std::string _getReplSetStatusString(); diff --git a/src/mongo/db/repl/topology_coordinator_v1_test.cpp b/src/mongo/db/repl/topology_coordinator_v1_test.cpp index 2ef75f15402..96b9f3a9cda 100644 --- a/src/mongo/db/repl/topology_coordinator_v1_test.cpp +++ b/src/mongo/db/repl/topology_coordinator_v1_test.cpp @@ -2893,24 +2893,6 @@ TEST_F(TopoCoordTest, RespondToHeartbeatsWithNullLastAppliedAndLastDurableWhileI ASSERT_EQUALS(OpTime(), response.getDurableOpTime()); } -TEST_F(TopoCoordTest, BecomeCandidateWhenBecomingSecondaryInSingleNodeSet) { - ASSERT_TRUE(TopologyCoordinator::Role::kFollower == getTopoCoord().getRole()); - ASSERT_EQUALS(MemberState::RS_STARTUP, getTopoCoord().getMemberState().s); - updateConfig(BSON("_id" - << "rs0" - << "version" << 1 << "members" - << BSON_ARRAY(BSON("_id" << 1 << "host" - << "hself"))), - 0); - ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s); - - // if we are the only node, we should become a candidate when we transition to SECONDARY - ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); - getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY); - ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); - ASSERT_EQUALS(MemberState::RS_SECONDARY, getTopoCoord().getMemberState().s); -} - TEST_F(TopoCoordTest, DoNotBecomeCandidateWhenBecomingSecondaryInSingleNodeSetIfInMaintenanceMode) { ASSERT_TRUE(TopologyCoordinator::Role::kFollower == getTopoCoord().getRole()); ASSERT_EQUALS(MemberState::RS_STARTUP, getTopoCoord().getMemberState().s); @@ -2938,36 +2920,6 @@ TEST_F(TopoCoordTest, DoNotBecomeCandidateWhenBecomingSecondaryInSingleNodeSetIf ASSERT_EQUALS(MemberState::RS_SECONDARY, getTopoCoord().getMemberState().s); } -TEST_F(TopoCoordTest, BecomeCandidateWhenReconfigToBeElectableInSingleNodeSet) { - ASSERT_TRUE(TopologyCoordinator::Role::kFollower == getTopoCoord().getRole()); - ASSERT_EQUALS(MemberState::RS_STARTUP, getTopoCoord().getMemberState().s); - ReplSetConfig cfg; - cfg.initialize(BSON("_id" - << "rs0" - << "version" << 1 << "protocolVersion" << 1 << "members" - << BSON_ARRAY(BSON("_id" << 1 << "host" - << "hself" - << "priority" << 0)))) - .transitional_ignore(); - getTopoCoord().updateConfig(cfg, 0, now()++); - ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s); - - ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); - getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY); - ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); - ASSERT_EQUALS(MemberState::RS_SECONDARY, getTopoCoord().getMemberState().s); - - // we should become a candidate when we reconfig to become electable - - updateConfig(BSON("_id" - << "rs0" - << "version" << 1 << "members" - << BSON_ARRAY(BSON("_id" << 1 << "host" - << "hself"))), - 0); - ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); -} - TEST_F(TopoCoordTest, DoNotBecomeCandidateWhenReconfigToBeElectableInSingleNodeSetIfInMaintenanceMode) { ASSERT_TRUE(TopologyCoordinator::Role::kFollower == getTopoCoord().getRole()); @@ -3095,7 +3047,9 @@ TEST_F(TopoCoordTest, NodeTransitionsToRemovedWhenRemovedFromConfigEvenWhenPrima ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s); getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY); - ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); + setMyOpTime({Timestamp(1, 1), 0}); + ASSERT_OK(getTopoCoord().becomeCandidateIfElectable(Date_t(), + StartElectionReasonEnum::kElectionTimeout)); // win election and primary getTopoCoord().processWinElection(OID::gen(), Timestamp()); @@ -3128,7 +3082,9 @@ TEST_F(TopoCoordTest, NodeTransitionsToSecondaryWhenReconfiggingToBeUnelectable) ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s); getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY); - ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); + setMyOpTime({Timestamp(1, 1), 0}); + ASSERT_OK(getTopoCoord().becomeCandidateIfElectable(Date_t(), + StartElectionReasonEnum::kElectionTimeout)); // win election and primary getTopoCoord().processWinElection(OID::gen(), Timestamp()); @@ -3164,7 +3120,9 @@ TEST_F(TopoCoordTest, NodeMaintainsPrimaryStateAcrossReconfigIfNodeRemainsElecta ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s); getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY); - ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole()); + setMyOpTime({Timestamp(1, 1), 0}); + ASSERT_OK(getTopoCoord().becomeCandidateIfElectable(Date_t(), + StartElectionReasonEnum::kElectionTimeout)); // win election and primary getTopoCoord().processWinElection(OID::gen(), Timestamp()); |