Browse Source

refactor(server): Optimize product import

Remove redundant DB calls which sped up test import of ~8000 products from 20+ minutes to 9.5 minutes.
Michael Bromley 7 years ago
parent
commit
a7f63b060e

+ 21 - 10
server/src/data-import/providers/importer/importer.ts

@@ -27,6 +27,11 @@ export type OnProgressFn = (progess: ImportProgress) => void;
 @Injectable()
 export class Importer {
     private taxCategoryMatches: { [name: string]: string } = {};
+    /**
+     * This map is used to cache created assets against the file name. This prevents
+     * multiple assets being created for a single identical file.
+     */
+    private assetMap = new Map<string, Asset>();
 
     constructor(
         private configService: ConfigService,
@@ -202,17 +207,23 @@ export class Importer {
         const errors: string[] = [];
         const { importAssetsDir } = this.configService.importExportOptions;
         for (const assetPath of assetPaths) {
-            const filename = path.join(importAssetsDir, assetPath);
-            if (fs.existsSync(filename)) {
-                try {
-                    const stream = fs.createReadStream(filename);
-                    const asset = await this.assetService.createFromFileStream(stream);
-                    assets.push(asset);
-                } catch (err) {
-                    errors.push(err.toString());
-                }
+            const cachedAsset = this.assetMap.get(assetPath);
+            if (cachedAsset) {
+                assets.push(cachedAsset);
             } else {
-                errors.push(`File "${filename}" does not exist`);
+                const filename = path.join(importAssetsDir, assetPath);
+                if (fs.existsSync(filename)) {
+                    try {
+                        const stream = fs.createReadStream(filename);
+                        const asset = await this.assetService.createFromFileStream(stream);
+                        this.assetMap.set(assetPath, asset);
+                        assets.push(asset);
+                    } catch (err) {
+                        errors.push(err.toString());
+                    }
+                } else {
+                    errors.push(`File "${filename}" does not exist`);
+                }
             }
         }
         return { assets, errors };

+ 11 - 2
server/src/service/helpers/asset-updater/asset-updater.ts

@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import { Connection } from 'typeorm';
 
+import { idsAreEqual } from '../../../common/utils';
 import { Asset, VendureEntity } from '../../../entity';
 import { AssetService } from '../../services/asset.service';
 
@@ -27,8 +28,16 @@ export class AssetUpdater {
             if (input.assetIds) {
                 const assets = await this.assetService.findByIds(input.assetIds);
                 product.assets = assets;
-            }
-            if (input.featuredAssetId) {
+                const featuredAssetId = input.featuredAssetId;
+                if (featuredAssetId) {
+                    // If the featuredAsset is also being set, we save the additional
+                    // DB query since we already have that asset from the findByIds query.
+                    const featuredAsset = assets.find(a => idsAreEqual(a.id, featuredAssetId));
+                    if (featuredAsset) {
+                        product.featuredAsset = featuredAsset;
+                    }
+                }
+            } else if (input.featuredAssetId) {
                 const featuredAsset = await this.assetService.findOne(input.featuredAssetId);
                 if (featuredAsset) {
                     product.featuredAsset = featuredAsset;

+ 3 - 2
server/src/service/services/product-variant.service.ts

@@ -88,8 +88,9 @@ export class ProductVariantService {
             beforeSave: async variant => {
                 const { optionIds } = input;
                 if (optionIds && optionIds.length) {
-                    const options = await this.connection.getRepository(ProductOption).find();
-                    const selectedOptions = options.filter(og => optionIds.includes(og.id as string));
+                    const selectedOptions = await this.connection
+                        .getRepository(ProductOption)
+                        .findByIds(optionIds);
                     variant.options = selectedOptions;
                 }
                 variant.product = product;