summaryrefslogtreecommitdiff
path: root/jstests/replsets/tenant_migration_multikey_index.js
blob: 196a546ee5f2905d62c18fb279ff98c2786d529d (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
/**
 * Test that during tenant migration, multi-key indexes on donor collections can be
 * correctly rebuilt on recipient collections, with the right multi-key paths.
 *
 * @tags: [
 *   incompatible_with_eft,
 *   incompatible_with_macos,
 *   incompatible_with_windows_tls,
 *   requires_majority_read_concern,
 *   requires_persistence,
 *   serverless,
 * ]
 */

(function() {
"use strict";

load("jstests/libs/analyze_plan.js");
load("jstests/libs/fail_point_util.js");
load("jstests/libs/uuid_util.js");
load("jstests/replsets/libs/tenant_migration_test.js");
load("jstests/replsets/libs/tenant_migration_util.js");

const getQueryExplainIndexScanStage = function(coll) {
    const explain = coll.find().hint({"a.b": 1, "a.c": 1}).explain();
    assert.commandWorked(explain);
    return getPlanStage(getWinningPlan(explain.queryPlanner), "IXSCAN");
};

const verifyMultiKeyIndex = function(coll, isMultiKey, multiKeyPath) {
    const idxScan = getQueryExplainIndexScanStage(coll);
    assert.eq(idxScan.isMultiKey, isMultiKey);
    assert.eq(idxScan.multiKeyPaths, multiKeyPath);
};

const recipientRst = new ReplSetTest({
    nodes: 2,
    name: jsTestName() + "_recipient",
    nodeOptions: Object.assign(TenantMigrationUtil.makeX509OptionsForTest().recipient, {
        setParameter: {
            // Allow reads on recipient before migration completes for testing.
            'failpoint.tenantMigrationRecipientNotRejectReads': tojson({mode: 'alwaysOn'}),
        }
    })
});

recipientRst.startSet();
recipientRst.initiateWithHighElectionTimeout();
const recipientPrimary = recipientRst.getPrimary();

const tenantMigrationTest =
    new TenantMigrationTest({name: jsTestName(), recipientRst: recipientRst});
const donorPrimary = tenantMigrationTest.getDonorPrimary();

const tenantId = "testTenantId";
const dbName = tenantMigrationTest.tenantDB(tenantId, "testDB");

// The first collection on donor side already has the multi-key index.
const collName1 = "multiKeyColl_1";
const donorColl1 = donorPrimary.getDB(dbName).getCollection(collName1);
const recipientColl1 = recipientPrimary.getDB(dbName).getCollection(collName1);
tenantMigrationTest.insertDonorDB(
    dbName,
    collName1,
    [{_id: 0, a: {b: 10, c: 10}}, {_id: 1, a: [{b: [20, 30], c: 40}, {b: 50, c: 60}]}]);
assert.commandWorked(donorColl1.createIndex({"a.b": 1, "a.c": 1}));
// Both "a" and "a.b" are arrays, so the multi-key path on "a.b" is ["a", "a.b"],
// since "a.c" is scala value, the multi-key path on "a.c" only contains "a".
verifyMultiKeyIndex(donorColl1, true, {"a.b": ["a", "a.b"], "a.c": ["a"]});

// The second collection on donor side is not multi-key index yet, during migration,
// new entries will be inserted and it will be turned into multi-key index.
const collName2 = "multiKeyColl_2";
const donorColl2 = donorPrimary.getDB(dbName).getCollection(collName2);
const recipientColl2 = recipientPrimary.getDB(dbName).getCollection(collName2);
tenantMigrationTest.insertDonorDB(dbName, collName2, [{_id: 0, a: {b: 10, c: 10}}]);
assert.commandWorked(donorColl2.createIndex({"a.b": 1, "a.c": 1}));
// Since it is not multi-key index yet, both multi-key paths should be empty.
verifyMultiKeyIndex(donorColl2, false, {"a.b": [], "a.c": []});

const migrationId = UUID();
const migrationIdString = extractUUIDFromObject(migrationId);
const migrationOpts = {
    migrationIdString: migrationIdString,
    recipientConnString: tenantMigrationTest.getRecipientConnString(),
    tenantId: tenantId,
};

// Configure a failpoint to hang after recipient data becomes consistent.
const fpBeforeFulfillingDataConsistentPromise = configureFailPoint(
    recipientPrimary, "fpBeforeFulfillingDataConsistentPromise", {action: "hang"});

jsTestLog("Starting the tenant migration");
assert.commandWorked(tenantMigrationTest.startMigration(migrationOpts));

fpBeforeFulfillingDataConsistentPromise.wait();

// At this point, recipient data is consistent, so both collections on the recipient
// side should have the same multi-key state as they were in donor side.
verifyMultiKeyIndex(recipientColl1, true, {"a.b": ["a", "a.b"], "a.c": ["a"]});
verifyMultiKeyIndex(recipientColl2, false, {"a.b": [], "a.c": []});

// Update an entry in collection 1 on donor side, that make "a.c" to be an array as well.
// The recipient should continue fetching before migration finishes and thus change the
// multi-key path on "a.c" after it becomes consistent again.
assert.commandWorked(donorColl1.update(
    {_id: 1, "a.b": 50}, {$set: {"a.$.c": [60, 70]}}, {writeConcern: {w: 'majority'}}));
// Now the index on donor's collection 1 should still be multi-key, but the multi-key path
// on "a.c" should be changed, recipient should be the same after migration finishes.
verifyMultiKeyIndex(donorColl1, true, {"a.b": ["a", "a.b"], "a.c": ["a", "a.c"]});

// Insert new entries into collection 2 on donor side, that turns its index into multi-key.
// The recipient should continue fetching before migration finishes and likewise turn the
// index on its collection 2 into multi-key after it becomes consistent again.
tenantMigrationTest.insertDonorDB(
    dbName, collName2, [{_id: 1, a: [{b: [20, 30], c: 40}, {b: 50, c: 60}]}]);
// Now the index on donor's collection 2 should be multi-key, recipient should be the same
// after migration finishes.
verifyMultiKeyIndex(donorColl2, true, {"a.b": ["a", "a.b"], "a.c": ["a"]});

fpBeforeFulfillingDataConsistentPromise.off();

// Wait for tenant migration to finish.
TenantMigrationTest.assertCommitted(tenantMigrationTest.waitForMigrationToComplete(migrationOpts));

// Recipient now should have fetched the newly updated data, and changed the multi-key path
// on "a.c" in collection 1.
verifyMultiKeyIndex(recipientColl1, true, {"a.b": ["a", "a.b"], "a.c": ["a", "a.c"]});
// Recipient now should have fetched newly inserted data, and turned the index on its
// collection 2 into multi-key, with the same multi-key path as the donor's.
verifyMultiKeyIndex(recipientColl2, true, {"a.b": ["a", "a.b"], "a.c": ["a"]});

tenantMigrationTest.stop();
recipientRst.stopSet();
})();