fix(product): Update performance issue (#14150)
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
3cf1e5e9de
commit
0f835381e9
@@ -309,6 +309,7 @@ export function moduleIntegrationTestRunner<TService = any>({
|
||||
moduleDependencies,
|
||||
joinerConfig = [],
|
||||
schema = "public",
|
||||
dbName,
|
||||
debug = false,
|
||||
testSuite,
|
||||
resolve,
|
||||
@@ -337,6 +338,7 @@ export function moduleIntegrationTestRunner<TService = any>({
|
||||
moduleDependencies,
|
||||
joinerConfig,
|
||||
schema,
|
||||
dbName,
|
||||
debug,
|
||||
resolve,
|
||||
injectedDependencies,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ProductImage,
|
||||
ProductType,
|
||||
} from "@models"
|
||||
import { setTimeout } from "timers/promises"
|
||||
|
||||
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||
import { UpdateProductInput } from "@types"
|
||||
@@ -24,6 +25,8 @@ jest.setTimeout(300000)
|
||||
|
||||
moduleIntegrationTestRunner<IProductModuleService>({
|
||||
moduleName: Modules.PRODUCT,
|
||||
// dbName: "product_update_performance",
|
||||
// debug: true,
|
||||
testSuite: ({ MikroOrmWrapper, service }) => {
|
||||
describe("ProductModuleService products", function () {
|
||||
let productCollectionOne: ProductCollection
|
||||
@@ -171,6 +174,162 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
productTwo = res[1]
|
||||
})
|
||||
|
||||
it.skip("test update performance", async () => {
|
||||
const PRODUCT_COUNT = 1000
|
||||
const VARIANTS_PER_PRODUCT = 100
|
||||
const OPTION_VALUES_COUNT = 10 // 10 x 10 = 100 variant combinations
|
||||
|
||||
// Generate option values for 2 options
|
||||
const sizeValues = Array.from(
|
||||
{ length: OPTION_VALUES_COUNT },
|
||||
(_, i) => `size-${i + 1}`
|
||||
)
|
||||
const colorValues = Array.from(
|
||||
{ length: OPTION_VALUES_COUNT },
|
||||
(_, i) => `color-${i + 1}`
|
||||
)
|
||||
|
||||
// Generate all variant combinations
|
||||
const generateVariants = () => {
|
||||
const variants: {
|
||||
title: string
|
||||
sku: string
|
||||
options: { size: string; color: string }
|
||||
}[] = []
|
||||
|
||||
for (let s = 0; s < OPTION_VALUES_COUNT; s++) {
|
||||
for (let c = 0; c < OPTION_VALUES_COUNT; c++) {
|
||||
variants.push({
|
||||
title: `Variant ${sizeValues[s]}-${colorValues[c]}`,
|
||||
sku: `SKU-${sizeValues[s]}-${
|
||||
colorValues[c]
|
||||
}-${Date.now()}-${Math.random()}`,
|
||||
options: {
|
||||
size: sizeValues[s],
|
||||
color: colorValues[c],
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return variants
|
||||
}
|
||||
|
||||
// Generate random number of images (10-50)
|
||||
const generateImages = () => {
|
||||
const imageCount = Math.floor(Math.random() * 41) + 10 // 10-50 images
|
||||
return Array.from({ length: imageCount }, (_, i) => ({
|
||||
url: `https://example.com/image-${
|
||||
i + 1
|
||||
}-${Date.now()}-${Math.random()}.jpg`,
|
||||
}))
|
||||
}
|
||||
|
||||
// Build product data
|
||||
const productsData = Array.from(
|
||||
{ length: PRODUCT_COUNT },
|
||||
(_, i) => ({
|
||||
title: `Performance Test Product ${i + 1}`,
|
||||
handle: `perf-product-${i + 1}-${Date.now()}`,
|
||||
status: ProductStatus.PUBLISHED,
|
||||
options: [
|
||||
{ title: "size", values: sizeValues },
|
||||
{ title: "color", values: colorValues },
|
||||
],
|
||||
variants: generateVariants(),
|
||||
images: generateImages(),
|
||||
})
|
||||
)
|
||||
|
||||
console.log(`Creating ${PRODUCT_COUNT} products...`)
|
||||
console.log(`Each product has ${VARIANTS_PER_PRODUCT} variants`)
|
||||
console.log(
|
||||
`Each product has 2 options with ${OPTION_VALUES_COUNT} values each`
|
||||
)
|
||||
console.log(
|
||||
`Each product has 10-50 images (random), total images: ${productsData.reduce(
|
||||
(sum, p) => sum + p.images.length,
|
||||
0
|
||||
)}`
|
||||
)
|
||||
|
||||
const startTime = Date.now()
|
||||
|
||||
// Create products in batches to avoid memory issues
|
||||
const BATCH_SIZE = 10
|
||||
const createdProducts: any[] = []
|
||||
|
||||
for (let i = 0; i < PRODUCT_COUNT; i += BATCH_SIZE) {
|
||||
const batch = productsData.slice(i, i + BATCH_SIZE)
|
||||
const batchStart = Date.now()
|
||||
|
||||
const products = await service.createProducts(batch)
|
||||
createdProducts.push(...products)
|
||||
|
||||
const batchEnd = Date.now()
|
||||
console.log(
|
||||
`Batch ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(
|
||||
PRODUCT_COUNT / BATCH_SIZE
|
||||
)} created in ${batchEnd - batchStart}ms`
|
||||
)
|
||||
}
|
||||
|
||||
const createEndTime = Date.now()
|
||||
console.log(`\nTotal creation time: ${createEndTime - startTime}ms`)
|
||||
console.log(
|
||||
`Average per product: ${
|
||||
(createEndTime - startTime) / PRODUCT_COUNT
|
||||
}ms`
|
||||
)
|
||||
|
||||
// Retrieve a sample product to verify structure
|
||||
const sampleProduct = await service.retrieveProduct(
|
||||
createdProducts[0].id,
|
||||
{
|
||||
relations: ["variants", "images", "options", "options.values"],
|
||||
}
|
||||
)
|
||||
|
||||
console.log(`\nSample product verification:`)
|
||||
console.log(` - Variants: ${sampleProduct.variants.length}`)
|
||||
console.log(` - Options: ${sampleProduct.options.length}`)
|
||||
console.log(` - Images: ${sampleProduct.images.length}`)
|
||||
|
||||
/**
|
||||
* ----------------------------------------------------------------------------
|
||||
* ----------------------------------------------------------------------------
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
console.log(`IT IS TIME TO CLEAR THE LOGS`)
|
||||
await setTimeout(2000)
|
||||
|
||||
const productToUpdateId = createdProducts[0].id
|
||||
createdProducts[0].variants[0].title = "updated variant 1"
|
||||
|
||||
function formatVariantOptions(variant) {
|
||||
const result = {}
|
||||
for (const option of variant.options) {
|
||||
result[option.option.title] = option.value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
createdProducts[0].variants.forEach((variant) => {
|
||||
variant.options = formatVariantOptions(variant)
|
||||
})
|
||||
|
||||
const now = performance.now()
|
||||
await service.updateProducts(productToUpdateId, {
|
||||
title: "updated title",
|
||||
variants: createdProducts[0].variants,
|
||||
})
|
||||
const end = performance.now()
|
||||
console.log(`Update time: ${end - now}ms`)
|
||||
|
||||
console.log("break")
|
||||
}, 1000000)
|
||||
|
||||
it("should update multiple products", async () => {
|
||||
await service.upsertProducts([
|
||||
{ id: productOne.id, title: "updated title 1" },
|
||||
|
||||
@@ -58,8 +58,8 @@ import {
|
||||
UpdateTypeInput,
|
||||
VariantImageInputArray,
|
||||
} from "../types"
|
||||
import { joinerConfig } from "./../joiner-config"
|
||||
import { eventBuilders } from "../utils/events"
|
||||
import { joinerConfig } from "./../joiner-config"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -1743,12 +1743,15 @@ export default class ProductModuleService
|
||||
.registerSubscriber(new subscriber(sharedContext))
|
||||
}
|
||||
|
||||
const productIds = data.map((d) => d.id).filter(Boolean)
|
||||
|
||||
const originalProducts = await this.productService_.list(
|
||||
{
|
||||
id: data.map((d) => d.id),
|
||||
id: productIds,
|
||||
},
|
||||
{
|
||||
relations: ["options", "options.values", "variants", "images", "tags"],
|
||||
relations: ["options", "options.values", "tags"],
|
||||
take: productIds.length,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
@@ -2012,10 +2015,9 @@ export default class ProductModuleService
|
||||
// Re map options to handle non serialized data as well
|
||||
dbOptions =
|
||||
originalProducts
|
||||
?.map((originalProduct) =>
|
||||
?.flatMap((originalProduct) =>
|
||||
originalProduct.options.map((option) => option)
|
||||
)
|
||||
.flat()
|
||||
.filter(Boolean) ?? []
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user