Skip to content
This repository was archived by the owner on Oct 31, 2023. It is now read-only.

Commit a6cd89a

Browse files
will62794Evergreen Agent
authored andcommitted
SERVER-49167 Set the stable timestamp without using the stable optime candidates when enableMajorityReadConcern:false
1 parent 2863db2 commit a6cd89a

File tree

2 files changed

+131
-11
lines changed

2 files changed

+131
-11
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Test that a node does not take a stable checkpoint at a timestamp earlier than minValid after
3+
* crashing post rollbackViaRefetch. This test exercises that behavior when run with
4+
* enableMajorityReadConcern:false.
5+
*
6+
* @tags: [requires_persistence]
7+
*/
8+
(function() {
9+
"use strict";
10+
11+
load("jstests/replsets/libs/rollback_test.js");
12+
load("jstests/libs/fail_point_util.js");
13+
14+
TestData.rollbackShutdowns = true;
15+
let dbName = "test";
16+
let sourceCollName = "coll";
17+
18+
let doc1 = {_id: 1, x: "document_of_interest"};
19+
20+
let CommonOps = (node) => {
21+
// Insert a document that will exist on all nodes.
22+
assert.commandWorked(node.getDB(dbName)[sourceCollName].insert(doc1));
23+
};
24+
25+
let SyncSourceOps = (node) => {
26+
// Insert some documents on the sync source so the rollback node will have a minValid it needs
27+
// to catch up to.
28+
assert.commandWorked(node.getDB(dbName)[sourceCollName].insert({x: 1, sync_source: 1}));
29+
assert.commandWorked(node.getDB(dbName)[sourceCollName].insert({x: 2, sync_source: 1}));
30+
assert.commandWorked(node.getDB(dbName)[sourceCollName].insert({x: 3, sync_source: 1}));
31+
};
32+
33+
let RollbackOps = (node) => {
34+
// Delete the document on the rollback node so it will be refetched from sync source.
35+
assert.commandWorked(node.getDB(dbName)[sourceCollName].remove(doc1));
36+
};
37+
38+
const replTest = new ReplSetTest({nodes: 3, useBridge: true});
39+
replTest.startSet();
40+
// Speed up the test.
41+
replTest.nodes.forEach(node => {
42+
assert.commandWorked(
43+
node.adminCommand({configureFailPoint: 'setSmallOplogGetMoreMaxTimeMS', mode: 'alwaysOn'}));
44+
});
45+
let config = replTest.getReplSetConfig();
46+
config.members[2].priority = 0;
47+
config.settings = {
48+
chainingAllowed: false
49+
};
50+
replTest.initiateWithHighElectionTimeout(config);
51+
let rollbackTest = new RollbackTest("rollback_crash_before_reaching_minvalid", replTest);
52+
CommonOps(rollbackTest.getPrimary());
53+
54+
let rollbackNode = rollbackTest.transitionToRollbackOperations();
55+
56+
// Have the node hang after rollback has completed but before it starts applying ops again.
57+
rollbackNode.adminCommand({configureFailPoint: 'bgSyncHangAfterRunRollback', mode: 'alwaysOn'});
58+
RollbackOps(rollbackNode);
59+
60+
let node = rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
61+
SyncSourceOps(node);
62+
63+
// Let the rollback run.
64+
rollbackTest.transitionToSyncSourceOperationsDuringRollback();
65+
66+
jsTestLog("Waiting for the rollback node to hit the failpoint.");
67+
checkLog.contains(rollbackNode, "bgSyncHangAfterRunRollback failpoint is set");
68+
69+
// Kill the rollback node before it has reached minValid. Sending a shutdown signal to the node
70+
// should cause us to break out of the hung failpoint, so we don't need to explicitly turn the
71+
// failpoint off.
72+
jsTestLog("Killing the rollback node.");
73+
replTest.stop(0, 9, {allowedExitCode: MongoRunner.EXIT_SIGKILL}, {forRestart: true});
74+
replTest.start(
75+
0,
76+
{
77+
setParameter: {
78+
// Pause oplog fetching so the node doesn't advance past minValid after restart.
79+
"failpoint.stopReplProducer": "{'mode':'alwaysOn'}"
80+
}
81+
},
82+
true /* restart */);
83+
84+
// Wait long enough for the initial stable checkpoint to be triggered if it was going to be. We
85+
// expect that no stable checkpoints are taken. If they are, we expect the test to fail when we
86+
// restart below and recover from a stable checkpoint.
87+
//
88+
// First we wait until the node has a commit point, since learning of one should trigger an update
89+
// to the stable timestamp. Then, we wait for a bit after this for any potential checkpoint to
90+
// occur. In the worst case, if the checkpoint was very slow to complete, we might produce a false
91+
// negative test result (the test would pass even though a bug existed), but we consider this
92+
// acceptable if it happens rarely.
93+
assert.soonNoExcept(() => {
94+
let status = replTest.nodes[0].adminCommand({replSetGetStatus: 1});
95+
return status.optimes.lastCommittedOpTime.ts !== Timestamp(0, 0);
96+
});
97+
sleep(5000);
98+
99+
// Kill and restart the node to test that we don't recover from an inconsistent stable checkpoint
100+
// taken above.
101+
replTest.stop(0, 9, {allowedExitCode: MongoRunner.EXIT_SIGKILL}, {forRestart: true});
102+
replTest.start(
103+
0,
104+
{
105+
setParameter: {
106+
// Make sure this failpoint is not still enabled in the saved startup options.
107+
"failpoint.stopReplProducer": "{'mode':'off'}"
108+
}
109+
},
110+
true /* restart */);
111+
112+
rollbackTest.transitionToSteadyStateOperations();
113+
114+
// Check the replica set.
115+
rollbackTest.stop();
116+
}());

