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).