mongo db poor performance while doing text search with number - node.js

I have a collection called "torrents" with about 3.2M Documents. which basically contains public torrent metadata.
Schema:
let mongoose = require('mongoose');
let TorrentSchema = mongoose.Schema({
infohash: {type: String, index: true, unique: true},
title: {type: String, index: true},
category: {type: String, default: "Unknown", index: true},
size: {type: Number, default: 0},
trackers: [{
downloads: {type: Number},
peers: {type: Number},
seeds: {type: Number},
tracker: {type: String}
}],
files: [{path: String, length: Number}],
swarm: {
seeders: {type: Number, default: 0, index: -1},
leechers: {type: Number, default: 0}
},
imported: {type: Date, default: Date.now, index: true},
lastmod: {type: Date, default: Date.now, index: true}
});
TorrentSchema.virtual('getFiles').set(function () {
return this.map(res => {
if (typeof res === "string") return [{path: res, length: 0}];
return res;
})
});
TorrentSchema.virtual('downloads').get(function () {
let downloads = 0;
for (let download of this.trackers) {
downloads += download.downloads
}
return downloads;
});
TorrentSchema.index({title: 'text'}, { language_override: 'none' });
module.exports = mongoose.model('Torrent', TorrentSchema);
Now that the problem is when I am doing a text search with a keyword which also contents number(s) the search query taking a long time to execute.
let q = req.query.q;
q = q.split(/[\s]/).filter(n => n).map( str => `"${str}"`).join(" ");
let PERPAGE = 20;
let query = {$text: {$search: q}};
// Tottent Is the same schema as above.
let search = await Torrent.find(query).sort({"swarm.seeders" : -1}).select("-files").limit(PERPAGE).skip(skip);
now the issue is. when searching with letters like "ubuntu" it going really fast. but the problem arises when searching with a string which also contains numbers. Like "ubuntu 18", strings with no numbers like "ubuntu iso" taking nowhere near the time. the same thing happening when searching some other keywords like "somevideo 1080p", "somemovie 2" etc.
Do you have any fix for this issue?

It seems, after querying with same keyword couple of times, the query speed seems increased significantly.

Related

Express, mongoose: I want to 'req.body' inside an object, but I have no idea how to do it

I am learning how to use Express with mongoDB, but I have a problem when I want to concatenate a req.body inside an object and I don't know with what arguments to specify it.
pd.1: Is my first request for help on stackoverflow.
pd.2: I've already googled, I usually find the solution in other Stackoverflow threads.
My scheme works in its entirety, I have already done petitions in 'Postman'.
Here's the schema
const clothesSchema = new mongoose.Schema({
type: {type: String, required: [true, 'The type of clothe is necesary']},
color: {type: String, required: [true, 'The color is necessary']},
price: {type: Number, required: [true, 'The price is necessary']},
image: {type: String, required: [true, 'The image is necessary ']},
size_S: {
size: {type: String, required: true, default: () => {return "S"}},
quantity: {type: Number, required: true}
},
size_M: {
talla: {type: String, required: true, default: () => {return "M"}},
quantity: {type: Number, required: true}
},
size_L: {
talla: {type: String, required: true, default: () => {return "L"}},
quantity: {type: Number, required: true}
},
})
Heres the Route and the heelp what i need
// Create New Clothe
router.route("/new").post((req, res) => {
let db = new Clothes();
db.type = req.body.type;
db.color = req.body.color;
db.price = req.body.price;
db.image = req.body.image;
// -------------- Need help or recommendations for this arguments ----------------
db.size_S = {
quantity: req.body.quantity
};
db.size_M = {
quantity: req.body.quantity
};
db.size:L ={
quantity: req.body.quantity
};
db.save((err) => {
if (err) {
response = { error: true, msg: "Something was wrong" };
} else {
response = { error: true, msg: "New clothe Succesfully created" };
}
res.json(response);
});
});
Previously I wanted to concatenate quantity on db.size_S for body request
My attempt:
db.size_S = {
quantity: req.body.quantity
};
After hours of multiple attempts I reached the
answer I was looking for
The solution I got:
db.size_S = req.body.size_S
When i doing req.body.size_S, I also call the arguments inside size_S scheema, the request in Postman has Succesfully completed!

How can I sum up values in my mongodb collection?

