summaryrefslogtreecommitdiff
path: root/jstests/replsets/update_commit_point_from_sync_source_ignores_term.js
blob: 61a4c339fdf29384ddeb01d8ae79524b7b7bb568 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
 * Tests that even if the sync source's lastOpCommitted is in a higher term than the node's
 * lastApplied, the node can update its own lastOpCommitted to its lastApplied.
 * @tags: [requires_majority_read_concern]
 */
(function() {
"use strict";

load("jstests/libs/write_concern_util.js");  // for [stop|restart]ServerReplication.

const dbName = "test";
const collName = "coll";

// Set up a ReplSetTest where nodes only sync one oplog entry at a time.
const rst = new ReplSetTest(
    {nodes: 5, useBridge: true, nodeOptions: {setParameter: "bgSyncOplogFetcherBatchSize=1"}});
rst.startSet();
const config = rst.getReplSetConfig();
// Ban chaining and prevent elections.
config.settings = {
    chainingAllowed: false,
    electionTimeoutMillis: 12 * 60 * 60 * 1000
};
rst.initiate(config);

const nodeA = rst.nodes[0];
const nodeB = rst.nodes[1];
const nodeC = rst.nodes[2];
const nodeD = rst.nodes[3];
const nodeE = rst.nodes[4];

jsTest.log("Node A is primary in term 1. Replicate a write to Node E that is not committed.");
assert.eq(nodeA, rst.getPrimary());
// Ensure Node E has a majority committed snapshot.
assert.commandWorked(nodeA.getDB(dbName)[collName].insert({_id: "dummy"}));
rst.awaitLastOpCommitted();
stopServerReplication([nodeB, nodeC, nodeD]);
assert.commandWorked(nodeA.getDB(dbName)[collName].insert({_id: "term 1, doc 1"}));
rst.awaitReplication(undefined, undefined, [nodeE]);
assert.eq(
    0,
    nodeE.getDB(dbName)[collName].find({_id: "term 1, doc 1"}).readConcern("majority").itcount());

jsTest.log("Disconnect Node E. Perform a new write.");
nodeE.disconnect([nodeA, nodeB, nodeC, nodeD]);
restartServerReplication([nodeB, nodeC, nodeD]);
assert.commandWorked(nodeA.getDB(dbName)[collName].insert({_id: "term 1, doc 2"}));

jsTest.log("Step up Node B in term 2. Commit a new write.");
// Ensure Node B is caught up, so that it can become primary.
rst.awaitReplication(undefined, undefined, [nodeB]);
assert.commandWorked(nodeB.adminCommand({replSetStepUp: 1}));
rst.waitForState(nodeA, ReplSetTest.State.SECONDARY);
assert.eq(nodeB, rst.getPrimary());
assert.commandWorked(
    nodeB.getDB(dbName)[collName].insert({_id: "term 2"}, {writeConcern: {w: "majority"}}));
// Node E might sync from Node A or Node B. Ensure they both have the new commit point.
rst.awaitLastOpCommitted(undefined, [nodeA]);

jsTest.log("Allow Node E to replicate the last write from term 1.");
// The stopReplProducerOnDocument failpoint ensures that Node E stops replicating before
// applying the document {msg: "new primary"}, which is the first document of term 2. This
// depends on the oplog fetcher batch size being 1.
assert.commandWorked(nodeE.adminCommand({
    configureFailPoint: "stopReplProducerOnDocument",
    mode: "alwaysOn",
    data: {document: {msg: "new primary"}}
}));
nodeE.reconnect([nodeA, nodeB, nodeC, nodeD]);
assert.soon(() => {
    return nodeE.getDB(dbName)[collName].find({_id: "term 1, doc 2"}).itcount() === 1;
});
assert.eq(0, nodeE.getDB(dbName)[collName].find({_id: "term 2"}).itcount());

jsTest.log("Node E now knows that its first write is majority committed.");
// It does not yet know that {_id: "term 1, doc 2"} is committed. Its last batch was {_id: "term
// 1, doc 2"}. The sync source's lastOpCommitted was in term 2, so Node E updated its
// lastOpCommitted to its lastApplied, which did not yet include {_id: "term 1, doc 2"}.
assert.eq(
    1,
    nodeE.getDB(dbName)[collName].find({_id: "term 1, doc 1"}).readConcern("majority").itcount());

assert.commandWorked(
    nodeE.adminCommand({configureFailPoint: "stopReplProducerOnDocument", mode: "off"}));
rst.stopSet();
}());