Skip to content

Commit 36232a6

Browse files
wolffcmMongoDB Bot
authored and
MongoDB Bot
committed
SERVER-100671 Create an operation scoped memory tracker (#32606)
GitOrigin-RevId: df1bb59b70e8d922d40350c2808352f9281c5fe1
1 parent 6714905 commit 36232a6

12 files changed

+363
-35
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1522,7 +1522,7 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot
15221522
/src/mongo/db/memory_tracking/**/OWNERS.yml @10gen/query-execution-staff-leads @10gen/query-integration-staff-leads @svc-auto-approve-bot
15231523
/src/mongo/db/memory_tracking/**/memory_usage_tracker* @10gen/query-execution-aggregation @svc-auto-approve-bot
15241524
/src/mongo/db/memory_tracking/**/op_memory_use* @10gen/query-integration-observability @svc-auto-approve-bot
1525-
/src/mongo/db/memory_tracking/**/operation_memory_usage_tracker.h @10gen/query-integration-observability @svc-auto-approve-bot
1525+
/src/mongo/db/memory_tracking/**/operation_memory_usage_tracker* @10gen/query-integration-observability @svc-auto-approve-bot
15261526

15271527
# The following patterns are parsed from ./src/mongo/db/modules/enterprise/OWNERS.yml
15281528
/src/mongo/db/modules/enterprise/BUILD.bazel @10gen/devprod-build @svc-auto-approve-bot

jstests/aggregation/sources/setWindowFields/explain.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ function checkExplainResult(pipeline, expectedFunctionMemUsages, expectedTotalMe
9090
// each function. For the default [unbounded, unbounded] window type, each function uses memory
9191
// usage comparable to it's $group counterpart.
9292
let expectedFunctionMemUsages = {
93-
count: 72,
93+
count: 176,
9494
push: nDocs * 1024,
9595
set: 1024,
9696
};
@@ -115,7 +115,7 @@ function checkExplainResult(pipeline, expectedFunctionMemUsages, expectedTotalMe
115115
},
116116
];
117117
expectedFunctionMemUsages = {
118-
count: 72,
118+
count: 176,
119119
push: (nDocs / nPartitions) * 1056 + 56, // 56 constant state size. Uses 1056 per document.
120120
set: 1144, // 1024 for the string, rest is constant state.
121121
};

jstests/noPassthrough/query/spill_to_disk_server_status.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ assert.commandWorked(
3636
setParameter(db, "internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill", 1));
3737
// Spilling memory threshold for $setWindowFields
3838
assert.commandWorked(setParameter(
39-
db, "internalDocumentSourceSetWindowFieldsMaxMemoryBytes", isSbeEnabled ? 129 : 264));
39+
db, "internalDocumentSourceSetWindowFieldsMaxMemoryBytes", isSbeEnabled ? 129 : 392));
4040
// Spilling memory threshold for $lookup
4141
assert.commandWorked(setParameter(
4242
db, "internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill", 1));

src/mongo/db/curop.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,13 +482,16 @@ void CurOp::setMemoryTrackingStats(const int64_t inUseMemoryBytes,
482482
tassert(9897000,
483483
"featureFlagQueryMemoryTracking must be turned on before writing memory stats to CurOp",
484484
feature_flags::gFeatureFlagQueryMemoryTracking.isEnabled());
485-
_inUseMemoryBytes.fetchAndAdd(inUseMemoryBytes);
486-
487485
// We recompute the max here (the memory tracker that calls this method will already computing
488486
// the max) in order to avoid writing to the atomic if the max does not change.
487+
//
488+
// Set the max first, so that we can maintain the invariant that the max is always equal to or
489+
// greater than the current in-use tally.
489490
if (maxUsedMemoryBytes > _maxUsedMemoryBytes.load()) {
490-
_maxUsedMemoryBytes.swap(maxUsedMemoryBytes);
491+
_maxUsedMemoryBytes.store(maxUsedMemoryBytes);
491492
}
493+
494+
_inUseMemoryBytes.store(inUseMemoryBytes);
492495
}
493496