src/mongo/db/repl/replication_coordinator_impl.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,19 @@ void ReplicationCoordinatorImpl::_finishLoadLocalConfig(
686686
OpTime minValid = _replicationProcess->getConsistencyMarkers()->getMinValid(opCtx.get());
687687
consistency =
688688
(lastOpTime >= minValid) ? DataConsistency::Consistent : DataConsistency::Inconsistent;
689+
690+
// It is not safe to take stable checkpoints until we reach minValid, so we set our
691+
// initialDataTimestamp to prevent this. It is expected that this is only necessary when
692+
// enableMajorityReadConcern:false.
693+
if (lastOpTime < minValid) {
694+
LOGV2_DEBUG(4916700,
695+
2,
696+
"Setting initialDataTimestamp to minValid since our last optime is less "
697+
"than minValid",
698+
"lastOpTime"_attr = lastOpTime,
699+
"minValid"_attr = minValid);
700+
_storage->setInitialDataTimestamp(getServiceContext(), minValid.getTimestamp());
701+
}
689702
}
690703

691704
// Update the global timestamp before setting the last applied opTime forward so the last
@@ -4982,23 +4995,14 @@ boost::optional<OpTimeAndWallTime> ReplicationCoordinatorImpl::_recalculateStabl
49824995
// Make sure the stable optime does not surpass its maximum.
49834996
stableOpTime = OpTimeAndWallTime(std::min(noOverlap, maximumStableOpTime.opTime), Date_t());
49844997

4985-
// Keep EMRC=false behavior the same for now.
4986-
// TODO (SERVER-47844) Don't use stable optime candidates here.
4987-
if (!serverGlobalParams.enableMajorityReadConcern) {
4988-
stableOpTime =
4989-
_chooseStableOpTimeFromCandidates(lk, _stableOpTimeCandidates, maximumStableOpTime);
4990-
}
4991-
49924998
if (stableOpTime) {
49934999
// Check that the selected stable optime does not exceed our maximum and that it does not
49945000
// surpass the no-overlap point.
49955001
invariant(stableOpTime.get().opTime.getTimestamp() <=
49965002
maximumStableOpTime.opTime.getTimestamp());
49975003
invariant(stableOpTime.get().opTime <= maximumStableOpTime.opTime);
4998-
if (serverGlobalParams.enableMajorityReadConcern) {
4999-
invariant(stableOpTime.get().opTime.getTimestamp() <= noOverlap.getTimestamp());
5000-
invariant(stableOpTime.get().opTime <= noOverlap);
5001-
}
5004+
invariant(stableOpTime.get().opTime.getTimestamp() <= noOverlap.getTimestamp());
5005+
invariant(stableOpTime.get().opTime <= noOverlap);
50025006
}
50035007

50045008
return stableOpTime;

0 commit comments

Comments
 (0)