|
28 | 28 |
|
29 | 29 | #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery |
30 | 30 |
|
| 31 | +#include <atomic> |
| 32 | + |
31 | 33 | #include "mongo/db/exec/projection.h" |
32 | 34 |
|
33 | 35 | #include "mongo/db/exec/plan_stage.h" |
|
40 | 42 | #include "mongo/util/log.h" |
41 | 43 | #include "mongo/util/mongoutils/str.h" |
42 | 44 |
|
| 45 | +#include <butil/time.h> |
| 46 | + |
43 | 47 | namespace mongo { |
44 | 48 |
|
45 | 49 | using std::endl; |
@@ -143,49 +147,77 @@ void ProjectionStage::transformSimpleInclusion(const BSONObj& in, |
143 | 147 | } |
144 | 148 |
|
145 | 149 | Status ProjectionStage::transform(WorkingSetMember* member) { |
146 | | - // The default no-fast-path case. |
147 | | - if (ProjectionStageParams::NO_FAST_PATH == _projImpl) { |
148 | | - return _exec->transform(member); |
149 | | - } |
150 | | - |
151 | | - BSONObjBuilder bob; |
| 150 | + // Track latency and statistics for transform (lock-free, resets every 10000 calls) |
| 151 | + static std::atomic<uint64_t> transformCallCount{0}; |
| 152 | + static std::atomic<uint64_t> totalLatencyUs{0}; |
| 153 | + static constexpr uint64_t WINDOW_SIZE = 100000; |
152 | 154 |
|
153 | | - // Note that even if our fast path analysis is bug-free something that is |
154 | | - // covered might be invalidated and just be an obj. In this case we just go |
155 | | - // through the SIMPLE_DOC path which is still correct if the covered data |
156 | | - // is not available. |
157 | | - // |
158 | | - // SIMPLE_DOC implies that we expect an object so it's kind of redundant. |
159 | | - if ((ProjectionStageParams::SIMPLE_DOC == _projImpl) || member->hasObj()) { |
160 | | - // If we got here because of SIMPLE_DOC the planner shouldn't have messed up. |
161 | | - invariant(member->hasObj()); |
| 155 | + butil::Timer timer; |
| 156 | + timer.start(); |
162 | 157 |
|
163 | | - // Apply the SIMPLE_DOC projection. |
164 | | - transformSimpleInclusion(member->obj.value(), _includedFields, bob); |
| 158 | + // The default no-fast-path case. |
| 159 | + Status result; |
| 160 | + if (ProjectionStageParams::NO_FAST_PATH == _projImpl) { |
| 161 | + result = _exec->transform(member); |
165 | 162 | } else { |
166 | | - invariant(ProjectionStageParams::COVERED_ONE_INDEX == _projImpl); |
167 | | - // We're pulling data out of the key. |
168 | | - invariant(1 == member->keyData.size()); |
169 | | - size_t keyIndex = 0; |
170 | | - |
171 | | - // Look at every key element... |
172 | | - BSONObjIterator keyIterator(member->keyData[0].keyData); |
173 | | - while (keyIterator.more()) { |
174 | | - BSONElement elt = keyIterator.next(); |
175 | | - // If we're supposed to include it... |
176 | | - if (_includeKey[keyIndex]) { |
177 | | - // Do so. |
178 | | - bob.appendAs(elt, _keyFieldNames[keyIndex]); |
| 163 | + BSONObjBuilder bob; |
| 164 | + |
| 165 | + // Note that even if our fast path analysis is bug-free something that is |
| 166 | + // covered might be invalidated and just be an obj. In this case we just go |
| 167 | + // through the SIMPLE_DOC path which is still correct if the covered data |
| 168 | + // is not available. |
| 169 | + // |
| 170 | + // SIMPLE_DOC implies that we expect an object so it's kind of redundant. |
| 171 | + if ((ProjectionStageParams::SIMPLE_DOC == _projImpl) || member->hasObj()) { |
| 172 | + // If we got here because of SIMPLE_DOC the planner shouldn't have messed up. |
| 173 | + invariant(member->hasObj()); |
| 174 | + |
| 175 | + // Apply the SIMPLE_DOC projection. |
| 176 | + transformSimpleInclusion(member->obj.value(), _includedFields, bob); |
| 177 | + } else { |
| 178 | + invariant(ProjectionStageParams::COVERED_ONE_INDEX == _projImpl); |
| 179 | + // We're pulling data out of the key. |
| 180 | + invariant(1 == member->keyData.size()); |
| 181 | + size_t keyIndex = 0; |
| 182 | + |
| 183 | + // Look at every key element... |
| 184 | + BSONObjIterator keyIterator(member->keyData[0].keyData); |
| 185 | + while (keyIterator.more()) { |
| 186 | + BSONElement elt = keyIterator.next(); |
| 187 | + // If we're supposed to include it... |
| 188 | + if (_includeKey[keyIndex]) { |
| 189 | + // Do so. |
| 190 | + bob.appendAs(elt, _keyFieldNames[keyIndex]); |
| 191 | + } |
| 192 | + ++keyIndex; |
179 | 193 | } |
180 | | - ++keyIndex; |
181 | 194 | } |
| 195 | + |
| 196 | + member->keyData.clear(); |
| 197 | + member->recordId = RecordId(); |
| 198 | + member->obj = Snapshotted<BSONObj>(SnapshotId(), bob.obj()); |
| 199 | + member->transitionToOwnedObj(); |
| 200 | + result = Status::OK(); |
| 201 | + } |
| 202 | + |
| 203 | + timer.stop(); |
| 204 | + uint64_t latencyUs = timer.u_elapsed(); |
| 205 | + |
| 206 | + // Update statistics (lock-free using atomics) |
| 207 | + uint64_t count = transformCallCount.fetch_add(1, std::memory_order_relaxed) + 1; |
| 208 | + totalLatencyUs.fetch_add(latencyUs, std::memory_order_relaxed); |
| 209 | + |
| 210 | + // Log every 10000 calls with statistics from recent window |
| 211 | + if (count % WINDOW_SIZE == 0) { |
| 212 | + // Use memory_order_acquire to ensure we see all updates before reading |
| 213 | + uint64_t windowTotalLatencyUs = totalLatencyUs.exchange(0, std::memory_order_acq_rel); |
| 214 | + uint64_t avgLatencyUs = windowTotalLatencyUs / WINDOW_SIZE; |
| 215 | + MONGO_LOG(0) << "ProjectionStage::transform statistics (recent " << WINDOW_SIZE |
| 216 | + << " calls): " |
| 217 | + << "average latency: " << avgLatencyUs << " us"; |
182 | 218 | } |
183 | 219 |
|
184 | | - member->keyData.clear(); |
185 | | - member->recordId = RecordId(); |
186 | | - member->obj = Snapshotted<BSONObj>(SnapshotId(), bob.obj()); |
187 | | - member->transitionToOwnedObj(); |
188 | | - return Status::OK(); |
| 220 | + return result; |
189 | 221 | } |
190 | 222 |
|
191 | 223 | bool ProjectionStage::isEOF() { |
|
0 commit comments