This example demonstrates a load test conducted on a MongoDB Database, hosted on the Atlas managed service. It creates a temporary database and collection, and then performs insert, update, and delete queries. The frequency of each of those queries is set by the PARAMS object defined in the globalInit() stage. Each VU maintains a persistent database connection, throughout the test, by establishing the connection in vuInit() and passing it down to the later test stages. In addition to capturing the query performance metrics, it is helpful to see how the database’s CPU is affected by load. Atlas provides an API endpoint to fetch the CPU load, and this example also captures that metric.

Prerequisites

  1. Sign up for a MongoDB Atlas account
  2. Deploy a cluster
  3. Gather the following environment variables from your cluster and account:
    • DB_URI: The connection string for your cluster
    • ATLAS_GROUP_ID: The ID of your Atlas organization
    • ATLAS_PROCESS_ENDPOINT: The endpoint for a specific instance in your cluster
    • ATLAS_PUBLIC_KEY: The public key for your Atlas API key
    • ATLAS_PRIVATE_KEY: The private key for your Atlas API key

Code

Environment Variables
DB_URI=mongodb+srv://user:password@host/dbname?retryWrites=true&w=majority

ATLAS_GROUP_ID=SECRET
ATLAS_PROCESS_ENDPOINT=cluster-name-shard-00-00.xxxxx.mongodb.net:27017

ATLAS_PUBLIC_KEY=SECRET
ATLAS_PRIVATE_KEY=SECRET
Test Spec
import { MongoClient, ServerApiVersion } from 'mongodb';

import { v4 as uuid } from 'uuid';

// Atlas uses digest auth
import AxiosDigestAuth from '@mhoc/axios-digest-auth';

class MongoDBTestSpec {
  npmDeps = {
    '@mhoc/axios-digest-auth': '0.8.0',
    mongodb: '5.1.0',
    uuid: '9.0.0',
  };

  async globalInit(ctx) {
    // Set parameters for the test
    const PARAMS = {
      DB_NAME: `temp-db-${Date.now()}`,
      COLLECTION_NAME: `temp-collection-${Date.now()}`,
      INSERT_PCT: 100, // MUST BE BETWEEN 0 and 100
      UPDATE_PCT: 70, // MUST BE BETWEEN 0 and 100
      DELETE_PCT: 50, // MUST BE BETWEEN 0 and 100
    };

    // Connect to MongoDB cluster to create the database and collection
    const dbClient = new MongoClient(process.env.DB_URI, {
      serverApi: {
        version: ServerApiVersion.v1,
        strict: true,
        deprecationErrors: true,
      },
    });
    await dbClient.connect();
    const db = dbClient.db(PARAMS.DB_NAME);
    const collection = await db.createCollection(PARAMS.COLLECTION_NAME);

    // Create an index on _id
    await collection.createIndex('_id');

    await dbClient.close();

    // Print the names of the database and collection created
    console.debug(
      `${Date.now()}: Collection Created! DB=${
        collection.dbName
      } | COLLECTION=${collection.collectionName}`,
    );

    return {
      PARAMS,
    };
  }

  async vuInit(ctx) {
    // Get PARAMS from globalInit()
    const { DB_NAME, COLLECTION_NAME } = ctx.globalInitData.PARAMS;

    // Open the db connection for each VU
    const dbClient = new MongoClient(process.env.DB_URI, {
      serverApi: {
        version: ServerApiVersion.v1,
        strict: true,
        deprecationErrors: true,
      },
    });
    await dbClient.connect();
    const collection = dbClient.db(DB_NAME).collection(COLLECTION_NAME);

    // Create an Atlas API clieant
    const atlasApi = new AxiosDigestAuth.default({
      username: process.env.ATLAS_PUBLIC_KEY,
      password: process.env.ATLAS_PRIVATE_KEY,
    });

    return {
      dbClient,
      collection,
      atlasApi,
    };
  }

  async vuLoop(ctx) {
    // Get PARAMS from globalInit()
    const { INSERT_PCT, UPDATE_PCT, DELETE_PCT } = ctx.globalInitData.PARAMS;
    // Get db collection and Atlas API client from vuInit()
    const { collection, atlasApi } = ctx.vuInitData;

    // Keep track of inserted id so we can update and delete
    let insertedId = null;

    // Insert Document
    if (Math.random() * 100 <= INSERT_PCT) {
      insertedId = uuid();

      const startTime = Date.now();
      await collection.insertOne({
        _id: insertedId,
        first: `Fake First Name - ${Math.random()}`,
        last: `Fake Last Name - ${Math.random()}`,
        createdAt: startTime,
        updatedAt: startTime,
      });
      ctx.metric('Insert Time', Date.now() - startTime, 'ms (mongo)');
    }

    // Update Document
    if (Math.random() * 100 <= UPDATE_PCT && insertedId) {
      const startTime = Date.now();
      await collection.updateOne(
        { _id: insertedId },
        {
          $set: {
            last: `Updated Last Name - ${Math.random()}`,
            updatedAt: startTime,
          },
        },
      );
      ctx.metric('Update Time', Date.now() - startTime, 'ms (mongo)');
    }

    // Delete Document
    if (Math.random() * 100 <= DELETE_PCT && insertedId) {
      const startTime = Date.now();
      await collection.deleteOne({ _id: insertedId });
      ctx.metric('Delete Time', Date.now() - startTime, 'ms (mongo)');
    }

    // Only first VU gets the process CPU metric from Atlas
    if (ctx.info.vuId === 0) {
      try {
        const atlasMetricsResponse = await atlasApi.request({
          headers: { Accept: 'application/json' },
          method: 'GET',
          url: `https://cloud.mongodb.com/api/atlas/v1.0/groups/${process.env.ATLAS_GROUP_ID}/processes/${process.env.ATLAS_PROCESS_ENDPOINT}/measurements`,
          params: {
            granularity: 'PT5M',
            period: 'PT5M',
            m: 'MAX_PROCESS_NORMALIZED_CPU_USER',
          },
        });

        const cpuMetric =
          atlasMetricsResponse.data.measurements[0]?.dataPoints[0]?.value || 0;
        ctx.metric('CPU (max user normalized)', cpuMetric, '%');
      } catch {}
    }
  }

  async vuCleanup(ctx) {
    // Get the db client from vuInit() and close the connection
    const { dbClient } = ctx.vuInitData;
    await dbClient.close();
  }

  async globalCleanup(ctx) {
    // Connect to MongoDB cluster to delete the database and collection we used for the test
    const dbClient = new MongoClient(process.env.DB_URI, {
      serverApi: {
        version: ServerApiVersion.v1,
        strict: true,
        deprecationErrors: true,
      },
    });
    await dbClient.connect();

    const db = dbClient.db(ctx.globalInitData.PARAMS.DB_NAME);
    await db.dropDatabase();
    await dbClient.close();
  }
}
MongoDB Load Test Result