summaryrefslogtreecommitdiff
path: root/jstests/replsets/use_history_after_restart.js
blob: f7de4d9ac60667ceff5fc9d68f996b987f129058 (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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
 * Demonstrate that durable history can be used across a restart. Also assert that any
 * collection/index minimum visible timestamps are set to legal values. The rough test outline:
 *
 * 1) Create a collection `existsAtOldestTs`. This collection and its `_id` index should be readable
 *    across a restart.
 * 2) Stop advancing the oldest timestamp with `WTPreserveSnapshotHistoryIndefinitely`.
 * 3) Create a secondary index on `existsAtOldestTs`. This index should not be readable across a
 *    restart. This is a conservative behavior that is perfectly reasonable to later relax.
 * 4) Create a collection `dneAtOldestTs`. This collection should not be readable across a restart.
 *
 * @tags: [requires_fcv_49, requires_majority_read_concern, requires_persistence]
 */
(function() {
"use strict";

let replTest = new ReplSetTest({
    name: "use_history_after_restart",
    nodes: 1,
    nodeOptions: {
        setParameter: {
            logComponentVerbosity: tojson({storage: {recovery: 2}}),
            // Set the history window to zero to explicitly control the oldest timestamp. This is
            // necessary to predictably exercise the minimum visible timestamp initialization of
            // collections and indexes across a restart.
            minSnapshotHistoryWindowInSeconds: 0,
            // Disable the noop writer to avoid background writes that can unintentionally advance
            // the stable/oldest timestamps.
            writePeriodicNoops: false
        }
    }
});
let nodes = replTest.startSet();
replTest.initiate();
let primary = replTest.getPrimary();
let testDB = primary.getDB("test");

// Create `existsAtOldestTs`. Insert one document with a majority write concern. The write concern
// guarantees the stable and oldest timestamp are bumped.
assert.commandWorked(testDB.createCollection("existsAtOldestTs"));
assert.commandWorked(testDB.runCommand(
    {insert: "existsAtOldestTs", documents: [{_id: 0, x: 0}], writeConcern: {w: "majority"}}));

// Preserving history stops advancing the oldest timestamp. All writes after this point must not be
// readable after the restart. Either due to a correct response being returned or the server
// responding with a `SnapshotUnavailable` error.
//
// Record the response. Treat the response's `opTime` as the `oldestTimestamp` that should be usable
// for a timestamped read after the restart.
let fpResp = assert.commandWorked(primary.adminCommand(
    {configureFailPoint: "WTPreserveSnapshotHistoryIndefinitely", mode: "alwaysOn"}));

// Insert a second document. This document will not be seen after the restart.
assert.commandWorked(testDB.runCommand({insert: "existsAtOldestTs", documents: [{x: 1}]}));

// Create an index that cannot be correctly used after a restart.
assert.commandWorked(testDB["existsAtOldestTs"].createIndex({x: 1}));

// Create a second collection that is not visible after a restart.
assert.commandWorked(testDB.createCollection("dneAtOldestTs"));

// Insert a document to the new collection with a majority write concern. This will bump the stable
// timestamp. Record the response and treat the response's `opTime` as the `stableTimestamp` that
// should be usable for a timestamped read after the restart.
let dneInsertResp = assert.commandWorked(testDB["dneAtOldestTs"].runCommand(
    {insert: "dneAtOldestTs", documents: [{}], writeConcern: {w: "majority"}}));

// Restart the node with the `WTPreserveSnapshotHistoryIndefinitely` set. This prevents startup from
// advancing the oldest timestamp. This allows us to reliably read at the existing `oldestTimestamp`
// after the restart.
replTest.restart(primary, {
    setParameter: {
        "failpoint.WTPreserveSnapshotHistoryIndefinitely": tojson({mode: "alwaysOn"}),
    }
});

primary = replTest.getPrimary();
let oldestTimestamp = fpResp["operationTime"];
let stableTimestamp = dneInsertResp["opTime"]["ts"];
jsTestLog({
    "Test Ops": primary.getDB("local")["oplog.rs"].find({ns: /test/}).sort({ts: -1}).toArray(),
    "OldestTimestamp (approximate)": oldestTimestamp,
    "StableTimestamp (approximate)": stableTimestamp
});

// We should successfully read the first insert from `existsAtOldestTs` when reading at the oldest
// timestamp. Querying for `_id: 0` will access both the `_id` index and the record
let result = primary.getDB("test").runCommand({
    find: "existsAtOldestTs",
    filter: {_id: 0},
    readConcern: {level: "snapshot", atClusterTime: oldestTimestamp}
});
jsTestLog({"ExistsRes": result});
assert.eq(1, result["cursor"]["firstBatch"].length);

// We should get an error that the newer secondary index is not available when reading at the oldest
// timestamp.
result = primary.getDB("test").runCommand({
    find: "existsAtOldestTs",
    hint: {x: 1},
    readConcern: {level: "snapshot", atClusterTime: oldestTimestamp}
});
jsTestLog({"Unavailable Index Result": result});
assert.commandFailedWithCode(result, ErrorCodes.BadValue);

// Reading from the index at the `stableTimestamp` should succeed and produce a correct result.
result = primary.getDB("test").runCommand({
    find: "existsAtOldestTs",
    hint: {x: 1},
    readConcern: {level: "snapshot", atClusterTime: stableTimestamp}
});
jsTestLog({"Available Index Result": result});
assert.eq(2, result["cursor"]["firstBatch"].length);

// Querying `dneAtOldestTs` at the oldest timestamp should fail with a `SnapshotUnavailable` error.
result = primary.getDB("test").runCommand(
    {find: "dneAtOldestTs", readConcern: {level: "snapshot", atClusterTime: oldestTimestamp}});
jsTestLog({"SnapshotUnavailable on dneAtOldestTs": result});
assert.commandFailedWithCode(result, ErrorCodes.SnapshotUnavailable);

// Querying `dneAtOldestTs` at the stable timestamp should succeed with a correct result.
result = primary.getDB("test").runCommand(
    {find: "dneAtOldestTs", readConcern: {level: "snapshot", atClusterTime: stableTimestamp}});
jsTestLog({"Available dneAtOldestTs result": result});
assert.eq(1, result["cursor"]["firstBatch"].length);

replTest.stopSet();
})();