Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to structure my mongoose schema: embedded array , populate, subdocument?

Here is my current Schema

Brand:

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var BrandSchema = new mongoose.Schema({
  name: { type: String, lowercase: true , unique: true, required: true },
  photo: { type: String , trim: true},
  email: { type: String , lowercase: true},
  year: { type: Number},
  timestamp: { type : Date, default: Date.now },
  description: { type: String},
  location: { },
  social: {
    website: {type: String},
    facebook: {type: String },
    twitter: {type: String },
    instagram: {type: String }
  }
});

Style:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var StyleSchema = new mongoose.Schema({
  name: { type: String, lowercase: true , required: true},
});

Product

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var ProductSchema = new mongoose.Schema({
  name: { type: String, lowercase: true , required: true},
  brandId : {type: mongoose.Schema.ObjectId, ref: 'Brand'},
  styleId: {type: mongoose.Schema.ObjectId, ref: 'Style'},
  year: { type: Number }, 
  avgRating: {type: Number}
});

Post:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var PostSchema = new mongoose.Schema({
  rating: { type: Number},
  upVote: {type: Number},
  brandId : {type: mongoose.Schema.ObjectId, ref: 'Brand'},
  comment: {type: String},
  productId: {type: mongoose.Schema.ObjectId, ref: 'Style'},
  styleId: {type: mongoose.Schema.ObjectId, ref: 'Style'},
  photo: {type: String}
});

I'm currently making use of the mongoose populate feature:

exports.productsByBrand = function(req, res){
  Product.find({product: req.params.id}).populate('style').exec(function(err, products){
    res.send({products:products});
  });
};

This works, however, being a noob --- i've started reading about performance issues with the mongoose populate, since it's really just adding an additional query.

For my post , especially, it seems that could be taxing. The intent for the post is to be a live twitter / instagram-like feed. It seems that could be a lot of queries, which could greatly slow my app down.

also, I want to be able to search prodcuts / post / brand by fields at some point.

Should i consider nesting / embedding this data (products nested / embedded in brands)?

What's the most efficient schema design or would my setup be alright -- given what i've specified I want to use it for?


User story:

There will be an Admin User.

The admin will be able to add the Brand with the specific fields in the Brand Schema.

Brands will have associated Products, each Product will have a Style / category.


Search:

Users will be able to search Brands by name and location (i'm looking into doing this with angular filtering / tags).

Users will be able to search Products by fields (name, style, etc).

Users will be able to search Post by Brand Product and Style.


Post:

Users will be able to Post into a feed. When making a Post, they will choose a Brand and a Product to associate the Post with. The Post will display the Brand name, Product name, and Style -- along with newly entered Post fields (photo, comment, and rating).

Other users can click on the Brand name to link to the Brand show page. They can click on the Product name to link to a Product show page.

Product show page:

Will show Product fields from the above Schema -- including associated Style name from Style schema. It will also display Post pertaining to the specific Product.

Brand show page:

Will simply show Brand fields and associated products.


My main worry is the Post, which will have to populate / query for the Brand , Product, and Style within a feed.

Again, I'm contemplating if I should embed the Products within the Brand -- then would I be able to associate the Brand Product and Style with the Post for later queries? Or, possibly $lookup or other aggregate features.

like image 325
NoobSter Avatar asked May 27 '16 19:05

NoobSter


People also ask

How does Mongoose populate?

Example 2: We will perform the query to find all the posts using populate() method. To overcome the above problem, populate() method is used to replace the user ObjectId field with the whole document consisting of all the user data.

What is Mongoose subdocument?

In Mongoose, subdocuments are documents that are nested in other documents. You can spot a subdocument when a schema is nested in another schema. Note: MongoDB calls subdocuments embedded documents.

Which are the valid schema types useful while defining a schema in Mongoose?

You can also define MongoDB indexes using schema type options. index : boolean, whether to define an index on this property. unique : boolean, whether to define a unique index on this property. sparse : boolean, whether to define a sparse index on this property.


1 Answers

Mongodb itself does not support joins. So, mongoose populate is an attempt at external reference resolution. The thing with mongodb is that you need to design your data so that:

  1. most of you queries need not to refer multiple collections.
  2. after getting data from query, you need not to transform it too much.

Consider the entities involved, and their relations:

  1. Brand is brand. Doesn't depend on anything else.
  2. Every Product belongs to a Brand.
  3. Every Product is associated with a Style.
  4. Every Post is associated with a Product.
  5. Indirectly, every Post is associated to a Brand and Style, via product.

Now about the use cases:

  1. Refer: If you are looking up one entity by id, then fetching 1-2 related entities is not really a big overhead.

  2. List: It is when you have to return a large set of objects and each object needs an additional query to get associated objects. This is a performance issue. This is usually reduced by processing "pages" of result set at a time, say 20 records per request. Lets suppose you query 20 products (using skip and limit). For 20 products you extract two id arrays, one of referred styles, and other of referred brands. You do 2 additional queries using $in:[ids], get brands and styles object and place them in result set. That's 3 queries per page. Users can request next page as they scroll down, and so on.

  3. Search: You want to search for products, but also want to specify brand name and style name. Sadly, product model only holds ids for style and brand. Same issue with searching Posts with brand and product. Popular solution is to maintain a separate "search index", a sort of table, that stores data exactly the way it will be searched for, with all searchable fields (like brand name, style name) at one place. Maintaining such search collections in mongodb manually can be a pain. This is where ElasticSearch comes in. Since you are already using mongoose, you can simply add mongoosastic to your models. ElasticSearch's search capabilities are far greater than a DB Storage engine will offer you.

  4. Extra Speed: There is still some room for speeding things up: Caching. Attach mongoose-redis-cache and have frequent repeated queries served, in-memory from Redis, reducing load on mongodb.

  5. Twitter like Feeds: Now if all Posts are public then listing them up for users in chronological order is a trivial query. However things change when you introduce "social networking" features. Then you need to list "activity feeds" of friends and followers. There's some wisdom about social inboxes and Fan-out lists in mongodb blog.

Moral of the story is that not all use cases have only "db schema query" solutions. Scalability is one of such cases. That's why other tools exist.

like image 179
S.D. Avatar answered Oct 03 '22 21:10

S.D.