Performance optimisation is not a new science and is very important for an online webshop that’s serving lots of customers and selling lots of SKU’s. In this article, I’ll try to explain how to save products 100 times faster.

First of all, this method can not be used to create products, only to update them and it’s coming with drawbacks: for example, cache tags are not getting invalidated and because the product itself won’t be saved, the _before_save and _after_save events will not fire. But this is usually not a problem because we run the indexer and clear the caches after a bulk product update programmatically or mark these out of sync so the admin user will be notified and can clear the cache manually. It’s a very safe method compared to raw SQL scripts because future updates of Magento will not cause it to break. Using core Magento methods will be more likely future proof in the code.

In the teaser, I used $product->save() to describe what needs to be done. The model’s save() method is deprecated and will be removed from future Magento versions, so don’t use it. Instead, add the ProductRepository class (\Magento\Catalog\Api\ProductRepositoryInterface) to your constructor and use it to save the product this way:

$this->productRepository->save($product);

This is another way to future-proof your code. The model’s save() method is deprecated since Magento 2.1, but still there in Magento 2.3-dev.

In order to know if the product is new while attempting to create it, we will use to ProductFactory class (\Magento\Catalog\Api\Data\ProductInterfaceFactory):

    try {
        $product = $this->productRepository->get($sku);
        $isNew = false;
    } catch (NoSuchEntityException $e) {
        $product = $this->productFactory->create();
        $isNew = true;
    }

Using this try-catch structure is safer than using the $product->getId() to test if the product is new.

Here comes the trick. In case it’s an existing product, we don’t have to save the whole product to update description, product images, stock status or any attribute on the product using \Magento\Catalog\Model\ResourceModel\Product. It’s entirely possible to update a single attribute in the fraction of time compared to saving the product:

$product->setDescription($description);
$productResource->saveAttribute($product, 'description');

To further speed up the process, we need to eliminate the loading of the product to check if the product exists. That’s why I used the ProductRepository::get() method. The ProductRepository is not only keeping a cached array of already loaded products, but it’s also using the ProductResource to test if the SKU exist before attempting to load the entity, saving valuable resources. Here is the code that’s doing the magic:

    /**
     * {@inheritdoc}
     */
    public function get($sku, $editMode = false, $storeId = null, $forceReload = false)
    {
        $cacheKey = $this->getCacheKey([$editMode, $storeId]);
        if (!isset($this->instances[$sku][$cacheKey]) || $forceReload) {
            $product = $this->productFactory->create();

            $productId = $this->resourceModel->getIdBySku($sku);
            if (!$productId) {
                throw new NoSuchEntityException(__('Requested product doesn\'t exist'));
            }
            if ($editMode) {
                $product->setData('_edit_mode', true);
            }
            if ($storeId !== null) {
                $product->setData('store_id', $storeId);
            }
            $product->load($productId);
            $this->cacheProduct($cacheKey, $product);
        }
        if (!isset($this->instances[$sku])) {
            $sku = trim($sku);
        }
        return $this->instances[$sku][$cacheKey];
    }

$productId = $this->resourceModel->getIdBySku($sku); is checking if the product SKU exist in the database and if not, it’s throwing a NoSuchEntityException. This is the exception we need to catch in our code and create the product if it didn’t exist.

Let’s be clear about saving > 20 thousand products periodically. If you are doing this every day, then it’s a design error, because you should not update the whole inventory daily. Instead, only change what’s changed in the warehouse. Unfortunately, there are old warehouse management software still running in production. Usually on a mainframe or on a DOS-era machine, because replacing them would not be cost-effective. For this reason, sometimes we need to update the whole catalogue every day or even more often.

Updated:

Comments