I have a model in mongoose that looks similar to this:
var TestSchema = new Schema({
test_username: {type: String, required: true},
test_content: {type: String},
reactions: [{
test_username: {type: String, required: true},
value: {type: Number, required: true},
sent_at: {type: Date, required: true, default: Date.now}
}],
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now}
})
it stores my Test object with many reactions in it. Each reaction contains either 1 or -1 value and different usernames.
Now I'm trying to create an endpoint that gets the Test id as an input and returns the total, summed amount from all reactions that it contains.
I started writing it as:
testRoutes.get('/:id/reactions/', functions.validateRequestsGET, function(req, res){
var testId = req.params.id;
var query = Test... //here I'm stuck
query.exec(function(err, reactions){
if(err) {
res.send(err);
return;
}
res.json(reactions);
});
});
can you give me a hint of how to create a query that could return me a json with the summed amount? something like {reactions: 17} or similar?
Try this:
Test.aggregate(
{ $match: {
_id: testId // you might want to convert this from string to ObjectId()
}},
{ $project: {
sumReactions: { $sum: "$reactions.value" }
}}
)
Take a look at group accumulators $group in documentation , good examples too.

Mongoose .find() is empty when querying on subdocument

I'm trying to query a document based off of its subdocument data. When I do this I get no data returned. When I remove the "where" or when I query on a primitive type field within the parent it works fine. What am I missing?
My collections are broken up into separate files, here they are together for simplicity:
var PlayerSchema = new Schema({
firstName: { type: String, default: '', required: true},
lastName: { type: String, default: '', required: true},
nickname: { type: String, default: '' },
});
module.exports = mongoose.model('Player', PlayerSchema);
var GameSchema = new Schema({
winner: { type: Schema.Types.ObjectId, ref: 'Player', required: true},
datePlayed: { type: Date, default: Date.now, required: true },
});
module.exports = mongoose.model('Game', GameSchema);
var GamePlayerSchema = new Schema({
game: { type: Schema.Types.ObjectId, ref: 'Game', required: true},
player: { type: Schema.Types.ObjectId, ref: 'Player', required: true},
points: { type: Number, default: 0 },
place: { type: Number, default: 0 },
});
module.exports = mongoose.model('GamePlayer', GamePlayerSchema);
My query:
GamePlayerModel.find()
//.where('player.firstName').equals('Brian') // returns empty
//.where(place).equals(1) // returns correct dataset
.where('game.datePlayed').gte(startDateRange).lt(endDateRange) // returns empty
.select('game player points place')
.populate('game')
.populate('player')
.exec(function (err, gamePlayers) {
if(err) return next(err);
res.json(gamePlayers);
});
So again, if I query on a subdocument in any way it returns an empty dataset. I've tried game.datePlayed and even games.datePlayed. I'm not sure what to do. I don't need the player.firstName results, however I figured that'd be an easy thing to test to make sure the query is setup correctly.
Lastly, this is how I setup the date ranges. The date objects come out correctly, but are they possibly the wrong type?
var now = new Date();
var month = req.query.month ? parseInt(req.query.month) : now.getUTCMonth();
var year = req.query.year ? parseInt(req.query.year) : now.getUTCFullYear();
var endMonth = month+1;
if(endMonth > 11) endMonth = 0;
var startDateRange = new Date(year, month, 1);
var endDateRange = new Date(year, endMonth, 1);
Joins are not supported in MongoDB, which is what you are trying to do. The closest you can get is to filter as part of your .populate call:
.populate('game', null, {datePlayed: {$gte: startDateRange, $lt: endDateRange}})
.populate('player', null, {firstName: 'Brian'})
However what this will do is get all GamePlayer documents and only get the Game and Player subdocuments that match your criteria. If the subdocuments don't match your criteria, the GamePlayer document will still be returned with .game or .player equal to null.
You may want to reconsider your schema to be less like a SQL schema and to take advantage of the benefits of MongoDB. I'm not sure what your requirements are, but consider something like this:
var PlayerSchema = new Schema({
firstName: { type: String, default: '', required: true},
lastName: { type: String, default: '', required: true},
nickname: { type: String, default: '' },
});
var GameSchema = new Schema({
winner: { type: Schema.Types.ObjectId, ref: 'Player', required: true},
datePlayed: { type: Date, default: Date.now, required: true },
players: [{
player: { type: Schema.Types.ObjectId, ref: 'Player', required: true},
points: { type: Number, default: 0 },
place: { type: Number, default: 0 }
}]
});
Then your query could look something like:
GameModel.find()
.where('players.place').equals(1) // This will limit the result set to include only Games which have at least one Player with a place of 1
.where('datePlayed').gte(startDateRange).lt(endDateRange)
.populate('players.player')
.exec(function (err, games) {
if(err) return next(err);
res.json(games);
});
The above example will include all Players in each returned Game, regardless of whether their place is 1, however it will only include Games which have at least one player with a place of 1.
If you want to limit the items in the players array, you might need to take it a step further and use an aggregate command to unwind the array then filter. For example:
GameModel.aggregate([
{$unwind: 'players'},
{$match: {'players.place' : 1}}
], function(err, results) {
if (err) return next(err);
res.json(results);
});
This will return a separate Game object for each Player with a place of 1. If a Game has more than one Player with a place of 1, it will return duplicate Game objects, each with a different Player.

Complex query with mongoose, including subdocuments, near condition,

I have a very (at least for me) complex query using Mongoose.
First of all my schema:
var ObjectSchema = new Schema({
pickupStartDate: {type: Date, required: true, default: Date},
pickupEndDate: {type: Date, required: true, default: Date},
...
handoverStartDate: {type: Date, required: true, default: Date},
handoverEndDate: {type: Date, required: true, default: Date},
...
});
By using the "plugin mechanism" my Object has two addresses (called pickupAddress and handoverAddress. The address looks like that:
var name = 'address';
var obj = {};
obj[name] = {
street: String,
zipCode: String,
city: String,
state: String,
country: String,
loc: {type: [Number], index: '2dsphere'}
};
schema.add(obj);
And the other schema:
var TripSchema = new Schema({
startDate: {type: Date, required: true, default: Date},
endDate: {type: Date, required: true, default: Date},
handoverRadius: {type: Number, required: true}
});
It has an address, too (using plugin mechanism again).
I want the following query:
Find all "objects" which "fit" to my trip.
"Fit" means:
handoverStartDate >= trip.startDate
handoverEndDate <= trip.endDate
`handoverAddress is near trip.address
...
I thought this would be a good approach:
ObjectSchema
.find()
.and([
{ handoverStartDate: {$gte: trip.startDate}},
{ handoverEndDate: {$lte: trip.endDate}},
{ 'handoverAddress.loc': {$near: {
'$maxDistance': 10 * 1000,
'$center': {
type: 'Point',
coordinates: trip.address.loc
}
}}}
])
.exec(function(err, cdObjects) {
console.log(err);
console.log(cdObjects);
});
But this leads to the following error:
{ message: 'Cast to number failed for value "[object Object]" at path "handoverAddress.loc"'.
I guess because of 'handoverAddress.loc'. But I'm not sure how to specify that as it has to be a string (because it's a subdocument).
You don't need the and. try
ObjectModel.find({
handoverStartDate: {$gte: trip.startDate},
handoverEndDate: {$lte: trip.endDate},
'handoverAddress.loc': {
$near: {
$geometry: {
type: "Point",
coordinates: trip.address.loc
},
$maxDistance: 10 * 1000
}
})
Make sure trip is defined as a variable and that startDate,endDate, and address are all defined properties fitting your expectations.
This is how it worked for me:
ObjectSchema
.where('handoverStartDate').gte(trip.startDate)
.where('handoverEndDate').lte(trip.endDate)
.where('handoverAddress.loc').near({
center: {
type: 'Point',
coordinates: trip.address.loc
},
maxDistance: 10 * 1000
});

How to update a subdocument inside another subdocument in mongoose using atomic query?

My Mongoose schema looks as follows:
var MousePos = Schema({
x: { type: Number},
y: { type: Number},
clickCount: {type: Number, default:0}
});
var MouseTrackInfo = Schema({
pos:{ type: [MousePos], default:[]},
element: {type: String, index:true},
clickCount:{ type: Number, default: 0 },
timeSpent: { type: Number, default: 0 }
});
var MouseTrackSchema = Schema({
pageUrl: { type: String},
applicationId: { type: String, index: true },
mouseTrackLog: { type: [MouseTrackInfo], default:[]},
urlId: {type: String, index: true}
});
Now what i want to do is to update clickCount in pos subdocument when element, x and y values are specified.
I tried a number of approaches, but nothings seems to be working. Moreover i can't use two $ operator in my query too. Suggestions will be welcome. I want to make this query as atomic as possible.
I was not able to figure out a reliable way so i changed my schema to following:
var MouseTrackInfo = Schema({
element: {type: String, index:true},
clickCount:{ type: Number, default: 0 },
timeSpent: { type: Number, default: 0 },
x: { type: Number, default:0},
y: { type: Number, default: 0},
identifier: {type: String}
});
var MouseTrackSchema = Schema({
pageUrl: { type: String},
applicationId: { type: String, index: true },
mouseTrackLog: { type: [MouseTrackInfo], default:[]},
urlId: {type: String, index: true}
});
MouseTrackSchema.methods.incrementClickCount = function(element,pageX,pageY,cb) {
var parent = this;
this.model('MouseTrackModel').update(
{
'applicationId':this.applicationId,
'urlId':this.urlId,
'mouseTrackLog.identifier': element+"X"+pageX+"Y"+pageY
},
{
'$inc':{'mouseTrackLog.$.clickCount':1}
},
{
'new': true
},
cb);
}
This allowed my execute a atomic query. Any other solution are welcome.

Resources