From 0ac779e3858182add7920d1b905cd9dd18821d3a Mon Sep 17 00:00:00 2001 From: Daniele Bissoli Date: Wed, 10 Jan 2024 16:17:58 +0100 Subject: [PATCH 01/70] perf: add initial k6 scripts for testing crud performances --- Dockerfile | 6 +- bench/.gitignore | 2 + bench/collections/users.js | 164 ++++++++++++++++++++++++++++++++ bench/data/index.js | 99 ++++++++++++++++++++ bench/data/package-lock.json | 176 +++++++++++++++++++++++++++++++++++ bench/data/package.json | 17 ++++ bench/dc-k6.yml | 16 ++++ bench/docker-compose.yml | 83 +++++++++++++++++ bench/scripts/healthcheck.js | 11 +++ bench/scripts/runner.js | 15 +++ 10 files changed, 586 insertions(+), 3 deletions(-) create mode 100644 bench/.gitignore create mode 100644 bench/collections/users.js create mode 100644 bench/data/index.js create mode 100644 bench/data/package-lock.json create mode 100644 bench/data/package.json create mode 100644 bench/dc-k6.yml create mode 100644 bench/docker-compose.yml create mode 100644 bench/scripts/healthcheck.js create mode 100644 bench/scripts/runner.js diff --git a/Dockerfile b/Dockerfile index b24f43c4..1723771c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.8.1-bullseye-slim as base-with-encryption +FROM node:20.10.0-bullseye-slim as base-with-encryption WORKDIR /cryptd @@ -10,7 +10,7 @@ RUN apt-get update && \ ######################################################################################################################## -FROM node:20.8.1-bullseye-slim as build +FROM node:20.10.0-bullseye-slim as build ARG COMMIT_SHA= ENV NODE_ENV=production @@ -30,7 +30,7 @@ RUN echo "crud-service: $COMMIT_SHA" >> ./commit.sha # create a CRUD Service image that does not support automatic CSFLE # and therefore it can be employed by everybody in any MongoDB product -FROM node:20.8.1-bullseye-slim as crud-service-no-encryption +FROM node:20.10.0-bullseye-slim as crud-service-no-encryption # note: zlib can be removed once node image version is updated RUN apt-get update \ diff --git a/bench/.gitignore b/bench/.gitignore new file mode 100644 index 00000000..f106c487 --- /dev/null +++ b/bench/.gitignore @@ -0,0 +1,2 @@ +data/dump +node_modules \ No newline at end of file diff --git a/bench/collections/users.js b/bench/collections/users.js new file mode 100644 index 00000000..a4c5dd52 --- /dev/null +++ b/bench/collections/users.js @@ -0,0 +1,164 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +module.exports = { + name: 'users', + endpointBasePath: '/users', + defaultState: 'PUBLIC', + fields: [ + { + name: '_id', + type: 'ObjectId', + required: true, + }, + { + name: 'updaterId', + type: 'string', + description: 'User id that has requested the last change successfully', + required: true, + }, + { + name: 'updatedAt', + type: 'Date', + description: 'Date of the request that has performed the last change', + required: true, + }, + { + name: 'creatorId', + type: 'string', + description: 'User id that has created this object', + required: true, + }, + { + name: 'createdAt', + type: 'Date', + description: 'Date of the request that has performed the object creation', + required: true, + }, + { + name: '__STATE__', + type: 'string', + description: 'The state of the document', + required: true, + }, + { + name: 'firstName', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'lastName', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'email', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'bio', + type: 'string', + required: false, + nullable: false, + }, + { + name: 'birthDate', + type: 'Date', + required: true, + nullable: false, + }, + { + name: 'shopID', + type: 'number', + required: true, + nullable: false, + }, + { + name: 'subscriptionNumber', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'purchases', + type: 'number', + required: false, + nullable: false, + }, + { + name: 'happy', + type: 'boolean', + required: false, + nullable: false, + }, + ], + indexes: [ + { + name: '_id', + type: 'normal', + unique: true, + fields: [ + { + name: '_id', + order: 1, + }, + ], + }, + { + name: 'createdAt', + type: 'normal', + unique: false, + fields: [ + { + name: 'createdAt', + order: -1, + }, + ], + }, + { + name: 'exportIndex', + type: 'normal', + unique: false, + fields: [ + { + name: 'shopID', + order: 1, + }, + { + name: '__STATE__', + order: 1, + }, + ], + }, + { + name: 'email', + type: 'normal', + unique: true, + fields: [ + { + name: 'email', + order: 1, + }, + ], + }, + ], +} diff --git a/bench/data/index.js b/bench/data/index.js new file mode 100644 index 00000000..d9de96ed --- /dev/null +++ b/bench/data/index.js @@ -0,0 +1,99 @@ +/* eslint-disable no-console */ +'use strict' + +const { Command } = require('commander') +const { MongoClient } = require('mongodb') +const { faker } = require('@faker-js/faker') + +const { version, description } = require('./package.json') + +async function main() { + const program = new Command() + + program.version(version) + + program + .description(description) + .requiredOption('-c, --connection-string ', 'MongoDB connection string') + .requiredOption('-d, --database ', 'MongoDB database name') + .requiredOption('-cl, --collection ', 'MongoDB collection name') + .option('-s, --shop-ids ', 'number of shop identifiers', '5') + .option('-u, --users ', 'number of users to generate and split across the different shop-ids', '500000') + .option('-b, --batch-size ', 'number of records inserted per batch', '1000') + .action(generateData) + + await program.parseAsync() +} + +async function generateData(options) { + const { + connectionString, + database, + collection, + shopIds: rawShopIds, + users: rawUsers, + batchSize: rawBatchSize, + } = options + + const shopIds = Math.max(Number.parseInt(rawShopIds), 1) + const numUsers = Math.max(Number.parseInt(rawUsers), 10) + const batchSize = Math.max(Number.parseInt(rawBatchSize), 10) + + const mongo = new MongoClient(connectionString) + await mongo.connect() + + const coll = mongo.db(database).collection(collection) + + try { + let i = numUsers + while (i > 0) { + const numberToGenerate = Math.min(batchSize, i) + + const users = [] + for (let j = 0; j < numberToGenerate; j++) { + users.push(getUser(shopIds)) + } + + // eslint-disable-next-line no-await-in-loop + await coll.insertMany(users) + + i -= numberToGenerate + process.stdout.write(`\r(${numUsers - i}/${numUsers}) ${((numUsers - i) / numUsers * 100).toFixed(2)}%`) + } + } catch (error) { + console.error(`failed to generate data: ${error}`) + } finally { + await mongo.close() + } +} + +function getUser(shopIds) { + return { + updaterId: faker.string.uuid(), + updatedAt: faker.date.recent(), + creatorId: faker.string.uuid(), + createdAt: faker.date.past(), + __STATE__: 'PUBLIC', + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + email: faker.internet.email(), + birthDate: faker.date.birthdate(), + bio: faker.hacker.phrase(), + shopID: faker.number.int({ min: 1, max: shopIds }), + subscriptionNumber: faker.finance.creditCardNumber(), + purchases: faker.number.int({ min: 1, max: 451 }), + happy: faker.datatype.boolean(0.88), + } +} + +if (require.main === module) { + main() + .then(() => { + console.info(`\n\n 🦋 records successfully created\n`) + process.exitCode = 0 + }) + .catch(error => { + console.error(`\n ❌ failed to create records ${error.message}\n`) + process.exitCode = 1 + }) +} diff --git a/bench/data/package-lock.json b/bench/data/package-lock.json new file mode 100644 index 00000000..3dd02e24 --- /dev/null +++ b/bench/data/package-lock.json @@ -0,0 +1,176 @@ +{ + "name": "data-generaotr", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "data-generaotr", + "version": "1.0.0", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@faker-js/faker": "^8.3.1", + "commander": "^11.1.0", + "mongodb": "^6.3.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.3.1.tgz", + "integrity": "sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", + "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.3.tgz", + "integrity": "sha512-z1ELvMijRL1QmU7QuzDkeYXSF2+dXI0ITKoQsIoVKcNBOiK5RMmWy+pYYxJTHFt8vkpZe7UsvRErQwcxZkjoUw==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/bson": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz", + "integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "node_modules/mongodb": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/bench/data/package.json b/bench/data/package.json new file mode 100644 index 00000000..a748efa2 --- /dev/null +++ b/bench/data/package.json @@ -0,0 +1,17 @@ +{ + "name": "data-generaotr", + "version": "1.0.0", + "description": "generate a batch of data into the specified collection", + "main": "index.js", + "scripts": { + "start": "node index.js -c mongodb://localhost:27017/bench-test -d bench-test -cl users -s 5 -u 100 -b 1000" + }, + "keywords": [], + "author": "", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@faker-js/faker": "^8.3.1", + "commander": "^11.1.0", + "mongodb": "^6.3.0" + } +} diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml new file mode 100644 index 00000000..05021897 --- /dev/null +++ b/bench/dc-k6.yml @@ -0,0 +1,16 @@ +services: + k6: + image: grafana/k6:0.48.0 + deploy: + resources: + limits: + memory: 4Gb + cpus: "2" + volumes: + - ${PWD}/scripts:/app + networks: + - k6-net + command: [ "run", "/app/runner.js" ] + +networks: + k6-net: diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml new file mode 100644 index 00000000..c110cabe --- /dev/null +++ b/bench/docker-compose.yml @@ -0,0 +1,83 @@ +services: + database: + image: mongo:6.0 + ports: + - '27017:27017' + volumes: + - mongo:/data/db + networks: + - k6-net + deploy: + resources: + limits: + memory: 4GB + cpus: "2" + healthcheck: + test: [ "CMD", "mongosh", "--eval", "db.adminCommand('ping')" ] + interval: 10s + timeout: 5s + retries: 3 + start_period: 5s + + restore-mongo: + image: mongo:6.0 + depends_on: + database: + condition: service_healthy + networks: + - k6-net + deploy: + resources: + limits: + memory: 2GB + cpus: '1' + entrypoint: + - /bin/bash + command: + - "-c" + - mongorestore --uri="mongodb://database:27017" --drop --maintainInsertionOrder --gzip + volumes: + - ${PWD}/data/dump:/dump + + crud-service: + image: crud-service:latest + build: + context: .. + dockerfile: Dockerfile + ports: + - '3000:3000' + depends_on: + database: + condition: service_healthy + restore-mongo: + condition: service_completed_successfully + networks: + - k6-net + deploy: + resources: + limits: + memory: 500Mb + cpus: "2" + environment: + # Crud Service + LOG_LEVEL: info + COLLECTION_DEFINITION_FOLDER: /home/node/app/collections + USER_ID_HEADER_KEY: userid + CRUD_LIMIT_CONSTRAINT_ENABLED: true + CRUD_MAX_LIMIT: 200 + MONGODB_URL: "mongodb://database:27017/bench-test" + volumes: + - ${PWD}/collections:/home/node/app/collections + - ${PWD}/scripts/healthcheck.js:/home/node/app/healthcheck/healthcheck.js + healthcheck: + test: [ "CMD-SHELL", "node /home/node/app/healthcheck/healthcheck.js" ] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + +networks: + k6-net: + +volumes: + mongo: diff --git a/bench/scripts/healthcheck.js b/bench/scripts/healthcheck.js new file mode 100644 index 00000000..0f65ae01 --- /dev/null +++ b/bench/scripts/healthcheck.js @@ -0,0 +1,11 @@ +'use strict' + +async function main() { + try { + await fetch('http://localhost:3000/-/healthz') + } catch (error) { + process.exitCode = 1 + } +} + +main() diff --git a/bench/scripts/runner.js b/bench/scripts/runner.js new file mode 100644 index 00000000..824a3e1c --- /dev/null +++ b/bench/scripts/runner.js @@ -0,0 +1,15 @@ +import http from 'k6/http'; +import { sleep } from 'k6'; + +export const options = { + stages: [ + { duration: '30s', target: 5 }, + { duration: '2m', target: 10 }, + { duration: '30s', target: 0 }, + ], +} + +export default function () { + http.get('http://crud-service:3000/users/export?shopID=2') + sleep(1) +} \ No newline at end of file From dc4fc71c8dd5559012cb6f65f754c175a9bb6df6 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Fri, 12 Jan 2024 15:25:13 +0100 Subject: [PATCH 02/70] wip: updateds on k6 tests --- bench/collections/customers.js | 245 +++++++++++++++++++++++++ bench/collections/users.js | 22 +-- bench/data/index.js | 12 +- bench/data/regenerate.js | 144 +++++++++++++++ bench/dc-k6.yml | 22 ++- bench/docker-compose.yml | 6 +- bench/scripts/customers/smoke-test.js | 92 ++++++++++ bench/scripts/customers/spike-test.js | 82 +++++++++ bench/scripts/customers/stress-test.js | 83 +++++++++ 9 files changed, 686 insertions(+), 22 deletions(-) create mode 100644 bench/collections/customers.js create mode 100644 bench/data/regenerate.js create mode 100644 bench/scripts/customers/smoke-test.js create mode 100644 bench/scripts/customers/spike-test.js create mode 100644 bench/scripts/customers/stress-test.js diff --git a/bench/collections/customers.js b/bench/collections/customers.js new file mode 100644 index 00000000..61eedb21 --- /dev/null +++ b/bench/collections/customers.js @@ -0,0 +1,245 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +module.exports = { + name: 'customers', + endpointBasePath: '/customers', + defaultState: 'PUBLIC', + fields: [ + { + name: '_id', + type: 'ObjectId', + required: true, + }, + { + name: 'updaterId', + type: 'string', + description: 'User id that has requested the last change successfully', + required: true, + }, + { + name: 'updatedAt', + type: 'Date', + description: 'Date of the request that has performed the last change', + required: true, + }, + { + name: 'creatorId', + type: 'string', + description: 'User id that has created this object', + required: true, + }, + { + name: 'createdAt', + type: 'Date', + description: 'Date of the request that has performed the object creation', + required: true, + }, + { + name: '__STATE__', + type: 'string', + description: 'The state of the document', + required: true, + }, + { + name: 'customerId', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'firstName', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'lastName', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'gender', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'birthDate', + type: 'Date', + required: true, + nullable: false, + }, + { + name: 'creditCardDetail', + type: 'RawObject', + required: true, + nullable: false, + schema: { + properties: { + name: { type: 'string' }, + cardNo: { type: 'number' }, + expirationDate: { type: 'string' }, + cvv: { type: 'string' }, + }, + required: ['name', 'cardNo', 'expirationDate', 'cardCode'], + }, + }, + { + name: 'canBeContacted', + type: 'boolean', + required: false, + nullable: false, + }, + { + name: 'email', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'phoneNumber', + type: 'string', + required: false, + nullable: false, + }, + { + name: 'address', + type: 'RawObject', + required: false, + nullable: true, + schema: { + properties: { + line: { type: 'string' }, + city: { type: 'string' }, + county: { type: 'string' }, + country: { type: 'string' }, + }, + required: ['line', 'city', 'county', 'country'], + }, + }, + { + name: 'socialNetworkProfiles', + type: 'RawObject', + required: false, + nullable: true, + schema: { + properties: { + 'twitter': { type: 'string' }, + 'instagram': { type: 'string' }, + 'facebook': { type: 'string' }, + 'threads': { type: 'string' }, + 'reddit': { type: 'string' }, + 'linkedin': { type: 'string' }, + 'tiktok': { type: 'string' }, + }, + }, + }, + { + name: 'subscriptionNumber', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'shopID', + type: 'number', + required: true, + nullable: false, + }, + { + name: 'purchasesCount', + type: 'number', + required: true, + nullable: false, + }, + { + name: 'purchases', + type: 'Array', + items: { + type: 'RawObject', + schema: { + properties: { + name: { type: 'string' }, + category: { type: 'string' }, + price: { type: 'number' }, + employeeId: { type: 'string' }, + boughtOnline: { type: 'boolean' }, + }, + }, + }, + }, + { + name: 'details', + type: 'string', + required: false, + nullable: false, + }, + ], + indexes: [ + { + name: '_id', + type: 'normal', + unique: true, + fields: [ + { + name: '_id', + order: 1, + }, + ], + }, + { + name: 'createdAt', + type: 'normal', + unique: false, + fields: [ + { + name: 'createdAt', + order: -1, + }, + ], + }, + { + name: 'exportIndex', + type: 'normal', + unique: false, + fields: [ + { + name: 'shopID', + order: 1, + }, + { + name: '__STATE__', + order: 1, + }, + ], + }, + { + name: 'customerId', + type: 'normal', + unique: true, + fields: [ + { + name: 'customerId', + order: 1, + }, + ], + }, + ], +} diff --git a/bench/collections/users.js b/bench/collections/users.js index a4c5dd52..7f7736f2 100644 --- a/bench/collections/users.js +++ b/bench/collections/users.js @@ -149,16 +149,16 @@ module.exports = { }, ], }, - { - name: 'email', - type: 'normal', - unique: true, - fields: [ - { - name: 'email', - order: 1, - }, - ], - }, + // { + // name: 'email', + // type: 'normal', + // unique: true, + // fields: [ + // { + // name: 'email', + // order: 1, + // }, + // ], + // }, ], } diff --git a/bench/data/index.js b/bench/data/index.js index d9de96ed..df0db68e 100644 --- a/bench/data/index.js +++ b/bench/data/index.js @@ -68,15 +68,21 @@ async function generateData(options) { } function getUser(shopIds) { + const firstName = faker.person.firstName() + const lastName = faker.person.lastName() + // email is generated manually to ensure unicity + const email = `${firstName}.${lastName}.${faker.number.int({ max: 99 })}@email.com` + + return { updaterId: faker.string.uuid(), updatedAt: faker.date.recent(), creatorId: faker.string.uuid(), createdAt: faker.date.past(), __STATE__: 'PUBLIC', - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - email: faker.internet.email(), + firstName, + lastName, + email, birthDate: faker.date.birthdate(), bio: faker.hacker.phrase(), shopID: faker.number.int({ min: 1, max: shopIds }), diff --git a/bench/data/regenerate.js b/bench/data/regenerate.js new file mode 100644 index 00000000..88da9551 --- /dev/null +++ b/bench/data/regenerate.js @@ -0,0 +1,144 @@ +/* eslint-disable no-console */ +'use strict' + +const { Command } = require('commander') +const { MongoClient } = require('mongodb') +const { faker } = require('@faker-js/faker') + +const { version, description } = require('./package.json') + +function generateCustomer({ index, shopCount }) { + const firstName = faker.person.firstName() + const lastName = faker.person.lastName() + + const fullNameCode = `${firstName}.${lastName}` + // email is generated manually to ensure unicity + const email = `${fullNameCode}.${faker.number.int({ max: 99 })}@email.com` + + const creditCardDetail = { + name: `${firstName} ${lastName}`, + cardNo: faker.finance.creditCardNumber(), + expirationDate: `${faker.number.int({ min: 1, max: 12 })}/${faker.number.int({ min: 2024, max: 2031 })}`, + cvv: faker.finance.creditCardCVV, + } + + const address = { + line: faker.location.street(), + city: faker.location.city(), + county: faker.location.county(), + country: faker.location.country(), + } + + const socialNetworkProfiles = { + twitter: `http://www.xcom/${fullNameCode}`, + instagram: `http://www.instagram.com/${fullNameCode}`, + facebook: `http://www.facebook.com/${fullNameCode}`, + threads: `http://www.threads.com/@${fullNameCode}`, + reddit: `http://www.reddit.com/u/${fullNameCode}`, + linkedin: `http://www.linked.in/${fullNameCode}`, + tiktok: `http://www.tiktok.co/${fullNameCode}`, + } + + const purchasesCount = faker.number.int({ min: 1, max: 51 }) + const purchases = [] + for (let i = 0; i < purchasesCount; i++) { + purchases.push({ + name: faker.commerce.productName(), + category: faker.commerce.department(), + price: faker.commerce.price({ min: '1', symbol: '$' }), + employeeId: faker.number.int({ min: 1, max: shopCount * 10 }), + boughtOnline: faker.datatype.boolean(0.2), + }) + } + + return { + updaterId: faker.string.uuid(), + updatedAt: faker.date.recent(), + creatorId: faker.string.uuid(), + createdAt: faker.date.past(), + __STATE__: 'PUBLIC', + customerId: index, + firstName, + lastName, + gender: faker.person.gender(), + birthDate: faker.date.birthdate(), + creditCardDetail, + canBeContacted: faker.datatype.boolean(0.9), + email, + phoneNumber: faker.phone.number(), + address, + socialNetworkProfiles, + subscriptionNumber: faker.finance.creditCardNumber(), + shopID: faker.number.int({ min: 1, max: shopCount }), + purchasesCount, + purchases, + detail: faker.hacker.phrase(), + } +} + +async function main() { + const program = new Command() + + program.version(version) + + program + .description(description) + .requiredOption('-c, --connection-string ', 'MongoDB connection string') + .requiredOption('-d, --database ', 'MongoDB database name') + .action(generateData) + + await program.parseAsync() +} + +async function generateData(options) { + const { + connectionString, + database, + } = options + // #region constants + const customerCollectionName = 'customers' + const shopCount = 250 + const customerCount = 85000 + const customerBatchSize = 8500 + // #endregion + + const mongo = new MongoClient(connectionString) + await mongo.connect() + + const coll = mongo.db(database).collection(customerCollectionName) + + try { + let i = customerCount + while (i > 0) { + process.stdout.write(`\rStarting the creation of documents for collection "customers".`) + const numberToGenerate = Math.min(customerBatchSize, i) + + const users = [] + for (let j = 0; j < numberToGenerate; j++) { + users.push(generateCustomer({ index: j + i, shopCount })) + } + + // eslint-disable-next-line no-await-in-loop + await coll.insertMany(users) + + i -= numberToGenerate + process.stdout.write(`\r(${customerCount - i}/${customerCount}) ${((customerCount - i) / customerCount * 100).toFixed(2)}%`) + } + } catch (error) { + console.error(`failed to generate data: ${error}`) + } finally { + await mongo.close() + } +} + +if (require.main === module) { + main() + .then(() => { + console.info(`\n\n 🦋 records successfully created\n`) + process.exitCode = 0 + }) + .catch(error => { + console.error(`\n ❌ failed to create records ${error.message}\n`) + process.exitCode = 1 + }) +} diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml index 05021897..093d7ecc 100644 --- a/bench/dc-k6.yml +++ b/bench/dc-k6.yml @@ -1,16 +1,28 @@ services: - k6: + k6-smoke-test: image: grafana/k6:0.48.0 deploy: resources: limits: - memory: 4Gb - cpus: "2" + memory: 512Mb + cpus: "1" volumes: - - ${PWD}/scripts:/app + - ./scripts:/app networks: - k6-net - command: [ "run", "/app/runner.js" ] + command: [ "run", "/app/customers/spike-test.js" ] + # k6-stress-test: + # image: grafana/k6:0.48.0 + # deploy: + # resources: + # limits: + # memory: 512Mb + # cpus: "1" + # volumes: + # - ./scripts:/app + # networks: + # - k6-net + # command: [ "run", "/app/customers/stress-test.js" ] networks: k6-net: diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index c110cabe..aaca79c5 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -37,7 +37,7 @@ services: - "-c" - mongorestore --uri="mongodb://database:27017" --drop --maintainInsertionOrder --gzip volumes: - - ${PWD}/data/dump:/dump + - ./data/dump:/dump crud-service: image: crud-service:latest @@ -67,8 +67,8 @@ services: CRUD_MAX_LIMIT: 200 MONGODB_URL: "mongodb://database:27017/bench-test" volumes: - - ${PWD}/collections:/home/node/app/collections - - ${PWD}/scripts/healthcheck.js:/home/node/app/healthcheck/healthcheck.js + - ./collections:/home/node/app/collections + - ./scripts/healthcheck.js:/home/node/app/healthcheck/healthcheck.js healthcheck: test: [ "CMD-SHELL", "node /home/node/app/healthcheck/healthcheck.js" ] interval: 10s diff --git a/bench/scripts/customers/smoke-test.js b/bench/scripts/customers/smoke-test.js new file mode 100644 index 00000000..6a1e33f5 --- /dev/null +++ b/bench/scripts/customers/smoke-test.js @@ -0,0 +1,92 @@ +import http from 'k6/http'; +import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; +import { check, group, sleep } from 'k6'; + + +export const options = { + vus: 5, + iterations: 25, + duration: '2m', + thresholds: { + checks: ['rate==1'], // every check must pass + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + http_req_duration: ['p(90)<125'], // 90% of requests should be below 250ms, 95% below 500ms + }, +} + +export function setup() { + // TODO: +} + +export default function () { + // #region helper fns + const is200 = r => r.status === 200 + //#endregion + + group('GET methods', () => { + // GET / request + const get = http.get('http://crud-service:3000/customers?shopID=2') + check(get, { 'GET / returns status 200': is200 }) + sleep(1) + + // GET /_q=... request + const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) + const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(1) + + // GET /count request + const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true') + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(1) + + // GET /export request + const getExport = http.get('http://crud-service:3000/customers/export?shopID=2') + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(1) + }) + +} + +export function handleSummary(data) { + // console.log({ data: JSON.stringify(data, null, 2)}) + return { + stdout: textSummary(data, { enableColors: true }), + // TODO: "Permission denied" when trying to save to file. How to fix this? + // '/app/smoke-test-results.json': JSON.stringify(data) + }; + } + +export function teardown(data) { + // TODO +} + +// bench-k6-1 | █ GET methods +// bench-k6-1 | +// bench-k6-1 | ✓ GET / returns status 200 +// bench-k6-1 | ✓ GET /?_q=... returns status 200 +// bench-k6-1 | ✓ GET /count returns status 200 +// bench-k6-1 | ✓ GET /export returns status 200 +// bench-k6-1 | +// bench-k6-1 | █ teardown +// bench-k6-1 | +// bench-k6-1 | ✓ checks.........................: 100.00% ✓ 100 ✗ 0 +// bench-k6-1 | data_received..................: 18 MB 856 kB/s +// bench-k6-1 | data_sent......................: 11 kB 529 B/s +// bench-k6-1 | group_duration.................: avg=4.25s min=4.15s med=4.22s max=4.46s p(90)=4.39s p(95)=4.43s +// bench-k6-1 | http_req_blocked...............: avg=26.78µs min=2.68µs med=5.72µs max=441.73µs p(90)=9.22µs p(95)=39.07µs +// bench-k6-1 | http_req_connecting............: avg=5.75µs min=0s med=0s max=148.6µs p(90)=0s p(95)=4.54µs +// bench-k6-1 | ✓ http_req_duration..............: avg=63.53ms min=2.93ms med=58.95ms max=317.12ms p(90)=97.44ms p(95)=212.9ms +// bench-k6-1 | { expected_response:true }...: avg=63.53ms min=2.93ms med=58.95ms max=317.12ms p(90)=97.44ms p(95)=212.9ms +// bench-k6-1 | ✓ http_req_failed................: 0.00% ✓ 0 ✗ 100 +// bench-k6-1 | http_req_receiving.............: avg=6.21ms min=27.46µs med=178.72µs max=43.2ms p(90)=22.46ms p(95)=27.49ms +// bench-k6-1 | http_req_sending...............: avg=25.21µs min=9.72µs med=23.84µs max=73.14µs p(90)=36.86µs p(95)=39.35µs +// bench-k6-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s +// bench-k6-1 | http_req_waiting...............: avg=57.29ms min=2.19ms med=50.74ms max=316.99ms p(90)=97.33ms p(95)=212.81ms +// bench-k6-1 | http_reqs......................: 100 4.654264/s +// bench-k6-1 | iteration_duration.............: avg=3.94s min=1.56µs med=4.21s max=4.46s p(90)=4.39s p(95)=4.43s +// bench-k6-1 | iterations.....................: 25 1.163566/s +// bench-k6-1 | vus............................: 5 min=5 max=5 +// bench-k6-1 | vus_max........................: 5 min=5 max=5 +// bench-k6-1 | running (0m21.5s), 0/5 VUs, 25 complete and 0 interrupted iterations +// bench-k6-1 | default ✓ [ 100% ] 5 VUs 0m21.5s/2m0s 25/25 shared iters diff --git a/bench/scripts/customers/spike-test.js b/bench/scripts/customers/spike-test.js new file mode 100644 index 00000000..be491999 --- /dev/null +++ b/bench/scripts/customers/spike-test.js @@ -0,0 +1,82 @@ +import http from 'k6/http'; +import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; +import { check, group, sleep } from 'k6'; + + +export const options = { + stages: [ + { duration: '5s', target: 5 }, // base level, 5 users + { duration: '20s', target: 500 }, // traffic ramp-up from 5 to a higher 500 users over 20 seconds + { duration: '20s', target: 5 }, // back down to 5 users in 20s + ], + thresholds: { + checks: ['rate==1'], // every check must pass + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + // http_req_duration: ['p(95)<100'], // 95% of requests should be below 50ms + } +} + +export default function () { + // #region helper fns + const is200 = r => r.status === 200 + //#endregion + + group('GET methods', () => { + // GET / request + const get = http.get('http://crud-service:3000/customers?shopID=2') + check(get, { 'GET / returns status 200': is200 }) + sleep(1) + + // GET /_q=... request + const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) + const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(1) + + // GET /count request + const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true') + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(1) + + // GET /export request + const getExport = http.get('http://crud-service:3000/customers/export?shopID=2') + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(1) + }) +} + +export function handleSummary(data) { + return { + stdout: textSummary(data, { enableColors: true }), + }; + } + +// bench-k6-1 | █ GET methods +// bench-k6-1 | +// bench-k6-1 | ✓ GET / returns status 200 +// bench-k6-1 | ✓ GET /?_q=... returns status 200 +// bench-k6-1 | ✓ GET /count returns status 200 +// bench-k6-1 | ✓ GET /export returns status 200 +// bench-k6-1 | +// bench-k6-1 | ✓ checks.........................: 100.00% ✓ 876 ✗ 0 +// bench-k6-1 | data_received..................: 38 MB 503 kB/s +// bench-k6-1 | data_sent......................: 156 kB 2.1 kB/s +// bench-k6-1 | group_duration.................: avg=31.05s min=4.19s med=34.85s max=1m8s p(90)=1m0s p(95)=1m4s +// bench-k6-1 | http_req_blocked...............: avg=325.64µs min=1.6µs med=152.88µs max=32.59ms p(90)=477.81µs p(95)=562.96µs +// bench-k6-1 | http_req_connecting............: avg=253.44µs min=0s med=108.03µs max=32.45ms p(90)=338.39µs p(95)=402.68µs +// bench-k6-1 | http_req_duration..............: avg=13.49s min=3.15ms med=8.39s max=50.62s p(90)=33.21s p(95)=38s +// bench-k6-1 | { expected_response:true }...: avg=13.49s min=3.15ms med=8.39s max=50.62s p(90)=33.21s p(95)=38s +// bench-k6-1 | ✓ http_req_failed................: 0.00% ✓ 0 ✗ 876 +// bench-k6-1 | http_req_receiving.............: avg=257.1ms min=16.1µs med=332.81µs max=36.69s p(90)=1.77ms p(95)=3.41ms +// bench-k6-1 | http_req_sending...............: avg=56.71µs min=6.05µs med=47.38µs max=524.89µs p(90)=104.83µs p(95)=123.63µs +// bench-k6-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s +// bench-k6-1 | http_req_waiting...............: avg=13.23s min=2.75ms med=8.34s max=49.76s p(90)=33.07s p(95)=37.94s +// bench-k6-1 | http_reqs......................: 876 11.679636/s +// bench-k6-1 | iteration_duration.............: avg=31.06s min=4.19s med=34.85s max=1m8s p(90)=1m0s p(95)=1m4s +// bench-k6-1 | iterations.....................: 14 0.186661/s +// bench-k6-1 | vus............................: 9 min=1 max=500 +// bench-k6-1 | vus_max........................: 500 min=500 max=500 +// bench-k6-1 | running (1m15.0s), 000/500 VUs, 14 complete and 494 interrupted iterations +// bench-k6-1 | default ✓ [ 100% ] 001/500 VUs 45s + + \ No newline at end of file diff --git a/bench/scripts/customers/stress-test.js b/bench/scripts/customers/stress-test.js new file mode 100644 index 00000000..f7cfdf38 --- /dev/null +++ b/bench/scripts/customers/stress-test.js @@ -0,0 +1,83 @@ +import http from 'k6/http'; +import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; +import { check, group, sleep } from 'k6'; + + +export const options = { + stages: [ + { duration: '5s', target: 5 }, // base level, 5 users + { duration: '10s', target: 200 }, // traffic ramp-up from 5 to a higher 200 users over 10 seconds + { duration: '45s', target: 200 }, // stay at higher 200 users for 30 seconds + { duration: '30s', target: 5 }, // ramp-down to 5 users + ], + thresholds: { + checks: ['rate==1'], // every check must pass + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + // http_req_duration: ['p(95)<100'], // 95% of requests should be below 50ms + } +} + +export default function () { + // #region helper fns + const is200 = r => r.status === 200 + //#endregion + + group('GET methods', () => { + // GET / request + const get = http.get('http://crud-service:3000/customers?shopID=2') + check(get, { 'GET / returns status 200': is200 }) + sleep(1) + + // GET /_q=... request + const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) + const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(1) + + // GET /count request + const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true') + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(1) + + // GET /export request + const getExport = http.get('http://crud-service:3000/customers/export?shopID=2') + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(1) + }) + +} + +export function handleSummary(data) { + return { + stdout: textSummary(data, { enableColors: true }), + }; + } + +// bench-k6-1 | █ GET methods +// bench-k6-1 | +// bench-k6-1 | ✓ GET / returns status 200 +// bench-k6-1 | ✓ GET /?_q=... returns status 200 +// bench-k6-1 | ✓ GET /count returns status 200 +// bench-k6-1 | ✓ GET /export returns status 200 +// bench-k6-1 | +// bench-k6-1 | ✓ checks.........................: 100.00% ✓ 1565 ✗ 0 +// bench-k6-1 | data_received..................: 275 MB 2.7 MB/s +// bench-k6-1 | data_sent......................: 182 kB 1.8 kB/s +// bench-k6-1 | group_duration.................: avg=41.88s min=4.18s med=43.79s max=1m5s p(90)=47.93s p(95)=50.12s +// bench-k6-1 | http_req_blocked...............: avg=89.56µs min=946ns med=7.14µs max=35.15ms p(90)=228.48µs p(95)=395.63µs +// bench-k6-1 | http_req_connecting............: avg=70.79µs min=0s med=0s max=34.97ms p(90)=162.61µs p(95)=279.92µs +// bench-k6-1 | http_req_duration..............: avg=9.61s min=2.8ms med=8.34s max=30.19s p(90)=21.42s p(95)=23.46s +// bench-k6-1 | { expected_response:true }...: avg=9.61s min=2.8ms med=8.34s max=30.19s p(90)=21.42s p(95)=23.46s +// bench-k6-1 | ✓ http_req_failed................: 0.00% ✓ 0 ✗ 1565 +// bench-k6-1 | http_req_receiving.............: avg=543.79ms min=9.41µs med=193.43µs max=12.03s p(90)=1.02s p(95)=4.71s +// bench-k6-1 | http_req_sending...............: avg=33.32µs min=3.1µs med=28.15µs max=312.75µs p(90)=65.42µs p(95)=86.11µs +// bench-k6-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s +// bench-k6-1 | http_req_waiting...............: avg=9.07s min=2.37ms med=7.15s max=30.19s p(90)=21.42s p(95)=23.46s +// bench-k6-1 | http_reqs......................: 1565 15.578937/s +// bench-k6-1 | iteration_duration.............: avg=41.88s min=4.18s med=43.79s max=1m5s p(90)=47.93s p(95)=50.12s +// bench-k6-1 | iterations.....................: 365 3.633426/s +// bench-k6-1 | vus............................: 2 min=1 max=200 +// bench-k6-1 | vus_max........................: 200 min=200 max=200 +// bench-k6-1 | running (1m40.5s), 000/200 VUs, 365 complete and 45 interrupted iterations +// bench-k6-1 | default ✓ [ 100% ] 000/200 VUs 1m30s + \ No newline at end of file From 3d8565d93fcf8f36c6db0f568767dca5318a1d05 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 16 Jan 2024 12:17:44 +0100 Subject: [PATCH 03/70] feat: view registered-customers --- bench/collections/registered-customers.js | 120 ++++++++++++++++++++++ bench/docker-compose.yml | 2 + bench/views/registered-customers.js | 42 ++++++++ 3 files changed, 164 insertions(+) create mode 100644 bench/collections/registered-customers.js create mode 100644 bench/views/registered-customers.js diff --git a/bench/collections/registered-customers.js b/bench/collections/registered-customers.js new file mode 100644 index 00000000..43d13301 --- /dev/null +++ b/bench/collections/registered-customers.js @@ -0,0 +1,120 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +module.exports = { + name: 'registered-customers', + endpointBasePath: '/registered-customers', + defaultState: 'PUBLIC', + fields: [ + { + name: '_id', + type: 'ObjectId', + required: true, + }, + { + name: 'updaterId', + type: 'string', + description: 'User id that has requested the last change successfully', + required: true, + }, + { + name: 'updatedAt', + type: 'Date', + description: 'Date of the request that has performed the last change', + required: true, + }, + { + name: 'creatorId', + type: 'string', + description: 'User id that has created this object', + required: true, + }, + { + name: 'createdAt', + type: 'Date', + description: 'Date of the request that has performed the object creation', + required: true, + }, + { + name: '__STATE__', + type: 'string', + description: 'The state of the document', + required: true, + }, + { + name: 'customerId', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'firstName', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'lastName', + type: 'string', + required: true, + nullable: false, + }, + { + name: 'shopID', + type: 'number', + required: true, + nullable: false, + }, + { + name: 'canBeContacted', + type: 'boolean', + required: false, + nullable: false, + }, + { + name: 'purchasesCount', + type: 'number', + required: true, + nullable: false, + }, + ], + indexes: [ + { + name: '_id', + type: 'normal', + unique: true, + fields: [ + { + name: '_id', + order: 1, + }, + ], + }, + { + name: 'customerId', + type: 'normal', + unique: true, + fields: [ + { + name: 'customerId', + order: 1, + }, + ], + }, + ], +} diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index aaca79c5..90a6a599 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -62,12 +62,14 @@ services: # Crud Service LOG_LEVEL: info COLLECTION_DEFINITION_FOLDER: /home/node/app/collections + VIEWS_DEFINITION_FOLDER: /home/node/app/views USER_ID_HEADER_KEY: userid CRUD_LIMIT_CONSTRAINT_ENABLED: true CRUD_MAX_LIMIT: 200 MONGODB_URL: "mongodb://database:27017/bench-test" volumes: - ./collections:/home/node/app/collections + - ./views:/home/node/app/views - ./scripts/healthcheck.js:/home/node/app/healthcheck/healthcheck.js healthcheck: test: [ "CMD-SHELL", "node /home/node/app/healthcheck/healthcheck.js" ] diff --git a/bench/views/registered-customers.js b/bench/views/registered-customers.js new file mode 100644 index 00000000..02dfb393 --- /dev/null +++ b/bench/views/registered-customers.js @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +module.exports = { + name: 'registered-customers', + source: 'customers', + type: 'view', + pipeline: [ + { + $project: { + _id: 1, + name: 1, + updaterId: 1, + updatedAt: 1, + creatorId: 1, + createdAt: 1, + customerId: 1, + firstName: 1, + lastName: 1, + shopID: 1, + canBeContacted: 1, + purchasesCount: 1, + __STATE__: 1, + }, + }, + ], +} From ebd32c120862b785259fc7dd2b5ae7e7beb18857 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 16 Jan 2024 18:18:58 +0100 Subject: [PATCH 04/70] refactor: regenerate.js --- bench/data/regenerate.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bench/data/regenerate.js b/bench/data/regenerate.js index 88da9551..ec35ff4d 100644 --- a/bench/data/regenerate.js +++ b/bench/data/regenerate.js @@ -7,7 +7,7 @@ const { faker } = require('@faker-js/faker') const { version, description } = require('./package.json') -function generateCustomer({ index, shopCount }) { +function generateCustomers({ index, shopCount }) { const firstName = faker.person.firstName() const lastName = faker.person.lastName() @@ -76,20 +76,6 @@ function generateCustomer({ index, shopCount }) { } } -async function main() { - const program = new Command() - - program.version(version) - - program - .description(description) - .requiredOption('-c, --connection-string ', 'MongoDB connection string') - .requiredOption('-d, --database ', 'MongoDB database name') - .action(generateData) - - await program.parseAsync() -} - async function generateData(options) { const { connectionString, @@ -115,7 +101,7 @@ async function generateData(options) { const users = [] for (let j = 0; j < numberToGenerate; j++) { - users.push(generateCustomer({ index: j + i, shopCount })) + users.push(generateCustomers({ index: j + i, shopCount })) } // eslint-disable-next-line no-await-in-loop @@ -131,6 +117,20 @@ async function generateData(options) { } } +async function main() { + const program = new Command() + + program.version(version) + + program + .description(description) + .requiredOption('-c, --connection-string ', 'MongoDB connection string') + .requiredOption('-d, --database ', 'MongoDB database name') + .action(generateData) + + await program.parseAsync() +} + if (require.main === module) { main() .then(() => { From 8f9dff90c724b2557db60118ec73b83db97ead3c Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 16 Jan 2024 18:19:09 +0100 Subject: [PATCH 05/70] feat: items collection, update users --- bench/collections/items.js | 147 +++++++++++++++++++++++++++++++++++++ bench/collections/users.js | 22 +++--- 2 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 bench/collections/items.js diff --git a/bench/collections/items.js b/bench/collections/items.js new file mode 100644 index 00000000..5ad73f6b --- /dev/null +++ b/bench/collections/items.js @@ -0,0 +1,147 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +module.exports = { + name: 'items', + endpointBasePath: '/items', + defaultState: 'PUBLIC', + fields: [ + { + name: '_id', + type: 'ObjectId', + required: true, + }, + { + name: 'updaterId', + type: 'string', + description: 'User id that has requested the last change successfully', + required: true, + }, + { + name: 'updatedAt', + type: 'Date', + description: 'Date of the request that has performed the last change', + required: true, + }, + { + name: 'creatorId', + type: 'string', + description: 'User id that has created this object', + required: true, + }, + { + name: 'createdAt', + type: 'Date', + description: 'Date of the request that has performed the object creation', + required: true, + }, + { + name: '__STATE__', + type: 'string', + description: 'The state of the document', + required: true, + }, + { + name: 'string', + type: 'string', + required: false, + nullable: true, + }, + { + name: 'number', + type: 'number', + required: false, + nullable: true, + }, + { + name: 'boolean', + type: 'boolean', + required: false, + nullable: true, + }, + { + name: 'date', + type: 'Date', + required: false, + nullable: true, + }, + { + name: 'object', + type: 'RawObject', + required: false, + nullable: true, + schema: { + properties: { + string: { type: 'string' }, + number: { type: 'number' }, + boolean: { type: 'boolean' }, + counter: { type: 'number' }, + }, + }, + }, + { + name: 'array', + type: 'Array', + items: { + type: 'RawObject', + schema: { + properties: { + string: { type: 'string' }, + number: { type: 'number' }, + boolean: { type: 'boolean' }, + }, + }, + }, + }, + ], + indexes: [ + { + name: '_id', + type: 'normal', + unique: true, + fields: [ + { + name: '_id', + order: 1, + }, + ], + }, + { + name: 'createdAt', + type: 'normal', + unique: false, + fields: [ + { + name: 'createdAt', + order: -1, + }, + ], + }, + { + name: 'stringIndex', + type: 'normal', + unique: false, + fields: [ + { + name: 'string', + order: 1, + }, + ], + }, + ], +} diff --git a/bench/collections/users.js b/bench/collections/users.js index 7f7736f2..a4c5dd52 100644 --- a/bench/collections/users.js +++ b/bench/collections/users.js @@ -149,16 +149,16 @@ module.exports = { }, ], }, - // { - // name: 'email', - // type: 'normal', - // unique: true, - // fields: [ - // { - // name: 'email', - // order: 1, - // }, - // ], - // }, + { + name: 'email', + type: 'normal', + unique: true, + fields: [ + { + name: 'email', + order: 1, + }, + ], + }, ], } From b47a48c4a1209ee245ef34391a3a98c490db0535 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 16 Jan 2024 18:24:56 +0100 Subject: [PATCH 06/70] feat: runners --- bench/scripts/customers/smoke-test.js | 13 +- bench/scripts/customers/spike-test.js | 8 + bench/scripts/customers/stress-test.js | 9 + bench/scripts/items/load-test.js | 193 ++++++++++++++++++ .../registered-customers/stress-test.js | 92 +++++++++ 5 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 bench/scripts/items/load-test.js create mode 100644 bench/scripts/registered-customers/stress-test.js diff --git a/bench/scripts/customers/smoke-test.js b/bench/scripts/customers/smoke-test.js index 6a1e33f5..698fac7c 100644 --- a/bench/scripts/customers/smoke-test.js +++ b/bench/scripts/customers/smoke-test.js @@ -2,16 +2,21 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { check, group, sleep } from 'k6'; +// +// Test on collection "customers" +// Type of test: smoke test +// +// 5 concurrent users for 1 minutes +// export const options = { vus: 5, - iterations: 25, - duration: '2m', + duration: '1m', thresholds: { checks: ['rate==1'], // every check must pass http_req_failed: ['rate<0.01'], // http errors should be less than 1% - http_req_duration: ['p(90)<125'], // 90% of requests should be below 250ms, 95% below 500ms - }, + http_req_duration: ['p(90)<150', 'p(95)<300'], // 90% of requests should be below 150ms, 95% below 300ms + } } export function setup() { diff --git a/bench/scripts/customers/spike-test.js b/bench/scripts/customers/spike-test.js index be491999..294416fd 100644 --- a/bench/scripts/customers/spike-test.js +++ b/bench/scripts/customers/spike-test.js @@ -2,6 +2,14 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { check, group, sleep } from 'k6'; +// +// Test on collection "customers" +// Type of test: spike test +// +// 5 concurrent users for the first 5 seconds +// Then number of users rise to 500 in a 20-seconds span +// Before to cool down to 5 users in 20 seconds to conclude the test +// export const options = { stages: [ diff --git a/bench/scripts/customers/stress-test.js b/bench/scripts/customers/stress-test.js index f7cfdf38..d575cfe0 100644 --- a/bench/scripts/customers/stress-test.js +++ b/bench/scripts/customers/stress-test.js @@ -2,6 +2,15 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { check, group, sleep } from 'k6'; +// +// Test on collection "customers" +// Type of test: stress test +// +// 5 concurrent users for the first 5 seconds +// Then number of users rise to 200 in a 10-seconds span +// It stays high for 45 seconds +// Then it goes back to 5 users in 30 seconds to conclude the test +// export const options = { stages: [ diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js new file mode 100644 index 00000000..9dac0106 --- /dev/null +++ b/bench/scripts/items/load-test.js @@ -0,0 +1,193 @@ +import http from 'k6/http'; +import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; +import { check, group, sleep } from 'k6'; + +import { + randomIntBetween, + randomString, + } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; + +// +// Test on collection "items" +// Type of test: load test +// +// We have a first stage where we insert documents for 1 minute +// When we're done, then we execute a round GET (list), GET by _q, PATCH by _q and DELETE by _q +// The following for 100 VUs for a minute +// + +export const options = { + // discardResponseBodies: true, + scenarios: { + 'initialLoad': { + executor: 'constant-vus', + exec: 'initialLoad', + vus: 10, + duration: '1m', + tags: { test_type: 'initialLoad' } + }, + 'loadTest': { + executor: 'constant-vus', + exec: 'loadTest', + vus: 100, + startTime: '1m', + duration: '1m', + tags: { test_type: 'loadTest' } + } + }, + thresholds: { + checks: ['rate==1'], // every check must pass + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], // 90% of requests should be below 250ms, 95% below 500ms + 'http_req_duration{test_type:loadTest}': ['p(90)<200'], // 90% of requests should be below 250ms, 95% below 500ms + 'http_req_duration{verb:GET}': ['p(90)<500'], // 90% of requests should be below 250ms, 95% below 500ms + }, +} + +// #region helper fns +const is200 = r => r.status === 200 + +let counter = 0 +let idToSearchCounter = 250 +let idToPatchCounter = 750 +let idToDeleteCounter = 1250 + +const generateItem = () => { + const array = [] + const len = randomIntBetween(0, 10) + for (let i = 0; i < len; i++) { + array.push({ + string: `array-${i}-${randomString(5)}`, + number: randomIntBetween(1, 10), + boolean: randomIntBetween(0, 1) === 1, + }) + } + + counter += 1 + + return { + string: randomString(5), + number: randomIntBetween(1, 10), + boolean: randomIntBetween(0, 1) === 1, + date: new Date(randomIntBetween(0, 1705414020030)), + object: { + string: `object-${randomString(5)}`, + number: randomIntBetween(1, 10), + boolean: randomIntBetween(0, 1) === 1, + counter + }, + array + } +} +//#endregion + +export function initialLoad () { + let post = http.post( + 'http://crud-service:3000/items', + JSON.stringify(generateItem()), + { headers: { 'Content-Type': 'application/json' } } + ); + check(post, { 'POST / returns status 200': is200 }) + + sleep(1) +} + +export function loadTest () { + group('GET requests', () => { + // GET / request + const get = http.get(`http://crud-service:3000/items?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) + check(get, { 'GET / returns status 200': is200 }) + + const _q = JSON.stringify({ 'object.counter': idToSearchCounter }) + const getById = http.get(`http://crud-service:3000/items/?_q=${_q}`, { tags: { verb: 'GET' }}) + check(getById, { 'GET /{id} returns status 200': is200 }) + + sleep(1) + idToSearchCounter += 1 + // // GET /_q=... request + // const _q = JSON.stringify({ 'object.number': { $gte: randomIntBetween(1, 10) }}) + // const getWithQuery = http.get(`http://crud-service:3000/items/?_q=${_q}`, { tags: { verb: 'GET' }}) + // check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + + // // GET /count request + // const getCount = http.get(`http://crud-service:3000/items/count?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) + // check(getCount, { 'GET /count returns status 200': is200 }) + + // // GET /export request + // const getExport = http.get(`http://crud-service:3000/items/export?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) + // check(getExport, { 'GET /export returns status 200': is200 }) + + // sleep(1) + }) + + group('PATCH requests', () => { + const _q = JSON.stringify({ 'object.counter': idToPatchCounter }) + + // PATCH / request + const patch = http.patch( + `http://crud-service:3000/items/?_q=${_q}`, + JSON.stringify(generateItem()), + { + headers: { 'Content-Type': 'application/json' }, + tags: { verb: 'PATCH' } + } + ); + check(patch, { 'PATCH / returns status 200': is200 }) + + sleep(1) + idToPatchCounter += 1 + }) + + group('DELETE requests', () => { + const _q = JSON.stringify({ 'object.counter': idToDeleteCounter }) + + // DELETE / request + const deleteReq = http.del(`http://crud-service:3000/items/?_q=${_q}`, null, { tags: { verb: 'DELETE' }}) + check(deleteReq, { 'DELETE / returns status 200': is200 }) + + sleep(1) + idToDeleteCounter += 1 + }) +} + +export function handleSummary(data) { + return { + stdout: textSummary(data, { enableColors: true }), + // TODO: "Permission denied" when trying to save to file. How to fix this? + // '/app/smoke-test-results.json': JSON.stringify(data) + }; +} + +// bench-k6-load-test-1 | initialLoad ✓ [ 100% ] 10 VUs 20s +// bench-k6-load-test-1 | loadTest ↓ [ 100% ] 100 VUs 1m0s +// bench-k6-load-test-1 | ✗ POST / returns status 200 +// bench-k6-load-test-1 | ↳ 9% — ✓ 6147 / ✗ 61975 +// bench-k6-load-test-1 | ✓ GET / returns status 200 +// bench-k6-load-test-1 | ✓ GET /?_q=... returns status 200 +// bench-k6-load-test-1 | ✓ GET /count returns status 200 +// bench-k6-load-test-1 | ✓ GET /export returns status 200 +// bench-k6-load-test-1 | +// bench-k6-load-test-1 | ✗ checks.........................: 15.87% ✓ 11699 ✗ 61975 +// bench-k6-load-test-1 | data_received..................: 345 MB 4.1 MB/s +// bench-k6-load-test-1 | data_sent......................: 22 MB 264 kB/s +// bench-k6-load-test-1 | http_req_blocked...............: avg=8.75µs min=531ns med=1.15µs max=60.34ms p(90)=3.59µs p(95)=5.6µs +// bench-k6-load-test-1 | http_req_connecting............: avg=6.4µs min=0s med=0s max=59.98ms p(90)=0s p(95)=0s +// bench-k6-load-test-1 | http_req_duration..............: avg=12.21ms min=212.77µs med=1.98ms max=1.67s p(90)=6.14ms p(95)=15.37ms +// bench-k6-load-test-1 | { expected_response:true }...: avg=63.72ms min=885.44µs med=5.76ms max=1.67s p(90)=186.8ms p(95)=385.07ms +// bench-k6-load-test-1 | ✓ { test_type:initialLoad }....: avg=2.81ms min=212.77µs med=1.89ms max=60.03ms p(90)=4.78ms p(95)=6.32ms +// bench-k6-load-test-1 | ✗ { test_type:loadTest }.......: avg=127.57ms min=885.44µs med=24.53ms max=1.67s p(90)=400.25ms p(95)=608.73ms +// bench-k6-load-test-1 | ✗ http_req_failed................: 84.12% ✓ 61975 ✗ 11699 +// bench-k6-load-test-1 | http_req_receiving.............: avg=2.86ms min=5.51µs med=15.97µs max=1.38s p(90)=48.76µs p(95)=88.73µs +// bench-k6-load-test-1 | http_req_sending...............: avg=12.08µs min=3.22µs med=7.58µs max=11.12ms p(90)=17.17µs p(95)=24.43µs +// bench-k6-load-test-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s +// bench-k6-load-test-1 | http_req_waiting...............: avg=9.33ms min=193.11µs med=1.95ms max=1.48s p(90)=6.04ms p(95)=13.08ms +// bench-k6-load-test-1 | http_reqs......................: 73674 875.496253/s +// bench-k6-load-test-1 | iteration_duration.............: avg=93ms min=310.4µs med=2.02ms max=5.89s p(90)=5.35ms p(95)=7.9ms +// bench-k6-load-test-1 | iterations.....................: 69510 826.013852/s +// bench-k6-load-test-1 | vus............................: 8 min=8 max=100 +// bench-k6-load-test-1 | vus_max........................: 110 min=110 max=110 +// bench-k6-load-test-1 | running (1m24.2s), 000/110 VUs, 69510 complete and 0 interrupted iterations +// bench-k6-load-test-1 | initialLoad ✓ [ 100% ] 10 VUs 20s +// bench-k6-load-test-1 | loadTest ✓ [ 100% ] 100 VUs 1m0s +// bench-k6-load-test-1 | time="2024-01-16T15:08:18Z" level=error msg="thresholds on metrics 'checks, http_req_duration{test_type:loadTest}, http_req_failed' have been crossed" + diff --git a/bench/scripts/registered-customers/stress-test.js b/bench/scripts/registered-customers/stress-test.js new file mode 100644 index 00000000..dc37e17e --- /dev/null +++ b/bench/scripts/registered-customers/stress-test.js @@ -0,0 +1,92 @@ +import http from 'k6/http'; +import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; +import { check, group, sleep } from 'k6'; + +// +// Test on collection "customers" +// Type of test: stress test +// +// 5 concurrent users for the first 5 seconds +// Then number of users rise to 200 in a 10-seconds span +// It stays high for 45 seconds +// Then it goes back to 5 users in 30 seconds to conclude the test +// + +export const options = { + stages: [ + { duration: '5s', target: 5 }, // base level, 5 users + { duration: '10s', target: 200 }, // traffic ramp-up from 5 to a higher 200 users over 10 seconds + { duration: '45s', target: 200 }, // stay at higher 200 users for 30 seconds + { duration: '30s', target: 5 }, // ramp-down to 5 users + ], + thresholds: { + checks: ['rate==1'], // every check must pass + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + // http_req_duration: ['p(95)<100'], // 95% of requests should be below 50ms + } +} + +export default function () { + // #region helper fns + const is200 = r => r.status === 200 + //#endregion + + group('GET methods', () => { + // GET / request + const get = http.get('http://crud-service:3000/registered-customers?shopID=2') + check(get, { 'GET / returns status 200': is200 }) + sleep(1) + + // GET /_q=... request + const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) + const getWithQuery = http.get(`http://crud-service:3000/registered-customers/?_q=${_q}`) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(1) + + // GET /count request + const getCount = http.get('http://crud-service:3000/registered-customers/count?canBeContacted=true') + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(1) + + // GET /export request + const getExport = http.get('http://crud-service:3000/registered-customers/export?shopID=2') + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(1) + }) + +} + +export function handleSummary(data) { + return { + stdout: textSummary(data, { enableColors: true }), + }; + } + +// bench-k6-stress-test-1 | █ GET methods +// bench-k6-stress-test-1 | +// bench-k6-stress-test-1 | ✓ GET / returns status 200 +// bench-k6-stress-test-1 | ✓ GET /?_q=... returns status 200 +// bench-k6-stress-test-1 | ✓ GET /count returns status 200 +// bench-k6-stress-test-1 | ✓ GET /export returns status 200 +// bench-k6-stress-test-1 | +// bench-k6-stress-test-1 | ✓ checks.........................: 100.00% ✓ 1384 ✗ 0 +// bench-k6-stress-test-1 | data_received..................: 40 MB 379 kB/s +// bench-k6-stress-test-1 | data_sent......................: 175 kB 1.7 kB/s +// bench-k6-stress-test-1 | group_duration.................: avg=48.09s min=4.16s med=49.49s max=1m16s p(90)=1m4s p(95)=1m5s +// bench-k6-stress-test-1 | http_req_blocked...............: avg=93.14µs min=2.08µs med=8.7µs max=23.86ms p(90)=326.77µs p(95)=437.9µs +// bench-k6-stress-test-1 | http_req_connecting............: avg=65.97µs min=0s med=0s max=23.8ms p(90)=226.75µs p(95)=315.6µs +// bench-k6-stress-test-1 | http_req_duration..............: avg=11.05s min=1.95ms med=10.7s max=31.7s p(90)=23.27s p(95)=25.31s +// bench-k6-stress-test-1 | { expected_response:true }...: avg=11.05s min=1.95ms med=10.7s max=31.7s p(90)=23.27s p(95)=25.31s +// bench-k6-stress-test-1 | ✓ http_req_failed................: 0.00% ✓ 0 ✗ 1384 +// bench-k6-stress-test-1 | http_req_receiving.............: avg=688.45ms min=23.07µs med=139.52µs max=11.61s p(90)=2.39s p(95)=5s +// bench-k6-stress-test-1 | http_req_sending...............: avg=39.57µs min=6.08µs med=34.25µs max=258.58µs p(90)=66.86µs p(95)=90.71µs +// bench-k6-stress-test-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s +// bench-k6-stress-test-1 | http_req_waiting...............: avg=10.36s min=1.88ms med=8.05s max=31.7s p(90)=23.27s p(95)=25.31s +// bench-k6-stress-test-1 | http_reqs......................: 1384 13.097311/s +// bench-k6-stress-test-1 | iteration_duration.............: avg=48.09s min=4.16s med=49.49s max=1m16s p(90)=1m4s p(95)=1m5s +// bench-k6-stress-test-1 | iterations.....................: 329 3.11345/s +// bench-k6-stress-test-1 | vus............................: 9 min=1 max=200 +// bench-k6-stress-test-1 | vus_max........................: 200 min=200 max=200 +// bench-k6-stress-test-1 | running (1m45.7s), 000/200 VUs, 329 complete and 27 interrupted iterations +// bench-k6-stress-test-1 | default ✓ [ 100% ] 000/200 VUs 1m30s + \ No newline at end of file From cf5dc3ad6124eb25940b82e5658e63cccf4219c8 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 11:47:03 +0100 Subject: [PATCH 07/70] removed comments --- bench/scripts/customers/smoke-test.js | 6 ++--- bench/scripts/customers/spike-test.js | 12 ++++----- bench/scripts/customers/stress-test.js | 14 +++++----- bench/scripts/items/load-test.js | 26 +++++-------------- .../registered-customers/stress-test.js | 14 +++++----- 5 files changed, 30 insertions(+), 42 deletions(-) diff --git a/bench/scripts/customers/smoke-test.js b/bench/scripts/customers/smoke-test.js index 698fac7c..bf7d279c 100644 --- a/bench/scripts/customers/smoke-test.js +++ b/bench/scripts/customers/smoke-test.js @@ -13,9 +13,9 @@ export const options = { vus: 5, duration: '1m', thresholds: { - checks: ['rate==1'], // every check must pass - http_req_failed: ['rate<0.01'], // http errors should be less than 1% - http_req_duration: ['p(90)<150', 'p(95)<300'], // 90% of requests should be below 150ms, 95% below 300ms + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + http_req_duration: ['p(90)<150', 'p(95)<300'], } } diff --git a/bench/scripts/customers/spike-test.js b/bench/scripts/customers/spike-test.js index 294416fd..f7e8ef78 100644 --- a/bench/scripts/customers/spike-test.js +++ b/bench/scripts/customers/spike-test.js @@ -13,14 +13,14 @@ import { check, group, sleep } from 'k6'; export const options = { stages: [ - { duration: '5s', target: 5 }, // base level, 5 users - { duration: '20s', target: 500 }, // traffic ramp-up from 5 to a higher 500 users over 20 seconds - { duration: '20s', target: 5 }, // back down to 5 users in 20s + { duration: '5s', target: 5 }, + { duration: '20s', target: 500 }, + { duration: '20s', target: 5 }, ], thresholds: { - checks: ['rate==1'], // every check must pass - http_req_failed: ['rate<0.01'], // http errors should be less than 1% - // http_req_duration: ['p(95)<100'], // 95% of requests should be below 50ms + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + http_req_duration: ['p(95)<250'], } } diff --git a/bench/scripts/customers/stress-test.js b/bench/scripts/customers/stress-test.js index d575cfe0..3d1579bb 100644 --- a/bench/scripts/customers/stress-test.js +++ b/bench/scripts/customers/stress-test.js @@ -14,15 +14,15 @@ import { check, group, sleep } from 'k6'; export const options = { stages: [ - { duration: '5s', target: 5 }, // base level, 5 users - { duration: '10s', target: 200 }, // traffic ramp-up from 5 to a higher 200 users over 10 seconds - { duration: '45s', target: 200 }, // stay at higher 200 users for 30 seconds - { duration: '30s', target: 5 }, // ramp-down to 5 users + { duration: '5s', target: 5 }, + { duration: '10s', target: 200 }, + { duration: '45s', target: 200 }, + { duration: '30s', target: 5 }, ], thresholds: { - checks: ['rate==1'], // every check must pass - http_req_failed: ['rate<0.01'], // http errors should be less than 1% - // http_req_duration: ['p(95)<100'], // 95% of requests should be below 50ms + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + http_req_duration: ['p(95)<250'], } } diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index 9dac0106..e1c4e877 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -17,6 +17,7 @@ import { // export const options = { + // TODO: Should I keep it? // discardResponseBodies: true, scenarios: { 'initialLoad': { @@ -36,11 +37,11 @@ export const options = { } }, thresholds: { - checks: ['rate==1'], // every check must pass - http_req_failed: ['rate<0.01'], // http errors should be less than 1% - 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], // 90% of requests should be below 250ms, 95% below 500ms - 'http_req_duration{test_type:loadTest}': ['p(90)<200'], // 90% of requests should be below 250ms, 95% below 500ms - 'http_req_duration{verb:GET}': ['p(90)<500'], // 90% of requests should be below 250ms, 95% below 500ms + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], + 'http_req_duration{test_type:loadTest}': ['p(90)<200'], + 'http_req_duration{verb:GET}': ['p(90)<500'], }, } @@ -93,6 +94,7 @@ export function initialLoad () { } export function loadTest () { + // TODO: Should I put everything in the same request so I can group('GET requests', () => { // GET / request const get = http.get(`http://crud-service:3000/items?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) @@ -104,20 +106,6 @@ export function loadTest () { sleep(1) idToSearchCounter += 1 - // // GET /_q=... request - // const _q = JSON.stringify({ 'object.number': { $gte: randomIntBetween(1, 10) }}) - // const getWithQuery = http.get(`http://crud-service:3000/items/?_q=${_q}`, { tags: { verb: 'GET' }}) - // check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - - // // GET /count request - // const getCount = http.get(`http://crud-service:3000/items/count?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) - // check(getCount, { 'GET /count returns status 200': is200 }) - - // // GET /export request - // const getExport = http.get(`http://crud-service:3000/items/export?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) - // check(getExport, { 'GET /export returns status 200': is200 }) - - // sleep(1) }) group('PATCH requests', () => { diff --git a/bench/scripts/registered-customers/stress-test.js b/bench/scripts/registered-customers/stress-test.js index dc37e17e..9dd19ee0 100644 --- a/bench/scripts/registered-customers/stress-test.js +++ b/bench/scripts/registered-customers/stress-test.js @@ -14,15 +14,15 @@ import { check, group, sleep } from 'k6'; export const options = { stages: [ - { duration: '5s', target: 5 }, // base level, 5 users - { duration: '10s', target: 200 }, // traffic ramp-up from 5 to a higher 200 users over 10 seconds - { duration: '45s', target: 200 }, // stay at higher 200 users for 30 seconds - { duration: '30s', target: 5 }, // ramp-down to 5 users + { duration: '5s', target: 5 }, + { duration: '10s', target: 200 }, + { duration: '45s', target: 200 }, + { duration: '30s', target: 5 }, ], thresholds: { - checks: ['rate==1'], // every check must pass - http_req_failed: ['rate<0.01'], // http errors should be less than 1% - // http_req_duration: ['p(95)<100'], // 95% of requests should be below 50ms + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + http_req_duration: ['p(95)<250'], } } From 13f2ed4a9a444566660f591dcdadee67598ba659 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 11:47:45 +0100 Subject: [PATCH 08/70] cleared up dc-k6.yml --- bench/dc-k6.yml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml index 093d7ecc..f682729e 100644 --- a/bench/dc-k6.yml +++ b/bench/dc-k6.yml @@ -1,5 +1,5 @@ services: - k6-smoke-test: + k6-load-test: image: grafana/k6:0.48.0 deploy: resources: @@ -10,19 +10,7 @@ services: - ./scripts:/app networks: - k6-net - command: [ "run", "/app/customers/spike-test.js" ] - # k6-stress-test: - # image: grafana/k6:0.48.0 - # deploy: - # resources: - # limits: - # memory: 512Mb - # cpus: "1" - # volumes: - # - ./scripts:/app - # networks: - # - k6-net - # command: [ "run", "/app/customers/stress-test.js" ] + command: [ "run", "/app/items/load-test.js" ] networks: k6-net: From 0866a40404f3a0853165ecef3653b0702f25580d Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 11:52:26 +0100 Subject: [PATCH 09/70] feat: perf-test github action --- .github/workflows/main.yml | 5 ++++ .github/workflows/perf-test.yml | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .github/workflows/perf-test.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 610a0060..45319de7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,3 +65,8 @@ jobs: version: main secrets: security_checks_token: ${{ secrets.CRUD_SERVICE_SYSDIG_CHECK_TRIGGER }} + + perf-test: + needs: + - checks + uses: ./.github/workflows/perf-test.yml diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml new file mode 100644 index 00000000..bfdec75a --- /dev/null +++ b/.github/workflows/perf-test.yml @@ -0,0 +1,44 @@ +name: 'Performance Test' + +on: + workflow_call: + inputs: + node-version: + default: 20.x + required: false + type: string + # TODO: Update this when we're ready to go - it should happen at every tag or something + push: + branches: + - 'feat/perf-test' + + +jobs: + k6_ci_test: + name: k6 CI Test run + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: start MongoDB instance + uses: supercharge/mongodb-github-action@v1.10.0 + with: + mongodb-version: "7.0" + + - name: 'start CRUD Service instance' + run: npm ci && npm run start + env: + LOG_LEVEL: info + COLLECTION_DEFINITION_FOLDER: ./bench/collections + VIEWS_DEFINITION_FOLDER: /bench/views + USER_ID_HEADER_KEY: userid + CRUD_LIMIT_CONSTRAINT_ENABLED: true + CRUD_MAX_LIMIT: 200 + MONGODB_URL: "mongodb://database:27017/bench-test" + + - name: Run k6 test + uses: grafana/k6-action@v0.3.1 + with: + filename: bench/scripts/items/load-test.js From d2ea5056a5a0d3f5e7810ef0cf85405bd7a1af35 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 12:00:28 +0100 Subject: [PATCH 10/70] feat: add commands to perf-test --- .github/workflows/perf-test.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index bfdec75a..82c1ba95 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -20,15 +20,24 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: start MongoDB instance uses: supercharge/mongodb-github-action@v1.10.0 with: mongodb-version: "7.0" - - - name: 'start CRUD Service instance' - run: npm ci && npm run start + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install + run: npm ci + + - name: 'Start CRUD Service instance' + run: npm run start env: LOG_LEVEL: info COLLECTION_DEFINITION_FOLDER: ./bench/collections From a059cb90d9b3bed520c537a3f8e3f8ed9d0547a4 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 12:29:01 +0100 Subject: [PATCH 11/70] test: execute pwd for testing reasons --- .github/workflows/perf-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 82c1ba95..1334e139 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -36,6 +36,9 @@ jobs: - name: Install run: npm ci + - name: Print "pwd" + run: pwd + - name: 'Start CRUD Service instance' run: npm run start env: From 9a74d3e1bca57ea30df5f4a636f5900e00e7798c Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 12:40:37 +0100 Subject: [PATCH 12/70] feat(perf-test): use docker to create mongo, crud instances --- .github/workflows/perf-test.yml | 29 ++----------------------- bench/docker-compose.yml | 38 ++++++++++++++++----------------- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 1334e139..bbb5a4f7 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -22,33 +22,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: start MongoDB instance - uses: supercharge/mongodb-github-action@v1.10.0 - with: - mongodb-version: "7.0" - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.x - cache: npm - - - name: Install - run: npm ci - - - name: Print "pwd" - run: pwd - - - name: 'Start CRUD Service instance' - run: npm run start - env: - LOG_LEVEL: info - COLLECTION_DEFINITION_FOLDER: ./bench/collections - VIEWS_DEFINITION_FOLDER: /bench/views - USER_ID_HEADER_KEY: userid - CRUD_LIMIT_CONSTRAINT_ENABLED: true - CRUD_MAX_LIMIT: 200 - MONGODB_URL: "mongodb://database:27017/bench-test" + - name: start MongoDB and CRUD Service via docker file + run: docker compose up --file bench/docker-compose.yml up -d --force-recreate - name: Run k6 test uses: grafana/k6-action@v0.3.1 diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index 90a6a599..adecacbe 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -19,25 +19,25 @@ services: retries: 3 start_period: 5s - restore-mongo: - image: mongo:6.0 - depends_on: - database: - condition: service_healthy - networks: - - k6-net - deploy: - resources: - limits: - memory: 2GB - cpus: '1' - entrypoint: - - /bin/bash - command: - - "-c" - - mongorestore --uri="mongodb://database:27017" --drop --maintainInsertionOrder --gzip - volumes: - - ./data/dump:/dump + # restore-mongo: + # image: mongo:6.0 + # depends_on: + # database: + # condition: service_healthy + # networks: + # - k6-net + # deploy: + # resources: + # limits: + # memory: 2GB + # cpus: '1' + # entrypoint: + # - /bin/bash + # command: + # - "-c" + # - mongorestore --uri="mongodb://database:27017" --drop --maintainInsertionOrder --gzip + # volumes: + # - ./data/dump:/dump crud-service: image: crud-service:latest From 6a3be86c27d690cab5872153116b70b4347c8e99 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 12:42:10 +0100 Subject: [PATCH 13/70] fix: typo --- .github/workflows/perf-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index bbb5a4f7..d6249559 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v4 - name: start MongoDB and CRUD Service via docker file - run: docker compose up --file bench/docker-compose.yml up -d --force-recreate + run: docker compose --file bench/docker-compose.yml up -d --force-recreate - name: Run k6 test uses: grafana/k6-action@v0.3.1 From 55172745886ab98d25ac07e142b9a55e0a3e0ac8 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 12:43:59 +0100 Subject: [PATCH 14/70] fix: uncomment lines on docker-compose.yml --- bench/docker-compose.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index adecacbe..90a6a599 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -19,25 +19,25 @@ services: retries: 3 start_period: 5s - # restore-mongo: - # image: mongo:6.0 - # depends_on: - # database: - # condition: service_healthy - # networks: - # - k6-net - # deploy: - # resources: - # limits: - # memory: 2GB - # cpus: '1' - # entrypoint: - # - /bin/bash - # command: - # - "-c" - # - mongorestore --uri="mongodb://database:27017" --drop --maintainInsertionOrder --gzip - # volumes: - # - ./data/dump:/dump + restore-mongo: + image: mongo:6.0 + depends_on: + database: + condition: service_healthy + networks: + - k6-net + deploy: + resources: + limits: + memory: 2GB + cpus: '1' + entrypoint: + - /bin/bash + command: + - "-c" + - mongorestore --uri="mongodb://database:27017" --drop --maintainInsertionOrder --gzip + volumes: + - ./data/dump:/dump crud-service: image: crud-service:latest From 4d2b6876a0513e716341154d14246b615817b632 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 13:33:51 +0100 Subject: [PATCH 15/70] feat: trying to save artifact --- .github/workflows/perf-test.yml | 36 +++++++++++++++++++++++++++++++- bench/scripts/items/load-test.js | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index d6249559..18a78cd4 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -21,11 +21,45 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - + - name: start MongoDB and CRUD Service via docker file run: docker compose --file bench/docker-compose.yml up -d --force-recreate + + # - name: start MongoDB instance + # uses: supercharge/mongodb-github-action@v1.10.0 + # with: + # mongodb-version: "7.0" + + # - name: Use Node.js + # uses: actions/setup-node@v4 + # with: + # node-version: 20.x + # cache: npm + + # - name: Install + # run: npm ci + + # - name: 'Start CRUD Service instance' + # run: npm run start + # env: + # LOG_LEVEL: info + # COLLECTION_DEFINITION_FOLDER: ./bench/collections + # VIEWS_DEFINITION_FOLDER: /bench/views + # USER_ID_HEADER_KEY: userid + # CRUD_LIMIT_CONSTRAINT_ENABLED: true + # CRUD_MAX_LIMIT: 200 + # MONGODB_URL: "mongodb://database:27017/bench-test" - name: Run k6 test uses: grafana/k6-action@v0.3.1 with: filename: bench/scripts/items/load-test.js + flags: --out json=load-test-results.json + + - name: Upload performance test results + uses: actions/upload-artifact@v3 + with: + name: load-test-report + path: load-test-results.json + + diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index e1c4e877..dc18403f 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -142,7 +142,7 @@ export function handleSummary(data) { return { stdout: textSummary(data, { enableColors: true }), // TODO: "Permission denied" when trying to save to file. How to fix this? - // '/app/smoke-test-results.json': JSON.stringify(data) + // '/app/load-test-results.json': JSON.stringify(data) }; } From 28eaaa0f8d65c3bbe419c8bda6ed65fa6494a39f Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 13:43:43 +0100 Subject: [PATCH 16/70] test: remove threshold --- bench/scripts/items/load-test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index dc18403f..c62a7eeb 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -36,13 +36,13 @@ export const options = { tags: { test_type: 'loadTest' } } }, - thresholds: { - checks: ['rate==1'], - http_req_failed: ['rate<0.01'], - 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], - 'http_req_duration{test_type:loadTest}': ['p(90)<200'], - 'http_req_duration{verb:GET}': ['p(90)<500'], - }, + // thresholds: { + // checks: ['rate==1'], + // http_req_failed: ['rate<0.01'], + // 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], + // 'http_req_duration{test_type:loadTest}': ['p(90)<200'], + // 'http_req_duration{verb:GET}': ['p(90)<500'], + // }, } // #region helper fns From ef496e94416bc9f98bf206e049f51e0594760454 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 14:33:18 +0100 Subject: [PATCH 17/70] refactor: change regenerate script name to generate-customer-data --- .../{regenerate.js => generate-customer-data.js} | 12 +++++++----- bench/data/package.json | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) rename bench/data/{regenerate.js => generate-customer-data.js} (88%) diff --git a/bench/data/regenerate.js b/bench/data/generate-customer-data.js similarity index 88% rename from bench/data/regenerate.js rename to bench/data/generate-customer-data.js index ec35ff4d..e8991f20 100644 --- a/bench/data/regenerate.js +++ b/bench/data/generate-customer-data.js @@ -80,12 +80,12 @@ async function generateData(options) { const { connectionString, database, + numDocumentsToCreate = 100000, + shopCount = 250 } = options // #region constants const customerCollectionName = 'customers' - const shopCount = 250 - const customerCount = 85000 - const customerBatchSize = 8500 + const customerBatchSize = numDocumentsToCreate / 10 // #endregion const mongo = new MongoClient(connectionString) @@ -94,7 +94,7 @@ async function generateData(options) { const coll = mongo.db(database).collection(customerCollectionName) try { - let i = customerCount + let i = numDocumentsToCreate while (i > 0) { process.stdout.write(`\rStarting the creation of documents for collection "customers".`) const numberToGenerate = Math.min(customerBatchSize, i) @@ -108,7 +108,7 @@ async function generateData(options) { await coll.insertMany(users) i -= numberToGenerate - process.stdout.write(`\r(${customerCount - i}/${customerCount}) ${((customerCount - i) / customerCount * 100).toFixed(2)}%`) + process.stdout.write(`\r(${numDocumentsToCreate - i}/${numDocumentsToCreate}) ${((numDocumentsToCreate - i) / numDocumentsToCreate * 100).toFixed(2)}%`) } } catch (error) { console.error(`failed to generate data: ${error}`) @@ -126,6 +126,8 @@ async function main() { .description(description) .requiredOption('-c, --connection-string ', 'MongoDB connection string') .requiredOption('-d, --database ', 'MongoDB database name') + .requiredOption('-n, --number ', 'Number of documents to generate') + .requiredOption('-s, --v ', 'Number of shops to be used inside the "shopID" field inside each database document') .action(generateData) await program.parseAsync() diff --git a/bench/data/package.json b/bench/data/package.json index a748efa2..e24b3e37 100644 --- a/bench/data/package.json +++ b/bench/data/package.json @@ -4,7 +4,8 @@ "description": "generate a batch of data into the specified collection", "main": "index.js", "scripts": { - "start": "node index.js -c mongodb://localhost:27017/bench-test -d bench-test -cl users -s 5 -u 100 -b 1000" + "start": "node index.js -c mongodb://localhost:27017/bench-test -d bench-test -cl users -s 5 -u 100 -b 1000", + "start:customer": "node customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test" }, "keywords": [], "author": "", From aa2aeac6388a829a7da6b39fdfd5bf2585ddcca0 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 14:34:54 +0100 Subject: [PATCH 18/70] test: tentative with multiple k6 tests --- .github/workflows/perf-test.yml | 32 +++++++++++++++---- bench/scripts/customers/spike-test.js | 11 ++++--- bench/scripts/items/load-test.js | 1 + .../registered-customers/stress-test.js | 11 ++++--- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 18a78cd4..2fccf9cd 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -22,8 +22,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 + # - name: start MongoDB instance + # uses: supercharge/mongodb-github-action@v1.10.0 + # with: + # mongodb-version: "7.0" + - name: start MongoDB and CRUD Service via docker file run: docker compose --file bench/docker-compose.yml up -d --force-recreate + + - name: Populate Customer collection and Registered Customer View + run: cd bench/data && npm i && node customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 # - name: start MongoDB instance # uses: supercharge/mongodb-github-action@v1.10.0 @@ -50,16 +58,28 @@ jobs: # CRUD_MAX_LIMIT: 200 # MONGODB_URL: "mongodb://database:27017/bench-test" - - name: Run k6 test + - name: Run k6 load test (collection Item) uses: grafana/k6-action@v0.3.1 with: filename: bench/scripts/items/load-test.js - flags: --out json=load-test-results.json + # flags: --out json=load-test-results.json - - name: Upload performance test results - uses: actions/upload-artifact@v3 + - name: Run k6 spike test (collection Customers) + uses: grafana/k6-action@v0.3.1 + with: + filename: bench/scripts/customers/spike-test.js + # flags: --out json=spike-test-results.json + + - name: Run k6 stress test (view Registered Customers) + uses: grafana/k6-action@v0.3.1 with: - name: load-test-report - path: load-test-results.json + filename: bench/scripts/registered-customers/stress-test.js + # flags: --out json=stress-test-results.json + + # - name: Upload performance test results + # uses: actions/upload-artifact@v3 + # with: + # name: load-test-report + # path: load-test-results.json diff --git a/bench/scripts/customers/spike-test.js b/bench/scripts/customers/spike-test.js index f7e8ef78..e4297466 100644 --- a/bench/scripts/customers/spike-test.js +++ b/bench/scripts/customers/spike-test.js @@ -17,11 +17,12 @@ export const options = { { duration: '20s', target: 500 }, { duration: '20s', target: 5 }, ], - thresholds: { - checks: ['rate==1'], - http_req_failed: ['rate<0.01'], - http_req_duration: ['p(95)<250'], - } + // TODO: Restore threshold + // thresholds: { + // checks: ['rate==1'], + // http_req_failed: ['rate<0.01'], + // http_req_duration: ['p(95)<250'], + // } } export default function () { diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index c62a7eeb..505b0090 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -36,6 +36,7 @@ export const options = { tags: { test_type: 'loadTest' } } }, + // TODO: Restore threshold // thresholds: { // checks: ['rate==1'], // http_req_failed: ['rate<0.01'], diff --git a/bench/scripts/registered-customers/stress-test.js b/bench/scripts/registered-customers/stress-test.js index 9dd19ee0..6e0d9c59 100644 --- a/bench/scripts/registered-customers/stress-test.js +++ b/bench/scripts/registered-customers/stress-test.js @@ -19,11 +19,12 @@ export const options = { { duration: '45s', target: 200 }, { duration: '30s', target: 5 }, ], - thresholds: { - checks: ['rate==1'], - http_req_failed: ['rate<0.01'], - http_req_duration: ['p(95)<250'], - } + // TODO: Restore threshold + // thresholds: { + // checks: ['rate==1'], + // http_req_failed: ['rate<0.01'], + // http_req_duration: ['p(95)<250'], + // } } export default function () { From 03648de8bce818491ecf05371f8d49df415400ef Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 14:39:44 +0100 Subject: [PATCH 19/70] fix: typo on k6_ci_test pipeline --- .github/workflows/perf-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 2fccf9cd..24ddc28c 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -31,7 +31,7 @@ jobs: run: docker compose --file bench/docker-compose.yml up -d --force-recreate - name: Populate Customer collection and Registered Customer View - run: cd bench/data && npm i && node customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 + run: cd bench/data && npm i && node generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 # - name: start MongoDB instance # uses: supercharge/mongodb-github-action@v1.10.0 From 2570cb6959d4275c3fe83eaa828e33039011ae98 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 15:31:02 +0100 Subject: [PATCH 20/70] fix: update perf-test workflow --- .github/workflows/perf-test.yml | 51 +++++++++++++++------------------ 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 24ddc28c..be839ece 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -22,41 +22,36 @@ jobs: - name: Checkout uses: actions/checkout@v4 - # - name: start MongoDB instance - # uses: supercharge/mongodb-github-action@v1.10.0 - # with: - # mongodb-version: "7.0" + # - name: start MongoDB and CRUD Service via docker file + # run: docker compose --file bench/docker-compose.yml up -d --force-recreate - - name: start MongoDB and CRUD Service via docker file - run: docker compose --file bench/docker-compose.yml up -d --force-recreate + - name: start MongoDB instance + uses: supercharge/mongodb-github-action@v1.10.0 + with: + mongodb-version: "7.0" - name: Populate Customer collection and Registered Customer View run: cd bench/data && npm i && node generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 - - # - name: start MongoDB instance - # uses: supercharge/mongodb-github-action@v1.10.0 - # with: - # mongodb-version: "7.0" - # - name: Use Node.js - # uses: actions/setup-node@v4 - # with: - # node-version: 20.x - # cache: npm + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm - # - name: Install - # run: npm ci + - name: Install + run: npm ci - # - name: 'Start CRUD Service instance' - # run: npm run start - # env: - # LOG_LEVEL: info - # COLLECTION_DEFINITION_FOLDER: ./bench/collections - # VIEWS_DEFINITION_FOLDER: /bench/views - # USER_ID_HEADER_KEY: userid - # CRUD_LIMIT_CONSTRAINT_ENABLED: true - # CRUD_MAX_LIMIT: 200 - # MONGODB_URL: "mongodb://database:27017/bench-test" + - name: 'Start CRUD Service instance' + run: npm run start + env: + LOG_LEVEL: info + COLLECTION_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/collections + VIEWS_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/views + USER_ID_HEADER_KEY: userid + CRUD_LIMIT_CONSTRAINT_ENABLED: true + CRUD_MAX_LIMIT: 200 + MONGODB_URL: "mongodb://database:27017/bench-test" - name: Run k6 load test (collection Item) uses: grafana/k6-action@v0.3.1 From 910bbbd44999cb044aaa679e401642b288d542dd Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 15:34:52 +0100 Subject: [PATCH 21/70] fix: connection string in perf-test.yml --- .github/workflows/perf-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index be839ece..fef3dc4b 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -51,7 +51,7 @@ jobs: USER_ID_HEADER_KEY: userid CRUD_LIMIT_CONSTRAINT_ENABLED: true CRUD_MAX_LIMIT: 200 - MONGODB_URL: "mongodb://database:27017/bench-test" + MONGODB_URL: "mongodb://database:27017/bench-test?directConnection=true" - name: Run k6 load test (collection Item) uses: grafana/k6-action@v0.3.1 From e5949fc975f091f3b944399a5b623b42c5336930 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 15:46:52 +0100 Subject: [PATCH 22/70] fix: add database name in supercharge --- .github/workflows/perf-test.yml | 3 ++- bench/data/generate-customer-data.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index fef3dc4b..5910302a 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -28,7 +28,8 @@ jobs: - name: start MongoDB instance uses: supercharge/mongodb-github-action@v1.10.0 with: - mongodb-version: "7.0" + mongodb-version: "6.0" + mongodb-db: bench-test - name: Populate Customer collection and Registered Customer View run: cd bench/data && npm i && node generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 diff --git a/bench/data/generate-customer-data.js b/bench/data/generate-customer-data.js index e8991f20..5aa9951a 100644 --- a/bench/data/generate-customer-data.js +++ b/bench/data/generate-customer-data.js @@ -81,7 +81,7 @@ async function generateData(options) { connectionString, database, numDocumentsToCreate = 100000, - shopCount = 250 + shopCount = 250, } = options // #region constants const customerCollectionName = 'customers' From 71795362196ab765b023ce49e223e25784cfe01e Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 15:49:59 +0100 Subject: [PATCH 23/70] fix wrong DB name --- .github/workflows/perf-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 5910302a..76fe9dbf 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -52,7 +52,7 @@ jobs: USER_ID_HEADER_KEY: userid CRUD_LIMIT_CONSTRAINT_ENABLED: true CRUD_MAX_LIMIT: 200 - MONGODB_URL: "mongodb://database:27017/bench-test?directConnection=true" + MONGODB_URL: "mongodb://localhost:27017/bench-test - name: Run k6 load test (collection Item) uses: grafana/k6-action@v0.3.1 From 17bb55c2ccc7ef5f8cdf4e85753e5297eb534199 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 15:51:44 +0100 Subject: [PATCH 24/70] fix: syntax error --- .github/workflows/perf-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 76fe9dbf..94ce494f 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -52,7 +52,7 @@ jobs: USER_ID_HEADER_KEY: userid CRUD_LIMIT_CONSTRAINT_ENABLED: true CRUD_MAX_LIMIT: 200 - MONGODB_URL: "mongodb://localhost:27017/bench-test + MONGODB_URL: mongodb://localhost:27017/bench-test - name: Run k6 load test (collection Item) uses: grafana/k6-action@v0.3.1 From ea42ae2475f9f2610c59f04ba4367073d4ad29d5 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 16:13:25 +0100 Subject: [PATCH 25/70] fix(perf-test): start CRUD without blocking deamon --- .github/workflows/perf-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 94ce494f..ed2b2342 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -44,7 +44,7 @@ jobs: run: npm ci - name: 'Start CRUD Service instance' - run: npm run start + run: (npm run start&) env: LOG_LEVEL: info COLLECTION_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/collections From 1b1f0ee2dbaf082aa36abd302ef7c7d79499b1d7 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 16:20:26 +0100 Subject: [PATCH 26/70] fix(perf-test): CRUD path --- .github/workflows/perf-test.yml | 2 +- bench/scripts/items/load-test.js | 28 +++++++++++++--------------- bench/scripts/utils.js | 7 +++++++ 3 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 bench/scripts/utils.js diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index ed2b2342..135cb13a 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -54,7 +54,7 @@ jobs: CRUD_MAX_LIMIT: 200 MONGODB_URL: mongodb://localhost:27017/bench-test - - name: Run k6 load test (collection Item) + - name: Run k6 load test (collection Items) uses: grafana/k6-action@v0.3.1 with: filename: bench/scripts/items/load-test.js diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index 505b0090..2e7d650e 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -1,6 +1,7 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { check, group, sleep } from 'k6'; +import { is200, CRUD_BASE_URL } from '../utils'; import { randomIntBetween, @@ -36,19 +37,16 @@ export const options = { tags: { test_type: 'loadTest' } } }, - // TODO: Restore threshold - // thresholds: { - // checks: ['rate==1'], - // http_req_failed: ['rate<0.01'], - // 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], - // 'http_req_duration{test_type:loadTest}': ['p(90)<200'], - // 'http_req_duration{verb:GET}': ['p(90)<500'], - // }, + thresholds: { + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], + 'http_req_duration{test_type:loadTest}': ['p(90)<200'], + 'http_req_duration{verb:GET}': ['p(90)<500'], + }, } // #region helper fns -const is200 = r => r.status === 200 - let counter = 0 let idToSearchCounter = 250 let idToPatchCounter = 750 @@ -85,7 +83,7 @@ const generateItem = () => { export function initialLoad () { let post = http.post( - 'http://crud-service:3000/items', + '${CRUD_BASE_URL}', JSON.stringify(generateItem()), { headers: { 'Content-Type': 'application/json' } } ); @@ -98,11 +96,11 @@ export function loadTest () { // TODO: Should I put everything in the same request so I can group('GET requests', () => { // GET / request - const get = http.get(`http://crud-service:3000/items?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) + const get = http.get(`${CRUD_BASE_URL}?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) check(get, { 'GET / returns status 200': is200 }) const _q = JSON.stringify({ 'object.counter': idToSearchCounter }) - const getById = http.get(`http://crud-service:3000/items/?_q=${_q}`, { tags: { verb: 'GET' }}) + const getById = http.get(`${CRUD_BASE_URL}/?_q=${_q}`, { tags: { verb: 'GET' }}) check(getById, { 'GET /{id} returns status 200': is200 }) sleep(1) @@ -114,7 +112,7 @@ export function loadTest () { // PATCH / request const patch = http.patch( - `http://crud-service:3000/items/?_q=${_q}`, + `${CRUD_BASE_URL}/?_q=${_q}`, JSON.stringify(generateItem()), { headers: { 'Content-Type': 'application/json' }, @@ -131,7 +129,7 @@ export function loadTest () { const _q = JSON.stringify({ 'object.counter': idToDeleteCounter }) // DELETE / request - const deleteReq = http.del(`http://crud-service:3000/items/?_q=${_q}`, null, { tags: { verb: 'DELETE' }}) + const deleteReq = http.del(`${CRUD_BASE_URL}/?_q=${_q}`, null, { tags: { verb: 'DELETE' }}) check(deleteReq, { 'DELETE / returns status 200': is200 }) sleep(1) diff --git a/bench/scripts/utils.js b/bench/scripts/utils.js new file mode 100644 index 00000000..ce73adf9 --- /dev/null +++ b/bench/scripts/utils.js @@ -0,0 +1,7 @@ +'use strict' + +const CRUD_BASE_URL = 'http://localhost:3000' + +const is200 = res => res.status === 200 + +module.exports = { CRUD_BASE_URL, is200 } From cb0b11dea64b822c3b80a7ac1bc0b8a8886d0d85 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 16:24:07 +0100 Subject: [PATCH 27/70] fix(perf-test): not using utils.js in load-test --- bench/scripts/items/load-test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index 2e7d650e..6ee01316 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -1,7 +1,6 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { check, group, sleep } from 'k6'; -import { is200, CRUD_BASE_URL } from '../utils'; import { randomIntBetween, @@ -47,6 +46,10 @@ export const options = { } // #region helper fns +const CRUD_BASE_URL = 'http://localhost:3000' + +const is200 = res => res.status === 200 + let counter = 0 let idToSearchCounter = 250 let idToPatchCounter = 750 From 29e0b28c6fa7c211781b0d9d9e079381a7228011 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 17 Jan 2024 16:32:30 +0100 Subject: [PATCH 28/70] fix(perf-test): typo on POST requests --- bench/scripts/items/load-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index 6ee01316..c7eaafb1 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -86,7 +86,7 @@ const generateItem = () => { export function initialLoad () { let post = http.post( - '${CRUD_BASE_URL}', + `${CRUD_BASE_URL}`, JSON.stringify(generateItem()), { headers: { 'Content-Type': 'application/json' } } ); From f1d388d4450e756cf086068f1619544f7debd898 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 11:10:28 +0100 Subject: [PATCH 29/70] updated tests --- bench/scripts/customers/smoke-test.js | 3 - bench/scripts/customers/spike-test.js | 13 ++- bench/scripts/items/load-test.js | 92 ++++++++++--------- .../registered-customers/stress-test.js | 13 ++- 4 files changed, 62 insertions(+), 59 deletions(-) diff --git a/bench/scripts/customers/smoke-test.js b/bench/scripts/customers/smoke-test.js index bf7d279c..502937ba 100644 --- a/bench/scripts/customers/smoke-test.js +++ b/bench/scripts/customers/smoke-test.js @@ -54,11 +54,8 @@ export default function () { } export function handleSummary(data) { - // console.log({ data: JSON.stringify(data, null, 2)}) return { stdout: textSummary(data, { enableColors: true }), - // TODO: "Permission denied" when trying to save to file. How to fix this? - // '/app/smoke-test-results.json': JSON.stringify(data) }; } diff --git a/bench/scripts/customers/spike-test.js b/bench/scripts/customers/spike-test.js index e4297466..26b9bd82 100644 --- a/bench/scripts/customers/spike-test.js +++ b/bench/scripts/customers/spike-test.js @@ -16,13 +16,12 @@ export const options = { { duration: '5s', target: 5 }, { duration: '20s', target: 500 }, { duration: '20s', target: 5 }, - ], - // TODO: Restore threshold - // thresholds: { - // checks: ['rate==1'], - // http_req_failed: ['rate<0.01'], - // http_req_duration: ['p(95)<250'], - // } + ], + thresholds: { + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + http_req_duration: ['p(95)<250'], + } } export default function () { diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index c7eaafb1..bcf9eb44 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -46,14 +46,14 @@ export const options = { } // #region helper fns -const CRUD_BASE_URL = 'http://localhost:3000' +const CRUD_BASE_URL = 'http://crud-service:3000' const is200 = res => res.status === 200 let counter = 0 -let idToSearchCounter = 250 -let idToPatchCounter = 750 -let idToDeleteCounter = 1250 +let idToSearchCounter = 125 +let idToPatchCounter = 250 +let idToDeleteCounter = 375 const generateItem = () => { const array = [] @@ -86,7 +86,7 @@ const generateItem = () => { export function initialLoad () { let post = http.post( - `${CRUD_BASE_URL}`, + `${CRUD_BASE_URL}/items`, JSON.stringify(generateItem()), { headers: { 'Content-Type': 'application/json' } } ); @@ -99,11 +99,11 @@ export function loadTest () { // TODO: Should I put everything in the same request so I can group('GET requests', () => { // GET / request - const get = http.get(`${CRUD_BASE_URL}?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) + const get = http.get(`${CRUD_BASE_URL}/items?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) check(get, { 'GET / returns status 200': is200 }) const _q = JSON.stringify({ 'object.counter': idToSearchCounter }) - const getById = http.get(`${CRUD_BASE_URL}/?_q=${_q}`, { tags: { verb: 'GET' }}) + const getById = http.get(`${CRUD_BASE_URL}/items?_q=${_q}`, { tags: { verb: 'GET' }}) check(getById, { 'GET /{id} returns status 200': is200 }) sleep(1) @@ -111,11 +111,12 @@ export function loadTest () { }) group('PATCH requests', () => { + // TODO: Patch not working correctly, find out why const _q = JSON.stringify({ 'object.counter': idToPatchCounter }) // PATCH / request const patch = http.patch( - `${CRUD_BASE_URL}/?_q=${_q}`, + `${CRUD_BASE_URL}/items?_q=${_q}`, JSON.stringify(generateItem()), { headers: { 'Content-Type': 'application/json' }, @@ -132,7 +133,7 @@ export function loadTest () { const _q = JSON.stringify({ 'object.counter': idToDeleteCounter }) // DELETE / request - const deleteReq = http.del(`${CRUD_BASE_URL}/?_q=${_q}`, null, { tags: { verb: 'DELETE' }}) + const deleteReq = http.del(`${CRUD_BASE_URL}/items?_q=${_q}`, null, { tags: { verb: 'DELETE' }}) check(deleteReq, { 'DELETE / returns status 200': is200 }) sleep(1) @@ -142,42 +143,49 @@ export function loadTest () { export function handleSummary(data) { return { - stdout: textSummary(data, { enableColors: true }), - // TODO: "Permission denied" when trying to save to file. How to fix this? - // '/app/load-test-results.json': JSON.stringify(data) + stdout: textSummary(data, { enableColors: true }) }; } -// bench-k6-load-test-1 | initialLoad ✓ [ 100% ] 10 VUs 20s -// bench-k6-load-test-1 | loadTest ↓ [ 100% ] 100 VUs 1m0s -// bench-k6-load-test-1 | ✗ POST / returns status 200 -// bench-k6-load-test-1 | ↳ 9% — ✓ 6147 / ✗ 61975 -// bench-k6-load-test-1 | ✓ GET / returns status 200 -// bench-k6-load-test-1 | ✓ GET /?_q=... returns status 200 -// bench-k6-load-test-1 | ✓ GET /count returns status 200 -// bench-k6-load-test-1 | ✓ GET /export returns status 200 +// bench-k6-load-test-1 | ✓ POST / returns status 200 // bench-k6-load-test-1 | -// bench-k6-load-test-1 | ✗ checks.........................: 15.87% ✓ 11699 ✗ 61975 -// bench-k6-load-test-1 | data_received..................: 345 MB 4.1 MB/s -// bench-k6-load-test-1 | data_sent......................: 22 MB 264 kB/s -// bench-k6-load-test-1 | http_req_blocked...............: avg=8.75µs min=531ns med=1.15µs max=60.34ms p(90)=3.59µs p(95)=5.6µs -// bench-k6-load-test-1 | http_req_connecting............: avg=6.4µs min=0s med=0s max=59.98ms p(90)=0s p(95)=0s -// bench-k6-load-test-1 | http_req_duration..............: avg=12.21ms min=212.77µs med=1.98ms max=1.67s p(90)=6.14ms p(95)=15.37ms -// bench-k6-load-test-1 | { expected_response:true }...: avg=63.72ms min=885.44µs med=5.76ms max=1.67s p(90)=186.8ms p(95)=385.07ms -// bench-k6-load-test-1 | ✓ { test_type:initialLoad }....: avg=2.81ms min=212.77µs med=1.89ms max=60.03ms p(90)=4.78ms p(95)=6.32ms -// bench-k6-load-test-1 | ✗ { test_type:loadTest }.......: avg=127.57ms min=885.44µs med=24.53ms max=1.67s p(90)=400.25ms p(95)=608.73ms -// bench-k6-load-test-1 | ✗ http_req_failed................: 84.12% ✓ 61975 ✗ 11699 -// bench-k6-load-test-1 | http_req_receiving.............: avg=2.86ms min=5.51µs med=15.97µs max=1.38s p(90)=48.76µs p(95)=88.73µs -// bench-k6-load-test-1 | http_req_sending...............: avg=12.08µs min=3.22µs med=7.58µs max=11.12ms p(90)=17.17µs p(95)=24.43µs -// bench-k6-load-test-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s -// bench-k6-load-test-1 | http_req_waiting...............: avg=9.33ms min=193.11µs med=1.95ms max=1.48s p(90)=6.04ms p(95)=13.08ms -// bench-k6-load-test-1 | http_reqs......................: 73674 875.496253/s -// bench-k6-load-test-1 | iteration_duration.............: avg=93ms min=310.4µs med=2.02ms max=5.89s p(90)=5.35ms p(95)=7.9ms -// bench-k6-load-test-1 | iterations.....................: 69510 826.013852/s -// bench-k6-load-test-1 | vus............................: 8 min=8 max=100 -// bench-k6-load-test-1 | vus_max........................: 110 min=110 max=110 -// bench-k6-load-test-1 | running (1m24.2s), 000/110 VUs, 69510 complete and 0 interrupted iterations -// bench-k6-load-test-1 | initialLoad ✓ [ 100% ] 10 VUs 20s +// bench-k6-load-test-1 | █ GET requests +// bench-k6-load-test-1 | +// bench-k6-load-test-1 | ✓ GET / returns status 200 +// bench-k6-load-test-1 | ✓ GET /{id} returns status 200 +// bench-k6-load-test-1 | +// bench-k6-load-test-1 | █ PATCH requests +// bench-k6-load-test-1 | +// bench-k6-load-test-1 | ✗ PATCH / returns status 200 +// bench-k6-load-test-1 | ↳ 0% — ✓ 0 / ✗ 2000 +// bench-k6-load-test-1 | +// bench-k6-load-test-1 | █ DELETE requests +// bench-k6-load-test-1 | +// bench-k6-load-test-1 | ✓ DELETE / returns status 200 +// bench-k6-load-test-1 | +// bench-k6-load-test-1 | ✗ checks.........................: 76.74% ✓ 6600 ✗ 2000 +// bench-k6-load-test-1 | data_received..................: 33 MB 272 kB/s +// bench-k6-load-test-1 | data_sent......................: 2.2 MB 18 kB/s +// bench-k6-load-test-1 | group_duration.................: avg=1.01s min=1s med=1s max=1.69s p(90)=1.02s p(95)=1.03s +// bench-k6-load-test-1 | http_req_blocked...............: avg=32.36µs min=553ns med=2.84µs max=21.67ms p(90)=8µs p(95)=12.93µs +// bench-k6-load-test-1 | http_req_connecting............: avg=21.35µs min=0s med=0s max=20.69ms p(90)=0s p(95)=0s +// bench-k6-load-test-1 | http_req_duration..............: avg=9.73ms min=199.34µs med=3.16ms max=642.29ms p(90)=14.21ms p(95)=20.81ms +// bench-k6-load-test-1 | { expected_response:true }...: avg=12.32ms min=633.29µs med=4.35ms max=642.29ms p(90)=16.46ms p(95)=24.39ms +// bench-k6-load-test-1 | ✓ { test_type:initialLoad }....: avg=4.77ms min=668.34µs med=4.01ms max=23.76ms p(90)=7.51ms p(95)=11.65ms +// bench-k6-load-test-1 | ✓ { test_type:loadTest }.......: avg=10.1ms min=199.34µs med=3ms max=642.29ms p(90)=14.53ms p(95)=21.81ms +// bench-k6-load-test-1 | ✓ { verb:GET }.................: avg=18.38ms min=782.67µs med=7ms max=642.29ms p(90)=21.81ms p(95)=34.3ms +// bench-k6-load-test-1 | ✗ http_req_failed................: 23.25% ✓ 2000 ✗ 6600 +// bench-k6-load-test-1 | http_req_receiving.............: avg=58.79µs min=6.89µs med=35.88µs max=1.98ms p(90)=113.02µs p(95)=166.26µs +// bench-k6-load-test-1 | http_req_sending...............: avg=25.92µs min=3.02µs med=11.77µs max=22.49ms p(90)=39.13µs p(95)=50.69µs +// bench-k6-load-test-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s +// bench-k6-load-test-1 | http_req_waiting...............: avg=9.64ms min=178.89µs med=3.08ms max=642.23ms p(90)=14.11ms p(95)=20.71ms +// bench-k6-load-test-1 | http_reqs......................: 8600 70.885514/s +// bench-k6-load-test-1 | iteration_duration.............: avg=2.57s min=1s med=3.01s max=3.7s p(90)=3.04s p(95)=3.07s +// bench-k6-load-test-1 | iterations.....................: 2600 21.430504/s +// bench-k6-load-test-1 | vus............................: 39 min=10 max=100 +// bench-k6-load-test-1 | vus_max........................: 110 min=110 max=110 +// bench-k6-load-test-1 | running (2m01.3s), 000/110 VUs, 2600 complete and 0 interrupted iterations +// bench-k6-load-test-1 | initialLoad ✓ [ 100% ] 10 VUs 1m0s // bench-k6-load-test-1 | loadTest ✓ [ 100% ] 100 VUs 1m0s -// bench-k6-load-test-1 | time="2024-01-16T15:08:18Z" level=error msg="thresholds on metrics 'checks, http_req_duration{test_type:loadTest}, http_req_failed' have been crossed" +// bench-k6-load-test-1 | time="2024-01-18T09:37:59Z" level=error msg="thresholds on metrics 'checks, http_req_failed' have been crossed" diff --git a/bench/scripts/registered-customers/stress-test.js b/bench/scripts/registered-customers/stress-test.js index 6e0d9c59..3d87c666 100644 --- a/bench/scripts/registered-customers/stress-test.js +++ b/bench/scripts/registered-customers/stress-test.js @@ -18,13 +18,12 @@ export const options = { { duration: '10s', target: 200 }, { duration: '45s', target: 200 }, { duration: '30s', target: 5 }, - ], - // TODO: Restore threshold - // thresholds: { - // checks: ['rate==1'], - // http_req_failed: ['rate<0.01'], - // http_req_duration: ['p(95)<250'], - // } + ], + thresholds: { + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + http_req_duration: ['p(95)<250'], + } } export default function () { From b4d63d096a15e3f28548e61b51a65f288263f084 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 11:11:25 +0100 Subject: [PATCH 30/70] updated dc-k6 with all tests --- bench/dc-k6.yml | 75 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml index f682729e..f529b549 100644 --- a/bench/dc-k6.yml +++ b/bench/dc-k6.yml @@ -1,4 +1,21 @@ services: + runner: + image: grafana/k6:0.48.0 + deploy: + resources: + limits: + memory: 512Mb + cpus: "1" + volumes: + - ./scripts:/app + networks: + - k6-net + command: [ + "run", + "--out", + "json=runner-results.json", + "/app/runner-test.js", + ] k6-load-test: image: grafana/k6:0.48.0 deploy: @@ -10,7 +27,63 @@ services: - ./scripts:/app networks: - k6-net - command: [ "run", "/app/items/load-test.js" ] + command: [ + "run", + "--out", + "json=load-test-results.json", + "/app/items/load-test.js", + ] + k6-stress-test-on-collection: + image: grafana/k6:0.48.0 + deploy: + resources: + limits: + memory: 512Mb + cpus: "1" + volumes: + - ./scripts:/app + networks: + - k6-net + command: [ + "run", + "--out", + "json=stress-test-on-collection-results.json", + "/app/customers/stress-test.js", + ] + k6-stress-test-on-view: + image: grafana/k6:0.48.0 + deploy: + resources: + limits: + memory: 512Mb + cpus: "1" + volumes: + - ./scripts:/app + networks: + - k6-net + command: [ + "run", + "--out", + "json=stress-test-on-view-results.json", + "/app/registered-customers/stress-test.js", + ] + k6-spike-test: + image: grafana/k6:0.48.0 + deploy: + resources: + limits: + memory: 512Mb + cpus: "1" + volumes: + - ./scripts:/app + networks: + - k6-net + command: [ + "run", + "--out", + "json=spike-test-results.json", + "/app/customers/spike-test.js", + ] networks: k6-net: From ce341df2d200c9b7e4ff889b30c08e326834396d Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 11:12:52 +0100 Subject: [PATCH 31/70] test: updated perf-test to use k6 local docker --- .github/workflows/perf-test.yml | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 135cb13a..6fc6d3fd 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -31,7 +31,7 @@ jobs: mongodb-version: "6.0" mongodb-db: bench-test - - name: Populate Customer collection and Registered Customer View + - name: Populate "Customer" collection and "Registered Customer" View run: cd bench/data && npm i && node generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 - name: Use Node.js @@ -40,7 +40,7 @@ jobs: node-version: 20.x cache: npm - - name: Install + - name: Install CRUD Service node_modules run: npm ci - name: 'Start CRUD Service instance' @@ -55,22 +55,25 @@ jobs: MONGODB_URL: mongodb://localhost:27017/bench-test - name: Run k6 load test (collection Items) - uses: grafana/k6-action@v0.3.1 - with: - filename: bench/scripts/items/load-test.js - # flags: --out json=load-test-results.json + # uses: grafana/k6-action@v0.3.1 + # with: + # filename: bench/scripts/items/load-test.js + # # flags: --out json=load-test-results.json + run: docker compose -f dc-k6.yml up k6-load-test - name: Run k6 spike test (collection Customers) - uses: grafana/k6-action@v0.3.1 - with: - filename: bench/scripts/customers/spike-test.js - # flags: --out json=spike-test-results.json + # uses: grafana/k6-action@v0.3.1 + # with: + # filename: bench/scripts/customers/spike-test.js + # # flags: --out json=spike-test-results.json + run: docker compose -f dc-k6.yml up k6-spike-test - name: Run k6 stress test (view Registered Customers) - uses: grafana/k6-action@v0.3.1 - with: - filename: bench/scripts/registered-customers/stress-test.js - # flags: --out json=stress-test-results.json + # uses: grafana/k6-action@v0.3.1 + # with: + # filename: bench/scripts/registered-customers/stress-test.js + # # flags: --out json=stress-test-results.json + run: docker compose -f dc-k6.yml up k6-stress-test-on-view # - name: Upload performance test results # uses: actions/upload-artifact@v3 From 0386b560dca2a03b49e3e1bc3e72596e295a22d3 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 11:16:47 +0100 Subject: [PATCH 32/70] fix: correct folder in perf-test workflow --- .github/workflows/perf-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 6fc6d3fd..91bd4431 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -55,6 +55,7 @@ jobs: MONGODB_URL: mongodb://localhost:27017/bench-test - name: Run k6 load test (collection Items) + working-directory: bench # uses: grafana/k6-action@v0.3.1 # with: # filename: bench/scripts/items/load-test.js @@ -62,6 +63,7 @@ jobs: run: docker compose -f dc-k6.yml up k6-load-test - name: Run k6 spike test (collection Customers) + working-directory: bench # uses: grafana/k6-action@v0.3.1 # with: # filename: bench/scripts/customers/spike-test.js @@ -69,6 +71,7 @@ jobs: run: docker compose -f dc-k6.yml up k6-spike-test - name: Run k6 stress test (view Registered Customers) + working-directory: bench # uses: grafana/k6-action@v0.3.1 # with: # filename: bench/scripts/registered-customers/stress-test.js From c7d8749fb14192304919b091bd5c10b04e1fbe20 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 11:45:05 +0100 Subject: [PATCH 33/70] feat: using crud-service in docker container --- .github/workflows/perf-test.yml | 28 ++++++++++++++-------------- bench/dc-k6.yml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 91bd4431..95e48d49 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -22,8 +22,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - # - name: start MongoDB and CRUD Service via docker file - # run: docker compose --file bench/docker-compose.yml up -d --force-recreate + - name: start CRUD Service via docker file + run: docker compose --file bench/docker-compose.yml up crud-service -d - name: start MongoDB instance uses: supercharge/mongodb-github-action@v1.10.0 @@ -40,19 +40,19 @@ jobs: node-version: 20.x cache: npm - - name: Install CRUD Service node_modules - run: npm ci + # - name: Install CRUD Service node_modules + # run: npm ci - - name: 'Start CRUD Service instance' - run: (npm run start&) - env: - LOG_LEVEL: info - COLLECTION_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/collections - VIEWS_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/views - USER_ID_HEADER_KEY: userid - CRUD_LIMIT_CONSTRAINT_ENABLED: true - CRUD_MAX_LIMIT: 200 - MONGODB_URL: mongodb://localhost:27017/bench-test + # - name: 'Start CRUD Service instance' + # run: (npm run start&) + # env: + # LOG_LEVEL: info + # COLLECTION_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/collections + # VIEWS_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/views + # USER_ID_HEADER_KEY: userid + # CRUD_LIMIT_CONSTRAINT_ENABLED: true + # CRUD_MAX_LIMIT: 200 + # MONGODB_URL: mongodb://localhost:27017/bench-test - name: Run k6 load test (collection Items) working-directory: bench diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml index f529b549..5470a731 100644 --- a/bench/dc-k6.yml +++ b/bench/dc-k6.yml @@ -83,7 +83,7 @@ services: "--out", "json=spike-test-results.json", "/app/customers/spike-test.js", - ] + ] networks: k6-net: From ffd6f0d8467d8a68019428c07e61f6376a221d33 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 11:45:50 +0100 Subject: [PATCH 34/70] fix: perf-test to have docker-compose to start crud and mongo --- .github/workflows/perf-test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 95e48d49..d87c67f8 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -23,13 +23,13 @@ jobs: uses: actions/checkout@v4 - name: start CRUD Service via docker file - run: docker compose --file bench/docker-compose.yml up crud-service -d + run: docker compose --file bench/docker-compose.yml up -d - - name: start MongoDB instance - uses: supercharge/mongodb-github-action@v1.10.0 - with: - mongodb-version: "6.0" - mongodb-db: bench-test + # - name: start MongoDB instance + # uses: supercharge/mongodb-github-action@v1.10.0 + # with: + # mongodb-version: "6.0" + # mongodb-db: bench-test - name: Populate "Customer" collection and "Registered Customer" View run: cd bench/data && npm i && node generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 From aeb229e75337c0cc39233a3de89fd9e654b37796 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 11:56:31 +0100 Subject: [PATCH 35/70] feat: add index in registeted-customers --- bench/collections/registered-customers.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/bench/collections/registered-customers.js b/bench/collections/registered-customers.js index 43d13301..23ff6fde 100644 --- a/bench/collections/registered-customers.js +++ b/bench/collections/registered-customers.js @@ -106,12 +106,23 @@ module.exports = { ], }, { - name: 'customerId', + name: 'shopID', + type: 'normal', + unique: true, + fields: [ + { + name: 'shopID', + order: 1, + }, + ], + }, + { + name: 'purchasesCount', type: 'normal', unique: true, fields: [ { - name: 'customerId', + name: 'shopID', order: 1, }, ], From 7caea6ca17e8403867ef6fe600b3d689597d3d6d Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 12:59:47 +0100 Subject: [PATCH 36/70] fix: patch on load-test, including tags --- bench/scripts/customers/smoke-test.js | 89 ++++------- bench/scripts/customers/spike-test.js | 89 ++++------- bench/scripts/customers/stress-test.js | 90 ++++-------- bench/scripts/items/load-test.js | 139 ++++++------------ .../registered-customers/stress-test.js | 90 ++++-------- 5 files changed, 167 insertions(+), 330 deletions(-) diff --git a/bench/scripts/customers/smoke-test.js b/bench/scripts/customers/smoke-test.js index 502937ba..327663d8 100644 --- a/bench/scripts/customers/smoke-test.js +++ b/bench/scripts/customers/smoke-test.js @@ -1,6 +1,6 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, group, sleep } from 'k6'; +import { check, sleep } from 'k6'; // // Test on collection "customers" @@ -20,75 +20,42 @@ export const options = { } export function setup() { - // TODO: + // Here it goes any code we want to execute before running our tests } -export default function () { - // #region helper fns - const is200 = r => r.status === 200 - //#endregion - - group('GET methods', () => { - // GET / request - const get = http.get('http://crud-service:3000/customers?shopID=2') - check(get, { 'GET / returns status 200': is200 }) - sleep(1) - - // GET /_q=... request - const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) - const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`) - check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - sleep(1) - - // GET /count request - const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true') - check(getCount, { 'GET /count returns status 200': is200 }) - sleep(1) +// #region helper fns +const is200 = r => r.status === 200 +//#endregion - // GET /export request - const getExport = http.get('http://crud-service:3000/customers/export?shopID=2') - check(getExport, { 'GET /export returns status 200': is200 }) - sleep(1) - }) - +export default function () { + // GET / request + const get = http.get('http://crud-service:3000/customers?shopID=2', { tags: { request: 'getList' }}) + check(get, { 'GET / returns status 200': is200 }) + sleep(1) + + // GET /_q=... request + const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) + const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { request: 'getList via _q' }}) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(1) + + // GET /count request + const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true', { tags: { request: 'count' }}) + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(1) + + // GET /export request + const getExport = http.get('http://crud-service:3000/customers/export?shopID=2', { tags: { request: 'export' }}) + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(1) } export function handleSummary(data) { return { stdout: textSummary(data, { enableColors: true }), }; - } +} export function teardown(data) { - // TODO + // Here it goes any code we want to execute after running our tests } - -// bench-k6-1 | █ GET methods -// bench-k6-1 | -// bench-k6-1 | ✓ GET / returns status 200 -// bench-k6-1 | ✓ GET /?_q=... returns status 200 -// bench-k6-1 | ✓ GET /count returns status 200 -// bench-k6-1 | ✓ GET /export returns status 200 -// bench-k6-1 | -// bench-k6-1 | █ teardown -// bench-k6-1 | -// bench-k6-1 | ✓ checks.........................: 100.00% ✓ 100 ✗ 0 -// bench-k6-1 | data_received..................: 18 MB 856 kB/s -// bench-k6-1 | data_sent......................: 11 kB 529 B/s -// bench-k6-1 | group_duration.................: avg=4.25s min=4.15s med=4.22s max=4.46s p(90)=4.39s p(95)=4.43s -// bench-k6-1 | http_req_blocked...............: avg=26.78µs min=2.68µs med=5.72µs max=441.73µs p(90)=9.22µs p(95)=39.07µs -// bench-k6-1 | http_req_connecting............: avg=5.75µs min=0s med=0s max=148.6µs p(90)=0s p(95)=4.54µs -// bench-k6-1 | ✓ http_req_duration..............: avg=63.53ms min=2.93ms med=58.95ms max=317.12ms p(90)=97.44ms p(95)=212.9ms -// bench-k6-1 | { expected_response:true }...: avg=63.53ms min=2.93ms med=58.95ms max=317.12ms p(90)=97.44ms p(95)=212.9ms -// bench-k6-1 | ✓ http_req_failed................: 0.00% ✓ 0 ✗ 100 -// bench-k6-1 | http_req_receiving.............: avg=6.21ms min=27.46µs med=178.72µs max=43.2ms p(90)=22.46ms p(95)=27.49ms -// bench-k6-1 | http_req_sending...............: avg=25.21µs min=9.72µs med=23.84µs max=73.14µs p(90)=36.86µs p(95)=39.35µs -// bench-k6-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s -// bench-k6-1 | http_req_waiting...............: avg=57.29ms min=2.19ms med=50.74ms max=316.99ms p(90)=97.33ms p(95)=212.81ms -// bench-k6-1 | http_reqs......................: 100 4.654264/s -// bench-k6-1 | iteration_duration.............: avg=3.94s min=1.56µs med=4.21s max=4.46s p(90)=4.39s p(95)=4.43s -// bench-k6-1 | iterations.....................: 25 1.163566/s -// bench-k6-1 | vus............................: 5 min=5 max=5 -// bench-k6-1 | vus_max........................: 5 min=5 max=5 -// bench-k6-1 | running (0m21.5s), 0/5 VUs, 25 complete and 0 interrupted iterations -// bench-k6-1 | default ✓ [ 100% ] 5 VUs 0m21.5s/2m0s 25/25 shared iters diff --git a/bench/scripts/customers/spike-test.js b/bench/scripts/customers/spike-test.js index 26b9bd82..fc63af4c 100644 --- a/bench/scripts/customers/spike-test.js +++ b/bench/scripts/customers/spike-test.js @@ -1,6 +1,6 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, group, sleep } from 'k6'; +import { check, sleep } from 'k6'; // // Test on collection "customers" @@ -20,71 +20,42 @@ export const options = { thresholds: { checks: ['rate==1'], http_req_failed: ['rate<0.01'], - http_req_duration: ['p(95)<250'], + 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], + 'http_req_duration{type:count}': ['p(90)<250'], + 'http_req_duration{type:export}': ['p(90)<250'], } } -export default function () { - // #region helper fns - const is200 = r => r.status === 200 - //#endregion - - group('GET methods', () => { - // GET / request - const get = http.get('http://crud-service:3000/customers?shopID=2') - check(get, { 'GET / returns status 200': is200 }) - sleep(1) +// #region helper fns +const is200 = r => r.status === 200 +//#endregion - // GET /_q=... request - const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) - const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`) - check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - sleep(1) - - // GET /count request - const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true') - check(getCount, { 'GET /count returns status 200': is200 }) - sleep(1) - - // GET /export request - const getExport = http.get('http://crud-service:3000/customers/export?shopID=2') - check(getExport, { 'GET /export returns status 200': is200 }) - sleep(1) - }) +export default function () { + // GET / request + const get = http.get('http://crud-service:3000/customers?shopID=2', { tags: { type: 'getList' }}) + check(get, { 'GET / returns status 200': is200 }) + sleep(1) + + // GET /_q=... request + const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) + const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(1) + + // GET /count request + const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true', { tags: { type: 'count' }}) + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(1) + + // GET /export request + const getExport = http.get('http://crud-service:3000/customers/export?shopID=2', { tags: { type: 'export' }}) + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(1) } export function handleSummary(data) { return { stdout: textSummary(data, { enableColors: true }), }; - } - -// bench-k6-1 | █ GET methods -// bench-k6-1 | -// bench-k6-1 | ✓ GET / returns status 200 -// bench-k6-1 | ✓ GET /?_q=... returns status 200 -// bench-k6-1 | ✓ GET /count returns status 200 -// bench-k6-1 | ✓ GET /export returns status 200 -// bench-k6-1 | -// bench-k6-1 | ✓ checks.........................: 100.00% ✓ 876 ✗ 0 -// bench-k6-1 | data_received..................: 38 MB 503 kB/s -// bench-k6-1 | data_sent......................: 156 kB 2.1 kB/s -// bench-k6-1 | group_duration.................: avg=31.05s min=4.19s med=34.85s max=1m8s p(90)=1m0s p(95)=1m4s -// bench-k6-1 | http_req_blocked...............: avg=325.64µs min=1.6µs med=152.88µs max=32.59ms p(90)=477.81µs p(95)=562.96µs -// bench-k6-1 | http_req_connecting............: avg=253.44µs min=0s med=108.03µs max=32.45ms p(90)=338.39µs p(95)=402.68µs -// bench-k6-1 | http_req_duration..............: avg=13.49s min=3.15ms med=8.39s max=50.62s p(90)=33.21s p(95)=38s -// bench-k6-1 | { expected_response:true }...: avg=13.49s min=3.15ms med=8.39s max=50.62s p(90)=33.21s p(95)=38s -// bench-k6-1 | ✓ http_req_failed................: 0.00% ✓ 0 ✗ 876 -// bench-k6-1 | http_req_receiving.............: avg=257.1ms min=16.1µs med=332.81µs max=36.69s p(90)=1.77ms p(95)=3.41ms -// bench-k6-1 | http_req_sending...............: avg=56.71µs min=6.05µs med=47.38µs max=524.89µs p(90)=104.83µs p(95)=123.63µs -// bench-k6-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s -// bench-k6-1 | http_req_waiting...............: avg=13.23s min=2.75ms med=8.34s max=49.76s p(90)=33.07s p(95)=37.94s -// bench-k6-1 | http_reqs......................: 876 11.679636/s -// bench-k6-1 | iteration_duration.............: avg=31.06s min=4.19s med=34.85s max=1m8s p(90)=1m0s p(95)=1m4s -// bench-k6-1 | iterations.....................: 14 0.186661/s -// bench-k6-1 | vus............................: 9 min=1 max=500 -// bench-k6-1 | vus_max........................: 500 min=500 max=500 -// bench-k6-1 | running (1m15.0s), 000/500 VUs, 14 complete and 494 interrupted iterations -// bench-k6-1 | default ✓ [ 100% ] 001/500 VUs 45s - - \ No newline at end of file +} diff --git a/bench/scripts/customers/stress-test.js b/bench/scripts/customers/stress-test.js index 3d1579bb..a34cd088 100644 --- a/bench/scripts/customers/stress-test.js +++ b/bench/scripts/customers/stress-test.js @@ -1,6 +1,6 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, group, sleep } from 'k6'; +import { check, sleep } from 'k6'; // // Test on collection "customers" @@ -15,78 +15,50 @@ import { check, group, sleep } from 'k6'; export const options = { stages: [ { duration: '5s', target: 5 }, - { duration: '10s', target: 200 }, - { duration: '45s', target: 200 }, + { duration: '10s', target: 100 }, + { duration: '45s', target: 100 }, { duration: '30s', target: 5 }, - ], + { duration: '10s', target: 5 }, + ], thresholds: { checks: ['rate==1'], http_req_failed: ['rate<0.01'], - http_req_duration: ['p(95)<250'], + 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], + 'http_req_duration{type:count}': ['p(90)<250'], + 'http_req_duration{type:export}': ['p(90)<250'], } } +// #region helper fns +const is200 = r => r.status === 200 +//#endregion + export default function () { - // #region helper fns - const is200 = r => r.status === 200 - //#endregion + // GET / request + const get = http.get('http://crud-service:3000/customers?shopID=2', { tags: { type: 'getList' }}) + check(get, { 'GET / returns status 200': is200 }) + sleep(1) - group('GET methods', () => { - // GET / request - const get = http.get('http://crud-service:3000/customers?shopID=2') - check(get, { 'GET / returns status 200': is200 }) - sleep(1) + // GET /_q=... request + const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) + const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(1) - // GET /_q=... request - const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) - const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`) - check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - sleep(1) - - // GET /count request - const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true') - check(getCount, { 'GET /count returns status 200': is200 }) - sleep(1) - - // GET /export request - const getExport = http.get('http://crud-service:3000/customers/export?shopID=2') - check(getExport, { 'GET /export returns status 200': is200 }) - sleep(1) - }) + // GET /count request + const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true', { tags: { type: 'count' }}) + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(1) + // GET /export request + const getExport = http.get('http://crud-service:3000/customers/export?shopID=2', { tags: { type: 'export' }}) + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(1) } export function handleSummary(data) { return { stdout: textSummary(data, { enableColors: true }), }; - } - -// bench-k6-1 | █ GET methods -// bench-k6-1 | -// bench-k6-1 | ✓ GET / returns status 200 -// bench-k6-1 | ✓ GET /?_q=... returns status 200 -// bench-k6-1 | ✓ GET /count returns status 200 -// bench-k6-1 | ✓ GET /export returns status 200 -// bench-k6-1 | -// bench-k6-1 | ✓ checks.........................: 100.00% ✓ 1565 ✗ 0 -// bench-k6-1 | data_received..................: 275 MB 2.7 MB/s -// bench-k6-1 | data_sent......................: 182 kB 1.8 kB/s -// bench-k6-1 | group_duration.................: avg=41.88s min=4.18s med=43.79s max=1m5s p(90)=47.93s p(95)=50.12s -// bench-k6-1 | http_req_blocked...............: avg=89.56µs min=946ns med=7.14µs max=35.15ms p(90)=228.48µs p(95)=395.63µs -// bench-k6-1 | http_req_connecting............: avg=70.79µs min=0s med=0s max=34.97ms p(90)=162.61µs p(95)=279.92µs -// bench-k6-1 | http_req_duration..............: avg=9.61s min=2.8ms med=8.34s max=30.19s p(90)=21.42s p(95)=23.46s -// bench-k6-1 | { expected_response:true }...: avg=9.61s min=2.8ms med=8.34s max=30.19s p(90)=21.42s p(95)=23.46s -// bench-k6-1 | ✓ http_req_failed................: 0.00% ✓ 0 ✗ 1565 -// bench-k6-1 | http_req_receiving.............: avg=543.79ms min=9.41µs med=193.43µs max=12.03s p(90)=1.02s p(95)=4.71s -// bench-k6-1 | http_req_sending...............: avg=33.32µs min=3.1µs med=28.15µs max=312.75µs p(90)=65.42µs p(95)=86.11µs -// bench-k6-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s -// bench-k6-1 | http_req_waiting...............: avg=9.07s min=2.37ms med=7.15s max=30.19s p(90)=21.42s p(95)=23.46s -// bench-k6-1 | http_reqs......................: 1565 15.578937/s -// bench-k6-1 | iteration_duration.............: avg=41.88s min=4.18s med=43.79s max=1m5s p(90)=47.93s p(95)=50.12s -// bench-k6-1 | iterations.....................: 365 3.633426/s -// bench-k6-1 | vus............................: 2 min=1 max=200 -// bench-k6-1 | vus_max........................: 200 min=200 max=200 -// bench-k6-1 | running (1m40.5s), 000/200 VUs, 365 complete and 45 interrupted iterations -// bench-k6-1 | default ✓ [ 100% ] 000/200 VUs 1m30s - \ No newline at end of file +} diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index bcf9eb44..6d96d3d9 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -1,6 +1,6 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, group, sleep } from 'k6'; +import { check, sleep } from 'k6'; import { randomIntBetween, @@ -41,7 +41,10 @@ export const options = { http_req_failed: ['rate<0.01'], 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], 'http_req_duration{test_type:loadTest}': ['p(90)<200'], - 'http_req_duration{verb:GET}': ['p(90)<500'], + 'http_req_duration{type:getList}': ['p(90)<500'], + 'http_req_duration{type:getById}': ['p(90)<500'], + 'http_req_duration{type:patchByQuery}': ['p(90)<500'], + 'http_req_duration{type:deleteByQuery}': ['p(90)<500'], }, } @@ -51,9 +54,9 @@ const CRUD_BASE_URL = 'http://crud-service:3000' const is200 = res => res.status === 200 let counter = 0 -let idToSearchCounter = 125 -let idToPatchCounter = 250 -let idToDeleteCounter = 375 +let idToSearchCounter = 1250 +let idToPatchCounter = 2500 +let idToDeleteCounter = 3750 const generateItem = () => { const array = [] @@ -92,53 +95,48 @@ export function initialLoad () { ); check(post, { 'POST / returns status 200': is200 }) - sleep(1) + sleep(0.01) } export function loadTest () { - // TODO: Should I put everything in the same request so I can - group('GET requests', () => { - // GET / request - const get = http.get(`${CRUD_BASE_URL}/items?number=${randomIntBetween(1, 10)}`, { tags: { verb: 'GET' }}) - check(get, { 'GET / returns status 200': is200 }) - - const _q = JSON.stringify({ 'object.counter': idToSearchCounter }) - const getById = http.get(`${CRUD_BASE_URL}/items?_q=${_q}`, { tags: { verb: 'GET' }}) - check(getById, { 'GET /{id} returns status 200': is200 }) - - sleep(1) - idToSearchCounter += 1 - }) - - group('PATCH requests', () => { - // TODO: Patch not working correctly, find out why - const _q = JSON.stringify({ 'object.counter': idToPatchCounter }) - - // PATCH / request - const patch = http.patch( - `${CRUD_BASE_URL}/items?_q=${_q}`, - JSON.stringify(generateItem()), - { - headers: { 'Content-Type': 'application/json' }, - tags: { verb: 'PATCH' } - } - ); - check(patch, { 'PATCH / returns status 200': is200 }) - - sleep(1) - idToPatchCounter += 1 - }) - - group('DELETE requests', () => { - const _q = JSON.stringify({ 'object.counter': idToDeleteCounter }) - - // DELETE / request - const deleteReq = http.del(`${CRUD_BASE_URL}/items?_q=${_q}`, null, { tags: { verb: 'DELETE' }}) - check(deleteReq, { 'DELETE / returns status 200': is200 }) - - sleep(1) - idToDeleteCounter += 1 - }) + // TODO: This can be improved by using results from GET to have values to execute GET (by Id), PATCH and DELETE methods + // TODO: Evaluate if add a post stage also here + + // GET / request + const get = http.get(`${CRUD_BASE_URL}/items?number=${randomIntBetween(1, 10)}`, { tags: { type: 'getList' }}) + check(get, { 'GET / returns status 200': is200 }) + + const getQuery = JSON.stringify({ 'object.counter': idToSearchCounter }) + const getById = http.get(`${CRUD_BASE_URL}/items?_q=${getQuery}`, { tags: { type: 'getById' }}) + check(getById, { 'GET /{id} returns status 200': is200 }) + + sleep(1) + idToSearchCounter += 1 + + const patchQuery = JSON.stringify({ 'object.counter': idToPatchCounter }) + + // PATCH / request + const patch = http.patch( + `${CRUD_BASE_URL}/items?_q=${patchQuery}`, + JSON.stringify({ $set: generateItem() }), + { + headers: { 'Content-Type': 'application/json' }, + tags: { type: 'patchByQuery' } + } + ); + check(patch, { 'PATCH / returns status 200': is200 }) + + sleep(1) + idToPatchCounter += 1 + + const deleteQuery = JSON.stringify({ 'object.counter': idToDeleteCounter }) + + // DELETE / request + const deleteReq = http.del(`${CRUD_BASE_URL}/items?_q=${deleteQuery}`, null, { tags: { type: 'deleteByQuery' }}) + check(deleteReq, { 'DELETE / returns status 200': is200 }) + + sleep(1) + idToDeleteCounter += 1 } export function handleSummary(data) { @@ -146,46 +144,3 @@ export function handleSummary(data) { stdout: textSummary(data, { enableColors: true }) }; } - -// bench-k6-load-test-1 | ✓ POST / returns status 200 -// bench-k6-load-test-1 | -// bench-k6-load-test-1 | █ GET requests -// bench-k6-load-test-1 | -// bench-k6-load-test-1 | ✓ GET / returns status 200 -// bench-k6-load-test-1 | ✓ GET /{id} returns status 200 -// bench-k6-load-test-1 | -// bench-k6-load-test-1 | █ PATCH requests -// bench-k6-load-test-1 | -// bench-k6-load-test-1 | ✗ PATCH / returns status 200 -// bench-k6-load-test-1 | ↳ 0% — ✓ 0 / ✗ 2000 -// bench-k6-load-test-1 | -// bench-k6-load-test-1 | █ DELETE requests -// bench-k6-load-test-1 | -// bench-k6-load-test-1 | ✓ DELETE / returns status 200 -// bench-k6-load-test-1 | -// bench-k6-load-test-1 | ✗ checks.........................: 76.74% ✓ 6600 ✗ 2000 -// bench-k6-load-test-1 | data_received..................: 33 MB 272 kB/s -// bench-k6-load-test-1 | data_sent......................: 2.2 MB 18 kB/s -// bench-k6-load-test-1 | group_duration.................: avg=1.01s min=1s med=1s max=1.69s p(90)=1.02s p(95)=1.03s -// bench-k6-load-test-1 | http_req_blocked...............: avg=32.36µs min=553ns med=2.84µs max=21.67ms p(90)=8µs p(95)=12.93µs -// bench-k6-load-test-1 | http_req_connecting............: avg=21.35µs min=0s med=0s max=20.69ms p(90)=0s p(95)=0s -// bench-k6-load-test-1 | http_req_duration..............: avg=9.73ms min=199.34µs med=3.16ms max=642.29ms p(90)=14.21ms p(95)=20.81ms -// bench-k6-load-test-1 | { expected_response:true }...: avg=12.32ms min=633.29µs med=4.35ms max=642.29ms p(90)=16.46ms p(95)=24.39ms -// bench-k6-load-test-1 | ✓ { test_type:initialLoad }....: avg=4.77ms min=668.34µs med=4.01ms max=23.76ms p(90)=7.51ms p(95)=11.65ms -// bench-k6-load-test-1 | ✓ { test_type:loadTest }.......: avg=10.1ms min=199.34µs med=3ms max=642.29ms p(90)=14.53ms p(95)=21.81ms -// bench-k6-load-test-1 | ✓ { verb:GET }.................: avg=18.38ms min=782.67µs med=7ms max=642.29ms p(90)=21.81ms p(95)=34.3ms -// bench-k6-load-test-1 | ✗ http_req_failed................: 23.25% ✓ 2000 ✗ 6600 -// bench-k6-load-test-1 | http_req_receiving.............: avg=58.79µs min=6.89µs med=35.88µs max=1.98ms p(90)=113.02µs p(95)=166.26µs -// bench-k6-load-test-1 | http_req_sending...............: avg=25.92µs min=3.02µs med=11.77µs max=22.49ms p(90)=39.13µs p(95)=50.69µs -// bench-k6-load-test-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s -// bench-k6-load-test-1 | http_req_waiting...............: avg=9.64ms min=178.89µs med=3.08ms max=642.23ms p(90)=14.11ms p(95)=20.71ms -// bench-k6-load-test-1 | http_reqs......................: 8600 70.885514/s -// bench-k6-load-test-1 | iteration_duration.............: avg=2.57s min=1s med=3.01s max=3.7s p(90)=3.04s p(95)=3.07s -// bench-k6-load-test-1 | iterations.....................: 2600 21.430504/s -// bench-k6-load-test-1 | vus............................: 39 min=10 max=100 -// bench-k6-load-test-1 | vus_max........................: 110 min=110 max=110 -// bench-k6-load-test-1 | running (2m01.3s), 000/110 VUs, 2600 complete and 0 interrupted iterations -// bench-k6-load-test-1 | initialLoad ✓ [ 100% ] 10 VUs 1m0s -// bench-k6-load-test-1 | loadTest ✓ [ 100% ] 100 VUs 1m0s -// bench-k6-load-test-1 | time="2024-01-18T09:37:59Z" level=error msg="thresholds on metrics 'checks, http_req_failed' have been crossed" - diff --git a/bench/scripts/registered-customers/stress-test.js b/bench/scripts/registered-customers/stress-test.js index 3d87c666..a827cfc3 100644 --- a/bench/scripts/registered-customers/stress-test.js +++ b/bench/scripts/registered-customers/stress-test.js @@ -1,9 +1,9 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, group, sleep } from 'k6'; +import { check, sleep } from 'k6'; // -// Test on collection "customers" +// Test on view "registered-customers" // Type of test: stress test // // 5 concurrent users for the first 5 seconds @@ -15,78 +15,50 @@ import { check, group, sleep } from 'k6'; export const options = { stages: [ { duration: '5s', target: 5 }, - { duration: '10s', target: 200 }, - { duration: '45s', target: 200 }, + { duration: '10s', target: 100 }, + { duration: '45s', target: 100 }, { duration: '30s', target: 5 }, + { duration: '10s', target: 5 }, ], thresholds: { checks: ['rate==1'], http_req_failed: ['rate<0.01'], - http_req_duration: ['p(95)<250'], + 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], + 'http_req_duration{type:count}': ['p(90)<250'], + 'http_req_duration{type:export}': ['p(90)<250'], } } +// #region helper fns +const is200 = r => r.status === 200 +//#endregion + export default function () { - // #region helper fns - const is200 = r => r.status === 200 - //#endregion + // GET / request + const get = http.get('http://crud-service:3000/registered-customers?shopID=2', { tags: { type: 'getList' }}) + check(get, { 'GET / returns status 200': is200 }) + sleep(0.1) - group('GET methods', () => { - // GET / request - const get = http.get('http://crud-service:3000/registered-customers?shopID=2') - check(get, { 'GET / returns status 200': is200 }) - sleep(1) + // GET /_q=... request + const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) + const getWithQuery = http.get(`http://crud-service:3000/registered-customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(0.1) - // GET /_q=... request - const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) - const getWithQuery = http.get(`http://crud-service:3000/registered-customers/?_q=${_q}`) - check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - sleep(1) - - // GET /count request - const getCount = http.get('http://crud-service:3000/registered-customers/count?canBeContacted=true') - check(getCount, { 'GET /count returns status 200': is200 }) - sleep(1) - - // GET /export request - const getExport = http.get('http://crud-service:3000/registered-customers/export?shopID=2') - check(getExport, { 'GET /export returns status 200': is200 }) - sleep(1) - }) + // GET /count request + const getCount = http.get('http://crud-service:3000/registered-customers/count?canBeContacted=true', { tags: { type: 'count' }}) + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(0.1) + // GET /export request + const getExport = http.get('http://crud-service:3000/registered-customers/export?shopID=2', { tags: { type: 'export' }}) + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(0.1) } export function handleSummary(data) { return { stdout: textSummary(data, { enableColors: true }), }; - } - -// bench-k6-stress-test-1 | █ GET methods -// bench-k6-stress-test-1 | -// bench-k6-stress-test-1 | ✓ GET / returns status 200 -// bench-k6-stress-test-1 | ✓ GET /?_q=... returns status 200 -// bench-k6-stress-test-1 | ✓ GET /count returns status 200 -// bench-k6-stress-test-1 | ✓ GET /export returns status 200 -// bench-k6-stress-test-1 | -// bench-k6-stress-test-1 | ✓ checks.........................: 100.00% ✓ 1384 ✗ 0 -// bench-k6-stress-test-1 | data_received..................: 40 MB 379 kB/s -// bench-k6-stress-test-1 | data_sent......................: 175 kB 1.7 kB/s -// bench-k6-stress-test-1 | group_duration.................: avg=48.09s min=4.16s med=49.49s max=1m16s p(90)=1m4s p(95)=1m5s -// bench-k6-stress-test-1 | http_req_blocked...............: avg=93.14µs min=2.08µs med=8.7µs max=23.86ms p(90)=326.77µs p(95)=437.9µs -// bench-k6-stress-test-1 | http_req_connecting............: avg=65.97µs min=0s med=0s max=23.8ms p(90)=226.75µs p(95)=315.6µs -// bench-k6-stress-test-1 | http_req_duration..............: avg=11.05s min=1.95ms med=10.7s max=31.7s p(90)=23.27s p(95)=25.31s -// bench-k6-stress-test-1 | { expected_response:true }...: avg=11.05s min=1.95ms med=10.7s max=31.7s p(90)=23.27s p(95)=25.31s -// bench-k6-stress-test-1 | ✓ http_req_failed................: 0.00% ✓ 0 ✗ 1384 -// bench-k6-stress-test-1 | http_req_receiving.............: avg=688.45ms min=23.07µs med=139.52µs max=11.61s p(90)=2.39s p(95)=5s -// bench-k6-stress-test-1 | http_req_sending...............: avg=39.57µs min=6.08µs med=34.25µs max=258.58µs p(90)=66.86µs p(95)=90.71µs -// bench-k6-stress-test-1 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s -// bench-k6-stress-test-1 | http_req_waiting...............: avg=10.36s min=1.88ms med=8.05s max=31.7s p(90)=23.27s p(95)=25.31s -// bench-k6-stress-test-1 | http_reqs......................: 1384 13.097311/s -// bench-k6-stress-test-1 | iteration_duration.............: avg=48.09s min=4.16s med=49.49s max=1m16s p(90)=1m4s p(95)=1m5s -// bench-k6-stress-test-1 | iterations.....................: 329 3.11345/s -// bench-k6-stress-test-1 | vus............................: 9 min=1 max=200 -// bench-k6-stress-test-1 | vus_max........................: 200 min=200 max=200 -// bench-k6-stress-test-1 | running (1m45.7s), 000/200 VUs, 329 complete and 27 interrupted iterations -// bench-k6-stress-test-1 | default ✓ [ 100% ] 000/200 VUs 1m30s - \ No newline at end of file +} From ac17505f9fcf8641140a33914b5672114c1a9106 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 14:32:38 +0100 Subject: [PATCH 37/70] feat(docker-compose): remove crud-service:latest image ref --- bench/docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index 90a6a599..36d14218 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -40,7 +40,6 @@ services: - ./data/dump:/dump crud-service: - image: crud-service:latest build: context: .. dockerfile: Dockerfile From c23b511a905ba9106510598ef3e9ab74fadf5ad9 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 16:08:10 +0100 Subject: [PATCH 38/70] feat: improvements in tests --- bench/scripts/customers/smoke-test.js | 12 +++ bench/scripts/customers/spike-test.js | 13 +++ bench/scripts/customers/stress-test.js | 13 +++ bench/scripts/items/load-test.js | 79 ++++++++++++------- .../registered-customers/stress-test.js | 17 +++- 5 files changed, 105 insertions(+), 29 deletions(-) diff --git a/bench/scripts/customers/smoke-test.js b/bench/scripts/customers/smoke-test.js index 327663d8..34197d8b 100644 --- a/bench/scripts/customers/smoke-test.js +++ b/bench/scripts/customers/smoke-test.js @@ -33,6 +33,18 @@ export default function () { check(get, { 'GET / returns status 200': is200 }) sleep(1) + // Fetch for the seventh document from the getList request to get an id to use for a getById request + const getLitResults = JSON.parse(getList.body) + const count = getLitResults.length + const document = getLitResults[7 % count] + + if (document) { + // GET /{id} request + const getById = http.get(`http://crud-service:3000/customers/${document._id}`, { tags: { type: 'getById' }}) + check(getById, { 'GET/{id} returns status 200': is200 }) + sleep(0.1) + } + // GET /_q=... request const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { request: 'getList via _q' }}) diff --git a/bench/scripts/customers/spike-test.js b/bench/scripts/customers/spike-test.js index fc63af4c..36419a76 100644 --- a/bench/scripts/customers/spike-test.js +++ b/bench/scripts/customers/spike-test.js @@ -21,6 +21,7 @@ export const options = { checks: ['rate==1'], http_req_failed: ['rate<0.01'], 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getById}': ['p(90)<250'], 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], 'http_req_duration{type:count}': ['p(90)<250'], 'http_req_duration{type:export}': ['p(90)<250'], @@ -37,6 +38,18 @@ export default function () { check(get, { 'GET / returns status 200': is200 }) sleep(1) + // Fetch for the seventh document from the getList request to get an id to use for a getById request + const getLitResults = JSON.parse(getList.body) + const count = getLitResults.length + const document = getLitResults[7 % count] + + if (document) { + // GET /{id} request + const getById = http.get(`http://crud-service:3000/customers/${document._id}`, { tags: { type: 'getById' }}) + check(getById, { 'GET/{id} returns status 200': is200 }) + sleep(0.1) + } + // GET /_q=... request const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) diff --git a/bench/scripts/customers/stress-test.js b/bench/scripts/customers/stress-test.js index a34cd088..c03cfe57 100644 --- a/bench/scripts/customers/stress-test.js +++ b/bench/scripts/customers/stress-test.js @@ -24,6 +24,7 @@ export const options = { checks: ['rate==1'], http_req_failed: ['rate<0.01'], 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getById}': ['p(90)<250'], 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], 'http_req_duration{type:count}': ['p(90)<250'], 'http_req_duration{type:export}': ['p(90)<250'], @@ -40,6 +41,18 @@ export default function () { check(get, { 'GET / returns status 200': is200 }) sleep(1) + // Fetch for the seventh document from the getList request to get an id to use for a getById request + const getLitResults = JSON.parse(getList.body) + const count = getLitResults.length + const document = getLitResults[7 % count] + + if (document) { + // GET /{id} request + const getById = http.get(`http://crud-service:3000/customers/${document._id}`, { tags: { type: 'getById' }}) + check(getById, { 'GET/{id} returns status 200': is200 }) + sleep(0.1) + } + // GET /_q=... request const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) diff --git a/bench/scripts/items/load-test.js b/bench/scripts/items/load-test.js index 6d96d3d9..52a84e60 100644 --- a/bench/scripts/items/load-test.js +++ b/bench/scripts/items/load-test.js @@ -17,8 +17,6 @@ import { // export const options = { - // TODO: Should I keep it? - // discardResponseBodies: true, scenarios: { 'initialLoad': { executor: 'constant-vus', @@ -39,12 +37,13 @@ export const options = { thresholds: { checks: ['rate==1'], http_req_failed: ['rate<0.01'], - 'http_req_duration{test_type:initialLoad}': ['p(90)<100'], - 'http_req_duration{test_type:loadTest}': ['p(90)<200'], + 'http_req_duration{type:post}': ['p(90)<100'], 'http_req_duration{type:getList}': ['p(90)<500'], 'http_req_duration{type:getById}': ['p(90)<500'], 'http_req_duration{type:patchByQuery}': ['p(90)<500'], + 'http_req_duration{type:patchById}': ['p(90)<500'], 'http_req_duration{type:deleteByQuery}': ['p(90)<500'], + 'http_req_duration{type:deleteById}': ['p(90)<500'], }, } @@ -52,12 +51,10 @@ export const options = { const CRUD_BASE_URL = 'http://crud-service:3000' const is200 = res => res.status === 200 +const is200or404 = res => [200, 404].includes(res.status) +const is204or404 = res => [204, 404].includes(res.status) let counter = 0 -let idToSearchCounter = 1250 -let idToPatchCounter = 2500 -let idToDeleteCounter = 3750 - const generateItem = () => { const array = [] const len = randomIntBetween(0, 10) @@ -91,9 +88,12 @@ export function initialLoad () { let post = http.post( `${CRUD_BASE_URL}/items`, JSON.stringify(generateItem()), - { headers: { 'Content-Type': 'application/json' } } + { + headers: { 'Content-Type': 'application/json' }, + tags: { type: 'post' } + } ); - check(post, { 'POST / returns status 200': is200 }) + check(post, { 'POST / returns status 200': is200or404 }) sleep(0.01) } @@ -103,19 +103,48 @@ export function loadTest () { // TODO: Evaluate if add a post stage also here // GET / request - const get = http.get(`${CRUD_BASE_URL}/items?number=${randomIntBetween(1, 10)}`, { tags: { type: 'getList' }}) - check(get, { 'GET / returns status 200': is200 }) + const getList = http.get(`${CRUD_BASE_URL}/items?number=${randomIntBetween(1, 10)}`, { tags: { type: 'getList' }}) + check(getList, { 'GET / returns status 200': is200 }) + sleep(1) - const getQuery = JSON.stringify({ 'object.counter': idToSearchCounter }) - const getById = http.get(`${CRUD_BASE_URL}/items?_q=${getQuery}`, { tags: { type: 'getById' }}) - check(getById, { 'GET /{id} returns status 200': is200 }) + // Fetch for the seventh document from the getList request to get an id to use for a getById request + const getListResults = JSON.parse(getList.body) + const count = getListResults.length + if (count === 0) { + return + } + // GET /{id} request + const documentIdToFetch = getListResults[randomIntBetween(0, count - 1)]._id + const getById = http.get(`${CRUD_BASE_URL}/items/${documentIdToFetch}`, { tags: { type: 'getById' }}) + const isGetByIdValid = check(getById, { 'GET /{id} returns status 200 or 404': is200or404 }) + if (!isGetByIdValid) { console.log({ failed: 'getById', error: getById.error, status: getById.status, documentId: documentIdToFetch })} sleep(1) - idToSearchCounter += 1 - const patchQuery = JSON.stringify({ 'object.counter': idToPatchCounter }) - - // PATCH / request + // PATCH /{id} request + const documentIdToPatch = getListResults[randomIntBetween(0, count - 1)]._id + const patchById = http.patch( + `${CRUD_BASE_URL}/items/${documentIdToPatch}`, + JSON.stringify({ $set: generateItem() }), + { + headers: { 'Content-Type': 'application/json' }, + tags: { type: 'patchById' } + } + ) + const isPatchByIdValid = check(patchById, { 'PATCH /{id} returns status 200 or 404': is200or404 }) + if (!isPatchByIdValid) { console.log({ failed: 'patchById', error: patchById.error, status: patchById.status, documentId: documentIdToFetch })} + sleep(1) + + // DELETE /{id} request + const documentIdToDelete = getListResults[randomIntBetween(0, count - 1)]._id + const deleteById = http.del(`${CRUD_BASE_URL}/items/${documentIdToDelete}`, null, { tags: { type: 'deleteById' }}) + const isDeleteByIdValid = check(deleteById, { 'DELETE /{id} returns status 204 or 404': is204or404 }) + if (!isDeleteByIdValid) { console.log({ failed: 'deleteById', error: deleteById.error, status: deleteById.status, documentId: documentIdToFetch })} + sleep(1) + + // PATCH /?_q=... request + const counterValueForPatch = getListResults[randomIntBetween(0, count - 1)].object.counter + const patchQuery = JSON.stringify({ 'object.counter': counterValueForPatch }) const patch = http.patch( `${CRUD_BASE_URL}/items?_q=${patchQuery}`, JSON.stringify({ $set: generateItem() }), @@ -124,19 +153,15 @@ export function loadTest () { tags: { type: 'patchByQuery' } } ); - check(patch, { 'PATCH / returns status 200': is200 }) - + check(patch, { 'PATCH / returns status 200': is200 }) sleep(1) - idToPatchCounter += 1 - - const deleteQuery = JSON.stringify({ 'object.counter': idToDeleteCounter }) - // DELETE / request + // DELETE /?_q=... request + const counterValueForDelete = getListResults[randomIntBetween(0, count - 1)].object.counter + const deleteQuery = JSON.stringify({ 'object.counter': counterValueForDelete }) const deleteReq = http.del(`${CRUD_BASE_URL}/items?_q=${deleteQuery}`, null, { tags: { type: 'deleteByQuery' }}) check(deleteReq, { 'DELETE / returns status 200': is200 }) - sleep(1) - idToDeleteCounter += 1 } export function handleSummary(data) { diff --git a/bench/scripts/registered-customers/stress-test.js b/bench/scripts/registered-customers/stress-test.js index a827cfc3..55713d6e 100644 --- a/bench/scripts/registered-customers/stress-test.js +++ b/bench/scripts/registered-customers/stress-test.js @@ -24,6 +24,7 @@ export const options = { checks: ['rate==1'], http_req_failed: ['rate<0.01'], 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getById}': ['p(90)<250'], 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], 'http_req_duration{type:count}': ['p(90)<250'], 'http_req_duration{type:export}': ['p(90)<250'], @@ -36,10 +37,22 @@ const is200 = r => r.status === 200 export default function () { // GET / request - const get = http.get('http://crud-service:3000/registered-customers?shopID=2', { tags: { type: 'getList' }}) - check(get, { 'GET / returns status 200': is200 }) + const getList = http.get('http://crud-service:3000/registered-customers?shopID=2', { tags: { type: 'getList' }}) + check(getList, { 'GET / returns status 200': is200 }) sleep(0.1) + // Fetch for the seventh document from the getList request to get an id to use for a getById request + const getLitResults = JSON.parse(getList.body) + const count = getLitResults.length + const document = getLitResults[7 % count] + + if (document) { + // GET /{id} request + const getById = http.get(`http://crud-service:3000/registered-customers/${document._id}`, { tags: { type: 'getById' }}) + check(getById, { 'GET/{id} returns status 200': is200 }) + sleep(0.1) + } + // GET /_q=... request const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) const getWithQuery = http.get(`http://crud-service:3000/registered-customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) From d6fddfe6d607d1bd3124233a9d5fde22ceb578a1 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 16:50:27 +0100 Subject: [PATCH 39/70] refactor: runners on same folder, introducing utils.js --- bench/dc-k6.yml | 8 +- bench/scripts/{items => }/load-test.js | 0 .../registered-customers/stress-test.js | 77 ------------------- bench/scripts/{customers => }/smoke-test.js | 0 bench/scripts/{customers => }/spike-test.js | 0 ...s-test.js => stress-test-on-collection.js} | 4 +- bench/scripts/stress-test-on-view.js | 42 ++++++++++ bench/scripts/utils.js | 52 ++++++++++++- 8 files changed, 97 insertions(+), 86 deletions(-) rename bench/scripts/{items => }/load-test.js (100%) delete mode 100644 bench/scripts/registered-customers/stress-test.js rename bench/scripts/{customers => }/smoke-test.js (100%) rename bench/scripts/{customers => }/spike-test.js (100%) rename bench/scripts/{customers/stress-test.js => stress-test-on-collection.js} (93%) create mode 100644 bench/scripts/stress-test-on-view.js diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml index 5470a731..44be7ed5 100644 --- a/bench/dc-k6.yml +++ b/bench/dc-k6.yml @@ -31,7 +31,7 @@ services: "run", "--out", "json=load-test-results.json", - "/app/items/load-test.js", + "/app/load-test.js", ] k6-stress-test-on-collection: image: grafana/k6:0.48.0 @@ -48,7 +48,7 @@ services: "run", "--out", "json=stress-test-on-collection-results.json", - "/app/customers/stress-test.js", + "/app/stress-test-on-collection.js", ] k6-stress-test-on-view: image: grafana/k6:0.48.0 @@ -65,7 +65,7 @@ services: "run", "--out", "json=stress-test-on-view-results.json", - "/app/registered-customers/stress-test.js", + "/app/stress-test-on-view.js", ] k6-spike-test: image: grafana/k6:0.48.0 @@ -82,7 +82,7 @@ services: "run", "--out", "json=spike-test-results.json", - "/app/customers/spike-test.js", + "/app/spike-test.js", ] networks: diff --git a/bench/scripts/items/load-test.js b/bench/scripts/load-test.js similarity index 100% rename from bench/scripts/items/load-test.js rename to bench/scripts/load-test.js diff --git a/bench/scripts/registered-customers/stress-test.js b/bench/scripts/registered-customers/stress-test.js deleted file mode 100644 index 55713d6e..00000000 --- a/bench/scripts/registered-customers/stress-test.js +++ /dev/null @@ -1,77 +0,0 @@ -import http from 'k6/http'; -import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, sleep } from 'k6'; - -// -// Test on view "registered-customers" -// Type of test: stress test -// -// 5 concurrent users for the first 5 seconds -// Then number of users rise to 200 in a 10-seconds span -// It stays high for 45 seconds -// Then it goes back to 5 users in 30 seconds to conclude the test -// - -export const options = { - stages: [ - { duration: '5s', target: 5 }, - { duration: '10s', target: 100 }, - { duration: '45s', target: 100 }, - { duration: '30s', target: 5 }, - { duration: '10s', target: 5 }, - ], - thresholds: { - checks: ['rate==1'], - http_req_failed: ['rate<0.01'], - 'http_req_duration{type:getList}': ['p(90)<250'], - 'http_req_duration{type:getById}': ['p(90)<250'], - 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], - 'http_req_duration{type:count}': ['p(90)<250'], - 'http_req_duration{type:export}': ['p(90)<250'], - } -} - -// #region helper fns -const is200 = r => r.status === 200 -//#endregion - -export default function () { - // GET / request - const getList = http.get('http://crud-service:3000/registered-customers?shopID=2', { tags: { type: 'getList' }}) - check(getList, { 'GET / returns status 200': is200 }) - sleep(0.1) - - // Fetch for the seventh document from the getList request to get an id to use for a getById request - const getLitResults = JSON.parse(getList.body) - const count = getLitResults.length - const document = getLitResults[7 % count] - - if (document) { - // GET /{id} request - const getById = http.get(`http://crud-service:3000/registered-customers/${document._id}`, { tags: { type: 'getById' }}) - check(getById, { 'GET/{id} returns status 200': is200 }) - sleep(0.1) - } - - // GET /_q=... request - const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) - const getWithQuery = http.get(`http://crud-service:3000/registered-customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) - check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - sleep(0.1) - - // GET /count request - const getCount = http.get('http://crud-service:3000/registered-customers/count?canBeContacted=true', { tags: { type: 'count' }}) - check(getCount, { 'GET /count returns status 200': is200 }) - sleep(0.1) - - // GET /export request - const getExport = http.get('http://crud-service:3000/registered-customers/export?shopID=2', { tags: { type: 'export' }}) - check(getExport, { 'GET /export returns status 200': is200 }) - sleep(0.1) -} - -export function handleSummary(data) { - return { - stdout: textSummary(data, { enableColors: true }), - }; -} diff --git a/bench/scripts/customers/smoke-test.js b/bench/scripts/smoke-test.js similarity index 100% rename from bench/scripts/customers/smoke-test.js rename to bench/scripts/smoke-test.js diff --git a/bench/scripts/customers/spike-test.js b/bench/scripts/spike-test.js similarity index 100% rename from bench/scripts/customers/spike-test.js rename to bench/scripts/spike-test.js diff --git a/bench/scripts/customers/stress-test.js b/bench/scripts/stress-test-on-collection.js similarity index 93% rename from bench/scripts/customers/stress-test.js rename to bench/scripts/stress-test-on-collection.js index c03cfe57..33e9cb85 100644 --- a/bench/scripts/customers/stress-test.js +++ b/bench/scripts/stress-test-on-collection.js @@ -37,8 +37,8 @@ const is200 = r => r.status === 200 export default function () { // GET / request - const get = http.get('http://crud-service:3000/customers?shopID=2', { tags: { type: 'getList' }}) - check(get, { 'GET / returns status 200': is200 }) + const getList = http.get('http://crud-service:3000/customers?shopID=2', { tags: { type: 'getList' }}) + check(getList, { 'GET / returns status 200': is200 }) sleep(1) // Fetch for the seventh document from the getList request to get an id to use for a getById request diff --git a/bench/scripts/stress-test-on-view.js b/bench/scripts/stress-test-on-view.js new file mode 100644 index 00000000..3b5fd014 --- /dev/null +++ b/bench/scripts/stress-test-on-view.js @@ -0,0 +1,42 @@ + +import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; +import { executeGetTests } from './utils.js'; + +// +// Test on view "registered-customers" +// Type of test: stress test +// +// 5 concurrent users for the first 5 seconds +// Then number of users rise to 200 in a 10-seconds span +// It stays high for 45 seconds +// Then it goes back to 5 users in 30 seconds to conclude the test +// + +export const options = { + stages: [ + { duration: '5s', target: 5 }, + { duration: '10s', target: 100 }, + { duration: '45s', target: 100 }, + { duration: '30s', target: 5 }, + { duration: '10s', target: 5 }, + ], + thresholds: { + checks: ['rate==1'], + http_req_failed: ['rate<0.01'], + 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getById}': ['p(90)<250'], + 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], + 'http_req_duration{type:count}': ['p(90)<250'], + 'http_req_duration{type:export}': ['p(90)<250'], + } +} + +export default function () { + executeGetTests('registered-customers') +} + +export function handleSummary(data) { + return { + stdout: textSummary(data, { enableColors: true }), + }; +} diff --git a/bench/scripts/utils.js b/bench/scripts/utils.js index ce73adf9..9120e1da 100644 --- a/bench/scripts/utils.js +++ b/bench/scripts/utils.js @@ -1,7 +1,53 @@ 'use strict' +import http from 'k6/http'; +import { check, sleep } from 'k6'; -const CRUD_BASE_URL = 'http://localhost:3000' +export const CRUD_BASE_URL = 'http://crud-service:3000' -const is200 = res => res.status === 200 +export const is200 = res => res.status === 200 +export const is204 = res => res.status === 204 +export const is200or404 = res => [200, 404].includes(res.status) +export const is204or404 = res => [204, 404].includes(res.status) -module.exports = { CRUD_BASE_URL, is200 } +export const executeGetTests = ( + collectionName, + { + getListQueryString = 'shopID=2', + getWithQueryOperatorQueryString = JSON.stringify({ purchasesCount: { $gte: 100 }}), + getCountQueryString = 'canBeContacted=true', + getExportQueryString = 'shopID=2', + sleepTime = 0.1 + } = {} +) => { + // GET / request + const getList = http.get(`${CRUD_BASE_URL}/${collectionName}?${getListQueryString}`, { tags: { type: 'getList' }}) + check(getList, { 'GET / returns status 200': is200 }) + sleep(sleepTime) + + // Fetch for the seventh document from the getList request to get an id to use for a getById request + const getLitResults = JSON.parse(getList.body) + const count = getLitResults.length + const document = getLitResults[7 % count] + + if (document) { + // GET /{id} request + const getById = http.get(`${CRUD_BASE_URL}/${collectionName}/${document._id}`, { tags: { type: 'getById' }}) + check(getById, { 'GET/{id} returns status 200': is200 }) + sleep(sleepTime) + } + + // GET /_q=... request + const getWithQuery = http.get(`${CRUD_BASE_URL}/${collectionName}/?_q=${getWithQueryOperatorQueryString}`, { tags: { type: 'getWithQueryOperator' }}) + check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) + sleep(sleepTime) + + // GET /count request + const getCount = http.get(`${CRUD_BASE_URL}/${collectionName}/count?${getCountQueryString}`, { tags: { type: 'count' }}) + check(getCount, { 'GET /count returns status 200': is200 }) + sleep(sleepTime) + + // GET /export request + const getExport = http.get(`${CRUD_BASE_URL}/${collectionName}/export?${getExportQueryString}`, { tags: { type: 'export' }}) + check(getExport, { 'GET /export returns status 200': is200 }) + sleep(sleepTime) +} From 1975240987ccb262d5a6114477f9c1e51a9f79f9 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 17:01:41 +0100 Subject: [PATCH 40/70] refactor: using utils.js everywhere --- bench/scripts/load-test.js | 3 -- bench/scripts/smoke-test.js | 40 +------------------- bench/scripts/spike-test.js | 44 ++-------------------- bench/scripts/stress-test-on-collection.js | 42 ++------------------- bench/scripts/stress-test-on-view.js | 2 +- bench/scripts/utils.js | 2 +- 6 files changed, 11 insertions(+), 122 deletions(-) diff --git a/bench/scripts/load-test.js b/bench/scripts/load-test.js index 52a84e60..138f6c0f 100644 --- a/bench/scripts/load-test.js +++ b/bench/scripts/load-test.js @@ -99,9 +99,6 @@ export function initialLoad () { } export function loadTest () { - // TODO: This can be improved by using results from GET to have values to execute GET (by Id), PATCH and DELETE methods - // TODO: Evaluate if add a post stage also here - // GET / request const getList = http.get(`${CRUD_BASE_URL}/items?number=${randomIntBetween(1, 10)}`, { tags: { type: 'getList' }}) check(getList, { 'GET / returns status 200': is200 }) diff --git a/bench/scripts/smoke-test.js b/bench/scripts/smoke-test.js index 34197d8b..aa3ccd40 100644 --- a/bench/scripts/smoke-test.js +++ b/bench/scripts/smoke-test.js @@ -1,6 +1,5 @@ -import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, sleep } from 'k6'; +import { executeGetTests } from './utils'; // // Test on collection "customers" @@ -22,44 +21,9 @@ export const options = { export function setup() { // Here it goes any code we want to execute before running our tests } - -// #region helper fns -const is200 = r => r.status === 200 -//#endregion export default function () { - // GET / request - const get = http.get('http://crud-service:3000/customers?shopID=2', { tags: { request: 'getList' }}) - check(get, { 'GET / returns status 200': is200 }) - sleep(1) - - // Fetch for the seventh document from the getList request to get an id to use for a getById request - const getLitResults = JSON.parse(getList.body) - const count = getLitResults.length - const document = getLitResults[7 % count] - - if (document) { - // GET /{id} request - const getById = http.get(`http://crud-service:3000/customers/${document._id}`, { tags: { type: 'getById' }}) - check(getById, { 'GET/{id} returns status 200': is200 }) - sleep(0.1) - } - - // GET /_q=... request - const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) - const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { request: 'getList via _q' }}) - check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - sleep(1) - - // GET /count request - const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true', { tags: { request: 'count' }}) - check(getCount, { 'GET /count returns status 200': is200 }) - sleep(1) - - // GET /export request - const getExport = http.get('http://crud-service:3000/customers/export?shopID=2', { tags: { request: 'export' }}) - check(getExport, { 'GET /export returns status 200': is200 }) - sleep(1) + executeGetTests('customers') } export function handleSummary(data) { diff --git a/bench/scripts/spike-test.js b/bench/scripts/spike-test.js index 36419a76..8e5a87d0 100644 --- a/bench/scripts/spike-test.js +++ b/bench/scripts/spike-test.js @@ -1,6 +1,5 @@ -import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, sleep } from 'k6'; +import { executeGetTests } from './utils'; // // Test on collection "customers" @@ -21,50 +20,15 @@ export const options = { checks: ['rate==1'], http_req_failed: ['rate<0.01'], 'http_req_duration{type:getList}': ['p(90)<250'], - 'http_req_duration{type:getById}': ['p(90)<250'], - 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], + 'http_req_duration{type:getListWithQueryOperator}': ['p(90)<250'], + 'http_req_duration{type:getById}': ['p(90)<250'], 'http_req_duration{type:count}': ['p(90)<250'], 'http_req_duration{type:export}': ['p(90)<250'], } } -// #region helper fns -const is200 = r => r.status === 200 -//#endregion - export default function () { - // GET / request - const get = http.get('http://crud-service:3000/customers?shopID=2', { tags: { type: 'getList' }}) - check(get, { 'GET / returns status 200': is200 }) - sleep(1) - - // Fetch for the seventh document from the getList request to get an id to use for a getById request - const getLitResults = JSON.parse(getList.body) - const count = getLitResults.length - const document = getLitResults[7 % count] - - if (document) { - // GET /{id} request - const getById = http.get(`http://crud-service:3000/customers/${document._id}`, { tags: { type: 'getById' }}) - check(getById, { 'GET/{id} returns status 200': is200 }) - sleep(0.1) - } - - // GET /_q=... request - const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) - const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) - check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - sleep(1) - - // GET /count request - const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true', { tags: { type: 'count' }}) - check(getCount, { 'GET /count returns status 200': is200 }) - sleep(1) - - // GET /export request - const getExport = http.get('http://crud-service:3000/customers/export?shopID=2', { tags: { type: 'export' }}) - check(getExport, { 'GET /export returns status 200': is200 }) - sleep(1) + executeGetTests('customers') } export function handleSummary(data) { diff --git a/bench/scripts/stress-test-on-collection.js b/bench/scripts/stress-test-on-collection.js index 33e9cb85..0ce0a1fa 100644 --- a/bench/scripts/stress-test-on-collection.js +++ b/bench/scripts/stress-test-on-collection.js @@ -1,6 +1,5 @@ -import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { check, sleep } from 'k6'; +import { executeGetTests } from './utils'; // // Test on collection "customers" @@ -24,50 +23,15 @@ export const options = { checks: ['rate==1'], http_req_failed: ['rate<0.01'], 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getListWithQueryOperator}': ['p(90)<250'], 'http_req_duration{type:getById}': ['p(90)<250'], - 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], 'http_req_duration{type:count}': ['p(90)<250'], 'http_req_duration{type:export}': ['p(90)<250'], } } -// #region helper fns -const is200 = r => r.status === 200 -//#endregion - export default function () { - // GET / request - const getList = http.get('http://crud-service:3000/customers?shopID=2', { tags: { type: 'getList' }}) - check(getList, { 'GET / returns status 200': is200 }) - sleep(1) - - // Fetch for the seventh document from the getList request to get an id to use for a getById request - const getLitResults = JSON.parse(getList.body) - const count = getLitResults.length - const document = getLitResults[7 % count] - - if (document) { - // GET /{id} request - const getById = http.get(`http://crud-service:3000/customers/${document._id}`, { tags: { type: 'getById' }}) - check(getById, { 'GET/{id} returns status 200': is200 }) - sleep(0.1) - } - - // GET /_q=... request - const _q = JSON.stringify({ purchasesCount: { $gte: 100 }}) - const getWithQuery = http.get(`http://crud-service:3000/customers/?_q=${_q}`, { tags: { type: 'getListViaQuery' }}) - check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) - sleep(1) - - // GET /count request - const getCount = http.get('http://crud-service:3000/customers/count?canBeContacted=true', { tags: { type: 'count' }}) - check(getCount, { 'GET /count returns status 200': is200 }) - sleep(1) - - // GET /export request - const getExport = http.get('http://crud-service:3000/customers/export?shopID=2', { tags: { type: 'export' }}) - check(getExport, { 'GET /export returns status 200': is200 }) - sleep(1) + executeGetTests('customers') } export function handleSummary(data) { diff --git a/bench/scripts/stress-test-on-view.js b/bench/scripts/stress-test-on-view.js index 3b5fd014..72ff2671 100644 --- a/bench/scripts/stress-test-on-view.js +++ b/bench/scripts/stress-test-on-view.js @@ -24,8 +24,8 @@ export const options = { checks: ['rate==1'], http_req_failed: ['rate<0.01'], 'http_req_duration{type:getList}': ['p(90)<250'], + 'http_req_duration{type:getListWithQueryOperator}': ['p(90)<250'], 'http_req_duration{type:getById}': ['p(90)<250'], - 'http_req_duration{type:getListViaQuery}': ['p(90)<250'], 'http_req_duration{type:count}': ['p(90)<250'], 'http_req_duration{type:export}': ['p(90)<250'], } diff --git a/bench/scripts/utils.js b/bench/scripts/utils.js index 9120e1da..7870feab 100644 --- a/bench/scripts/utils.js +++ b/bench/scripts/utils.js @@ -37,7 +37,7 @@ export const executeGetTests = ( } // GET /_q=... request - const getWithQuery = http.get(`${CRUD_BASE_URL}/${collectionName}/?_q=${getWithQueryOperatorQueryString}`, { tags: { type: 'getWithQueryOperator' }}) + const getWithQuery = http.get(`${CRUD_BASE_URL}/${collectionName}/?_q=${getWithQueryOperatorQueryString}`, { tags: { type: 'getListWithQueryOperator' }}) check(getWithQuery, { 'GET /?_q=... returns status 200': is200 }) sleep(sleepTime) From b1993258d5899126c6b3d3676e54e24aa06c53e5 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Thu, 18 Jan 2024 17:23:43 +0100 Subject: [PATCH 41/70] fix: import in spike-test.js --- bench/scripts/spike-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/scripts/spike-test.js b/bench/scripts/spike-test.js index 8e5a87d0..2cfc362f 100644 --- a/bench/scripts/spike-test.js +++ b/bench/scripts/spike-test.js @@ -1,5 +1,5 @@ import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { executeGetTests } from './utils'; +import { executeGetTests } from './utils.js'; // // Test on collection "customers" From 03739eddb16441106fae9a752b7ed3ee60c3f2a3 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 14:52:16 +0100 Subject: [PATCH 42/70] fix: imports in load-test --- bench/scripts/load-test.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bench/scripts/load-test.js b/bench/scripts/load-test.js index 138f6c0f..58ddda1f 100644 --- a/bench/scripts/load-test.js +++ b/bench/scripts/load-test.js @@ -1,11 +1,11 @@ import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { check, sleep } from 'k6'; - import { randomIntBetween, randomString, - } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; +} from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; +import { is200, is200or404, is204or404, CRUD_BASE_URL } from './utils.js' // // Test on collection "items" @@ -48,11 +48,6 @@ export const options = { } // #region helper fns -const CRUD_BASE_URL = 'http://crud-service:3000' - -const is200 = res => res.status === 200 -const is200or404 = res => [200, 404].includes(res.status) -const is204or404 = res => [204, 404].includes(res.status) let counter = 0 const generateItem = () => { From caa22653714c90bbc2082dbac92ec698c8aabd11 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 14:52:25 +0100 Subject: [PATCH 43/70] feat: add smoke-test in dc-k6 --- bench/dc-k6.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml index 44be7ed5..a8c2813d 100644 --- a/bench/dc-k6.yml +++ b/bench/dc-k6.yml @@ -33,6 +33,23 @@ services: "json=load-test-results.json", "/app/load-test.js", ] + k6-smoke-test: + image: grafana/k6:0.48.0 + deploy: + resources: + limits: + memory: 512Mb + cpus: "1" + volumes: + - ./scripts:/app + networks: + - k6-net + command: [ + "run", + "--out", + "json=smoke-test-results.json", + "/app/smoke-test.js", + ] k6-stress-test-on-collection: image: grafana/k6:0.48.0 deploy: From a79a82ac1ddf9d251a0f030e4f20cc74b9e9f521 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 14:53:31 +0100 Subject: [PATCH 44/70] add indexes in collections --- bench/collections/customers.js | 24 +++++++++++------------ bench/collections/items.js | 10 +++++----- bench/collections/registered-customers.js | 23 ++++++++++++++++++---- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/bench/collections/customers.js b/bench/collections/customers.js index 61eedb21..3fde6a86 100644 --- a/bench/collections/customers.js +++ b/bench/collections/customers.js @@ -205,38 +205,38 @@ module.exports = { ], }, { - name: 'createdAt', + name: 'shopIdIndex', type: 'normal', unique: false, fields: [ { - name: 'createdAt', - order: -1, + name: 'shopID', + order: 1, + }, + { + name: '__STATE__', + order: 1, }, ], }, { - name: 'exportIndex', + name: 'purchasesCountIndex', type: 'normal', - unique: false, + unique: true, fields: [ { - name: 'shopID', - order: 1, - }, - { - name: '__STATE__', + name: 'purchasesCount', order: 1, }, ], }, { - name: 'customerId', + name: 'canBeContactedIndex', type: 'normal', unique: true, fields: [ { - name: 'customerId', + name: 'canBeContacted', order: 1, }, ], diff --git a/bench/collections/items.js b/bench/collections/items.js index 5ad73f6b..64dc776d 100644 --- a/bench/collections/items.js +++ b/bench/collections/items.js @@ -122,23 +122,23 @@ module.exports = { ], }, { - name: 'createdAt', + name: 'numberIndex', type: 'normal', unique: false, fields: [ { - name: 'createdAt', - order: -1, + name: 'number', + order: 1, }, ], }, { - name: 'stringIndex', + name: 'counterIndex', type: 'normal', unique: false, fields: [ { - name: 'string', + name: 'object.counter', order: 1, }, ], diff --git a/bench/collections/registered-customers.js b/bench/collections/registered-customers.js index 23ff6fde..da198b69 100644 --- a/bench/collections/registered-customers.js +++ b/bench/collections/registered-customers.js @@ -106,23 +106,38 @@ module.exports = { ], }, { - name: 'shopID', + name: 'shopIdIndex', type: 'normal', - unique: true, + unique: false, fields: [ { name: 'shopID', order: 1, }, + { + name: '__STATE__', + order: 1, + }, ], }, { - name: 'purchasesCount', + name: 'purchasesCountIndex', type: 'normal', unique: true, fields: [ { - name: 'shopID', + name: 'purchasesCount', + order: 1, + }, + ], + }, + { + name: 'canBeContactedIndex', + type: 'normal', + unique: true, + fields: [ + { + name: 'canBeContacted', order: 1, }, ], From 2d86994c19c0857a39f72d2085c15ad77941fdd4 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 15:03:18 +0100 Subject: [PATCH 45/70] refactor: documenting code --- bench/scripts/runner.js | 10 +++++++++- bench/scripts/smoke-test.js | 8 -------- bench/scripts/utils.js | 17 ++++++++++++++++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/bench/scripts/runner.js b/bench/scripts/runner.js index 824a3e1c..8f83f98f 100644 --- a/bench/scripts/runner.js +++ b/bench/scripts/runner.js @@ -9,7 +9,15 @@ export const options = { ], } +export function setup() { + // Here it goes any code we want to execute before running our tests +} + export default function () { http.get('http://crud-service:3000/users/export?shopID=2') sleep(1) -} \ No newline at end of file +} + +export function teardown(data) { + // Here it goes any code we want to execute after running our tests +} diff --git a/bench/scripts/smoke-test.js b/bench/scripts/smoke-test.js index aa3ccd40..a801ad99 100644 --- a/bench/scripts/smoke-test.js +++ b/bench/scripts/smoke-test.js @@ -17,10 +17,6 @@ export const options = { http_req_duration: ['p(90)<150', 'p(95)<300'], } } - -export function setup() { - // Here it goes any code we want to execute before running our tests -} export default function () { executeGetTests('customers') @@ -31,7 +27,3 @@ export function handleSummary(data) { stdout: textSummary(data, { enableColors: true }), }; } - -export function teardown(data) { - // Here it goes any code we want to execute after running our tests -} diff --git a/bench/scripts/utils.js b/bench/scripts/utils.js index 7870feab..7d83c6a8 100644 --- a/bench/scripts/utils.js +++ b/bench/scripts/utils.js @@ -2,13 +2,28 @@ import http from 'k6/http'; import { check, sleep } from 'k6'; +/** Base URL of the CRUD Service instance deployed via `/bench/docker-compose.yml */ export const CRUD_BASE_URL = 'http://crud-service:3000' +/** Returns `true` if the response have status 200. Can be used in any GET returning results. */ export const is200 = res => res.status === 200 -export const is204 = res => res.status === 204 +/** Returns `true` if the response have status 200 or 404. Can be used for requests by id */ export const is200or404 = res => [200, 404].includes(res.status) +/** Returns `true` if the response have status 204 or 404. Can be used for the `DELETE \{id}` requests */ export const is204or404 = res => [204, 404].includes(res.status) +/** + * Execute the following GET requests: + * - `GET /`: returns a list of documents from the collection filtered by a specified queryString + * - `GET /{id}`: returns a document with given id (this Id is found) + * - `GET /?_q=...`: returns a list of documents that satisfy the condition in the `_q` operator passed in queryString + * - `GET /count`: count the documents in the collection that satisfy the condition specified in the queryString + * - `GET /export`: request to export data from the collection filtered by a specified queryString + * + * @param {string} collectionName Name of the collection to execute the request in + * @param {object} options The following object includes a list of queries to be used for the different GET requests, and the time to pass between each request (in ms - default: 0.1ms). + * Queries have default values, but can be modified at will (in that case, refer to the property names to understand which request will be affected) + */ export const executeGetTests = ( collectionName, { From 40a32fe09597395ea30f7d02450cb5d52bf48cd5 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 15:29:07 +0100 Subject: [PATCH 46/70] refactor: moved collections --- bench/collections/users.js | 164 ------------------ .../collections/customers.js | 0 bench/{ => definitions}/collections/items.js | 0 .../collections/registered-customers.js | 0 .../views/registered-customers.js | 0 5 files changed, 164 deletions(-) delete mode 100644 bench/collections/users.js rename bench/{ => definitions}/collections/customers.js (100%) rename bench/{ => definitions}/collections/items.js (100%) rename bench/{ => definitions}/collections/registered-customers.js (100%) rename bench/{ => definitions}/views/registered-customers.js (100%) diff --git a/bench/collections/users.js b/bench/collections/users.js deleted file mode 100644 index a4c5dd52..00000000 --- a/bench/collections/users.js +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2023 Mia s.r.l. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict' - -module.exports = { - name: 'users', - endpointBasePath: '/users', - defaultState: 'PUBLIC', - fields: [ - { - name: '_id', - type: 'ObjectId', - required: true, - }, - { - name: 'updaterId', - type: 'string', - description: 'User id that has requested the last change successfully', - required: true, - }, - { - name: 'updatedAt', - type: 'Date', - description: 'Date of the request that has performed the last change', - required: true, - }, - { - name: 'creatorId', - type: 'string', - description: 'User id that has created this object', - required: true, - }, - { - name: 'createdAt', - type: 'Date', - description: 'Date of the request that has performed the object creation', - required: true, - }, - { - name: '__STATE__', - type: 'string', - description: 'The state of the document', - required: true, - }, - { - name: 'firstName', - type: 'string', - required: true, - nullable: false, - }, - { - name: 'lastName', - type: 'string', - required: true, - nullable: false, - }, - { - name: 'email', - type: 'string', - required: true, - nullable: false, - }, - { - name: 'bio', - type: 'string', - required: false, - nullable: false, - }, - { - name: 'birthDate', - type: 'Date', - required: true, - nullable: false, - }, - { - name: 'shopID', - type: 'number', - required: true, - nullable: false, - }, - { - name: 'subscriptionNumber', - type: 'string', - required: true, - nullable: false, - }, - { - name: 'purchases', - type: 'number', - required: false, - nullable: false, - }, - { - name: 'happy', - type: 'boolean', - required: false, - nullable: false, - }, - ], - indexes: [ - { - name: '_id', - type: 'normal', - unique: true, - fields: [ - { - name: '_id', - order: 1, - }, - ], - }, - { - name: 'createdAt', - type: 'normal', - unique: false, - fields: [ - { - name: 'createdAt', - order: -1, - }, - ], - }, - { - name: 'exportIndex', - type: 'normal', - unique: false, - fields: [ - { - name: 'shopID', - order: 1, - }, - { - name: '__STATE__', - order: 1, - }, - ], - }, - { - name: 'email', - type: 'normal', - unique: true, - fields: [ - { - name: 'email', - order: 1, - }, - ], - }, - ], -} diff --git a/bench/collections/customers.js b/bench/definitions/collections/customers.js similarity index 100% rename from bench/collections/customers.js rename to bench/definitions/collections/customers.js diff --git a/bench/collections/items.js b/bench/definitions/collections/items.js similarity index 100% rename from bench/collections/items.js rename to bench/definitions/collections/items.js diff --git a/bench/collections/registered-customers.js b/bench/definitions/collections/registered-customers.js similarity index 100% rename from bench/collections/registered-customers.js rename to bench/definitions/collections/registered-customers.js diff --git a/bench/views/registered-customers.js b/bench/definitions/views/registered-customers.js similarity index 100% rename from bench/views/registered-customers.js rename to bench/definitions/views/registered-customers.js From 2f59eee68a7db9b19902084ab7f00a8b1b83a35d Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 15:29:29 +0100 Subject: [PATCH 47/70] fix: removed restore-mongo step and fix crud-service in docker-compose --- bench/docker-compose.yml | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index 36d14218..e586a6ea 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -19,26 +19,6 @@ services: retries: 3 start_period: 5s - restore-mongo: - image: mongo:6.0 - depends_on: - database: - condition: service_healthy - networks: - - k6-net - deploy: - resources: - limits: - memory: 2GB - cpus: '1' - entrypoint: - - /bin/bash - command: - - "-c" - - mongorestore --uri="mongodb://database:27017" --drop --maintainInsertionOrder --gzip - volumes: - - ./data/dump:/dump - crud-service: build: context: .. @@ -48,8 +28,6 @@ services: depends_on: database: condition: service_healthy - restore-mongo: - condition: service_completed_successfully networks: - k6-net deploy: @@ -67,8 +45,7 @@ services: CRUD_MAX_LIMIT: 200 MONGODB_URL: "mongodb://database:27017/bench-test" volumes: - - ./collections:/home/node/app/collections - - ./views:/home/node/app/views + - ./definitions:/home/node/app - ./scripts/healthcheck.js:/home/node/app/healthcheck/healthcheck.js healthcheck: test: [ "CMD-SHELL", "node /home/node/app/healthcheck/healthcheck.js" ] From 25dd1b651ceffe061b77c85424be2030b0f0093f Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 15:30:47 +0100 Subject: [PATCH 48/70] clean up code to create customer data in DB --- bench/data/index.js | 105 ----------- bench/data/package-lock.json | 176 ------------------ bench/data/package.json | 18 -- .../{data => utils}/generate-customer-data.js | 15 +- package-lock.json | 21 ++- package.json | 1 + 6 files changed, 27 insertions(+), 309 deletions(-) delete mode 100644 bench/data/index.js delete mode 100644 bench/data/package-lock.json delete mode 100644 bench/data/package.json rename bench/{data => utils}/generate-customer-data.js (89%) diff --git a/bench/data/index.js b/bench/data/index.js deleted file mode 100644 index df0db68e..00000000 --- a/bench/data/index.js +++ /dev/null @@ -1,105 +0,0 @@ -/* eslint-disable no-console */ -'use strict' - -const { Command } = require('commander') -const { MongoClient } = require('mongodb') -const { faker } = require('@faker-js/faker') - -const { version, description } = require('./package.json') - -async function main() { - const program = new Command() - - program.version(version) - - program - .description(description) - .requiredOption('-c, --connection-string ', 'MongoDB connection string') - .requiredOption('-d, --database ', 'MongoDB database name') - .requiredOption('-cl, --collection ', 'MongoDB collection name') - .option('-s, --shop-ids ', 'number of shop identifiers', '5') - .option('-u, --users ', 'number of users to generate and split across the different shop-ids', '500000') - .option('-b, --batch-size ', 'number of records inserted per batch', '1000') - .action(generateData) - - await program.parseAsync() -} - -async function generateData(options) { - const { - connectionString, - database, - collection, - shopIds: rawShopIds, - users: rawUsers, - batchSize: rawBatchSize, - } = options - - const shopIds = Math.max(Number.parseInt(rawShopIds), 1) - const numUsers = Math.max(Number.parseInt(rawUsers), 10) - const batchSize = Math.max(Number.parseInt(rawBatchSize), 10) - - const mongo = new MongoClient(connectionString) - await mongo.connect() - - const coll = mongo.db(database).collection(collection) - - try { - let i = numUsers - while (i > 0) { - const numberToGenerate = Math.min(batchSize, i) - - const users = [] - for (let j = 0; j < numberToGenerate; j++) { - users.push(getUser(shopIds)) - } - - // eslint-disable-next-line no-await-in-loop - await coll.insertMany(users) - - i -= numberToGenerate - process.stdout.write(`\r(${numUsers - i}/${numUsers}) ${((numUsers - i) / numUsers * 100).toFixed(2)}%`) - } - } catch (error) { - console.error(`failed to generate data: ${error}`) - } finally { - await mongo.close() - } -} - -function getUser(shopIds) { - const firstName = faker.person.firstName() - const lastName = faker.person.lastName() - // email is generated manually to ensure unicity - const email = `${firstName}.${lastName}.${faker.number.int({ max: 99 })}@email.com` - - - return { - updaterId: faker.string.uuid(), - updatedAt: faker.date.recent(), - creatorId: faker.string.uuid(), - createdAt: faker.date.past(), - __STATE__: 'PUBLIC', - firstName, - lastName, - email, - birthDate: faker.date.birthdate(), - bio: faker.hacker.phrase(), - shopID: faker.number.int({ min: 1, max: shopIds }), - subscriptionNumber: faker.finance.creditCardNumber(), - purchases: faker.number.int({ min: 1, max: 451 }), - happy: faker.datatype.boolean(0.88), - } -} - -if (require.main === module) { - main() - .then(() => { - console.info(`\n\n 🦋 records successfully created\n`) - process.exitCode = 0 - }) - .catch(error => { - console.error(`\n ❌ failed to create records ${error.message}\n`) - process.exitCode = 1 - }) -} diff --git a/bench/data/package-lock.json b/bench/data/package-lock.json deleted file mode 100644 index 3dd02e24..00000000 --- a/bench/data/package-lock.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "name": "data-generaotr", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "data-generaotr", - "version": "1.0.0", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@faker-js/faker": "^8.3.1", - "commander": "^11.1.0", - "mongodb": "^6.3.0" - } - }, - "node_modules/@faker-js/faker": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.3.1.tgz", - "integrity": "sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0", - "npm": ">=6.14.13" - } - }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", - "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" - }, - "node_modules/@types/whatwg-url": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.3.tgz", - "integrity": "sha512-z1ELvMijRL1QmU7QuzDkeYXSF2+dXI0ITKoQsIoVKcNBOiK5RMmWy+pYYxJTHFt8vkpZe7UsvRErQwcxZkjoUw==", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, - "node_modules/bson": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz", - "integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==", - "engines": { - "node": ">=16.20.1" - } - }, - "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "engines": { - "node": ">=16" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" - }, - "node_modules/mongodb": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", - "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.0", - "bson": "^6.2.0", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", - "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "dependencies": { - "punycode": "^2.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", - "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=16" - } - } - } -} diff --git a/bench/data/package.json b/bench/data/package.json deleted file mode 100644 index e24b3e37..00000000 --- a/bench/data/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "data-generaotr", - "version": "1.0.0", - "description": "generate a batch of data into the specified collection", - "main": "index.js", - "scripts": { - "start": "node index.js -c mongodb://localhost:27017/bench-test -d bench-test -cl users -s 5 -u 100 -b 1000", - "start:customer": "node customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test" - }, - "keywords": [], - "author": "", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@faker-js/faker": "^8.3.1", - "commander": "^11.1.0", - "mongodb": "^6.3.0" - } -} diff --git a/bench/data/generate-customer-data.js b/bench/utils/generate-customer-data.js similarity index 89% rename from bench/data/generate-customer-data.js rename to bench/utils/generate-customer-data.js index 5aa9951a..5de44d39 100644 --- a/bench/data/generate-customer-data.js +++ b/bench/utils/generate-customer-data.js @@ -5,7 +5,7 @@ const { Command } = require('commander') const { MongoClient } = require('mongodb') const { faker } = require('@faker-js/faker') -const { version, description } = require('./package.json') +const { version } = require('../../package.json') function generateCustomers({ index, shopCount }) { const firstName = faker.person.firstName() @@ -78,8 +78,8 @@ function generateCustomers({ index, shopCount }) { async function generateData(options) { const { - connectionString, - database, + connectionString = 'mongodb://localhost:27017', + database = 'benchTest', numDocumentsToCreate = 100000, shopCount = 250, } = options @@ -123,11 +123,10 @@ async function main() { program.version(version) program - .description(description) - .requiredOption('-c, --connection-string ', 'MongoDB connection string') - .requiredOption('-d, --database ', 'MongoDB database name') - .requiredOption('-n, --number ', 'Number of documents to generate') - .requiredOption('-s, --v ', 'Number of shops to be used inside the "shopID" field inside each database document') + .option('-c, --connection-string ', 'MongoDB connection string') + .option('-d, --database ', 'MongoDB database name') + .option('-n, --number ', 'Number of documents to generate') + .option('-s, --v ', 'Number of shops to be used inside the "shopID" field inside each database document') .action(generateData) await program.parseAsync() diff --git a/package-lock.json b/package-lock.json index 36c20129..91b3f298 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@fastify/env": "^4.3.0", "@fastify/mongodb": "^8.0.0", - "@fastify/multipart": "^8.0.0", + "@fastify/multipart": "^8.1.0", "@mia-platform/lc39": "^7.0.3", "@mia-platform/mongodb-healthchecker": "^1.0.1", "ajv-formats": "^2.1.1", @@ -36,13 +36,14 @@ "xlsx-write-stream": "^1.0.0" }, "devDependencies": { + "@faker-js/faker": "^8.3.1", "@mia-platform/eslint-config-mia": "^3.0.0", "abstract-logging": "^2.0.1", "eslint": "^8.56.0", "fastbench": "^1.0.1", "form-data": "^4.0.0", "mock-require": "^3.0.3", - "mongodb": "^6.2.0", + "mongodb": "^6.3.0", "node-xlsx": "^0.23.0", "pre-commit": "^1.2.2", "swagger-parser": "^10.0.3", @@ -236,6 +237,22 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@faker-js/faker": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.3.1.tgz", + "integrity": "sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, "node_modules/@fastify/accept-negotiator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", diff --git a/package.json b/package.json index f4d9bd8a..aeebdbad 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "ajv": "^8.12.0" }, "devDependencies": { + "@faker-js/faker": "^8.3.1", "@mia-platform/eslint-config-mia": "^3.0.0", "abstract-logging": "^2.0.1", "eslint": "^8.56.0", From 8ca0aa7722b6f114d83366dfdd76cd5b8ede86bf Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 15:32:39 +0100 Subject: [PATCH 49/70] refactor: moved healthcheck fn --- bench/docker-compose.yml | 2 +- bench/{scripts => utils}/healthcheck.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename bench/{scripts => utils}/healthcheck.js (100%) diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index e586a6ea..013e2013 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -46,7 +46,7 @@ services: MONGODB_URL: "mongodb://database:27017/bench-test" volumes: - ./definitions:/home/node/app - - ./scripts/healthcheck.js:/home/node/app/healthcheck/healthcheck.js + - ./utils/healthcheck.js:/home/node/app/healthcheck/healthcheck.js healthcheck: test: [ "CMD-SHELL", "node /home/node/app/healthcheck/healthcheck.js" ] interval: 10s diff --git a/bench/scripts/healthcheck.js b/bench/utils/healthcheck.js similarity index 100% rename from bench/scripts/healthcheck.js rename to bench/utils/healthcheck.js From a41dc6bcb1a0f88985ac24010bf010a0f13d47da Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 15:35:42 +0100 Subject: [PATCH 50/70] refactor: removed comments from workflows/perf-test.yml --- .github/workflows/perf-test.yml | 38 --------------------------------- 1 file changed, 38 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index d87c67f8..1f13d491 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -25,12 +25,6 @@ jobs: - name: start CRUD Service via docker file run: docker compose --file bench/docker-compose.yml up -d - # - name: start MongoDB instance - # uses: supercharge/mongodb-github-action@v1.10.0 - # with: - # mongodb-version: "6.0" - # mongodb-db: bench-test - - name: Populate "Customer" collection and "Registered Customer" View run: cd bench/data && npm i && node generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 @@ -39,49 +33,17 @@ jobs: with: node-version: 20.x cache: npm - - # - name: Install CRUD Service node_modules - # run: npm ci - - # - name: 'Start CRUD Service instance' - # run: (npm run start&) - # env: - # LOG_LEVEL: info - # COLLECTION_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/collections - # VIEWS_DEFINITION_FOLDER: /home/runner/work/crud-service/crud-service/bench/views - # USER_ID_HEADER_KEY: userid - # CRUD_LIMIT_CONSTRAINT_ENABLED: true - # CRUD_MAX_LIMIT: 200 - # MONGODB_URL: mongodb://localhost:27017/bench-test - name: Run k6 load test (collection Items) working-directory: bench - # uses: grafana/k6-action@v0.3.1 - # with: - # filename: bench/scripts/items/load-test.js - # # flags: --out json=load-test-results.json run: docker compose -f dc-k6.yml up k6-load-test - name: Run k6 spike test (collection Customers) working-directory: bench - # uses: grafana/k6-action@v0.3.1 - # with: - # filename: bench/scripts/customers/spike-test.js - # # flags: --out json=spike-test-results.json run: docker compose -f dc-k6.yml up k6-spike-test - name: Run k6 stress test (view Registered Customers) working-directory: bench - # uses: grafana/k6-action@v0.3.1 - # with: - # filename: bench/scripts/registered-customers/stress-test.js - # # flags: --out json=stress-test-results.json run: docker compose -f dc-k6.yml up k6-stress-test-on-view - # - name: Upload performance test results - # uses: actions/upload-artifact@v3 - # with: - # name: load-test-report - # path: load-test-results.json - From 10ec89790bf6c56625aa1eef37fb4dd99a0c73ae Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 17:15:25 +0100 Subject: [PATCH 51/70] tests updates --- bench/definitions/collections/customers.js | 2 +- bench/definitions/collections/registered-customers.js | 2 +- bench/scripts/spike-test.js | 6 +++--- bench/scripts/stress-test-on-collection.js | 6 +++--- bench/scripts/stress-test-on-view.js | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bench/definitions/collections/customers.js b/bench/definitions/collections/customers.js index 3fde6a86..7e52ea0d 100644 --- a/bench/definitions/collections/customers.js +++ b/bench/definitions/collections/customers.js @@ -233,7 +233,7 @@ module.exports = { { name: 'canBeContactedIndex', type: 'normal', - unique: true, + unique: false, fields: [ { name: 'canBeContacted', diff --git a/bench/definitions/collections/registered-customers.js b/bench/definitions/collections/registered-customers.js index da198b69..84f10aba 100644 --- a/bench/definitions/collections/registered-customers.js +++ b/bench/definitions/collections/registered-customers.js @@ -134,7 +134,7 @@ module.exports = { { name: 'canBeContactedIndex', type: 'normal', - unique: true, + unique: false, fields: [ { name: 'canBeContacted', diff --git a/bench/scripts/spike-test.js b/bench/scripts/spike-test.js index 2cfc362f..99f38f6f 100644 --- a/bench/scripts/spike-test.js +++ b/bench/scripts/spike-test.js @@ -12,9 +12,9 @@ import { executeGetTests } from './utils.js'; export const options = { stages: [ - { duration: '5s', target: 5 }, - { duration: '20s', target: 500 }, - { duration: '20s', target: 5 }, + { duration: '10s', target: 5 }, + { duration: '30s', target: 500 }, + { duration: '30s', target: 5 }, ], thresholds: { checks: ['rate==1'], diff --git a/bench/scripts/stress-test-on-collection.js b/bench/scripts/stress-test-on-collection.js index 0ce0a1fa..e594c04a 100644 --- a/bench/scripts/stress-test-on-collection.js +++ b/bench/scripts/stress-test-on-collection.js @@ -14,9 +14,9 @@ import { executeGetTests } from './utils'; export const options = { stages: [ { duration: '5s', target: 5 }, - { duration: '10s', target: 100 }, - { duration: '45s', target: 100 }, - { duration: '30s', target: 5 }, + { duration: '10s', target: 250 }, + { duration: '75s', target: 250 }, + { duration: '10s', target: 5 }, { duration: '10s', target: 5 }, ], thresholds: { diff --git a/bench/scripts/stress-test-on-view.js b/bench/scripts/stress-test-on-view.js index 72ff2671..30caadde 100644 --- a/bench/scripts/stress-test-on-view.js +++ b/bench/scripts/stress-test-on-view.js @@ -15,9 +15,9 @@ import { executeGetTests } from './utils.js'; export const options = { stages: [ { duration: '5s', target: 5 }, - { duration: '10s', target: 100 }, - { duration: '45s', target: 100 }, - { duration: '30s', target: 5 }, + { duration: '10s', target: 250 }, + { duration: '75s', target: 250 }, + { duration: '10s', target: 5 }, { duration: '10s', target: 5 }, ], thresholds: { From 3d7fc63e0cb57d2ef5973af48f9adf3dcfb0e12d Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 17:25:45 +0100 Subject: [PATCH 52/70] fix: perf-test workflow --- .github/workflows/perf-test.yml | 2 +- bench/utils/generate-customer-data.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 1f13d491..a5d50fc7 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -26,7 +26,7 @@ jobs: run: docker compose --file bench/docker-compose.yml up -d - name: Populate "Customer" collection and "Registered Customer" View - run: cd bench/data && npm i && node generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 + run: node bench/utils/generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 - name: Use Node.js uses: actions/setup-node@v4 diff --git a/bench/utils/generate-customer-data.js b/bench/utils/generate-customer-data.js index 5de44d39..8453bb20 100644 --- a/bench/utils/generate-customer-data.js +++ b/bench/utils/generate-customer-data.js @@ -126,7 +126,7 @@ async function main() { .option('-c, --connection-string ', 'MongoDB connection string') .option('-d, --database ', 'MongoDB database name') .option('-n, --number ', 'Number of documents to generate') - .option('-s, --v ', 'Number of shops to be used inside the "shopID" field inside each database document') + .option('-s, --shopCount ', 'Number of shops to be used inside the "shopID" field inside each database document') .action(generateData) await program.parseAsync() From 4bd1ac549da41c2b9ad8e201275e42cd0e01b3d3 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 17:43:50 +0100 Subject: [PATCH 53/70] feat: README --- .gitignore | 6 +++++- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 ++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 19fcdd90..106f72be 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,8 @@ kms test-collections .npmrc .tap -.local \ No newline at end of file +.local +bench/definitions +#bench/definitions/views +#bench/definitions/collections + diff --git a/README.md b/README.md index 0f07bd27..1b9c115e 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,55 @@ This API responses always in `application/application/x-ndjson` See the documentation to see which parameters are available. +## Performance test + +We use [k6](TODO) to simulate load of traffic directed to the CRUD Service and retrieve some performance metrics. At every version released, a workflow automatically starts executing the following tests: +- **Load Test**: 10 virtual users execute POST requests for one minute on the same collection, then 100 virtual users execute GET, PATCH and DELETE requests for another minute on the data created +- **Spike Test**: we simulate a spike of activity by increasing the number of users from 5 to 500 in 30 seconds, then a decrement of activity from 500 to 5 in another 30 seconds: during this test only GET requests are executed (returning a list of documents, search by `_id`, search via `_q` operator, count of documents and export of documents) on a collection that includes 100000 documents +- **Stress Test**: we simulate a brief time of intense activity by having 250 users for 90 seconds, then a decrement of activity to 5 in 30 seconds: during this test only GET requests are executed (returning a list of documents, search by `_id`, search via `_q` operator, count of documents and export of documents) on a collection that includes 100000 documents + +These tests are executed ahead of every version released to ensure that further updates do not cause a degradation of performance that might affect the usage of the CRUD Service. + +### Execute performance test on a local environment + +In case you want to run the tests on your local environment you have to: +- start the CRUD Service on a Docker container +- have a Mongo instance ready to be used, eventually already loaded with existing documents to simulate tests + +To simplify performing these operation, you can use the same setup for the tests executed during the GitHub workflow, by starting an instance of the CRUD Service using collections and view included in the folder `_bench/definitions`, and using the script `bench/utils/generate-customer-data.js` to quickly include mock documents on the _customers_ collection. + +The `generate-customer-data.js` script can be run at every time with the following command: + +``` +node bench/utils/generate-customer-data.js -c -d -n -s +``` +Where the script arguments are the following: +- **connection string** (default: _mongodb://localhost:27017_) to connect to your mongo instance +- **database name** (default: _bench-test_), the name of the database to write in +- **number of documents** (default: _100000_) that will be created and saved in the _customers_ collection of the aforementioned database +- **number of total shops** (default: _250_) that will be used to define a random value (from 1 to the chosen number) that will be applied to the _shopID_ field of each document to be saved + +To simplify these operations, you can execute the command `npm run bench:init` from your shell to start a container with a MongoDB 6.0 instance, a container with the CRUD Service (built from your current branch) and populate the collection _customers_ with 100000 documents. + +To execute any test, you should start the k6 service by executing the following command: +``` +docker compose -f bench/dc-k6.yml up +``` +Where the service name can be one of the following: + +| Service Name | Description | File name containing the test | +|--------------------------------|-------------------------------------------------------------------------------------------------|------------------------------------------| +| k6-load-test | Executes a Load Test (1 minute of POST, 1 minute of GET/PATCH/DELETE) on the _items_ collection | [load-test.js](bench/scripts/load-test.js) | +| k6-smoke-test | Executes a Smoke Test (1 minute of GET requests) on the _customers_ collection | [smoke-test.js](bench/scripts/smoke-test.js) | +| k6-stress-test-on-collections | Executes a Stress Test (GET requests for 90 seconds by 250 users) on the _customers_ collection | [stress-test-on-collections.js](bench/scripts/stress-test-on-collections.js) | +| k6-stress-test-on-view | Executes a Stress Test (GET requests for 90 seconds by 250 users) on the _registered-customers_ view | [stress-test-on-view.js](bench/scripts/stress-test-on-view.js) | +| k6-spike-test | Executes a Spike Test (simulate a spike of 500 concurrent users for GET requests) on the _customers_ collection | [spike-test.js](bench/scripts/spike-test.js) | +| runner | An empty test that can be populated for tests on local environment | [runner.js](bench/scripts/runner.js) | + +We suggest you to use the runner to execute customized tests for your own research. + +You are free to modify and improve those tests and the definitions used for them but please remember to not use any sensible data. + ## FAQ ### How do I change the Mongocryptd version on Debian diff --git a/package.json b/package.json index aeebdbad..074cd1a0 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "url": "https://github.com/mia-platform/crud-service" }, "scripts": { - "bench": "node bench/queryParser.bench.js", + "bench:queryParser": "node bench/queryParser.bench.js", + "bench:init": "docker compose -f bench/docker-compose.yml up -d --force-recreate && node bench/utils/generate-customer-data.js", "checkonly": "! grep -r '\\.only' tests/", "coverage": "npm run unit -- --jobs=4", "postcoverage": "tap report --coverage-report=lcovonly --coverage-report=text-summary", From 392b7b8ae95b4e538fcd8346fd664a9aeeacd87c Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 17:49:46 +0100 Subject: [PATCH 54/70] fix error on the README --- README.md | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 1b9c115e..c844c22c 100644 --- a/README.md +++ b/README.md @@ -325,39 +325,44 @@ See the documentation to see which parameters are available. ## Performance test -We use [k6](TODO) to simulate load of traffic directed to the CRUD Service and retrieve some performance metrics. At every version released, a workflow automatically starts executing the following tests: -- **Load Test**: 10 virtual users execute POST requests for one minute on the same collection, then 100 virtual users execute GET, PATCH and DELETE requests for another minute on the data created -- **Spike Test**: we simulate a spike of activity by increasing the number of users from 5 to 500 in 30 seconds, then a decrement of activity from 500 to 5 in another 30 seconds: during this test only GET requests are executed (returning a list of documents, search by `_id`, search via `_q` operator, count of documents and export of documents) on a collection that includes 100000 documents -- **Stress Test**: we simulate a brief time of intense activity by having 250 users for 90 seconds, then a decrement of activity to 5 in 30 seconds: during this test only GET requests are executed (returning a list of documents, search by `_id`, search via `_q` operator, count of documents and export of documents) on a collection that includes 100000 documents +## Performance Test -These tests are executed ahead of every version released to ensure that further updates do not cause a degradation of performance that might affect the usage of the CRUD Service. +We use [k6](https://example.com/k6) to simulate the load of traffic directed to the CRUD Service and retrieve some performance metrics. At every version released, a workflow automatically starts executing the following tests: -### Execute performance test on a local environment +- **Load Test**: 10 virtual users execute POST requests for one minute on the same collection, then 100 virtual users execute GET, PATCH, and DELETE requests for another minute on the data created. +- **Spike Test**: We simulate a spike of activity by increasing the number of users from 5 to 500 in 30 seconds, then a decrement of activity from 500 to 5 in another 30 seconds. During this test, only GET requests are executed on a collection that includes 100,000 documents. +- **Stress Test**: We simulate a brief time of intense activity with 250 users for 90 seconds, followed by a decrement of activity to 5 in 30 seconds. During this test, only GET requests are executed on a collection that includes 100,000 documents. -In case you want to run the tests on your local environment you have to: -- start the CRUD Service on a Docker container -- have a Mongo instance ready to be used, eventually already loaded with existing documents to simulate tests +These tests are executed ahead of every version release to ensure that further updates do not cause a degradation of performance that might affect the usage of the CRUD Service. -To simplify performing these operation, you can use the same setup for the tests executed during the GitHub workflow, by starting an instance of the CRUD Service using collections and view included in the folder `_bench/definitions`, and using the script `bench/utils/generate-customer-data.js` to quickly include mock documents on the _customers_ collection. +### Execute Performance Test on a Local Environment -The `generate-customer-data.js` script can be run at every time with the following command: +In case you want to run the tests on your local environment, follow these steps: -``` +- Start the CRUD Service in a Docker container. +- Have a MongoDB instance ready for use, eventually loaded with existing documents to simulate tests. + +To simplify these operations, you can use the same setup for the tests executed during the GitHub workflow, by starting an instance of the CRUD Service using collections and views included in the folder `_bench/definitions`. Use the script `bench/utils/generate-customer-data.js` to quickly include mock documents in the _customers_ collection. + +The `generate-customer-data.js` script can be executed at any time with the following command: + +```bash node bench/utils/generate-customer-data.js -c -d -n -s ``` Where the script arguments are the following: -- **connection string** (default: _mongodb://localhost:27017_) to connect to your mongo instance -- **database name** (default: _bench-test_), the name of the database to write in -- **number of documents** (default: _100000_) that will be created and saved in the _customers_ collection of the aforementioned database -- **number of total shops** (default: _250_) that will be used to define a random value (from 1 to the chosen number) that will be applied to the _shopID_ field of each document to be saved +- **connection string** (default: _mongodb://localhost:27017_): Connects to your MongoDB instance. +- **database name** (default: _bench-test_): Specifies the name of the database to write to. +- **number of documents** (default: _100000_): Sets the number of documents to be created and saved in the customers collection of the specified database. +- **number of total shops** (default: _250_): Defines a random value (from 1 to the specified number) applied to the shopID field of each document to be saved. + -To simplify these operations, you can execute the command `npm run bench:init` from your shell to start a container with a MongoDB 6.0 instance, a container with the CRUD Service (built from your current branch) and populate the collection _customers_ with 100000 documents. +To simplify these operations, you can execute the command npm run bench:init from your shell. This command starts a container with a MongoDB 6.0 instance, a container with the CRUD Service (built from your current branch), and populates the _customers_ collection with 100,000 documents. -To execute any test, you should start the k6 service by executing the following command: +To execute any test, start the k6 service with the following command: ``` docker compose -f bench/dc-k6.yml up ``` -Where the service name can be one of the following: +Remember to replace `` with one of the following: | Service Name | Description | File name containing the test | |--------------------------------|-------------------------------------------------------------------------------------------------|------------------------------------------| From 19c4a6cd36cc0500e5a0745ea178dd169c2d0df9 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 17:50:23 +0100 Subject: [PATCH 55/70] update perf-test.yml --- .github/workflows/perf-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index a5d50fc7..b6ea6f22 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -25,15 +25,15 @@ jobs: - name: start CRUD Service via docker file run: docker compose --file bench/docker-compose.yml up -d - - name: Populate "Customer" collection and "Registered Customer" View - run: node bench/utils/generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 - - name: Use Node.js uses: actions/setup-node@v4 with: node-version: 20.x cache: npm + - name: Populate "Customer" collection and "Registered Customer" View + run: node bench/utils/generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 + - name: Run k6 load test (collection Items) working-directory: bench run: docker compose -f dc-k6.yml up k6-load-test From ec136442a2b8dd2cfd0282e5eb3b43b7a57c8720 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 17:53:11 +0100 Subject: [PATCH 56/70] chore: add commander as devDep --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 91b3f298..6c8a47f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "@faker-js/faker": "^8.3.1", "@mia-platform/eslint-config-mia": "^3.0.0", "abstract-logging": "^2.0.1", + "commander": "^11.1.0", "eslint": "^8.56.0", "fastbench": "^1.0.1", "form-data": "^4.0.0", diff --git a/package.json b/package.json index 074cd1a0..3066b859 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@faker-js/faker": "^8.3.1", "@mia-platform/eslint-config-mia": "^3.0.0", "abstract-logging": "^2.0.1", + "commander": "^11.1.0", "eslint": "^8.56.0", "fastbench": "^1.0.1", "form-data": "^4.0.0", From 42d73b5164fe09a16e00445d81f6bba1b88af8ae Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 17:55:28 +0100 Subject: [PATCH 57/70] fix: add npm i stage in perf-test.yml --- .github/workflows/perf-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index b6ea6f22..0b13a07a 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -32,7 +32,7 @@ jobs: cache: npm - name: Populate "Customer" collection and "Registered Customer" View - run: node bench/utils/generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 + run: npm i && node bench/utils/generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 - name: Run k6 load test (collection Items) working-directory: bench From 89a53484466062ad506d7707f36f620fbdb88594 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 22 Jan 2024 18:08:23 +0100 Subject: [PATCH 58/70] refactor: clean up code --- bench/scripts/utils.js | 7 ++++--- bench/utils/generate-customer-data.js | 4 ---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/bench/scripts/utils.js b/bench/scripts/utils.js index 7d83c6a8..a50687e5 100644 --- a/bench/scripts/utils.js +++ b/bench/scripts/utils.js @@ -1,4 +1,5 @@ 'use strict' + import http from 'k6/http'; import { check, sleep } from 'k6'; @@ -40,9 +41,9 @@ export const executeGetTests = ( sleep(sleepTime) // Fetch for the seventh document from the getList request to get an id to use for a getById request - const getLitResults = JSON.parse(getList.body) - const count = getLitResults.length - const document = getLitResults[7 % count] + const getListResults = JSON.parse(getList.body) + const count = getListResults.length + const document = getListResults[7 % count] if (document) { // GET /{id} request diff --git a/bench/utils/generate-customer-data.js b/bench/utils/generate-customer-data.js index 8453bb20..4bda72bb 100644 --- a/bench/utils/generate-customer-data.js +++ b/bench/utils/generate-customer-data.js @@ -5,8 +5,6 @@ const { Command } = require('commander') const { MongoClient } = require('mongodb') const { faker } = require('@faker-js/faker') -const { version } = require('../../package.json') - function generateCustomers({ index, shopCount }) { const firstName = faker.person.firstName() const lastName = faker.person.lastName() @@ -120,8 +118,6 @@ async function generateData(options) { async function main() { const program = new Command() - program.version(version) - program .option('-c, --connection-string ', 'MongoDB connection string') .option('-d, --database ', 'MongoDB database name') From 5703d53478f917b2fc3bf35e092f667c1076ce8b Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 23 Jan 2024 09:07:58 +0100 Subject: [PATCH 59/70] fix: minor updates on code --- .github/workflows/perf-test.yml | 7 ++++--- bench/docker-compose.yml | 2 +- package.json | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 0b13a07a..5662aad5 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -22,15 +22,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: start CRUD Service via docker file - run: docker compose --file bench/docker-compose.yml up -d - - name: Use Node.js uses: actions/setup-node@v4 with: node-version: 20.x cache: npm + - name: start CRUD Service via docker file + run: docker compose --file bench/docker-compose.yml up -d + + - name: Populate "Customer" collection and "Registered Customer" View run: npm i && node bench/utils/generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index 013e2013..2a637ac4 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -41,7 +41,7 @@ services: COLLECTION_DEFINITION_FOLDER: /home/node/app/collections VIEWS_DEFINITION_FOLDER: /home/node/app/views USER_ID_HEADER_KEY: userid - CRUD_LIMIT_CONSTRAINT_ENABLED: true + CRUD_LIMIT_CONSTRAINT_ENABLED: 'true' CRUD_MAX_LIMIT: 200 MONGODB_URL: "mongodb://database:27017/bench-test" volumes: diff --git a/package.json b/package.json index 3066b859..d5ce0ac1 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "jsx": true } }, + "ignorePatterns": ["bench"], "overrides": [ { "files": [ From d9f9bf1fe548e949772db2711faccb4d4087a84b Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 23 Jan 2024 10:03:07 +0100 Subject: [PATCH 60/70] remove unused gitignore --- bench/.gitignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 bench/.gitignore diff --git a/bench/.gitignore b/bench/.gitignore deleted file mode 100644 index f106c487..00000000 --- a/bench/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -data/dump -node_modules \ No newline at end of file From 89d46e63985762136aa4e34064acfef924e3fb2f Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 23 Jan 2024 10:03:26 +0100 Subject: [PATCH 61/70] fix docker-compose folders --- bench/docker-compose.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml index 2a637ac4..44206e4a 100644 --- a/bench/docker-compose.yml +++ b/bench/docker-compose.yml @@ -36,16 +36,15 @@ services: memory: 500Mb cpus: "2" environment: - # Crud Service LOG_LEVEL: info - COLLECTION_DEFINITION_FOLDER: /home/node/app/collections - VIEWS_DEFINITION_FOLDER: /home/node/app/views + COLLECTION_DEFINITION_FOLDER: /home/node/app/definitions/collections + VIEWS_DEFINITION_FOLDER: /home/node/app/definitions/views USER_ID_HEADER_KEY: userid - CRUD_LIMIT_CONSTRAINT_ENABLED: 'true' + CRUD_LIMIT_CONSTRAINT_ENABLED: "true" CRUD_MAX_LIMIT: 200 MONGODB_URL: "mongodb://database:27017/bench-test" volumes: - - ./definitions:/home/node/app + - ./definitions:/home/node/app/definitions - ./utils/healthcheck.js:/home/node/app/healthcheck/healthcheck.js healthcheck: test: [ "CMD-SHELL", "node /home/node/app/healthcheck/healthcheck.js" ] From 31a9ad74d97fc312e63e8624881463a4215f402a Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 23 Jan 2024 10:25:19 +0100 Subject: [PATCH 62/70] fix: perf-test workflow executed after tests --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 45319de7..7b9c9459 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,4 +69,6 @@ jobs: perf-test: needs: - checks + - release-docker + - tests uses: ./.github/workflows/perf-test.yml From dd7a12492769bc3b9d4183691022bf9cb4eb91bd Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 23 Jan 2024 11:04:39 +0100 Subject: [PATCH 63/70] fix: minor fixes --- .github/workflows/perf-test.yml | 4 ++-- bench/scripts/smoke-test.js | 2 +- bench/utils/generate-customer-data.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 5662aad5..82ca9c75 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -29,8 +29,8 @@ jobs: cache: npm - name: start CRUD Service via docker file - run: docker compose --file bench/docker-compose.yml up -d - + working-directory: bench + run: docker compose --file docker-compose.yml up -d - name: Populate "Customer" collection and "Registered Customer" View run: npm i && node bench/utils/generate-customer-data.js -c mongodb://localhost:27017/bench-test -d bench-test -n 100000 -s 250 diff --git a/bench/scripts/smoke-test.js b/bench/scripts/smoke-test.js index a801ad99..1fa7ea2e 100644 --- a/bench/scripts/smoke-test.js +++ b/bench/scripts/smoke-test.js @@ -1,5 +1,5 @@ import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; -import { executeGetTests } from './utils'; +import { executeGetTests } from './utils.js'; // // Test on collection "customers" diff --git a/bench/utils/generate-customer-data.js b/bench/utils/generate-customer-data.js index 4bda72bb..f0072d99 100644 --- a/bench/utils/generate-customer-data.js +++ b/bench/utils/generate-customer-data.js @@ -77,7 +77,7 @@ function generateCustomers({ index, shopCount }) { async function generateData(options) { const { connectionString = 'mongodb://localhost:27017', - database = 'benchTest', + database = 'bench.test', numDocumentsToCreate = 100000, shopCount = 250, } = options From 42da87a0a5344d387a8fd53000f5b17495d66e5a Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 23 Jan 2024 17:13:03 +0100 Subject: [PATCH 64/70] minor fix on runner work, index in registered-customers --- bench/dc-k6.yml | 2 +- bench/definitions/collections/registered-customers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml index a8c2813d..8a04dfc7 100644 --- a/bench/dc-k6.yml +++ b/bench/dc-k6.yml @@ -14,7 +14,7 @@ services: "run", "--out", "json=runner-results.json", - "/app/runner-test.js", + "/app/runner.js", ] k6-load-test: image: grafana/k6:0.48.0 diff --git a/bench/definitions/collections/registered-customers.js b/bench/definitions/collections/registered-customers.js index 84f10aba..f452fd95 100644 --- a/bench/definitions/collections/registered-customers.js +++ b/bench/definitions/collections/registered-customers.js @@ -123,7 +123,7 @@ module.exports = { { name: 'purchasesCountIndex', type: 'normal', - unique: true, + unique: false, fields: [ { name: 'purchasesCount', From efec5f9964d5da0c6124658202dbb27a0b61d160 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Tue, 23 Jan 2024 17:13:35 +0100 Subject: [PATCH 65/70] fix: defaults on generate-customer-data --- bench/utils/generate-customer-data.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bench/utils/generate-customer-data.js b/bench/utils/generate-customer-data.js index f0072d99..537648f3 100644 --- a/bench/utils/generate-customer-data.js +++ b/bench/utils/generate-customer-data.js @@ -119,10 +119,10 @@ async function main() { const program = new Command() program - .option('-c, --connection-string ', 'MongoDB connection string') - .option('-d, --database ', 'MongoDB database name') - .option('-n, --number ', 'Number of documents to generate') - .option('-s, --shopCount ', 'Number of shops to be used inside the "shopID" field inside each database document') + .option('-c, --connection-string ', 'MongoDB connection string', 'mongodb://localhost:27017') + .option('-d, --database ', 'MongoDB database name', 'bench-test') + .option('-n, --number ', 'Number of documents to generate', 100000) + .option('-s, --shopCount ', 'Number of shops to be used inside the "shopID" field inside each database document', 250) .action(generateData) await program.parseAsync() @@ -135,7 +135,7 @@ if (require.main === module) { process.exitCode = 0 }) .catch(error => { - console.error(`\n ❌ failed to create records ${error.message}\n`) + console.error(`\n ❌ failed to create records: ${error.message}\n`) process.exitCode = 1 }) } From e5db3dc1c01388e9aaefbe7892005ada7eb79930 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 24 Jan 2024 16:43:00 +0100 Subject: [PATCH 66/70] fix generate-customer-data script --- bench/utils/generate-customer-data.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bench/utils/generate-customer-data.js b/bench/utils/generate-customer-data.js index 537648f3..f7d4bf25 100644 --- a/bench/utils/generate-customer-data.js +++ b/bench/utils/generate-customer-data.js @@ -78,12 +78,12 @@ async function generateData(options) { const { connectionString = 'mongodb://localhost:27017', database = 'bench.test', - numDocumentsToCreate = 100000, + number = 100000, shopCount = 250, } = options // #region constants const customerCollectionName = 'customers' - const customerBatchSize = numDocumentsToCreate / 10 + const customerBatchSize = number / 10 // #endregion const mongo = new MongoClient(connectionString) @@ -92,9 +92,9 @@ async function generateData(options) { const coll = mongo.db(database).collection(customerCollectionName) try { - let i = numDocumentsToCreate + let i = number while (i > 0) { - process.stdout.write(`\rStarting the creation of documents for collection "customers".`) + process.stdout.write(`\rStarting the creation of ${number} documents for collection "customers".`) const numberToGenerate = Math.min(customerBatchSize, i) const users = [] @@ -106,7 +106,7 @@ async function generateData(options) { await coll.insertMany(users) i -= numberToGenerate - process.stdout.write(`\r(${numDocumentsToCreate - i}/${numDocumentsToCreate}) ${((numDocumentsToCreate - i) / numDocumentsToCreate * 100).toFixed(2)}%`) + process.stdout.write(`\r(${number - i}/${number}) ${((number - i) / number * 100).toFixed(2)}%`) } } catch (error) { console.error(`failed to generate data: ${error}`) @@ -119,7 +119,7 @@ async function main() { const program = new Command() program - .option('-c, --connection-string ', 'MongoDB connection string', 'mongodb://localhost:27017') + .option('-c, --connectionString ', 'MongoDB connection string', 'mongodb://localhost:27017') .option('-d, --database ', 'MongoDB database name', 'bench-test') .option('-n, --number ', 'Number of documents to generate', 100000) .option('-s, --shopCount ', 'Number of shops to be used inside the "shopID" field inside each database document', 250) From 68811e575314f1ea1fa671ef650ca7c561efb186 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Wed, 24 Jan 2024 16:43:50 +0100 Subject: [PATCH 67/70] perf-test workflow to be executed at every tag released --- .github/workflows/perf-test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 82ca9c75..79f7719a 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -7,10 +7,9 @@ on: default: 20.x required: false type: string - # TODO: Update this when we're ready to go - it should happen at every tag or something push: - branches: - - 'feat/perf-test' + tags: + - "v*" jobs: From 62c54aa6659b22a5d121102dc91703fcf6015650 Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 29 Jan 2024 09:09:41 +0100 Subject: [PATCH 68/70] chore: add disclaimer in updated files --- .gitignore | 3 --- bench/dc-k6.yml | 0 bench/definitions/collections/customers.js | 0 bench/definitions/collections/items.js | 0 .../collections/registered-customers.js | 0 .../definitions/views/registered-customers.js | 0 bench/docker-compose.yml | 0 bench/queryParser.bench.js | 0 bench/scripts/load-test.js | 16 +++++++++++++++ bench/scripts/runner.js | 20 +++++++++++++++++++ bench/scripts/smoke-test.js | 16 +++++++++++++++ bench/scripts/spike-test.js | 16 +++++++++++++++ bench/scripts/stress-test-on-collection.js | 16 +++++++++++++++ bench/scripts/stress-test-on-view.js | 15 ++++++++++++++ bench/scripts/utils.js | 16 +++++++++++++++ bench/utils/generate-customer-data.js | 16 +++++++++++++++ bench/utils/healthcheck.js | 16 +++++++++++++++ 17 files changed, 147 insertions(+), 3 deletions(-) mode change 100644 => 100755 bench/dc-k6.yml mode change 100644 => 100755 bench/definitions/collections/customers.js mode change 100644 => 100755 bench/definitions/collections/items.js mode change 100644 => 100755 bench/definitions/collections/registered-customers.js mode change 100644 => 100755 bench/definitions/views/registered-customers.js mode change 100644 => 100755 bench/docker-compose.yml mode change 100644 => 100755 bench/queryParser.bench.js mode change 100644 => 100755 bench/scripts/load-test.js mode change 100644 => 100755 bench/scripts/runner.js mode change 100644 => 100755 bench/scripts/smoke-test.js mode change 100644 => 100755 bench/scripts/spike-test.js mode change 100644 => 100755 bench/scripts/stress-test-on-collection.js mode change 100644 => 100755 bench/scripts/stress-test-on-view.js mode change 100644 => 100755 bench/scripts/utils.js mode change 100644 => 100755 bench/utils/generate-customer-data.js mode change 100644 => 100755 bench/utils/healthcheck.js diff --git a/.gitignore b/.gitignore index 106f72be..aa25f15d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,4 @@ test-collections .npmrc .tap .local -bench/definitions -#bench/definitions/views -#bench/definitions/collections diff --git a/bench/dc-k6.yml b/bench/dc-k6.yml old mode 100644 new mode 100755 diff --git a/bench/definitions/collections/customers.js b/bench/definitions/collections/customers.js old mode 100644 new mode 100755 diff --git a/bench/definitions/collections/items.js b/bench/definitions/collections/items.js old mode 100644 new mode 100755 diff --git a/bench/definitions/collections/registered-customers.js b/bench/definitions/collections/registered-customers.js old mode 100644 new mode 100755 diff --git a/bench/definitions/views/registered-customers.js b/bench/definitions/views/registered-customers.js old mode 100644 new mode 100755 diff --git a/bench/docker-compose.yml b/bench/docker-compose.yml old mode 100644 new mode 100755 diff --git a/bench/queryParser.bench.js b/bench/queryParser.bench.js old mode 100644 new mode 100755 diff --git a/bench/scripts/load-test.js b/bench/scripts/load-test.js old mode 100644 new mode 100755 index 58ddda1f..0e1970f7 --- a/bench/scripts/load-test.js +++ b/bench/scripts/load-test.js @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import http from 'k6/http'; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { check, sleep } from 'k6'; diff --git a/bench/scripts/runner.js b/bench/scripts/runner.js old mode 100644 new mode 100755 index 8f83f98f..b1fecf2e --- a/bench/scripts/runner.js +++ b/bench/scripts/runner.js @@ -1,6 +1,26 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import http from 'k6/http'; import { sleep } from 'k6'; +// +// This file represent a stub of a k6 test. Feel free to modify based on your needs. +// + export const options = { stages: [ { duration: '30s', target: 5 }, diff --git a/bench/scripts/smoke-test.js b/bench/scripts/smoke-test.js old mode 100644 new mode 100755 index 1fa7ea2e..ea54845b --- a/bench/scripts/smoke-test.js +++ b/bench/scripts/smoke-test.js @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { executeGetTests } from './utils.js'; diff --git a/bench/scripts/spike-test.js b/bench/scripts/spike-test.js old mode 100644 new mode 100755 index 99f38f6f..97f7edb8 --- a/bench/scripts/spike-test.js +++ b/bench/scripts/spike-test.js @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { executeGetTests } from './utils.js'; diff --git a/bench/scripts/stress-test-on-collection.js b/bench/scripts/stress-test-on-collection.js old mode 100644 new mode 100755 index e594c04a..d5dcfb3a --- a/bench/scripts/stress-test-on-collection.js +++ b/bench/scripts/stress-test-on-collection.js @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { executeGetTests } from './utils'; diff --git a/bench/scripts/stress-test-on-view.js b/bench/scripts/stress-test-on-view.js old mode 100644 new mode 100755 index 30caadde..15f640e6 --- a/bench/scripts/stress-test-on-view.js +++ b/bench/scripts/stress-test-on-view.js @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; import { executeGetTests } from './utils.js'; diff --git a/bench/scripts/utils.js b/bench/scripts/utils.js old mode 100644 new mode 100755 index a50687e5..c90f780c --- a/bench/scripts/utils.js +++ b/bench/scripts/utils.js @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + 'use strict' import http from 'k6/http'; diff --git a/bench/utils/generate-customer-data.js b/bench/utils/generate-customer-data.js old mode 100644 new mode 100755 index f7d4bf25..1d6b7a19 --- a/bench/utils/generate-customer-data.js +++ b/bench/utils/generate-customer-data.js @@ -1,4 +1,20 @@ /* eslint-disable no-console */ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + 'use strict' const { Command } = require('commander') diff --git a/bench/utils/healthcheck.js b/bench/utils/healthcheck.js old mode 100644 new mode 100755 index 0f65ae01..487bafe4 --- a/bench/utils/healthcheck.js +++ b/bench/utils/healthcheck.js @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Mia s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + 'use strict' async function main() { From 3936e42d609aa1b48576a5891fd3b9668590788a Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 29 Jan 2024 10:39:00 +0100 Subject: [PATCH 69/70] updated readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c844c22c..7afdcd30 100644 --- a/README.md +++ b/README.md @@ -356,14 +356,13 @@ Where the script arguments are the following: - **number of total shops** (default: _250_): Defines a random value (from 1 to the specified number) applied to the shopID field of each document to be saved. -To simplify these operations, you can execute the command npm run bench:init from your shell. This command starts a container with a MongoDB 6.0 instance, a container with the CRUD Service (built from your current branch), and populates the _customers_ collection with 100,000 documents. +To simplify these operations, you can execute the command `npm run bench:init` from your shell. This command starts a container with a MongoDB 6.0 instance, a container with the CRUD Service (built from your current branch), and populates the _customers_ collection with 100,000 documents. To execute any test, start the k6 service with the following command: ``` docker compose -f bench/dc-k6.yml up ``` Remember to replace `` with one of the following: - | Service Name | Description | File name containing the test | |--------------------------------|-------------------------------------------------------------------------------------------------|------------------------------------------| | k6-load-test | Executes a Load Test (1 minute of POST, 1 minute of GET/PATCH/DELETE) on the _items_ collection | [load-test.js](bench/scripts/load-test.js) | @@ -373,7 +372,9 @@ Remember to replace `` with one of the following: | k6-spike-test | Executes a Spike Test (simulate a spike of 500 concurrent users for GET requests) on the _customers_ collection | [spike-test.js](bench/scripts/spike-test.js) | | runner | An empty test that can be populated for tests on local environment | [runner.js](bench/scripts/runner.js) | -We suggest you to use the runner to execute customized tests for your own research. +We suggest you use the runner to execute customized tests for your research. + +Also, do not run all the tests alltogether via `docker compose -f bench/dc-k6.yml up`, without specifying a test name, otherwise all the tests will run at the same time and the results will not be specific to any test but a global indication on how to service worked during the execution of **all** the tests. You are free to modify and improve those tests and the definitions used for them but please remember to not use any sensible data. From ebb0da54f5fa9f669d77a91f243abc48ffe2df6a Mon Sep 17 00:00:00 2001 From: Demetrio Marino Date: Mon, 29 Jan 2024 14:49:16 +0100 Subject: [PATCH 70/70] fix: removed duplicated title in README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 7afdcd30..83a7e3cb 100644 --- a/README.md +++ b/README.md @@ -325,8 +325,6 @@ See the documentation to see which parameters are available. ## Performance test -## Performance Test - We use [k6](https://example.com/k6) to simulate the load of traffic directed to the CRUD Service and retrieve some performance metrics. At every version released, a workflow automatically starts executing the following tests: - **Load Test**: 10 virtual users execute POST requests for one minute on the same collection, then 100 virtual users execute GET, PATCH, and DELETE requests for another minute on the data created.