From 198fb7268306f4ae886d09b655fd43883eae0257 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 6 Feb 2025 14:09:17 -0500 Subject: [PATCH] chore(NODE-6720): migrate multibench tests (#4399) --- .evergreen/config.yml | 2 +- .evergreen/generate_evergreen_tasks.js | 2 +- test/benchmarks/driver_bench/readme.md | 65 +++++++++++++++++++ test/benchmarks/driver_bench/src/driver.mts | 7 +- ...egate_a_million_documents_and_to_array.mts | 38 +++++++++++ ...ggregate_a_million_tweets_and_to_array.mts | 41 ++++++++++++ .../multi_bench/find_many_and_to_array.mts | 24 +++++++ .../suites/multi_bench/grid_fs_download.mts | 39 +++++++++++ .../src/suites/multi_bench/grid_fs_upload.mts | 40 ++++++++++++ .../multi_bench/large_doc_bulk_insert.mts | 30 +++++++++ 10 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 test/benchmarks/driver_bench/readme.md create mode 100644 test/benchmarks/driver_bench/src/suites/multi_bench/aggregate_a_million_documents_and_to_array.mts create mode 100644 test/benchmarks/driver_bench/src/suites/multi_bench/aggregate_a_million_tweets_and_to_array.mts create mode 100644 test/benchmarks/driver_bench/src/suites/multi_bench/find_many_and_to_array.mts create mode 100644 test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_download.mts create mode 100644 test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_upload.mts create mode 100644 test/benchmarks/driver_bench/src/suites/multi_bench/large_doc_bulk_insert.mts diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 2c5f6efac9..413e833e4d 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -3323,7 +3323,7 @@ tasks: tags: - run-spec-benchmark-tests - performance - exec_timeout_secs: 3600 + exec_timeout_secs: 18000 commands: - command: expansions.update type: setup diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 943796c76d..da6be83adf 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -756,7 +756,7 @@ function addPerformanceTasks() { const makePerfTaskNEW = (name, MONGODB_CLIENT_OPTIONS) => ({ name, tags: ['run-spec-benchmark-tests', 'performance'], - exec_timeout_secs: 3600, + exec_timeout_secs: 18000, commands: [ updateExpansions({ NODE_LTS_VERSION: 'v22.11.0', diff --git a/test/benchmarks/driver_bench/readme.md b/test/benchmarks/driver_bench/readme.md new file mode 100644 index 0000000000..81f8b85974 --- /dev/null +++ b/test/benchmarks/driver_bench/readme.md @@ -0,0 +1,65 @@ +# Node.js Driver Benchmarks + +Set up the driver for development (`npm ci` in the top level of this repo). + +Then: + +```sh +npm start +``` + +will build the benchmarks and run them. + +## Environment Configuration and Setup + +The benchmarks respond to a few environment variables: + +- `MONGODB_URI` + - The connection string to run operations against. + CI uses a standalone, you should be able to launch any cluster and point the benchmarks at it via this env var. + - default: `"mongodb://localhost:27017"` +- `MONGODB_DRIVER_PATH` + - The path to the MongoDB Node.js driver. + This MUST be set to the _directory_ the driver is installed in. + **NOT** the file "lib/index.js" that is the driver's export. + - default: 4 directories above driver.mjs (should be the root of this repo) +- `MONGODB_CLIENT_OPTIONS` + - A JSON string that will be passed to the MongoClient constructor + - default: `"{}"` + +## Running individual benchmarks + +`main.mjs` loops and launches the bench runner for you. + +You can launch `runner.mjs` directly and tell it which benchmark to run. + +```sh +node lib/runner.mjs suites/multi_bench/grid_fs_upload.mjs +``` + +## Writing your own benchmark + +In the suites directory you can add a new suite folder or add a new `.mts` file to an existing one. + +A benchmark must export the following: + +```ts +type BenchmarkModule = { + taskSize: number; + before?: () => Promise; + beforeEach?: () => Promise; + run: () => Promise; + afterEach?: () => Promise; + after?: () => Promise; +}; +``` + +Just like mocha we have once before and once after as well as before each and after each hooks. + +The `driver.mts` module is intended to hold various helpers for setup and teardown and help abstract some of the driver API. + +## Wishlist + +- Make it so runner can handle: `./lib/suites/multi_bench/grid_fs_upload.mjs` as an argument so shell path autocomplete makes it easier to pick a benchmark +- Make `main.mjs` accept a filter of some kind to run some of the benchmarks +- TBD diff --git a/test/benchmarks/driver_bench/src/driver.mts b/test/benchmarks/driver_bench/src/driver.mts index 6c399ffa16..ab92576b31 100644 --- a/test/benchmarks/driver_bench/src/driver.mts +++ b/test/benchmarks/driver_bench/src/driver.mts @@ -88,8 +88,11 @@ export const MONGODB_CLIENT_OPTIONS = (() => { })(); export const MONGODB_URI = (() => { - if (process.env.MONGODB_URI?.length) return process.env.MONGODB_URI; - return 'mongodb://127.0.0.1:27017'; + const connectionString = process.env.MONGODB_URI; + if (connectionString?.length) { + return connectionString; + } + return 'mongodb://localhost:27017'; })(); export function snakeToCamel(name: string) { diff --git a/test/benchmarks/driver_bench/src/suites/multi_bench/aggregate_a_million_documents_and_to_array.mts b/test/benchmarks/driver_bench/src/suites/multi_bench/aggregate_a_million_documents_and_to_array.mts new file mode 100644 index 0000000000..a210cfccbe --- /dev/null +++ b/test/benchmarks/driver_bench/src/suites/multi_bench/aggregate_a_million_documents_and_to_array.mts @@ -0,0 +1,38 @@ +import { driver, type mongodb } from '../../driver.mjs'; + +export const taskSize = 16; + +let db: mongodb.Db; + +export async function before() { + await driver.drop(); + await driver.create(); + + db = driver.db; +} + +export async function run() { + await db + .aggregate([ + { $documents: [{}] }, + { + $set: { + field: { + $reduce: { + input: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], + initialValue: [0], + in: { $concatArrays: ['$$value', '$$value'] } + } + } + } + }, + { $unwind: '$field' }, + { $limit: 1000000 } + ]) + .toArray(); +} + +export async function after() { + await driver.drop(); + await driver.close(); +} diff --git a/test/benchmarks/driver_bench/src/suites/multi_bench/aggregate_a_million_tweets_and_to_array.mts b/test/benchmarks/driver_bench/src/suites/multi_bench/aggregate_a_million_tweets_and_to_array.mts new file mode 100644 index 0000000000..e538657f85 --- /dev/null +++ b/test/benchmarks/driver_bench/src/suites/multi_bench/aggregate_a_million_tweets_and_to_array.mts @@ -0,0 +1,41 @@ +import { driver, type mongodb } from '../../driver.mjs'; + +export const taskSize = 1500; + +let db: mongodb.Db; +let tweet: Record; + +export async function before() { + await driver.drop(); + await driver.create(); + + tweet = await driver.load('single_and_multi_document/tweet.json', 'json'); + + db = driver.db; +} + +export async function run() { + await db + .aggregate([ + { $documents: [tweet] }, + { + $set: { + field: { + $reduce: { + input: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], + initialValue: [0], + in: { $concatArrays: ['$$value', '$$value'] } + } + } + } + }, + { $unwind: '$field' }, + { $limit: 1000000 } + ]) + .toArray(); +} + +export async function after() { + await driver.drop(); + await driver.close(); +} diff --git a/test/benchmarks/driver_bench/src/suites/multi_bench/find_many_and_to_array.mts b/test/benchmarks/driver_bench/src/suites/multi_bench/find_many_and_to_array.mts new file mode 100644 index 0000000000..88201f243c --- /dev/null +++ b/test/benchmarks/driver_bench/src/suites/multi_bench/find_many_and_to_array.mts @@ -0,0 +1,24 @@ +import { driver, type mongodb } from '../../driver.mjs'; + +export const taskSize = 16.22; + +let collection: mongodb.Collection; + +export async function before() { + await driver.drop(); + await driver.create(); + + const tweet = await driver.load('single_and_multi_document/tweet.json', 'json'); + await driver.insertManyOf(tweet, 10000); + + collection = driver.collection; +} + +export async function run() { + await collection.find({}).toArray(); +} + +export async function after() { + await driver.drop(); + await driver.close(); +} diff --git a/test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_download.mts b/test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_download.mts new file mode 100644 index 0000000000..813fab89c7 --- /dev/null +++ b/test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_download.mts @@ -0,0 +1,39 @@ +import { Readable, Writable } from 'node:stream'; +import { pipeline } from 'node:stream/promises'; + +import { driver, type mongodb } from '../../driver.mjs'; + +export const taskSize = 52.43; + +let bucket: mongodb.GridFSBucket; +let bin: Uint8Array; +let _id: mongodb.ObjectId; +const devNull = () => new Writable({ write: (_, __, callback) => callback() }); + +export async function before() { + bin = await driver.load('single_and_multi_document/gridfs_large.bin', 'buffer'); + + await driver.drop(); + await driver.create(); + + bucket = driver.bucket; + + await bucket.drop().catch(() => null); + + // Create the bucket. + const stream = bucket.openUploadStream('gridfstest'); + const largeBin = Readable.from(bin); + await pipeline(largeBin, stream); + + _id = stream.id; +} + +export async function run() { + const downloadStream = bucket.openDownloadStream(_id); + await pipeline(downloadStream, devNull()); +} + +export async function after() { + await driver.drop(); + await driver.close(); +} diff --git a/test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_upload.mts b/test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_upload.mts new file mode 100644 index 0000000000..66369bb4ec --- /dev/null +++ b/test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_upload.mts @@ -0,0 +1,40 @@ +import { Readable } from 'node:stream'; +import { pipeline } from 'node:stream/promises'; + +import { driver, type mongodb } from '../../driver.mjs'; + +export const taskSize = 52.43; + +let bucket: mongodb.GridFSBucket; +let uploadStream: mongodb.GridFSBucketWriteStream; +let bin: Uint8Array; + +export async function before() { + bin = await driver.load('single_and_multi_document/gridfs_large.bin', 'buffer'); + + await driver.drop(); + await driver.create(); + + bucket = driver.bucket; + + await bucket.drop().catch(() => null); +} + +export async function beforeEach() { + uploadStream = bucket.openUploadStream('gridfstest'); + + // Create the bucket. + const stream = bucket.openUploadStream('setup-file.txt'); + const oneByteFile = Readable.from('a'); + await pipeline(oneByteFile, stream); +} + +export async function run() { + const uploadData = Readable.from(bin); + await pipeline(uploadData, uploadStream); +} + +export async function after() { + await driver.drop(); + await driver.close(); +} diff --git a/test/benchmarks/driver_bench/src/suites/multi_bench/large_doc_bulk_insert.mts b/test/benchmarks/driver_bench/src/suites/multi_bench/large_doc_bulk_insert.mts new file mode 100644 index 0000000000..7a40b40665 --- /dev/null +++ b/test/benchmarks/driver_bench/src/suites/multi_bench/large_doc_bulk_insert.mts @@ -0,0 +1,30 @@ +import { driver, type mongodb } from '../../driver.mjs'; + +export const taskSize = 27.31; + +let collection: mongodb.Collection; +let documents: any[]; +let largeDoc: any; + +export async function before() { + largeDoc = await driver.load('single_and_multi_document/large_doc.json', 'json'); +} + +export async function beforeEach() { + await driver.drop(); + await driver.create(); + + // Make new "documents" so the _id field is not carried over from the last run + documents = Array.from({ length: 10 }, () => ({ ...largeDoc })) as any[]; + + collection = driver.collection; +} + +export async function run() { + await collection.insertMany(documents, { ordered: true }); +} + +export async function after() { + await driver.drop(); + await driver.close(); +}