summaryrefslogtreecommitdiff
path: root/jstests/libs/cleanup_orphaned_util.js
blob: 3990c148df4f9f744eadec6c7477534d21c5a53b (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
//
// Utilities for testing cleanupOrphaned command.
//

//
// Run cleanupOrphaned on a shard, and assert cleanupOrphaned runs the
// expected number of times before stopping.
//
function cleanupOrphaned(shardConnection, ns, expectedIterations) {
    var admin = shardConnection.getDB('admin'), result = admin.runCommand({cleanupOrphaned: ns}),
        iterations = 1;

    if (!result.ok) {
        printjson(result);
    }
    assert(result.ok);
    while (result.stoppedAtKey) {
        result = admin.runCommand({cleanupOrphaned: ns, startingFromKey: result.stoppedAtKey});

        assert(result.ok);
        ++iterations;
    }

    assert.eq(iterations,
              expectedIterations,
              'Expected to run ' +
                  'cleanupOrphaned' + expectedIterations + ' times, but it only ran ' + iterations +
                  ' times before stoppedAtKey was null.');
}

// Shards data from key range, then inserts orphan documents, runs cleanupOrphans
// and makes sure that orphans are removed.
// Pass an options object like:
// {
//     shardKey: { a: 1, b: 1 },
//     keyGen: function() { return [{ a: 'foo', b: 1 }, { a: 'bar', b: 2 }]; }
// }
function testCleanupOrphaned(options) {
    var st = new ShardingTest({shards: 2, mongos: 2});

    var mongos = st.s0, admin = mongos.getDB('admin'),
        shards = mongos.getCollection('config.shards').find().toArray(),
        coll = mongos.getCollection('foo.bar'),
        shard0Coll = st.shard0.getCollection(coll.getFullName()), keys = options.keyGen(),
        beginning = keys[0], oneQuarter = keys[Math.round(keys.length / 4)],
        middle = keys[Math.round(keys.length / 2)],
        threeQuarters = keys[Math.round(3 * keys.length / 4)];

    assert.commandWorked(admin.runCommand({enableSharding: coll.getDB().getName()}));

    printjson(admin.runCommand({movePrimary: coll.getDB() + "", to: shards[0]._id}));

    assert.commandWorked(
        admin.runCommand({shardCollection: coll.getFullName(), key: options.shardKey}));

    st.printShardingStatus();

    jsTest.log('Inserting some regular docs...');

    assert.commandWorked(admin.runCommand({split: coll.getFullName(), middle: middle}));

    assert.commandWorked(admin.runCommand(
        {moveChunk: coll.getFullName(), find: middle, to: shards[1]._id, _waitForDelete: true}));

    for (var i = 0; i < keys.length; i++)
        coll.insert(keys[i]);
    assert.eq(null, coll.getDB().getLastError());

    // Half of the data is on each shard:
    // shard 0: [beginning, middle)
    // shard 1:            [middle, end)
    //
    assert.eq(keys.length / 2, shard0Coll.count());
    assert.eq(keys.length, coll.find().itcount());

    jsTest.log('Inserting some orphaned docs...');

    shard0Coll.insert(threeQuarters);

    // I'll represent the orphan doc like {threeQuarters}, in this diagram:
    //
    // shard 0: [beginning, middle) {threeQuarters}
    // shard 1:            [middle,                 end)
    assert.eq(null, shard0Coll.getDB().getLastError());
    assert.eq(1 + keys.length / 2, shard0Coll.count());

    jsTest.log('Cleaning up orphaned data...');

    cleanupOrphaned(st.shard0, coll.getFullName(), 2);
    assert.eq(keys.length / 2, shard0Coll.count());
    assert.eq(keys.length, coll.find().itcount());

    jsTest.log('Moving half the data out again (making a hole)...');

    assert.commandWorked(admin.runCommand({split: coll.getFullName(), middle: oneQuarter}));

    assert.commandWorked(admin.runCommand(
        {moveChunk: coll.getFullName(), find: beginning, to: shards[1]._id, _waitForDelete: true}));

    // 1/4 of the data is on the first shard.
    // shard 0:            [threeQuarters,  middle)
    // shard 1: [beginning, threeQuarters) [middle, end)
    assert.eq(Math.round(keys.length / 4), shard0Coll.count());
    assert.eq(keys.length, coll.find().itcount());

    jsTest.log('Inserting some more orphaned docs...');

    shard0Coll.insert(beginning);
    shard0Coll.insert(middle);
    assert.eq(null, shard0Coll.getDB().getLastError());

    // shard 0: {beginning} [threeQuarters,  middle) {middle}
    // shard 1: [beginning,  threeQuarters)          [middle, end)
    assert.eq(2 + Math.round(keys.length / 4), shard0Coll.count());
    assert.eq(100, coll.find().itcount());

    jsTest.log('Cleaning up more orphaned data...');

    // Now cleanupOrphaned must iterate over 3 regions, not 2.
    cleanupOrphaned(st.shard0, coll.getFullName(), 3);
    assert.eq(Math.round(keys.length / 4), shard0Coll.count());
    assert.eq(keys.length, coll.find().itcount());

    jsTest.log('DONE!');

    st.stop();
}