Skip to content

Commit 67dc54b

Browse files
wbuerger46MongoDB Bot
authored andcommitted
SERVER-108468: Unavailable searchScore and vectorSearchScore log a warning instead of throwing an exception (#39476)
GitOrigin-RevId: 891e030c83dbfced2fd75b16a656df2366ae05fa
1 parent b517e4c commit 67dc54b

File tree

7 files changed

+193
-82
lines changed

7 files changed

+193
-82
lines changed

jstests/aggregation/sources/sort/sort_with_metadata.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,25 @@ assert.commandWorked(coll.insert({_id: 4, text: "cantaloupe", words: 1}));
1212

1313
assert.commandWorked(coll.createIndex({text: "text"}));
1414

15-
assert.throwsWithCode(() => coll.aggregate([
15+
assert.doesNotThrow(() => coll.aggregate([
1616
{$match: {$text: {$search: 'apple banana'}}},
1717
{$sort: {textScore: {$meta: 'searchScore'}}}
18-
]),
19-
kUnavailableMetadataErrCode);
18+
]));
2019

21-
assert.throwsWithCode(() => coll.aggregate([
20+
assert.doesNotThrow(() => coll.aggregate([
2221
{$match: {$text: {$search: 'apple banana'}}},
2322
{$set: {textScore: {$meta: 'searchScore'}}}
24-
]),
25-
kUnavailableMetadataErrCode);
23+
]));
2624

27-
assert.throwsWithCode(() => coll.aggregate([
25+
assert.doesNotThrow(() => coll.aggregate([
2826
{$match: {$text: {$search: 'apple banana'}}},
2927
{$sort: {textScore: {$meta: 'vectorSearchScore'}}}
30-
]),
31-
kUnavailableMetadataErrCode);
28+
]));
3229

33-
assert.throwsWithCode(() => coll.aggregate([
30+
assert.doesNotThrow(() => coll.aggregate([
3431
{$match: {$text: {$search: 'apple banana'}}},
3532
{$set: {textScore: {$meta: 'vectorSearchScore'}}}
36-
]),
37-
kUnavailableMetadataErrCode);
33+
]));
3834

3935
assert.throwsWithCode(() => coll.aggregate([
4036
{$match: {$text: {$search: 'apple banana'}}},
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* Tests that when "searchScore" and "vectorSearchScore" metadata fields are referenced in an
3+
* invalid way, a warning log line will occasionally be printed to the logs.
4+
*
5+
* @tags: [requires_fcv_82]
6+
*/
7+
import {iterateMatchingLogLines} from "jstests/libs/log.js";
8+
import {ShardingTest} from "jstests/libs/shardingtest.js";
9+
10+
const warningLogMessage =
11+
"The query attempts to retrieve metadata at a place in the pipeline where that metadata type is not available";
12+
const searchScoreWarningFieldMatcher = {
13+
msg: warningLogMessage,
14+
attr: {metadataType: "$search score"}
15+
};
16+
const vectorSearchScoreWarningFieldMatcher = {
17+
msg: warningLogMessage,
18+
attr: {metadataType: "$vectorSearch score"}
19+
};
20+
21+
const dbName = "test";
22+
const collName = "search_score_meta_validation";
23+
24+
const data = [
25+
{a: 1, b: "foo", score: 10},
26+
{a: 2, b: "bar", score: 20},
27+
{a: 3, b: "baz", score: 30},
28+
{a: 4, b: "qux", score: 40},
29+
{a: 5, b: "quux", score: 50},
30+
];
31+
32+
function checkLogs(db, warningLogFieldMatcher, numLogLines) {
33+
const globalLogs = db.adminCommand({getLog: 'global'});
34+
const matchingLogLines = [...iterateMatchingLogLines(globalLogs.log, warningLogFieldMatcher)];
35+
assert.eq(matchingLogLines.length, numLogLines, matchingLogLines);
36+
}
37+
38+
/**
39+
* Tests that the metadata validation warning log line is printed when the metadata is used in an
40+
* invalid way.
41+
*
42+
* Commands are sent to db but the logs are checked in the provided logDb. For a sharded cluster,
43+
* commands will be sent to the mongos and the logs will be checked on the primary shard.
44+
*/
45+
function warningLogTest(db, warningLogFieldMatcher, metaType, logDb) {
46+
assert.commandWorked(logDb.adminCommand({clearLog: "global"}));
47+
48+
// Assert that the warning message is not logged before the command is even run.
49+
checkLogs(logDb, warningLogFieldMatcher, 0);
50+
51+
assert.commandWorked(db.runCommand({
52+
aggregate: collName,
53+
pipeline: [
54+
{$setWindowFields: {sortBy: {score: {$meta: metaType}}, output: {rank: {$rank: {}}}}},
55+
],
56+
cursor: {}
57+
}));
58+
59+
// Now that we have run the command, make sure the warning message is logged once.
60+
checkLogs(logDb, warningLogFieldMatcher, 1);
61+
62+
// It will be printed once every 64 queries (every 128 ticks, with two ticks per query since
63+
// dependency tracking is called twice per query). Now run 64 more invalid queries (4 queries *
64+
// 32 iterations) to get another log line.
65+
for (let i = 0; i < 16; i++) {
66+
assert.commandWorked(db.runCommand(
67+
{aggregate: collName, pipeline: [{$sort: {score: {$meta: metaType}}}], cursor: {}}));
68+
assert.commandWorked(db.runCommand(
69+
{aggregate: collName, pipeline: [{$set: {score: {$meta: metaType}}}], cursor: {}}));
70+
71+
assert.commandWorked(db.runCommand({
72+
find: collName,
73+
projection: {score: {$meta: metaType}},
74+
}));
75+
assert.commandWorked(db.runCommand({
76+
find: collName,
77+
sort: {score: {$meta: metaType}},
78+
}));
79+
}
80+
81+
// Check that we only had one more log.
82+
checkLogs(logDb, warningLogFieldMatcher, 2);
83+
}
84+
85+
jsTest.log.info('Test standalone');
86+
{
87+
const mongod = MongoRunner.runMongod({});
88+
const db = mongod.getDB(dbName);
89+
const coll = db.getCollection(collName);
90+
coll.drop();
91+
assert.commandWorked(coll.insertMany(data));
92+
93+
warningLogTest(db, searchScoreWarningFieldMatcher, "searchScore", db);
94+
checkLogs(db, vectorSearchScoreWarningFieldMatcher, 0);
95+
warningLogTest(db, vectorSearchScoreWarningFieldMatcher, "vectorSearchScore", db);
96+
checkLogs(db, searchScoreWarningFieldMatcher, 0);
97+
98+
MongoRunner.stopMongod(mongod);
99+
}
100+
101+
jsTest.log.info('Test sharded');
102+
{
103+
const shardingTest = new ShardingTest({shards: 2, mongos: 1});
104+
const session = shardingTest.s.getDB(dbName).getMongo().startSession();
105+
const shardedDB = session.getDatabase(dbName);
106+
const primaryShardConn = shardingTest.shard0.getDB(dbName);
107+
108+
assert.commandWorked(shardingTest.s0.adminCommand(
109+
{enableSharding: shardedDB.getName(), primaryShard: shardingTest.shard0.shardName}));
110+
111+
const shardedColl = shardedDB.getCollection(collName);
112+
113+
assert.commandWorked(shardedDB.createCollection(shardedColl.getName()));
114+
115+
assert.commandWorked(
116+
shardingTest.s0.adminCommand({shardCollection: shardedColl.getFullName(), key: {_id: 1}}));
117+
118+
assert.commandWorked(shardedColl.insert(data));
119+
120+
warningLogTest(shardedDB, searchScoreWarningFieldMatcher, "searchScore", primaryShardConn);
121+
checkLogs(primaryShardConn, vectorSearchScoreWarningFieldMatcher, 0);
122+
warningLogTest(
123+
shardedDB, vectorSearchScoreWarningFieldMatcher, "vectorSearchScore", primaryShardConn);
124+
checkLogs(primaryShardConn, searchScoreWarningFieldMatcher, 0);
125+
126+
shardingTest.stop();
127+
}

jstests/with_mongot/e2e/metadata/meta_dependency_validation.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ const MetaFields = Object.freeze({
133133
{name: "randVal", shouldBeValidated: false, debugName: "rand val", validSortKey: true},
134134
SEARCH_SCORE: {
135135
name: "searchScore",
136-
shouldBeValidated: true,
136+
// For 8.2, "searchScore" is not strictly validated but instead logs a warning message (see
137+
// search_score_meta_validation_warning_logs.js).
138+
shouldBeValidated: false,
137139
debugName: "$search score",
138140
validSortKey: true,
139141
firstStageRequired: [
@@ -203,8 +205,10 @@ const MetaFields = Object.freeze({
203205
},
204206
VECTOR_SEARCH_SCORE: {
205207
name: "vectorSearchScore",
206-
shouldBeValidated: true,
207-
debugName: "$vectorSearch distance",
208+
// For 8.2, "vectorSearchScore" is not strictly validated but instead logs a warning message
209+
// (see search_score_meta_validation_warning_logs.js).
210+
shouldBeValidated: false,
211+
debugName: "$vectorSearch score",
208212
validSortKey: true,
209213
firstStageRequired: [FirstStageOptions.VECTOR_SEARCH]
210214
},

jstests/with_mongot/e2e/metadata/search_score_meta_validation.js

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/mongo/db/exec/document_value/document_metadata_fields.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ const char* DocumentMetadataFields::typeNameToDebugString(DocumentMetadataFields
642642
case DocumentMetadataFields::kSearchSequenceToken:
643643
return "$search sequence token";
644644
case DocumentMetadataFields::kVectorSearchScore:
645-
return "$vectorSearch distance";
645+
return "$vectorSearch score";
646646
case DocumentMetadataFields::kScore:
647647
return "score";
648648
case DocumentMetadataFields::kScoreDetails:

src/mongo/db/pipeline/dependencies.cpp

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,31 @@
4040
#include <bitset>
4141
#include <compare>
4242

43+
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery
44+
4345
namespace mongo {
46+
namespace {
47+
48+
// Ticks for "searchScore" and "vectorSearchScore" invalid usage warning log messages.
49+
Rarely _searchScoreValidator, _vectorSearchScoreValidator;
50+
51+
// Gently validated metadata fields will log a warning instead of throwing a uassert. Each one
52+
// has its own ticker to avoid flooding the logs with warnings.
53+
static const std::map<DocumentMetadataFields::MetaType, Rarely&>
54+
kMetadataFieldsToBeGentlyValidatedWithTicker = {
55+
{DocumentMetadataFields::MetaType::kSearchScore, _searchScoreValidator},
56+
{DocumentMetadataFields::MetaType::kVectorSearchScore, _vectorSearchScoreValidator},
57+
};
58+
// Strictly validated metadata fields will throw a uassert if they are not available.
59+
static const std::set<DocumentMetadataFields::MetaType> kMetadataFieldsToBeStrictlyValidated = {
60+
DocumentMetadataFields::MetaType::kTextScore,
61+
DocumentMetadataFields::MetaType::kGeoNearDist,
62+
DocumentMetadataFields::MetaType::kGeoNearPoint,
63+
DocumentMetadataFields::MetaType::kScore,
64+
DocumentMetadataFields::MetaType::kScoreDetails,
65+
DocumentMetadataFields::MetaType::kSearchRootDocumentId,
66+
};
67+
} // namespace
4468

4569
OrderedPathSet DepsTracker::simplifyDependencies(const OrderedPathSet& dependencies,
4670
TruncateToRootLevel truncateToRootLevel) {
@@ -105,25 +129,35 @@ BSONObj DepsTracker::toProjectionWithoutMetadata(
105129
}
106130

107131
void DepsTracker::setNeedsMetadata(DocumentMetadataFields::MetaType type) {
108-
static const std::set<DocumentMetadataFields::MetaType> kMetadataFieldsToBeValidated = {
109-
DocumentMetadataFields::MetaType::kTextScore,
110-
DocumentMetadataFields::MetaType::kGeoNearDist,
111-
DocumentMetadataFields::MetaType::kGeoNearPoint,
112-
DocumentMetadataFields::MetaType::kScore,
113-
DocumentMetadataFields::MetaType::kScoreDetails,
114-
DocumentMetadataFields::MetaType::kSearchScore,
115-
DocumentMetadataFields::MetaType::kVectorSearchScore,
116-
DocumentMetadataFields::MetaType::kSearchRootDocumentId,
117-
};
118-
119132
// Perform validation if necessary.
120-
if (!std::holds_alternative<NoMetadataValidation>(_availableMetadata) &&
121-
kMetadataFieldsToBeValidated.contains(type)) {
133+
const bool shouldBeValidated =
134+
!std::holds_alternative<NoMetadataValidation>(_availableMetadata) &&
135+
(kMetadataFieldsToBeGentlyValidatedWithTicker.contains(type) ||
136+
kMetadataFieldsToBeStrictlyValidated.contains(type));
137+
138+
if (shouldBeValidated) {
122139
auto& availableMetadataBitSet = std::get<QueryMetadataBitSet>(_availableMetadata);
123-
uassert(40218,
124-
str::stream() << "query requires " << type << " metadata, but it is not available",
125-
availableMetadataBitSet[type]);
140+
141+
// Occasionally log a message in the logs when this error is hit for gently validated
142+
// fields.
143+
if (kMetadataFieldsToBeGentlyValidatedWithTicker.contains(type) &&
144+
kMetadataFieldsToBeGentlyValidatedWithTicker.at(type).tick()) {
145+
LOGV2_WARNING(10846800,
146+
"The query attempts to retrieve metadata at a place in the pipeline "
147+
"where that metadata type is not available",
148+
"metadataType"_attr =
149+
DocumentMetadataFields::typeNameToDebugString(type));
150+
}
151+
152+
// uassert for stages that should be strictly validated.
153+
if (kMetadataFieldsToBeStrictlyValidated.contains(type)) {
154+
uassert(40218,
155+
str::stream() << "query requires " << type
156+
<< " metadata, but it is not available",
157+
availableMetadataBitSet[type]);
158+
}
126159
}
160+
127161
_metadataDeps[type] = true;
128162
}
129163

src/mongo/db/pipeline/dependencies_test.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,6 @@ TEST(DependenciesNeedsMetadataTest, OnlyChosenMetadataFieldsShouldThrowIfUnavail
123123
DocumentMetadataFields::MetaType::kGeoNearDist,
124124
DocumentMetadataFields::MetaType::kGeoNearPoint,
125125
DocumentMetadataFields::MetaType::kScore,
126-
DocumentMetadataFields::MetaType::kSearchScore,
127-
DocumentMetadataFields::MetaType::kVectorSearchScore,
128126
DocumentMetadataFields::MetaType::kScoreDetails,
129127
DocumentMetadataFields::MetaType::kSearchRootDocumentId,
130128
};

0 commit comments

Comments
 (0)