mongodb latency profile
MongoDB latency depends heavily on whether a query hits an index. The preset models indexed queries at p50 (8ms) with a realistic tail for collection scans, large document reads, and write concerns propagating to secondaries.
Production MongoDB shows bimodal latency: indexed queries at 2–15ms and collection scans at 100–500ms. If your local tests run against a small dataset where even unindexed queries are fast, you'll miss missing-index bugs. slowdep adds the realistic distribution that catches these.
Wrap collection methods
Wrap the specific collection methods you use. The native MongoDB driver exposes findOne, insertOne, updateOne, and friends as regular async functions.
import { MongoClient } from 'mongodb'; import { withLatency } from 'slowdep'; const client = new MongoClient(process.env.MONGODB_URI); await client.connect(); const db = client.db('myapp'); const users = db.collection('users'); // Wrap specific collection methods const slowFindOne = withLatency(users.findOne.bind(users), 'mongodb'); const slowInsertOne = withLatency(users.insertOne.bind(users), 'mongodb'); const slowUpdateOne = withLatency(users.updateOne.bind(users), 'mongodb'); // Now all reads and writes experience realistic latency const user = await slowFindOne({ email: 'alice@example.com' }); const { insertedId } = await slowInsertOne({ email: 'bob@example.com', createdAt: new Date() });
Wrap an entire collection with withLatencyAll
For comprehensive coverage, wrap the entire collection object. Every method — find, aggregate, bulkWrite, deleteOne — will experience realistic latency.
import { MongoClient, Collection } from 'mongodb'; import { withLatencyAll } from 'slowdep'; const client = new MongoClient(process.env.MONGODB_URI); await client.connect(); const orders = client.db('myapp').collection<Order>('orders'); // Wrap every async method on the collection const slowOrders = withLatencyAll(orders, 'mongodb'); // All collection methods now have realistic latency const order = await slowOrders.findOne({ _id: orderId }); const recentOrders = await slowOrders.find({ userId, status: 'pending' }).toArray(); const stats = await slowOrders.aggregate([ { $match: { status: 'completed' } }, { $group: { _id: '$userId', total: { $sum: '$amount' } } } ]).toArray();
Use with Mongoose via the native collection
Mongoose's model methods (User.findById, etc.) are chainable query builders, not plain async functions, so they can't be wrapped directly. Instead, access the underlying native driver collection.
import mongoose from 'mongoose'; import { withLatency, withLatencyAll } from 'slowdep'; await mongoose.connect(process.env.MONGODB_URI); // Access the native MongoDB collection through Mongoose's connection const nativeUsers = mongoose.connection.db.collection('users'); // Wrap the native collection — works identically to the native driver const slowUsers = withLatencyAll(nativeUsers, 'mongodb'); // Use the slow native collection for tests const user = await slowUsers.findOne({ email: 'alice@example.com' }); const result = await slowUsers.insertOne({ email: 'carol@example.com', createdAt: new Date() }); // Alternatively, wrap specific Mongoose exec() calls const slowExec = withLatency( (query: mongoose.Query<any, any>) => query.exec(), 'mongodb' ); const user2 = await slowExec(User.findById('507f1f77bcf86cd799439011'));
Mongoose model methods like User.findById() return a Query object, not a Promise. The Promise is only created when you call .exec() or await it. withLatency wraps the function call itself — wrapping User.findById would add latency before the Query is built, not before the DB round-trip. Wrap .exec() or use the native collection instead.
Jest tests for query patterns
import { withLatency } from 'slowdep'; import { getOrderById } from '../src/orders'; test('getOrderById times out on slow collection scan', async () => { // Simulate a missing index — query takes p99 time const slowFindOne = withLatency( jest.fn().mockResolvedValue({ _id: 'o1', status: 'pending' }), { p50: 150, p99: 600 } // collection scan latency ); await expect( getOrderById('o1', { findOne: slowFindOne, timeoutMs: 300 }) ).rejects.toThrow('Query timed out'); }, 5000);
import { withLatency } from 'slowdep'; test('bulk write completes within SLA even with latency', async () => { const docs = Array.from({ length: 50 }, (_, i) => ({ name: `item-${i}` })); const slowBulkWrite = withLatency( jest.fn().mockResolvedValue({ insertedCount: 50, acknowledged: true }), 'mongodb' ); const start = Date.now(); const result = await slowBulkWrite(docs); const elapsed = Date.now() - start; // Bulk write is one round-trip — latency applies once, not per document expect(result.insertedCount).toBe(50); expect(elapsed).toBeLessThan(1000); // well within SLA });
Conditional wrapping by environment
import { MongoClient, Collection } from 'mongodb'; import { withLatencyAll } from 'slowdep'; function getCollection<T>(name: string): Collection<T> { const col = client.db('myapp').collection<T>(name); return process.env.NODE_ENV === 'production' ? col : withLatencyAll(col, 'mongodb'); } export const usersCol = getCollection<User>('users'); export const ordersCol = getCollection<Order>('orders');
Scenarios this integration helps you verify
- Missing index detection — simulate 250ms p99 to reveal queries that would be fast on small local datasets but slow on large production collections
- Aggregation pipeline timeout — complex
$lookupand$groupstages can be slow; verify your service handles them within budget - Write concern latency — writes with
w: "majority"wait for replica acknowledgement; simulate the extra round-trip - Change stream lag — event-driven architectures using change streams need to account for the latency between the write and the stream event
- Cursor exhaustion — large
find().toArray()calls are expensive; realistic latency exposes code that should use pagination - Concurrent session handling — MongoDB multi-document transactions add latency; verify transaction timeout settings
Typed collection wrapper
import { MongoClient, Collection, ObjectId } from 'mongodb'; import { withLatencyAll } from 'slowdep'; interface User { _id?: ObjectId; email: string; name: string; createdAt: Date; } const users: Collection<User> = client.db('myapp').collection<User>('users'); // Collection<User> type preserved — full IntelliSense on slowUsers const slowUsers: Collection<User> = withLatencyAll(users, 'mongodb'); // Return type is inferred: Promise<User | null> const found = await slowUsers.findOne({ email: 'alice@example.com' });