Zend certified PHP/Magento developer

Why is programmatically updating stock and price so slow?

I’m trying to update the stock and price of the products in a magento 2.3 store from a csv file.

The file I have has about 5,000 rows, and about 3,500 of those correspond to products in magento. About 1,500 of those needed update of price or SKU or both, the rest had the correct price and quantity.

When I ran this, it took about 6 hours.

I used the code below (shortened to focus on the relevant bits).

An interesting observation I made is that the first ca. 200 products went real quick, but then it almost ground to a halt, taking several minutes per product.

Can someone enlighten me (a) why this is so slow, and (b) are there better ways of achieving this?

/** @var MagentoCatalogInventoryApiStockRegistryInterface $stockRegistry */
/** @var MagentoCatalogModelResourceModelProduct $productResourceModel */
/** @var MagentoFrameworkAppCacheManager $cacheManager */
/** @var MagentoIndexerModelIndexerFactory $indexerFactory */
/** @var MagentoCatalogModelResourceModelProductCollectionFactory $productCollection */

$records = $csv->getRecords(); //returns all the CSV records in an array

// keep track of updated entity ids, so that we can reindex.
$updatedProductStocks = [];
$updatedProductPrices = [];

// Get a list of existing products in an array so we don't hit the database all the time, unless we really need to
$existingProducts = [];

$collection = $productCollection->create();
$collection->addAttributeToSelect('price')
    ->addAttributeToSelect('sku')
    ->addAttributeToSelect('name')
    ->joinField(
        'qty', 'cataloginventory_stock_item', 'qty', 'product_id=entity_id', '{{table}}.stock_id=1', 'left'
    );

// index by sku so we can find it easily
foreach( $collection as $_product ) {
    $existingProducts[$_product->getSku()] = $_product;
}

foreach ($records as $record) {

    echo "SKU: ".$record['Product Code'];

    if( !array_key_exists( $record['Product Code'], $existingProducts ) ) {
        echo "Product with SKU ".$record['Product Code']." not found";
    } else {
        $product = $existingProducts[$record['Product Code'] ];
        echo "Name: ".$product->getName();
        // update qty if changed
        if( $product->getData('qty') !== $record['Quantity In Stock'] ) {
            $stockItem = $stockRegistry->getStockItemBySku($record['Product Code']);
            $stockItem->setData('qty', $record['Quantity In Stock']);
            $stockItem->setData('is_in_stock', ($record['Quantity In Stock'] > 0 ) ? 1 : 0); 
            $stockItem->save();
            array_push($updatedProductStocks, $product->getId());
            echo 'Quantity updated to '.$record['Quantity In Stock'];
        }

        // update price if changed
        if( $product->getPrice() !== $record['Sales Price'] ) {
            $product->setPrice($record['Sales Price']);
            $productResourceModel->saveAttribute($product,'price');
            array_push($updatedProductPrices, $product->getId());
            echo 'Price updated to '.$record['Sales Price'];
        }
    }
}

// reindex and refresh cache where needed
if( $counter['updatedStock'] > 0 ) {
    $stockIndexer = $indexerFactory->create();
    $stockIndexer->load('cataloginventory_stock');
    $stockIndexer->reindexList($updatedProductStocks);
}

if( $counter['updatedStock'] > 0 ) {
    $priceIndexer = $indexerFactory->create();
    $priceIndexer->load('catalog_product_price');
    $priceIndexer->reindexList($updatedProductPrices);
}

if( $counter['updatedPrice'] > 0 || $counter['updatedStock'] > 0 ) {
    $cacheManager->clean($cacheManager->getAvailableTypes());
}