summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/batched_multi_deletes_oplog.js
blob: 29116c615ee9f65cb481d249e2c50d5e6c3927ec (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
/**
 * Validate oplog behaviour of batched multi-deletes.
 *
 * @tags: [
 *  requires_fcv_61,
 *  requires_replication,
 * ]
 */

(function() {
"use strict";

// Verifies that batches replicate as applyOps entries.
function validateBatchedDeletesOplogDocsPerBatch(conn) {
    const db = conn.getDB("test");
    const coll = db.getCollection("c");

    const docsPerBatch = 100;
    const collCount = 5017;  // Intentionally not a multiple of docsPerBatch.

    // Disable size-based batching
    assert.commandWorked(db.adminCommand({setParameter: 1, batchedDeletesTargetStagedDocBytes: 0}));
    // Disable time-based batching
    assert.commandWorked(db.adminCommand({setParameter: 1, batchedDeletesTargetBatchTimeMS: 0}));
    // Set docs per batch target
    assert.commandWorked(
        db.adminCommand({setParameter: 1, batchedDeletesTargetBatchDocs: docsPerBatch}));

    coll.drop();
    assert.commandWorked(coll.insertMany(
        [...Array(collCount).keys()].map(x => ({_id: x, a: "a".repeat(1024)})), {ordered: false}));

    assert.eq(collCount, coll.find().itcount());
    assert.commandWorked(coll.deleteMany({_id: {$gte: 0}}));
    assert.eq(0, coll.find().itcount());

    // The deletion replicates as one applyOps per batch
    const applyOpsEntriesFull =
        db.getSiblingDB('local').oplog.rs.find({'o.applyOps': {$size: docsPerBatch}}).itcount();
    const applyOpsEntriesLast =
        db.getSiblingDB('local')
            .oplog.rs.find({'o.applyOps': {$size: collCount % docsPerBatch}})
            .itcount();
    const expectedApplyOpsEntries = Math.ceil(collCount / docsPerBatch);
    assert.eq(applyOpsEntriesFull + applyOpsEntriesLast, expectedApplyOpsEntries);
}

// Verifies the batched deleter cuts batches that stay within the 16MB BSON limit of one applyOps
// entry.
function validateBatchedDeletesOplogBatchAbove16MB(conn) {
    const db = conn.getDB("test");
    const coll = db.getCollection("c");

    // With non-clustered collections (_id of ObjectId type) the batched deleter's conservative
    // applyOps estimation would cut a batch at ~63k documents. Create a collection >> 63k
    // documents, make the docsPerBatch target high enough to hit the 16MB BSON limit with applyOps,
    // and disable other batch tunables that could cut a batch earlier than expected.
    const docsPerBatch = 500000;
    const collCount = 130000;
    const expectedApplyOpsEntries =
        Math.ceil(collCount / 63600 /* max docs per batch, see comment above. */);
    // Disable size-based batching
    assert.commandWorked(db.adminCommand({setParameter: 1, batchedDeletesTargetStagedDocBytes: 0}));
    // Disable time-based batching
    assert.commandWorked(db.adminCommand({setParameter: 1, batchedDeletesTargetBatchTimeMS: 0}));
    // Set artificially high docs per batch target
    assert.commandWorked(
        db.adminCommand({setParameter: 1, batchedDeletesTargetBatchDocs: docsPerBatch}));

    coll.drop();
    assert.commandWorked(
        coll.insertMany([...Array(collCount).keys()].map(x => ({_id: x})), {ordered: false}));

    assert.eq(collCount, coll.find().itcount());
    assert.commandWorked(coll.deleteMany({_id: {$gte: 0}}));
    assert.eq(0, coll.find().itcount());

    // The whole deletion breaks down the deletion into multiple batches/applyOps.
    const oplogEntriesApplyOps = db.getSiblingDB('local')
                                     .oplog.rs
                                     .aggregate([
                                         {
                                             $match: {
                                                 ns: "admin.$cmd",
                                                 'o.applyOps.op': 'd',
                                                 'o.applyOps.ns': coll.getFullName()
                                             }
                                         },
                                         {$project: {opsPerApplyOps: {$size: '$o.applyOps'}}}
                                     ])
                                     .toArray();
    assert.eq(expectedApplyOpsEntries, oplogEntriesApplyOps.length);
    let aggregateApplyOpsDeleteEntries = 0;
    for (let i = 0; i < expectedApplyOpsEntries; i++) {
        aggregateApplyOpsDeleteEntries += oplogEntriesApplyOps[i]['opsPerApplyOps'];
    }
    assert.eq(collCount, aggregateApplyOpsDeleteEntries);
}

function runTestInIsolation(testFunc, serverParam) {
    const nodeOptions = serverParam ? serverParam : {};
    const rst = new ReplSetTest({
        name: "batched_multi_deletes_test",
        nodes: 2,
        nodeOptions,
    });
    rst.startSet();
    rst.initiate();
    rst.awaitNodesAgreeOnPrimary();
    testFunc(rst.getPrimary());
    rst.awaitReplication();
    rst.checkReplicatedDataHashes();
    rst.stopSet();
}

// Validates oplog content for batches that fit within 16MB worth of oplog.
runTestInIsolation(validateBatchedDeletesOplogDocsPerBatch);
// Validates oplog content for batches that would exceed 16MB worth of oplog.
runTestInIsolation(validateBatchedDeletesOplogBatchAbove16MB);
})();