summaryrefslogtreecommitdiff
path: root/jstests/sharding/stale_mongos_updates_and_removes.js
blob: 193cfcb83bf9c659a2c951c1ff989ff2a9b78c10 (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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/**
 * Tests correctness of single and multi updates and removes sent by a *stale* mongos in the
 * absence of concurrent writes or migrations.
 *
 * Single updates and removes are always targeted and versioned, because they can be retried
 * without causing the operation to be repeated on another shard (only one shard can be originally
 * targeted for a single update or remove).
 *
 * Multi updates and removes containing an equality match on the shard key are also targeted and
 * versioned, because only one shard can be originally targeted for a point query on the shard key.
 *
 * All other multi updates and removes are sent to all shards and unversioned.
 */

// Create a new sharded collection with numDocs documents, with two docs sharing each shard key
// (used for testing *multi* removes to a *specific* shard key).
var resetCollection = function() {
    assert(staleMongos.getCollection(collNS).drop());
    st.ensurePrimaryShard(dbName, st._shardNames[0]);
    assert.commandWorked(staleMongos.adminCommand({ shardCollection: collNS, key: { x: 1 }}));
    for (var i=0; i<numShardKeys; i++) {
        assert.writeOK(staleMongos.getCollection(collNS).insert({ x: i, fieldToUpdate: 0 }));
        assert.writeOK(staleMongos.getCollection(collNS).insert({ x: i, fieldToUpdate: 0 }));
    }
};

// Create a new sharded collection, and split data into two chunks on different shards using the
// stale mongos. Then use the fresh mongos to consolidate the chunks onto one of the shards.
// In the end:
// staleMongos will see:
// shard0: (-inf, splitPoint]
// shard1: (splitPoint, inf]
// freshMongos will see:
// shard0: (-inf, splitPoint], (splitPoint, inf]
// shard1:
var makeStaleMongosTargetMultipleShards = function() {
    resetCollection();

    // Make sure staleMongos sees all data on first shard.
    var chunk = staleMongos.getCollection("config.chunks").findOne({ min: { x: MinKey },
                                                                     max: { x: MaxKey }});
    assert(chunk.shard === st._shardNames[0]);

    // Make sure staleMongos sees two chunks on two different shards.
    assert.commandWorked(staleMongos.adminCommand({ split: collNS, middle: { x: splitPoint }}));
    assert.commandWorked(staleMongos.adminCommand({ moveChunk: collNS,
                                                    find: { x: 0 },
                                                    to: st._shardNames[1],
                                                    _waitForDelete: true }));

    // Use freshMongos to consolidate the chunks on one shard.
    assert.commandWorked(freshMongos.adminCommand({ moveChunk: collNS,
                                                    find: { x: 0 },
                                                    to: st._shardNames[0],
                                                    _waitForDelete: true }));
};

// Create a new sharded collection and move a chunk from one shard to another. In the end,
// staleMongos will see:
// shard0: (-inf, inf]
// shard1:
// freshMongos will see:
// shard0:
// shard1: (-inf, inf]
var makeStaleMongosTargetSingleShard = function() {
    resetCollection();
    // Make sure staleMongos sees all data on first shard.
    var chunk = staleMongos.getCollection("config.chunks").findOne({ min: { x: MinKey },
                                                                     max: { x: MaxKey }});
    assert(chunk.shard === st._shardNames[0]);

    // Use freshMongos to move chunk to another shard.
    assert.commandWorked(freshMongos.adminCommand({ moveChunk: collNS,
                                                    find: { x: 0 },
                                                    to: st._shardNames[1],
                                                    _waitForDelete: true }));
};

var checkAllRemoveQueries = function(makeMongosStaleFunc) {
    var multi = { justOne: false };
    var single = { justOne: true };

    var doRemove = function(query, multiOption, makeMongosStaleFunc) {
        makeMongosStaleFunc();
        assert.writeOK(staleMongos.getCollection(collNS).remove(query, multiOption));
        if (multiOption.justOne) {
            // A total of one document should have been removed from the collection.
            assert.eq(numDocs - 1, staleMongos.getCollection(collNS).find().itcount());
        } else {
            // All documents matching the query should have been removed.
            assert.eq(0, staleMongos.getCollection(collNS).find(query).itcount());
        }
    };

    var checkRemoveIsInvalid = function(query, multiOption, makeMongosStaleFunc) {
        makeMongosStaleFunc();
        var res = staleMongos.getCollection(collNS).remove(query, multiOption); 
        assert.writeError(res);
    };

    // Not possible because single remove requires equality match on shard key.
    checkRemoveIsInvalid(emptyQuery, single, makeMongosStaleFunc);
    doRemove(emptyQuery, multi, makeMongosStaleFunc);

    doRemove(pointQuery, single, makeMongosStaleFunc);
    doRemove(pointQuery, multi, makeMongosStaleFunc);

    // Not possible because can't do range query on a single remove.
    checkRemoveIsInvalid(rangeQuery, single, makeMongosStaleFunc);
    doRemove(rangeQuery, multi, makeMongosStaleFunc);

    // Not possible because single remove must contain _id or shard key at top level
    // (not within $or).
    checkRemoveIsInvalid(multiPointQuery, single, makeMongosStaleFunc);
    doRemove(multiPointQuery, multi, makeMongosStaleFunc);
};

var checkAllUpdateQueries = function(makeMongosStaleFunc) {
    var oUpdate = { $inc: { fieldToUpdate: 1 }}; // op-style update (non-idempotent)
    var rUpdate = { x: 0, fieldToUpdate: 1 }; // replacement-style update (idempotent)
    var queryAfterUpdate = { fieldToUpdate: 1 };

    var multi = { multi: true };
    var single = { multi: false };

    var doUpdate = function(query, update, multiOption, makeMongosStaleFunc) {
        makeMongosStaleFunc();
        assert.writeOK(staleMongos.getCollection(collNS).update(query, update, multiOption));
        if (multiOption.multi) {
            // All documents matching the query should have been updated.
            assert.eq(staleMongos.getCollection(collNS).find(query).itcount(),
                      staleMongos.getCollection(collNS).find(queryAfterUpdate).itcount());
        } else {
            // A total of one document should have been updated.
            assert.eq(1, staleMongos.getCollection(collNS).find(queryAfterUpdate).itcount());
        }
    };

    var checkUpdateIsInvalid = function(query, update, multiOption, makeMongosStaleFunc, err) {
        makeMongosStaleFunc();
        var res = staleMongos.getCollection(collNS).update(query, update, multiOption);
        assert.writeError(res);
    };

    // This update has inconsistent behavior as explained in SERVER-22895.
    //doUpdate(emptyQuery, rUpdate, single, makeMongosStaleFunc);
    // Not possible because replacement-style requires equality match on shard key.
    checkUpdateIsInvalid(emptyQuery, rUpdate, multi, makeMongosStaleFunc);
    // Not possible because op-style requires equality match on shard key if single update.
    checkUpdateIsInvalid(emptyQuery, oUpdate, single, makeMongosStaleFunc);
    doUpdate(emptyQuery, oUpdate, multi, makeMongosStaleFunc);

    doUpdate(pointQuery, rUpdate, single, makeMongosStaleFunc);
    // Not possible because replacement-style requires multi=false.
    checkUpdateIsInvalid(pointQuery, rUpdate, multi, makeMongosStaleFunc);
    doUpdate(pointQuery, oUpdate, single, makeMongosStaleFunc);
    doUpdate(pointQuery, oUpdate, multi, makeMongosStaleFunc);

    doUpdate(rangeQuery, rUpdate, single, makeMongosStaleFunc);
    // Not possible because replacement-style requires multi=false.
    checkUpdateIsInvalid(rangeQuery, rUpdate, multi, makeMongosStaleFunc);
    // Not possible because can't do range query on a single update.
    checkUpdateIsInvalid(rangeQuery, oUpdate, single, makeMongosStaleFunc);
    doUpdate(rangeQuery, oUpdate, multi, makeMongosStaleFunc);

    doUpdate(multiPointQuery, rUpdate, single, makeMongosStaleFunc);
    // Not possible because replacement-style requires multi=false.
    checkUpdateIsInvalid(multiPointQuery, rUpdate, multi, makeMongosStaleFunc);
    // Not possible because single remove must contain _id or shard key at top level
    // (not within $or).
    checkUpdateIsInvalid(multiPointQuery, oUpdate, single, makeMongosStaleFunc);
    doUpdate(multiPointQuery, oUpdate, multi, makeMongosStaleFunc);
};

var st = new ShardingTest({shards: 2, mongos: 2, other: { mongosOptions: { noAutoSplit: "" }} });
var dbName = 'test';
var collNS = dbName + '.foo';
var numShardKeys = 10;
var numDocs = numShardKeys * 2;
var splitPoint = numShardKeys / 2;

assert.commandWorked(st.s.adminCommand({ enableSharding: dbName }));
assert.commandWorked(st.s.adminCommand({ shardCollection: collNS, key: { x: 1 }}));

var freshMongos = st.s0;
var staleMongos = st.s1;

var emptyQuery = {};
var pointQuery = { x: 0 };

// Choose a range that would fall on only one shard.
// Use (splitPoint - 1) because of SERVER-20768.
var rangeQuery = { x: { $gte: 0, $lt: splitPoint - 1 }};

// Choose points that would fall on two different shards.
var multiPointQuery = { $or: [{ x: 0 }, { x: numShardKeys }]};

checkAllRemoveQueries(makeStaleMongosTargetSingleShard);
checkAllRemoveQueries(makeStaleMongosTargetMultipleShards);

checkAllUpdateQueries(makeStaleMongosTargetSingleShard);
checkAllUpdateQueries(makeStaleMongosTargetMultipleShards);

st.stop();