SERVER-105047: Query using $or and $ne on sharded collection can incl… (#41365)

GitOrigin-RevId: c3579b8c8661091b73b885baf8e7dff59257306a
This commit is contained in:
Matt Olma 2025-10-07 16:37:43 -04:00 committed by MongoDB Bot
parent 3a37c48ae6
commit a165502772
3 changed files with 125 additions and 0 deletions

View File

@ -21,3 +21,6 @@ filters:
- "*targeting*":
approvers:
- 10gen/query-optimization
- "*orphan_filtering*":
approvers:
- 10gen/query-optimization

View File

@ -0,0 +1,116 @@
// Tests whether orphan document are filtered correctly for pipelines involving equalities on
// renamed shard key in presence of shards and moving chunks
// @tags: [requires_sharding, requires_fcv_82]
import {configureFailPoint} from "jstests/libs/fail_point_util.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";
const docs = [
{_id: 0, shardKey: 6, data: NumberInt(0)},
{_id: 1, shardKey: 0, data: NumberInt(0)},
];
// renaming sharding key and applying an $or on predicates on the renamed key and another field.
const renameShardKeyWithOrOnRenamedField = [
{$project: {renamedShardKey: "$shardKey"}},
{$match: {$or: [{renamedShardKey: 6, data: {$ne: 1}}]}},
];
// renaming sharding key and applying an $and on predicates on the renamed key and another field.
const renameShardKeyWithAndOnRenamedField = [
{$project: {renamedShardKey: "$shardKey"}},
{$match: {$and: [{renamedShardKey: 6, data: {$ne: 1}}]}},
];
// only applying an $or on predicates on the renamed key and another field.
const orOnShardKey = [{$match: {$or: [{shardKey: 6, data: {$ne: 1}}]}}];
// renaming sharding key and applying only an equality predicate on the renamed key.
const renameShardKeyWithSimpleQueryOnRenamedField = [
{$project: {renamedShardKey: "$shardKey"}},
{$match: {renamedShardKey: 6}},
];
// applying only an equality predicate on the shard key.
const simpleQueryOnShardKey = [{$match: {shardKey: 6}}];
// grouping on the sharding key and applying an equality on the grouping key.
const groupOnShardKeyAndQueryOnGroupField = [{$group: {_id: "$shardKey"}}, {$match: {_id: 6}}];
const queries = [
renameShardKeyWithOrOnRenamedField,
renameShardKeyWithAndOnRenamedField,
orOnShardKey,
renameShardKeyWithSimpleQueryOnRenamedField,
simpleQueryOnShardKey,
groupOnShardKeyAndQueryOnGroupField,
];
function dbHasOrphan(st, coll) {
let allShards =
st.getDB("admin")
.aggregate([{$shardedDataDistribution: {}}, {$match: {ns: coll.getFullName()}}])
.next();
let hasOrphan = false;
for (const shard of allShards.shards) {
hasOrphan = hasOrphan || shard.numOrphanedDocs;
}
return hasOrphan;
}
const st = new ShardingTest({shards: 2, mongos: 1});
const shardedDb = st.getDB("test");
assert(st.adminCommand({enablesharding: "test", primaryShard: st.shard0.shardName}));
const coll = shardedDb.coll;
for (const query of queries) {
assert(coll.drop());
assert.commandWorked(coll.insert(docs));
assert.commandWorked(coll.createIndex({shardKey: 1}));
const resultsBeforeSharding = coll.aggregate(query).toArray();
assert(st.adminCommand({shardcollection: coll.getFullName(), key: {shardKey: 1}}));
const resultsAfterSharding = coll.aggregate(query).toArray();
// The results before and after sharding should be identical.
assert.eq(
resultsBeforeSharding, resultsAfterSharding, {resultsBeforeSharding, resultsAfterSharding});
// one doc on each shard
assert(st.adminCommand({split: coll.getFullName(), middle: {shardKey: 3}}));
const resultsAfterSplitting = coll.aggregate(query).toArray();
// The results before and after splitting should be identical.
assert.eq(resultsBeforeSharding,
resultsAfterSplitting,
{resultsBeforeSharding, resultsAfterSplitting});
let suspendRangeDeletionShard0Fp =
configureFailPoint(st.rs0.getPrimary(), "suspendRangeDeletion");
// shard0 will have shardKey=0 doc and the orphaned shardKey=6 doc, and shard1 will have only
// the shardKey=6 document.
assert(st.adminCommand(
{moveChunk: coll.getFullName(), find: {shardKey: 6}, to: st.shard1.shardName}));
suspendRangeDeletionShard0Fp.wait();
const resultsAfterMovingChunk = coll.aggregate(query).toArray();
// ensure there is an orphaned document before the query.
assert.eq(dbHasOrphan(st, coll), 1);
// Assert on results of queries despite orphaned documents.
// The results before and after moving chunks should be identical.
assert.eq(resultsBeforeSharding,
resultsAfterMovingChunk,
{resultsBeforeSharding, resultsAfterMovingChunk});
// ensure there is an orphaned document even after the query.
assert.eq(dbHasOrphan(st, coll), 1);
// release range deletion to clean orphaned documents
suspendRangeDeletionShard0Fp.off();
}
st.stop();

View File

@ -715,6 +715,12 @@ bool QueryPlannerParams::requiresShardFiltering(const CanonicalQuery& canonicalQ
return false;
}
// Check whether the query is running over multiple shards and will require merging.
const auto expCtx = canonicalQuery.getExpCtx();
if (expCtx->getNeedsMerge()) {
return true;
}
const auto& shardKeyPattern = collection.getShardKeyPattern();
// Shards cannot own orphans for the key ranges they own, so there is no need
// to include a shard filtering stage. By omitting the shard filter, it may be