[SERVER-39387] Minimize fetch when indexed key is used with both $exists and $mod in $or Created: 05/Feb/19 Updated: 06/Dec/22 |
|
| Status: | Backlog |
| Project: | Core Server |
| Component/s: | Querying |
| Affects Version/s: | 4.0.5 |
| Fix Version/s: | None |
| Type: | Improvement | Priority: | Major - P3 |
| Reporter: | Joakim Edenholm | Assignee: | Backlog - Query Optimization |
| Resolution: | Unresolved | Votes: | 0 |
| Labels: | None | ||
| Remaining Estimate: | Not Specified | ||
| Time Spent: | Not Specified | ||
| Original Estimate: | Not Specified | ||
| Assigned Teams: |
Query Optimization
|
| Participants: |
| Description |
|
Expected: IXSCAN returns only items where the key is either missing or matched by $mod Observed: IXSCAN returns all documents in collection, filter is applied during FETCH. To be clear, in separate queries, using only $exists works fine and using only $mod works fine. Setup:
Demo query:
Observed execution stats:
|
| Comments |
| Comment by Asya Kamsky [ 02/Jul/20 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ognom regarding your last comment, note that for bar:null you will always get 3 keys reported from IXSCAN (output slightly different format as this is 4.4)
But with $exists:false I get 2 keys examined:
Isn't that just an artifact of the fact that in our query semantics null matches missing, undefined and present but null? I don't quite see what the issue here is aside from null semantics. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Joakim Edenholm [ 24/Nov/19 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Reflecting on this, perhaps it would be better to either split this request for improvement into two, or just change the scope of this request and wait for SERVER-12869 to be resolved? Request A: Given this setup:
And this query:
IXSCAN should return 3 keys, it currently returns 4. Specifically, IXSCAN should not return {_id: 2}.
Request B: Once A is done, add functionality to translate {$exists: false} into {$eq: null}. Request A provides value either way, while request B could be resolved by SERVER-12869. What are your thoughts on this? Should I create a new request for improvement matching request A? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by David Storch [ 08/Feb/19 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ognom I see what you're getting at, and this is a great improvement request! The {$exists: false} filter has to be attached to the FETCH stage, since right now literal null values and missing values are indexed in exactly the same way. This means that we have to fetch the full document in order to distinguish between documents like {_id: 1, bar: null} and {_id: 1} for the predicate {bar: {$exists: false}}. There is an open ticket about distinguishing between null and missing in the index: see SERVER-12869. However, even if SERVER-12869 is not fixed, there is still an improvement opportunity here. It would be possible for the IXSCAN stage to return the union of keys matching the $mod predicate and null keys (which are used for both the null and missing cases). So, for example, if the index contained keys (null, null, "foo", 1, 2, 3, 4) then the index scan would return (null, null, 1, 3) for your example predicate. After fetching the associated documents, say {_id: 1}, {_id: 2, bar: null}, {_id: 3, bar: 1}, and {_id: 4, bar: 3}, we need to reapply the filter in order to remove the non-matching document {_id: 2, bar: null}. I believe that implementing this improvement would require the system to translate the {$exists: false} predicate into an {$eq: null} predicate, which seems perfectly doable but likely explains why this optimization has not yet been implemented. I hope this helps clarify, and let us know if you have any questions! Best, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Joakim Edenholm [ 06/Feb/19 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I understand that the indexBounds means the entire index must be scanned. What does not make sense to me is that in your modified query, the IXSCAN stage has a filter on $mod. In the executionStats I posted, IXSCAN does NOT have a filter. Compare the two below queries. One is the original demo query from the description of this issue. The other is the same query, but without the filter on $exists.
Without $exists: IXSCAN has filter on $mod. With $exists: IXSCAN has no filter, returns all. This is the filter I am referring to:
Do you see my point now? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Danny Hatcher (Inactive) [ 06/Feb/19 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Hello Joakim, This appears to me as working as intended. The "bar" index is being used to satisfy both parts of the query: the $mod and the $exists. The two different bounds mentioned in the explain plan refer to the two separate operators and how the index satisfies them:
If we create another index on the field "test" and search for the existence of that instead, we can see that the explain uses both indexes to satisfy the two different parts of the query:
The bounds are now split between the two indexes so the "bar" index only needs to keysExamined 2 and nreturned 1 instead of the keysExamined 3 and nreturned 3 in your example. Does that make sense or do you think that there is still a bug here? Thank you, Danny |