Sling Academy
Home/Node.js/Weighted Random Selection in Mongoose: Explained with Examples

Weighted Random Selection in Mongoose: Explained with Examples

Last updated: January 18, 2024

Overview

Managing randomness in database queries is often an overlooked feature. However, in the world of modern web applications, the ability to select documents at random especially with certain weights can be a very handy feature for a diverse set of applications like gaming, polling, advertising, etc.

In this tutorial, we will explore how to implement weighted random selection using Mongoose, the popular MongoDB object modeling tool for Node.js. The concept can be somewhat complex, but with practical examples, we will make it quite comprehensive and easy to understand.

What is Weighted Random Selection

Firstly, let’s clarify what weighted random selection is. Compared to uniform random selection where every item has an equal chance of being selected, weighted random selection assigns different probabilities to each item. Hence, some items are more likely to be picked than others based on their assigned weights.

Understanding the Basics

To perform a weighted random selection in Mongoose, we’ll need to consider the schema of the documents. Let’s say we have a ‘Campaign’ schema representing different advertising campaigns, where each campaign has a ‘weight’ field indicating its probability of being selected.

const mongoose = require('mongoose');

const campaignSchema = new mongoose.Schema({
    name: String,
    weight: { type: Number, default: 1 } // default weight is 1
});

const Campaign = mongoose.model('Campaign', campaignSchema);

Once we have our schema set up with a weight field, we need to think about how we will use these weights to select our campaigns randomly.

Algorithm for Weighted Random Selection

The algorithm to perform weighted random selection is not natively supported by MongoDB or Mongoose but can be achieved using an extra step of processing. We’ll first fetch our weights, calculate the total sum of weights, and pick a random number within this range. After this, we’ll iterate through our documents and find where the random number fits within the cumulative weight.

// JavaScript pseudo-code for weighted random selection
async function weightedRandomSelection() {
    const campaigns = await Campaign.find(); // Fetch all campaigns
    let totalWeight = campaigns.reduce((total, campaign) => total + campaign.weight, 0);
    let randomNum = Math.random() * totalWeight;  // Random number

    for (let campaign of campaigns) {
        if (randomNum < campaign.weight) {
            return campaign; // Found our weighted random campaign
        }
        randomNum -= campaign.weight;
    }
}

However, it’s important to note that this approach can be inefficient if you have a large number of documents because it fetches all documents into memory. To improve this, we can aggregate directly within MongoDB.

Optimizing with Aggregation

MongoDB’s aggregation framework can be used to optimize weighted random selection. We can leverage the ‘$sample’ stage to efficiently randomize document order and ‘$cumulativeWeights’ to apply weighting.

async function optimizedWeightedRandomSelection() {
    let totalWeight = await Campaign.aggregate([
        { $group: { _id: null, totalWeight: { $sum: '$weight' } } }
    ]).then(res => res[0].totalWeight);

    let randomNum = Math.random() * totalWeight;

    return Campaign.aggregate([
        { $set: { cumulativeWeight: { $sum: '$weight' } } },
        { $match: { cumulativeWeight: { $gte: randomNum } } },
        { $sample: { size: 1 } }
    ]).then(docs => docs[0]);
}

Although aggregation improves performance, it is more complex to implement and can become more involved when applying sharding or parallel processing over different database nodes.

Scaling Considerations

For applications at scale, consider sharding your collection by the ‘weight’ field to distribute documents across different nodes. Additionally, caching the total weight or employing approximate algorithms can yield better performance with large datasets.

Drawbacks and Alternatives

One drawback of the weighted selection algorithm is its linearity, leading to potential performance issues. There are advanced probabilistic data structures like Alias Method that allow for O(1) selection times.

Final Thoughts

Random document selection in databases is a nuanced feature, but with the right approach, one can effectively use weighted random selection in Mongoose. It opens possibilities into making more dynamic, engaging, and varied user experiences, such as randomized content delivery, fair load distribution among varying resources or designing complex game mechanics.

Remember, the examples provided should be optimized and tested in the context of your particular use case and dataset size for best performance. As always, coding is as much about understanding your tools as applying them artfully to meet your user’s needs.

Next Article: How to use Mongoose without a schema

Previous Article: Fixing Mongoose Error: Missing ‘cursor’ Option

Series: Mongoose.js Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates