| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- /* eslint-disable no-console */
- import { INestApplication } from '@nestjs/common';
- import { GlobalFlag } from '@vendure/common/lib/generated-types';
- import {
- bootstrap,
- Importer,
- isGraphQlErrorResult,
- LanguageCode,
- OrderService,
- RequestContextService,
- } from '@vendure/core';
- import { populate } from '@vendure/core/cli/populate';
- import { ParsedProductWithVariants } from '@vendure/core/src/index';
- import { clearAllTables } from '@vendure/testing';
- import { spawn } from 'child_process';
- import program from 'commander';
- import path from 'path';
- import ProgressBar from 'progress';
- import { getLoadTestConfig } from './load-test-config';
- /**
- * This set of benchmarks aims to specifically test the performance issues discussed
- * in issue https://github.com/vendure-ecommerce/vendure/issues/1506.
- *
- * In order to test these issues, we need a test dataset that will create:
- *
- * 1. 1k Products, each with 10 ProductVariants
- * 2. 10k Orders, each with 10 OrderLines, each OrderLine with qty 5
- *
- * Then we will test:
- *
- * 1. Fetching 10 Products from the `products` query. This will test the effects of indexes, ListQueryBuilder & dataloader
- * 2. As above but with Orders
- * 3. Fetching a list of orders and selecting only the Order id.
- * This will test optimization of selecting & joining only the needed fields.
- */
- const DATABASE_NAME = 'vendure-benchmarks';
- const PRODUCT_COUNT = 1000;
- const VARIANTS_PER_PRODUCT = 10;
- const ORDER_COUNT = 10000;
- const LINES_PER_ORDER = 10;
- const QUANTITY_PER_ORDER_LINE = 5;
- interface Options {
- script?: string;
- db: 'mysql' | 'postgres';
- populate?: boolean;
- variant?: string;
- }
- program
- .option('--script <script>', 'Specify the k6 script to run')
- .option('--db <db>', 'Select which database to test against', /^(mysql|postgres)$/, 'mysql')
- .option('--populate', 'Whether to populate the database')
- .option('--variant <variant>', 'Which variant of the given script')
- .parse(process.argv);
- const opts = program.opts() as any;
- runBenchmark(opts).then(() => process.exit(0));
- async function runBenchmark(options: Options) {
- const config = getLoadTestConfig('bearer', DATABASE_NAME, options.db);
- if (options.populate) {
- console.log(`Populating benchmark database "${DATABASE_NAME}"`);
- await clearAllTables(config, true);
- const populateApp = await populate(
- () => bootstrap(config),
- path.join(__dirname, '../../create/assets/initial-data.json'),
- );
- await createProducts(populateApp);
- await createOrders(populateApp);
- await populateApp.close();
- } else {
- const app = await bootstrap(config);
- await new Promise((resolve, reject) => {
- const runArgs: string[] = ['run', `./scripts/${options.script}`];
- if (options.variant) {
- console.log(`Using variant "${options.variant}"`);
- runArgs.push('-e', `variant=${options.variant}`);
- }
- const loadTest = spawn('k6', runArgs, {
- cwd: __dirname,
- stdio: 'inherit',
- });
- loadTest.on('exit', code => {
- if (code === 0) {
- resolve(code);
- } else {
- reject();
- }
- });
- loadTest.on('error', err => {
- reject(err);
- });
- });
- await app.close();
- }
- }
- async function createProducts(app: INestApplication) {
- const importer = app.get(Importer);
- const ctx = await app.get(RequestContextService).create({
- apiType: 'admin',
- });
- const bar = new ProgressBar('creating products [:bar] (:current/:total) :percent :etas', {
- complete: '=',
- incomplete: ' ',
- total: PRODUCT_COUNT,
- width: 40,
- });
- const products: ParsedProductWithVariants[] = [];
- for (let i = 0; i < PRODUCT_COUNT; i++) {
- const product: ParsedProductWithVariants = {
- product: {
- optionGroups: [
- {
- translations: [
- {
- languageCode: LanguageCode.en,
- name: `prod-${i}-option`,
- values: Array.from({ length: VARIANTS_PER_PRODUCT }).map(
- (_, opt) => `prod-${i}-option-${opt}`,
- ),
- },
- ],
- },
- ],
- translations: [
- {
- languageCode: LanguageCode.en,
- name: `Product ${i}`,
- slug: `product-${i}`,
- description: '',
- customFields: {},
- },
- ],
- assetPaths: [],
- facets: [],
- },
- variants: Array.from({ length: VARIANTS_PER_PRODUCT }).map((value, index) => ({
- sku: `PROD-${i}-${index}`,
- facets: [],
- assetPaths: [],
- price: 1000,
- stockOnHand: 100,
- taxCategory: 'standard',
- trackInventory: GlobalFlag.INHERIT,
- translations: [
- {
- languageCode: LanguageCode.en,
- optionValues: [`prod-${i}-option-${index}`],
- customFields: {},
- },
- ],
- })),
- };
- products.push(product);
- }
- await importer.importProducts(ctx, products, progess => bar.tick());
- }
- async function createOrders(app: INestApplication) {
- const orderService = app.get(OrderService);
- const bar = new ProgressBar('creating orders [:bar] (:current/:total) :percent :etas', {
- complete: '=',
- incomplete: ' ',
- total: ORDER_COUNT,
- width: 40,
- });
- for (let i = 0; i < ORDER_COUNT; i++) {
- const ctx = await app.get(RequestContextService).create({
- apiType: 'shop',
- });
- const order = await orderService.create(ctx);
- const variantId = (i % (PRODUCT_COUNT * VARIANTS_PER_PRODUCT)) + 1;
- const result = await orderService.addItemToOrder(ctx, order.id, variantId, QUANTITY_PER_ORDER_LINE);
- if (isGraphQlErrorResult(result)) {
- console.log(result);
- }
- bar.tick();
- }
- }
|