494497
void CurOp::setNS(WithLock, NamespaceString nss) {

src/mongo/db/curop_test.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,12 +412,12 @@ TEST(CurOpTest, ShouldUpdateMemoryStats) {
412412
ASSERT_EQ(15, curop->getMaxUsedMemoryBytes());
413413

414414
// The max memory usage is updated if the new max is greater than the current max.
415-
curop->setMemoryTrackingStats(11 /*currentMemoryBytes*/, 20 /*maxUsedMemoryBytes*/);
415+
curop->setMemoryTrackingStats(21 /*currentMemoryBytes*/, 20 /*maxUsedMemoryBytes*/);
416416
ASSERT_EQ(21, curop->getInUseMemoryBytes());
417417
ASSERT_EQ(20, curop->getMaxUsedMemoryBytes());
418418

419419
// The max memory usage is not updated if the new max is not greater than the current max.
420-
curop->setMemoryTrackingStats(10 /*currentMemoryBytes*/, 15 /*maxUsedMemoryBytes*/);
420+
curop->setMemoryTrackingStats(31 /*currentMemoryBytes*/, 15 /*maxUsedMemoryBytes*/);
421421
ASSERT_EQ(31, curop->getInUseMemoryBytes());
422422
ASSERT_EQ(20, curop->getMaxUsedMemoryBytes());
423423
}

src/mongo/db/memory_tracking/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ mongo_cc_library(
1313
name = "memory_tracking",
1414
srcs = [
1515
"op_memory_use.cpp",
16+
"operation_memory_usage_tracker.cpp",
1617
],
1718
hdrs = [
1819
"memory_usage_tracker.h",
1920
"op_memory_use.h",
2021
"operation_memory_usage_tracker.h",
2122
],
2223
deps = [
24+
"//src/mongo/db:commands",
25+
"//src/mongo/db:server_feature_flags",
2326
"//src/mongo/db:service_context",
2427
],
2528
)
@@ -29,10 +32,14 @@ mongo_cc_unit_test(
2932
srcs = [
3033
"memory_usage_tracker_test.cpp",
3134
"op_memory_use_test.cpp",
35+
"operation_memory_usage_tracker_test.cpp",
3236
],
3337
tags = ["mongo_unittest_second_group"],
3438
deps = [
3539
":memory_tracking",
3640
"//src/mongo/db:service_context_test_fixture",
41+
"//src/mongo/db/pipeline:aggregation_context_fixture",
42+
"//src/mongo/db/pipeline:document_source_mock",
43+
"//src/mongo/idl:server_parameter_test_util",
3744
],
3845
)

src/mongo/db/memory_tracking/OWNERS.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ filters:
1414
- "op_memory_use*":
1515
approvers:
1616
- 10gen/query-integration-observability
17-
- "operation_memory_usage_tracker.h":
17+
- "operation_memory_usage_tracker*":
1818
approvers:
1919
- 10gen/query-integration-observability

src/mongo/db/memory_tracking/memory_usage_tracker.h

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ class SimpleMemoryUsageTracker {
5353
SimpleMemoryUsageTracker(SimpleMemoryUsageTracker* base, int64_t maxAllowedMemoryUsageBytes)
5454
: _base(base), _maxAllowedMemoryUsageBytes(maxAllowedMemoryUsageBytes) {}
5555

56-
SimpleMemoryUsageTracker(int64_t maxAllowedMemoryUsageBytes)
56+
explicit SimpleMemoryUsageTracker(int64_t maxAllowedMemoryUsageBytes)
5757
: SimpleMemoryUsageTracker(nullptr, maxAllowedMemoryUsageBytes) {}
5858

59+
SimpleMemoryUsageTracker() : SimpleMemoryUsageTracker(std::numeric_limits<int64_t>::max()) {}
60+
5961
void add(int64_t diff) {
6062
_currentMemoryBytes += diff;
6163
tassert(6128100,
@@ -68,6 +70,10 @@ class SimpleMemoryUsageTracker {
6870
if (_base) {
6971
_base->add(diff);
7072
}
73+
74+
if (_doExtraBookkeeping) {
75+
_doExtraBookkeeping(_currentMemoryBytes, _maxMemoryBytes);
76+
}
7177
}
7278

7379
void set(int64_t total) {
@@ -90,6 +96,15 @@ class SimpleMemoryUsageTracker {
9096
return _maxAllowedMemoryUsageBytes;
9197
}
9298

99+
protected:
100+
/**
101+
* Provide an extra function that is called whenever add() is invoked. Let it be set via this
102+
* method instead in the constructor to allow subclasses to capture "this."
103+
*/
104+
void setDoExtraBookkeeping(std::function<void(int64_t, int64_t)> doExtraBookkeeping) {
105+
_doExtraBookkeeping = std::move(doExtraBookkeeping);
106+
}
107+
93108
private:
94109
SimpleMemoryUsageTracker* _base = nullptr;
95110

@@ -99,6 +114,11 @@ class SimpleMemoryUsageTracker {
99114
int64_t _currentMemoryBytes = 0;
100115

101116
int64_t _maxAllowedMemoryUsageBytes;
117+
118+
// Allow for some extra bookkeeping to be done when add() is called. If set, this function will
119+
// be invoked with _currentMemoryBytes and _maxMemoryBytes. This mechanism exists to avoid
120+
// making add() virtual, since it has been shown to have an effect on performance in some cases.
121+
std::function<void(int64_t, int64_t)> _doExtraBookkeeping;
102122
};
103123

104124
/**
@@ -120,8 +140,13 @@ class SimpleMemoryUsageTracker {
120140
*/
121141
class MemoryUsageTracker {
122142
public:
143+
MemoryUsageTracker(SimpleMemoryUsageTracker* baseParent,
144+
bool allowDiskUse = false,
145+
int64_t maxMemoryUsageBytes = 0)
146+
: _allowDiskUse(allowDiskUse), _baseTracker(baseParent, maxMemoryUsageBytes) {}
147+
123148
MemoryUsageTracker(bool allowDiskUse = false, int64_t maxMemoryUsageBytes = 0)
124-
: _allowDiskUse(allowDiskUse), _baseTracker(nullptr, maxMemoryUsageBytes) {}
149+
: MemoryUsageTracker(nullptr, allowDiskUse, maxMemoryUsageBytes) {}
125150

126151
/**
127152
* Sets the new total for 'name', and updates the current total memory usage.
@@ -198,10 +223,6 @@ class MemoryUsageTracker {
198223
}
199224

200225
private:
201-
SimpleMemoryUsageTracker& _tracker() {
202-
return _baseTracker;
203-
}
204-
205226
static absl::string_view _key(StringData s) {
206227
return {s.rawData(), s.size()};
207228
}

src/mongo/db/memory_tracking/op_memory_use_test.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "mongo/db/memory_tracking/op_memory_use.h"
3131
#include "mongo/db/memory_tracking/operation_memory_usage_tracker.h"
3232
#include "mongo/db/service_context_test_fixture.h"
33+
#include "mongo/idl/server_parameter_test_util.h"
3334

3435
namespace mongo {
3536
namespace {
@@ -46,17 +47,22 @@ class MemoryTrackingTest : public ServiceContextTest {
4647
};
4748

4849
TEST_F(MemoryTrackingTest, OpMemoryUseCanAttachToOpCtx) {
50+
RAIIServerParameterControllerForTest featureFlagController("featureFlagQueryMemoryTracking",
51+
true);
52+
4953
auto opCtx = makeOpCtx();
5054

51-
// Attach mock memory usage stats to the opCtx and validate that the decoration was set.
52-
auto mockMemoryStats = std::make_unique<OperationMemoryUsageTracker>(10 /*currentMemoryBytes*/,
53-
15 /*maxUsedMemoryBytes*/);
54-
OpMemoryUse::operationMemoryAggregator(opCtx.get()) = std::move(mockMemoryStats);
55+
auto tracker = std::make_unique<OperationMemoryUsageTracker>(opCtx.get());
56+
tracker->add(15);
57+
tracker->add(-5);
58+
59+
OpMemoryUse::operationMemoryAggregator(opCtx.get()) = std::move(tracker);
5560
OperationMemoryUsageTracker* memoryTracker =
5661
OpMemoryUse::operationMemoryAggregator(opCtx.get()).get();
5762
ASSERT(memoryTracker);
5863
ASSERT_EQ(memoryTracker->currentMemoryBytes(), 10);
59-
ASSERT_EQ(memoryTracker->maxUsedMemoryBytes(), 15);
64+
ASSERT_EQ(memoryTracker->maxMemoryBytes(), 15);
6065
}
66+
6167
} // namespace
6268
} // namespace mongo
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Copyright (C) 2025-present MongoDB, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*
17+
* As a special exception, the copyright holders give permission to link the
18+
* code of portions of this program with the OpenSSL library under certain
19+
* conditions as described in each individual source file and distribute
20+
* linked combinations including the program with the OpenSSL library. You
21+
* must comply with the Server Side Public License in all respects for
22+
* all of the code used other than as permitted herein. If you modify file(s)
23+
* with this exception, you may extend this exception to your version of the
24+
* file(s), but you are not obligated to do so. If you do not wish to do so,
25+
* delete this exception statement from your version. If you delete this
26+
* exception statement from all source files in the program, then also delete
27+
* it in the license file.
28+
*/
29+
30+
#include "mongo/db/memory_tracking/operation_memory_usage_tracker.h"
31+
#include "mongo/db/memory_tracking/op_memory_use.h"
32+
33+
namespace mongo {
34+
35+
/**
36+
* Return the OperationMemoryUsageTracker for this operation. If we haven't yet created one, do it
37+
* now.
38+
*/
39+
OperationMemoryUsageTracker* OperationMemoryUsageTracker::getOperationMemoryUsageTracker(
40+
OperationContext* opCtx) {
41+
OperationMemoryUsageTracker* opTracker = OpMemoryUse::operationMemoryAggregator(opCtx).get();
42+
if (!opTracker) {
43+
auto uniqueTracker = std::make_unique<OperationMemoryUsageTracker>(opCtx);
44+
opTracker = uniqueTracker.get();
45+
opTracker->setDoExtraBookkeeping(
46+
[opTracker](int64_t currentMemoryBytes, int64_t maxUsedMemoryBytes) {
47+
CurOp::get(opTracker->_opCtx)
48+
->setMemoryTrackingStats(currentMemoryBytes, maxUsedMemoryBytes);
49+
});
50+
OpMemoryUse::operationMemoryAggregator(opCtx) = std::move(uniqueTracker);
51+
}
52+
53+
return opTracker;
54+
}
55+
56+
SimpleMemoryUsageTracker OperationMemoryUsageTracker::createSimpleMemoryUsageTrackerForStage(
57+
const ExpressionContext& expCtx, int64_t maxMemoryUsageBytes) {
58+
if (!feature_flags::gFeatureFlagQueryMemoryTracking.isEnabled()) {
59+
return SimpleMemoryUsageTracker{maxMemoryUsageBytes};
60+
}
61+
62+
OperationContext* opCtx = expCtx.getOperationContext();
63+
OperationMemoryUsageTracker* opTracker = getOperationMemoryUsageTracker(opCtx);
64+
return SimpleMemoryUsageTracker{opTracker, maxMemoryUsageBytes};
65+
}
66+
67+
MemoryUsageTracker OperationMemoryUsageTracker::createMemoryUsageTrackerForStage(
68+
const ExpressionContext& expCtx, bool allowDiskUse, int64_t maxMemoryUsageBytes) {
69+
if (!feature_flags::gFeatureFlagQueryMemoryTracking.isEnabled()) {
70+
return MemoryUsageTracker{allowDiskUse, maxMemoryUsageBytes};
71+
}
72+
73+
OperationContext* opCtx = expCtx.getOperationContext();
74+
OperationMemoryUsageTracker* opTracker = getOperationMemoryUsageTracker(opCtx);
75+
return MemoryUsageTracker{opTracker, allowDiskUse, maxMemoryUsageBytes};
76+
}
77+
78+
} // namespace mongo

src/mongo/db/memory_tracking/operation_memory_usage_tracker.h

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,42 @@
2929
#pragma once
3030
#include <cstdint>
3131

32+
#include "mongo/db/curop.h"
33+
#include "mongo/db/memory_tracking/memory_usage_tracker.h"
34+
3235
namespace mongo {
33-
// TODO SERVER-100671 Replace this.
34-
class OperationMemoryUsageTracker {
36+
37+
/**
38+
* A memory usage tracker class that aggregates memory statistics for the entire operation. Stages
39+
* that track memory will report to an instance of this class, which in turn will update statistics
40+
* in CurOp.
41+
*/
42+
class OperationMemoryUsageTracker : public SimpleMemoryUsageTracker {
43+
OperationMemoryUsageTracker() = delete;
44+
3545
public:
36-
OperationMemoryUsageTracker(int64_t currentMemoryBytes = 0, int64_t maxUsedMemoryBytes = 0)
37-
: _currentMemoryBytes(currentMemoryBytes), _maxUsedMemoryBytes(maxUsedMemoryBytes) {}
46+
/**
47+
* When constructing a stage containing a SimpleMemoryUsageTracker, use this method to ensure
48+
* that we aggregate operation-wide memory stats.
49+
*/
50+
static SimpleMemoryUsageTracker createSimpleMemoryUsageTrackerForStage(
51+
const ExpressionContext& expCtx,
52+
int64_t maxMemoryUsageBytes = std::numeric_limits<int64_t>::max());
3853

39-
int64_t currentMemoryBytes() const {
40-
return _currentMemoryBytes;
41-
}
54+
/**
55+
* When constructing a stage containing a MemoryUsageTracker, use this method to ensure that we
56+
* aggregate operation-wide memory stats.
57+
*/
58+
static MemoryUsageTracker createMemoryUsageTrackerForStage(
59+
const ExpressionContext& expCtx,
60+
bool allowDiskUse = false,
61+
int64_t maxMemoryUsageBytes = std::numeric_limits<int64_t>::max());
4262

43-
int64_t maxUsedMemoryBytes() const {
44-
return _maxUsedMemoryBytes;
45-
}
63+
explicit OperationMemoryUsageTracker(OperationContext* opCtx) : _opCtx(opCtx) {}
4664

4765
private:
48-
// Current amount of memory in use by all blocking stages.
49-
int64_t _currentMemoryBytes = 0;
66+
static OperationMemoryUsageTracker* getOperationMemoryUsageTracker(OperationContext* opCtx);
5067

51-
// High-water mark: the highest amount of memory that has been allocated at one time so far.
52-
int64_t _maxUsedMemoryBytes = 0;
68+
OperationContext* _opCtx;
5369
};
5470
} // namespace mongo

0 commit comments

Comments
 (0)