Oy,
I know it is old and might be optimized already. But if you only updating attribute values only, it is a little overshoot to save the whole product.
Please see
\Magento\Catalog\Model\ResourceModel\Product\Action::updateAttributes
public function updateAttributes($entityIds, $attrData, $storeId)
example:
$this->_updateAction->updateAttributes(
$productId,
[
'attribute_name_1' => value,
'attribute_name_2' => value,
...
],
$storeId
);
as indexers, you can check if the attributes required to index as
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
$attribute->isIndexable()
and trigger EAV indexer
/** @var \Magento\Catalog\Model\Indexer\Product\Eav\Processor $productEavIndexerProcessor */
$productEavIndexerProcessor->reindexList(array_unique($productIds)))
If you amending product-category connection, you might be required to run
/** @var \Magento\Catalog\Model\Indexer\Product\Category $categoryIndexer */
$categoryIndexer->reindexList(array_unique($productIds));
as well
attribute save happening directly to DB (that why it is required to handle indexer manually)
I know the whole process (with indexer) available as
\Magento\Catalog\Model\Product\Action::updateAttributes
but if you need to set different attribute values for different products it cannot be used as calling this method will reindex automatically over and over again and you will facing performance issues again