Automattic / Automattic/mongoose
toObject({getters:true}) on projected documents: applyGetters iterates all schema paths and each unselected path triggers an O(projectionKeys) isSelected scan — O(paths x projection) per doc
- Dominant language
- JavaScript
- Stars
- 27.5k
- Forks
- 4k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 35
Description
## toObject({getters:true}) on projected documents: applyGetters iterates all schema paths and each unselected path triggers an O(projectionKeys) isSelected scan — O(paths x projection) per doc
**Repo:** `Automattic/mongoose`
**Location:** lib/document.js:4394 (applyGetters per-path $__isSelected call) x lib/document.js:2572 (isSelected projection-key scan)
**Severity:** medium · **Confidence:** 0.75
**Type:** complexity-at-a-distance
### Description
applyGetters in lib/document.js (line ~4371) loops over every key of `schema.paths` and calls `self.$__isSelected(path)` for each. Document.prototype.isSelected (line ~2525) is O(S) for any path not literally present in the projection: after the O(1) `path in this.$__.selected` check fails, it loops all projection keys doing startsWith comparisons (lines ~2572-2584). So serializing one projected document with getters enabled (a very common toJSON config) costs O(P x S) where P = schema paths and S = projection keys — per document, per serialization. The chain crosses three modules: lib/query.js `_completeMany` builds projected docs (fields propagated into the Document constructor, `$__.selected` set at lib/document.js:158), then user/toJSON triggers `$toObject` -> applyGetters -> isSelected. Each function looks fine locally; the product only appears across the boundary.
### Benchmark
With schema width 2N and an inclusive projection of N keys, toObject({getters:true}) time should grow superlinearly, tracking the O(N^2) prediction (N=500 hundreds-to-thousands of times slower per pass than N=10) because each of the ~N unselected paths scans the N projection keys.
**Observed complexity:** O(N·M)
```
| Input | median_ms | ratio | note |
|-------|-----------|-------|------|
| N=10 | 2.75 | 1.00 | — |
| N=50 | 17.59 | 6.40 | — |
| N=100 | 68.20 | 24.79 | — |
| N=500 | 2199.44 | 799.43 | — |
```
benchmark confirmed
Contributor guide
Research direction
Start in lib/document.js at applyGetters and isSelected, then trace how lib/query.js _completeMany propagates projections into projected documents. Reproduce the reported benchmark with toObject({ getters: true }) across the listed schema widths. Done means projected serialization no longer exhibits the reported O(paths × projection) growth, with the benchmark confirming the improvement.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, mongodb
- Domain
- backend, databases, performance
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100