1
0

benchmarks.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /* eslint-disable no-console */
  2. import { INestApplication } from '@nestjs/common';
  3. import { GlobalFlag } from '@vendure/common/lib/generated-types';
  4. import {
  5. bootstrap,
  6. Importer,
  7. isGraphQlErrorResult,
  8. LanguageCode,
  9. OrderService,
  10. RequestContextService,
  11. } from '@vendure/core';
  12. import { populate } from '@vendure/core/cli/populate';
  13. import { ParsedProductWithVariants } from '@vendure/core/src/index';
  14. import { clearAllTables } from '@vendure/testing';
  15. import { spawn } from 'child_process';
  16. import program from 'commander';
  17. import path from 'path';
  18. import ProgressBar from 'progress';
  19. import { getLoadTestConfig } from './load-test-config';
  20. /**
  21. * This set of benchmarks aims to specifically test the performance issues discussed
  22. * in issue https://github.com/vendurehq/vendure/issues/1506.
  23. *
  24. * In order to test these issues, we need a test dataset that will create:
  25. *
  26. * 1. 1k Products, each with 10 ProductVariants
  27. * 2. 10k Orders, each with 10 OrderLines, each OrderLine with qty 5
  28. *
  29. * Then we will test:
  30. *
  31. * 1. Fetching 10 Products from the `products` query. This will test the effects of indexes, ListQueryBuilder & dataloader
  32. * 2. As above but with Orders
  33. * 3. Fetching a list of orders and selecting only the Order id.
  34. * This will test optimization of selecting & joining only the needed fields.
  35. */
  36. const DATABASE_NAME = 'vendure-benchmarks';
  37. const PRODUCT_COUNT = 1000;
  38. const VARIANTS_PER_PRODUCT = 10;
  39. const ORDER_COUNT = 10000;
  40. const LINES_PER_ORDER = 10;
  41. const QUANTITY_PER_ORDER_LINE = 5;
  42. interface Options {
  43. script?: string;
  44. db: 'mysql' | 'postgres';
  45. populate?: boolean;
  46. variant?: string;
  47. }
  48. program
  49. .option('--script <script>', 'Specify the k6 script to run')
  50. .option('--db <db>', 'Select which database to test against', /^(mysql|postgres)$/, 'mysql')
  51. .option('--populate', 'Whether to populate the database')
  52. .option('--variant <variant>', 'Which variant of the given script')
  53. .parse(process.argv);
  54. const opts = program.opts() as any;
  55. runBenchmark(opts).then(() => process.exit(0));
  56. async function runBenchmark(options: Options) {
  57. const config = getLoadTestConfig('bearer', DATABASE_NAME, options.db);
  58. if (options.populate) {
  59. console.log(`Populating benchmark database "${DATABASE_NAME}"`);
  60. await clearAllTables(config, true);
  61. const populateApp = await populate(
  62. () => bootstrap(config),
  63. path.join(__dirname, '../../create/assets/initial-data.json'),
  64. );
  65. await createProducts(populateApp);
  66. await createOrders(populateApp);
  67. await populateApp.close();
  68. } else {
  69. const app = await bootstrap(config);
  70. await new Promise((resolve, reject) => {
  71. const runArgs: string[] = ['run', `./scripts/${options.script}`];
  72. if (options.variant) {
  73. console.log(`Using variant "${options.variant}"`);
  74. runArgs.push('-e', `variant=${options.variant}`);
  75. }
  76. const loadTest = spawn('k6', runArgs, {
  77. cwd: __dirname,
  78. stdio: 'inherit',
  79. });
  80. loadTest.on('exit', code => {
  81. if (code === 0) {
  82. resolve(code);
  83. } else {
  84. reject();
  85. }
  86. });
  87. loadTest.on('error', err => {
  88. reject(err);
  89. });
  90. });
  91. await app.close();
  92. }
  93. }
  94. async function createProducts(app: INestApplication) {
  95. const importer = app.get(Importer);
  96. const ctx = await app.get(RequestContextService).create({
  97. apiType: 'admin',
  98. });
  99. const bar = new ProgressBar('creating products [:bar] (:current/:total) :percent :etas', {
  100. complete: '=',
  101. incomplete: ' ',
  102. total: PRODUCT_COUNT,
  103. width: 40,
  104. });
  105. const products: ParsedProductWithVariants[] = [];
  106. for (let i = 0; i < PRODUCT_COUNT; i++) {
  107. const product: ParsedProductWithVariants = {
  108. product: {
  109. optionGroups: [
  110. {
  111. translations: [
  112. {
  113. languageCode: LanguageCode.en,
  114. name: `prod-${i}-option`,
  115. values: Array.from({ length: VARIANTS_PER_PRODUCT }).map(
  116. (_, opt) => `prod-${i}-option-${opt}`,
  117. ),
  118. },
  119. ],
  120. },
  121. ],
  122. translations: [
  123. {
  124. languageCode: LanguageCode.en,
  125. name: `Product ${i}`,
  126. slug: `product-${i}`,
  127. description: '',
  128. customFields: {},
  129. },
  130. ],
  131. assetPaths: [],
  132. facets: [],
  133. },
  134. variants: Array.from({ length: VARIANTS_PER_PRODUCT }).map((value, index) => ({
  135. sku: `PROD-${i}-${index}`,
  136. facets: [],
  137. assetPaths: [],
  138. price: 1000,
  139. stockOnHand: 100,
  140. taxCategory: 'standard',
  141. trackInventory: GlobalFlag.INHERIT,
  142. translations: [
  143. {
  144. languageCode: LanguageCode.en,
  145. optionValues: [`prod-${i}-option-${index}`],
  146. customFields: {},
  147. },
  148. ],
  149. })),
  150. };
  151. products.push(product);
  152. }
  153. await importer.importProducts(ctx, products, progess => bar.tick());
  154. }
  155. async function createOrders(app: INestApplication) {
  156. const orderService = app.get(OrderService);
  157. const bar = new ProgressBar('creating orders [:bar] (:current/:total) :percent :etas', {
  158. complete: '=',
  159. incomplete: ' ',
  160. total: ORDER_COUNT,
  161. width: 40,
  162. });
  163. for (let i = 0; i < ORDER_COUNT; i++) {
  164. const ctx = await app.get(RequestContextService).create({
  165. apiType: 'shop',
  166. });
  167. const order = await orderService.create(ctx);
  168. const variantId = (i % (PRODUCT_COUNT * VARIANTS_PER_PRODUCT)) + 1;
  169. const result = await orderService.addItemToOrder(ctx, order.id, variantId, QUANTITY_PER_ORDER_LINE);
  170. if (isGraphQlErrorResult(result)) {
  171. console.log(result);
  172. }
  173. bar.tick();
  174. }
  175. }