6

I upsert products like this:

try {
    $newProduct = $this->productRepository->get($productData['sku']);
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
    $newProduct = $this->productInterfaceFactory->create();
}
$newProduct->setAttributeSetId($this->getAttributeSetIdFromLabel($productData['attribute_set']));

foreach ($straightAttributes as $attribute) {
    if (isset($productData[$attribute])) {
        $newProduct->setData($attribute, $productData[$attribute]);
    }
}
$savedProduct = $this->productRepository->save($newProduct);

This works, but is not performance enough. It takes me more than 8 seconds for each product in my local environment. This is too slow for my requirements: I need it for integrations, and would expect it to take less than a second in my local, and be even faster in an appropriately sized environment.

Are there simple ways to increase the performance, without switching to queues? Maybe I just do it the wrong way...?


Update: I've got a suggestion, now, open for feedback!

If I update only one attribute of a product, then the saveAttribute method of the product resource (~ 0.02s) is significantly faster than the save method of the product repository (~ 8s):

$newProduct = $this->productRepository->get($productData['sku']);
$myAttribute = 'ean13';
$newProduct->setData($myAttribute, '1234567890123');
/**
 * @var \Magento\Catalog\Model\ResourceModel\Product
 */
$productResource->saveAttribute($newProduct, $myAttribute);

This is great so far, but unfortunately, that saveAttribute method can handle only one attribute per call. If I want to update 30 attributes, that method has to be called 30 times. Not too difficult to implement with a loop, one could say. But the dark side of this workaround are the resulting 30 sql requests, which might sum up in a larger installation with some thousands of products to be updated.

I could reduce the number of SQL requests by checking the difference between $newProduct->get...() and the new value to be written. If I use the saveAttribute method only in cases where the two values differ, I can probably save a lot of time (depending on the number of values changed).

Still, that solution is not perfect yet: Some field values cannot be updated via the saveAttribute method. One example is the sku (see https://github.com/magento/magento2/issues/7247); I also had issues with the attribute set. And then, there are the more complex "attributes". It gets fancy, where the assignment of products to websites is required to be changed dynamically: While this can be done easily via the save method ...

$myProduct->setWebsiteIds($websiteIds);
$savedProduct = $this->productRepository->save($myProduct);

... the saveAttribute method horribly fails. It is meant to fail, because it is not much more than a wrapper for some upsert queries in the product attribute tables, - and that's not where the assignment of products to websites is stored.

Can one still make use of the significant performance advantages of the saveAttribute method for a product integration?

I would assume that one can check all conditions first, and go for the saveAttribute path only, if all changes on the product can be handled by the saveAttribute method:

if (!storeAssignmentHasChanged() && !complexProductAttributesHaveChanged()) {
  // saveAttribute
} else {
  setData...
  save...
}

All this seems, however, a bit too much for a simple product upsert task. Are there ideas to simplify it?


Update:

My colleague suggested to create a product with very "required attributes only", and then use the attribute update methods for all changes.

I've tried that out in a gist https://gist.github.com/kmddevdani/fd0c068e9bb36fec905ce259b44b13f5. It does create products, but still, the save is quite slow (around 22 seconds in my local environment).

1 Answer 1

0

Probably you already found a solution for this, but if not, I suggest you take a look at \Magento\Catalog\Model\Product\Action::updateAttributes. This was present in the codebase since 2011, so should be still relevant.

You could send in the $productIds param an array with only one product ID, and in the $attrData param a list of attributes as key => value (see \Magento\Catalog\Model\ResourceModel\Product\Action::updateAttributes how the attributes are updated).

This of course means that the product should still exist, otherwise how would you update a product that doesn't exist?

I think this logic would make sense:
If new -> create product with all attributes
If existing -> update only attributes

My colleague suggested to create a product with very "required attributes only", and then use the attribute update methods for all changes.

I don't think this will make a difference, but I may be wrong, so worth giving a try if it makes a difference when creating a new product. (the only thing I could think of is that for products with a larger amount of data, i.e. attributes, it would take more time to save, so creating it first with only the required attributes may make more sense)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.