How to improve mongoDb query performance? - node.js

I have a collection named Codes. This is how the Schema is defined:
import mongoose from 'mongoose'
import autoIncrement from 'mongoose-auto-increment';
const Schema = mongoose.Schema;
const CodesSchema = mongoose.Schema(
{
configId: { type: Number },
campaignId: { type: Number },
permissions: {
all: { type: Boolean, default: false },
users: { type: [Number], default: [] }
}
)
autoIncrement.initialize(mongoose.connection);
CodesSchema.plugin(autoIncrement.plugin, { model: 'Codes', field: 'campaignId' });
export default mongoose.model('Codes', CodesSchema)
There is one query that looks like this:
const query = {
$and:[
{$or: [
{'permissions.all': true},
{'permissions.users': 12}
]},
{configId: 3}
]
};
Codes.find(query, (err, res) => {
// do something with the result
})
This works fine, but if there is a huge number of documents in the database then this query is really slow.
Is there anything I can do to improve the performance of this specific query? I'm thinking that createIndex would help, but I'm not sure if that can be applied since there are $and and $or conditions.
UPDATE
I've added indexes this way:
CodesSchema.index({configId: 1, 'permissions.all': 1, 'permissions.users': 1});
But running the query with .explain('executionStats') option returns:
{
"executionSuccess" : true,
"nReturned" : 6,
"executionTimeMillis" : 0,
"totalKeysExamined" : 10,
"totalDocsExamined" : 10,
}
Which doesn't seems right because the number of docs examined is greater than the number of docs returned.

The index itself is correct.
It must be CodesSchema.index, not Code.index.
Ensure you call Code.syncIndexes to update indexes dbside.
The "explain" part - you should check winningPlan.
If no indexes are used by the query, it should be something like
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
When the index is being used it changes to
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "OR",
"inputStages" : [
{
"stage" : "IXSCAN",
"keyPattern" : {

Related

MongoDB update multiple items in an array of objects with corresponding data

I have an array in my MongoDB document as shown below:
{
...otherDocFields,
groupID: "group-id",
users: [
{id: "uid1", name: "User1"},
{id: "uid2", name: "User2"},
{id: "uid3", name: "User3"},
{id: "uid4", name: "User4"}
]
}
I'm trying to write a function that will update users' names based on that ID.
I tried something like:
async function updateNames(groupID: string, data: Array<{id: string, name: string}>) {
try {
// MongoDB Aggregation
await mongoDB.collection("users").aggregate([
{$match: {groupID}},
{$unwind: {
path: '$users',
includeArrayIndex: 'users.id',
preserveNullAndEmptyArrays: true
}
}
//....
])
} catch (e) {
console.log(e)
}
}
I'm stuck at the part to update the relevant names from the data param in the function.
A sample data would be:
[
{id: "uid1", name: "newName1"},
{id: "uid3", name: "newName3"}
]
I can for sure read, manually process it and update the document but I'm looking for a way to do it in single go using aggregation.
You can do this with an update statement using array filters (https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#---identifier--)
First, if we just declare some tests data which would be passed into your method:
const data = [
{id: "uid2", name: "ChangeName1"},
{id: "uid4", name: "ChangedName2"}
];
We can then create an update statement which updates all the users within that list within a map and a reduce:
const sets = data.map((element, index) => ({ [`users.$[f${index}].name`]: element.name })).reduce((accumulator, currentValue) => (Object.assign(currentValue, accumulator)), { });
const update = { $set: sets };
This will give us the following update statement:
{
"$set" : {
"users.$[f1].name" : "ChangedName2",
"users.$[f0].name" : "ChangeName1"
}
}
We can create a bunch of array filters with that data:
const arrayFilters = data.map((element, index) => ({ [`f${index}.id`]: element.id }));
This will give us the following which we can pass into the options of an update.
[ { "f0.id" : "uid2" }, { "f1.id" : "uid4" } ]
Last of all we can execute the following update command:
db.users.updateOne(
filter,
update,
{ arrayFilters }
);
Now if we check the output we'll get the following results
> db.users.find().pretty()
{
"_id" : ObjectId("60e20841351156603932c526"),
"groupID" : "123",
"users" : [
{
"id" : "uid1",
"name" : "User1"
},
{
"id" : "uid2",
"name" : "ChangeName1"
},
{
"id" : "uid3",
"name" : "User3"
},
{
"id" : "uid4",
"name" : "ChangedName2"
}
]
}

MongoDB: executionTimeMillis increased when adding index

I am pretty confused to why making the searched fields to be "index" is making the query "theoretically" slower.
I have a not very big collection of items (6240) and all of them have the following structure.
const SomeSchema = new mongoose.Schema({
data: String,
from: {
type: Number,
},
to: {
type: Number,
},
timeStamp: {
type: Date,
default: new Date()
}
})
SomeSchema.set('toJSON', {
getters: true,
transform: (doc, ret) => {
delete ret.from
delete ret.to
return sanitizeSensitiveProperties(ret)
}
})
export const Some = mongoose.model('Some', SomeSchema, 'somethings')
The strange thing came when after trying to improve the query I changed the schema to be
...
from: {
type: Number,
index: true
},
to: {
type: Number,
index: true
},
...
With this schema I run the following query
db.rolls.find({from: {$lte: 1560858984}, to: {$gte: 1560858984}}).explain("executionStats")
This are the results NOTE THAT THE 1st ONE is the one without index
"executionTimeMillis" : 6,
"totalKeysExamined" : 0,
"totalDocsExamined" : 6240,
"executionTimeMillis" : 15,
"totalKeysExamined" : 2895,
"totalDocsExamined" : 2895,
Does this result make any sense, or is just the mongo .explain() function messing around?
As you can see I am using the Mongoose Driver in the version ^5.5.13 and I am using Mongo in the version 4.0.5

Skip one nested level of subdocument in Mongoose

I have a parent schema with a subdocument. The subdocument has a property with an array of embedded objects:
Child schema
var snippetSchema = new Schema({
snippet: [
{
language: String,
text: String,
_id: false
}
]
});
Parent schema
var itemSchema = new Schema({
lsin: Number,
identifier: {
isbn13: Number,
},
title: snippetSchema,
});
Which upon Item.find returns an object like so:
[
{
_id: (...),
lsin: 676765,
identifier: {
isbn13: 8797734598763
},
title: {
_id: (...),
snippet: [
{
language: 'se',
text: 'Pippi Långstrump'
}
]
}
}
]
I would like to skip one nested level of the subdocument when the object is returned to the client:
[
{
_id: (...),
lsin: 676765,
identifier: {
isbn13: 8797734598763
},
title: {
language: 'se',
text: 'Pippi Långstrump'
}
}
]
So far I have tried:
#1 using a getter
function getter() {
return this.title.snippet[0];
}
var itemSchema = new Schema({
...
title: { type: snippetSchema, get: getter }
});
But it creates an infinite loop resulting in RangeError: Maximum call stack size exceeded.
#2 using a virtual attribute
var itemSchema = new Schema({
..., {
toObject: {
virtuals: true
}
});
itemSchema
.virtual('title2')
.get(function () {
return this.title.snippet[0];
});
Which will generate the desired nested level but under a new attribute, which is not acceptable. To my knowledge there is no way of overriding an attribute with an virtual attribute.
The question is: is there any other way to go about in getting the desired output? There will be several references to the snippetSchema across the application and a DRY method is preferred.
I am new to MongoDB and Mongoose.
You'll need to use the $project within an mongodb aggregation pipeline.
Within my database I have the following:
> db.items.find().pretty()
{
"_id" : 123,
"lsin" : 676765,
"identifier" : {
"isbn13" : 8797734598763
},
"title" : {
"_id" : 456,
"snippet" : [
{
"language" : "se",
"text" : "Pippi Långstrump"
}
]
}
}
Then we just need to create a simple aggregation query:
db.items.aggregate([
{$project: { lsin: 1, identifier: 1, title: { $arrayElemAt: [ '$title.snippet', 0 ] }}}
])
This just uses a $project (https://docs.mongodb.com/v3.2/reference/operator/aggregation/project/) and a $arrayElemAt (https://docs.mongodb.com/v3.2/reference/operator/aggregation/arrayElemAt/) to project the first item out of the array. If we execute that we will get the following:
{
"_id" : 123,
"lsin" : 676765,
"identifier" : {
"isbn13" : 8797734598763
},
"title" : {
"language" : "se",
"text" : "Pippi Långstrump"
}
}

Mongoose $in not working in nodeJS

I've created a collection containing some operation details like below
{ "_id" : ObjectId("580776455ecd3b4352705ec4"), "operation_number" : 10, "operation_description" : "SHEARING", "machine" : "GAS CUTT" }
{ "_id" : ObjectId("580776455ecd3b4352705ec5"), "operation_number" : 50, "operation_description" : "EYE ROLLING -1", "machine" : "E-ROLL-1" }
{ "_id" : ObjectId("580776455ecd3b4352705ec6"), "operation_number" : 60, "operation_description" : "EYE ROLLING -2", "machine" : "E-ROLL-1" }
{ "_id" : ObjectId("580776455ecd3b4352705ec7"), "operation_number" : 70, "operation_description" : "EYE REAMING", "machine" : "E-REAM" }
{ "_id" : ObjectId("580776455ecd3b4352705ec8"), "operation_number" : 80, "operation_description" : "COLD CENTER HOLE PUNCHING", "machine" : "C-PNCH-1" }
{ "_id" : ObjectId("580776455ecd3b4352705ec9"), "operation_number" : 150, "operation_description" : "READY FOR HT", "machine" : "RHT" }
using mongoose model as below
var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');
var Promise = require("bluebird");
mongoose.Promise = Promise;
var Schema = mongoose.Schema;
var operationSchema = new Schema({
operation_number: {
type: String,
required: [
true,
"Please select valid operation code"
]unique : true
},
operation_description: {
type: String,
required: [
true,
"Please select valid operation description"
]
}
}, { strict: false });
var operation = mongoose.model('operation', operationSchema);
operationSchema.plugin(uniqueValidator, { message: 'Error, {PATH} {VALUE} already exist.' });
// make this available to our users in our Node applications
module.exports = operation;
now if I query this collection operations using db.operations.find({operation_number : {$in : [10, 50, 60]}}) it works but when it comes to mongoose it's not working.
var mc = require("./data-models/operation")
var filter = {'operation_number':
{$in :
[10, 50, 60]
}
}
console.log(filter)
mc.find(filter, function(me, md){
console.log(me, md) // prints null []
})
Even I've tried removing single quotes around operation_number
Please help finding way !
Your schema says that operation_number is a string:
operation_number: {
type: String, <-- here
...
}
Therefore, Mongoose will cast the numbers in the $in array to strings.
However, the data in the database is numerical, which is a different type. You should change your schema so that operation_number becomes a Number:
operation_number: {
type: Number,
...
}
Sometimes it happens that you forget to define that attribute in the schema.

find and update one field in array of subdoc mongoose

i'm new in mongodb and mongoose. i'm using node js, and i want to find and update specific field in one of array in sub document.
here is my data structure :
var PostSchema = new Schema({
title: String,
comment: [
{
name: String,
value: String
}
],
createdAt: Date,
updateAt: Date
})
this is my data example :
{
_id : 12,
title : 'some article here...',
comment : [{
_id : 1,
name : 'joe',
value : 'wow that fantastic...'
},{
_id : 2,
name : 'rocky',
value : 'really... thats insane...'
},{
_id : 3,
name : 'jane',
value : 'i think its impossible to do...'
}],
createdAt : 2016-04-14 04:50:54.760Z,
updatedAt : 2016-04-14 04:50:54.760Z
}
i need to update comment which have _id : 2, i want to change the value become 'what???'. i try to update with :
Post.findOne({'_id': 12, 'comment._id': 2}, {_id: 0, 'comment.$': 1}, function (err, cb) {
cb.comment.value = 'what???'
cb.save()
})
but it failed... so, can you help me? Thanks
you can use findOneAndUpdate and positional operator $ to update specific comment value
Post.findOneAndUpdate({"_id": 12, "comment._id": 2},
{
$set: {
"comment.$.value ": "New value set here"
}
},
{ new: true } // return updated post
).exec(function(error, post) {
if(error) {
return res.status(400).send({msg: 'Update failed!'});
}
return res.status(200).send(post);
});
It will work for array updation in mongodb using mongoose
Post.findOneAndUpdate(
{ id: 12,comment_id:2 },
{ $push: { comment:"new comment" } }
)

Resources