0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-12-01 09:32:32 +01:00
mongodb/jstests/replsets/read_committed.js

180 lines
6.4 KiB
JavaScript
Raw Normal View History

/**
* Test basic read committed functionality, including:
* - Writes with writeConcern 'majority' should be visible once the write completes.
* - With the only data-bearing secondary down, committed reads should not include newly inserted
* data.
* - When data-bearing node comes back up and catches up, writes should be readable.
*/
load("jstests/replsets/rslib.js"); // For startSetIfSupportsReadMajority.
(function() {
"use strict";
const majorityWriteConcern = {
writeConcern: {w: "majority", wtimeout: 60 * 1000}
};
// Each test case includes a 'prepareCollection' method that sets up the initial state starting
// with an empty collection, a 'write' method that does some write, and two arrays,
// 'expectedBefore' and 'expectedAfter' that describe the expected contents of the collection
// before and after the write. The 'prepareCollection' and 'write' methods should leave the
// collection either empty or with a single document with _id: 1.
const testCases = {
insert: {
prepareCollection: function(coll) {}, // No-op
write: function(coll, writeConcern) {
assert.commandWorked(coll.insert({_id: 1}, writeConcern));
},
expectedBefore: [],
expectedAfter: [{_id: 1}],
},
update: {
prepareCollection: function(coll) {
assert.commandWorked(coll.insert({_id: 1, state: 'before'}, majorityWriteConcern));
},
write: function(coll, writeConcern) {
assert.commandWorked(coll.update({_id: 1}, {$set: {state: 'after'}}, writeConcern));
},
expectedBefore: [{_id: 1, state: 'before'}],
expectedAfter: [{_id: 1, state: 'after'}],
},
remove: {
prepareCollection: function(coll) {
assert.commandWorked(coll.insert({_id: 1}, majorityWriteConcern));
},
write: function(coll, writeConcern) {
assert.commandWorked(coll.remove({_id: 1}, writeConcern));
},
expectedBefore: [{_id: 1}],
expectedAfter: [],
},
};
// Set up a set and grab things for later.
var name = "read_committed";
var replTest =
new ReplSetTest({name: name, nodes: 3, nodeOptions: {enableMajorityReadConcern: ''}});
if (!startSetIfSupportsReadMajority(replTest)) {
jsTest.log("skipping test since storage engine doesn't support committed reads");
replTest.stopSet();
return;
}
var nodes = replTest.nodeList();
var config = {
"_id": name,
"members": [
{"_id": 0, "host": nodes[0]},
{"_id": 1, "host": nodes[1], priority: 0},
{"_id": 2, "host": nodes[2], arbiterOnly: true}
]
};
replTest.initiate(config);
// Get connections and collection.
var primary = replTest.getPrimary();
var secondary = replTest._slaves[0];
var coll = primary.getDB(name)[name];
var secondaryColl = secondary.getDB(name)[name];
function log(arg) {
jsTest.log(tojson(arg));
}
function doRead(coll, readConcern) {
readConcern.maxTimeMS = 3000;
var res = assert.commandWorked(coll.runCommand('find', readConcern));
return new DBCommandCursor(coll.getDB(), res).toArray();
}
function doDirtyRead(coll) {
log("doing dirty read");
var ret = doRead(coll, {"readConcern": {"level": "local"}});
log("done doing dirty read.");
return ret;
}
function doCommittedRead(coll) {
log("doing committed read");
var ret = doRead(coll, {"readConcern": {"level": "majority"}});
log("done doing committed read.");
return ret;
}
function readLatestOplogEntry(readConcernLevel) {
var oplog = primary.getDB('local').oplog.rs;
var res = oplog.runCommand('find', {
"readConcern": {"level": readConcernLevel},
"maxTimeMS": 3000,
sort: {$natural: -1},
limit: 1,
});
assert.commandWorked(res);
return new DBCommandCursor(coll.getDB(), res).toArray()[0];
}
for (var testName in testCases) {
jsTestLog('Running test ' + testName);
var test = testCases[testName];
const setUpInitialState = function setUpInitialState() {
assert.commandWorked(coll.remove({}, majorityWriteConcern));
test.prepareCollection(coll);
// Do some sanity checks.
assert.eq(doDirtyRead(coll), test.expectedBefore);
assert.eq(doCommittedRead(coll), test.expectedBefore);
};
// Writes done with majority write concern must be immediately visible to both dirty and
// committed reads.
setUpInitialState();
test.write(coll, majorityWriteConcern);
assert.eq(doDirtyRead(coll), test.expectedAfter);
assert.eq(doCommittedRead(coll), test.expectedAfter);
// Return to the initial state, then stop the secondary from applying new writes to prevent
// them from becoming committed.
setUpInitialState();
assert.commandWorked(
secondary.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "alwaysOn"}));
const initialOplogTs = readLatestOplogEntry('local').ts;
// Writes done without majority write concern must be immediately visible to dirty read
// and hidden from committed reads until they have been replicated. The rules for seeing
// an oplog entry for a write are the same as for the write itself.
test.write(coll, {});
assert.eq(doDirtyRead(coll), test.expectedAfter);
assert.neq(readLatestOplogEntry('local').ts, initialOplogTs);
assert.eq(doCommittedRead(coll), test.expectedBefore);
assert.eq(readLatestOplogEntry('majority').ts, initialOplogTs);
// Try the committed read again after sleeping to ensure it doesn't only work for
// queries immediately after the write.
sleep(1000);
assert.eq(doCommittedRead(coll), test.expectedBefore);
assert.eq(readLatestOplogEntry('majority').ts, initialOplogTs);
// Restart oplog application on the secondary and ensure the committed view is updated.
assert.commandWorked(
secondary.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "off"}));
coll.getDB().getLastError("majority", 60 * 1000);
assert.eq(doCommittedRead(coll), test.expectedAfter);
assert.neq(readLatestOplogEntry('majority').ts, initialOplogTs);
// The secondary will be able to make the write committed soon after the primary, but there
// is no way to block until it does.
try {
assert.soon(function() {
return friendlyEqual(doCommittedRead(secondaryColl), test.expectedAfter);
2016-05-28 23:55:12 +02:00
});
} catch (e) {
// generate useful error messages on failures.
assert.eq(doCommittedRead(secondaryColl), test.expectedAfter);
}
}
replTest.stopSet();
}());