Skip to content

Commit

Permalink
Merge pull request #3918 from magento-performance/MC-13613-PR
Browse files Browse the repository at this point in the history
[Performance] 
Execution of heavy admin operations in asynchronous flow
Tasks:
- MC-13864 Consumer always read config from memory
- MC-13613 Product mass update
  • Loading branch information
duhon authored Mar 20, 2019
2 parents d976a2b + 37b5352 commit 2883b82
Show file tree
Hide file tree
Showing 53 changed files with 1,296 additions and 1,307 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use Magento\Framework\MessageQueue\MessageLockException;
use Magento\Framework\MessageQueue\ConnectionLostException;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\MessageQueue\CallbackInvoker;
use Magento\Framework\MessageQueue\CallbackInvokerInterface;
use Magento\Framework\MessageQueue\ConsumerConfigurationInterface;
use Magento\Framework\MessageQueue\EnvelopeInterface;
use Magento\Framework\MessageQueue\QueueInterface;
Expand All @@ -30,7 +30,7 @@
class MassConsumer implements ConsumerInterface
{
/**
* @var \Magento\Framework\MessageQueue\CallbackInvoker
* @var CallbackInvokerInterface
*/
private $invoker;

Expand Down Expand Up @@ -67,7 +67,7 @@ class MassConsumer implements ConsumerInterface
/**
* Initialize dependencies.
*
* @param CallbackInvoker $invoker
* @param CallbackInvokerInterface $invoker
* @param ResourceConnection $resource
* @param MessageController $messageController
* @param ConsumerConfigurationInterface $configuration
Expand All @@ -76,7 +76,7 @@ class MassConsumer implements ConsumerInterface
* @param Registry $registry
*/
public function __construct(
CallbackInvoker $invoker,
CallbackInvokerInterface $invoker,
ResourceConnection $resource,
MessageController $messageController,
ConsumerConfigurationInterface $configuration,
Expand Down

Large diffs are not rendered by default.

163 changes: 163 additions & 0 deletions app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Catalog\Model\Attribute\Backend;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\EntityManager\EntityManager;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\TemporaryStateExceptionInterface;
use Magento\Framework\Bulk\OperationInterface;

/**
* Consumer for export message.
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Consumer
{
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;

/**
* @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor
*/
private $productFlatIndexerProcessor;

/**
* @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
*/
private $productPriceIndexerProcessor;

/**
* @var \Magento\Catalog\Helper\Product
*/
private $catalogProduct;

/**
* @var \Magento\Catalog\Model\Product\Action
*/
private $productAction;

/**
* @var \Magento\Framework\Serialize\SerializerInterface
*/
private $serializer;

/**
* @var \Magento\Framework\Bulk\OperationManagementInterface
*/
private $operationManagement;
/**
* @var EntityManager
*/
private $entityManager;

/**
* @param \Magento\Catalog\Helper\Product $catalogProduct
* @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor
* @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor
* @param \Magento\Framework\Bulk\OperationManagementInterface $operationManagement
* @param \Magento\Catalog\Model\Product\Action $action
* @param \Psr\Log\LoggerInterface $logger
* @param \Magento\Framework\Serialize\SerializerInterface $serializer
* @param EntityManager $entityManager
*/
public function __construct(
\Magento\Catalog\Helper\Product $catalogProduct,
\Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
\Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
\Magento\Framework\Bulk\OperationManagementInterface $operationManagement,
\Magento\Catalog\Model\Product\Action $action,
\Psr\Log\LoggerInterface $logger,
\Magento\Framework\Serialize\SerializerInterface $serializer,
EntityManager $entityManager
) {
$this->catalogProduct = $catalogProduct;
$this->productFlatIndexerProcessor = $productFlatIndexerProcessor;
$this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
$this->productAction = $action;
$this->logger = $logger;
$this->serializer = $serializer;
$this->operationManagement = $operationManagement;
$this->entityManager = $entityManager;
}

/**
* Process
*
* @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
* @throws \Exception
*
* @return void
*/
public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
{
try {
$serializedData = $operation->getSerializedData();
$data = $this->serializer->unserialize($serializedData);
$this->execute($data);
} catch (\Zend_Db_Adapter_Exception $e) {
$this->logger->critical($e->getMessage());
if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
|| $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
|| $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
) {
$status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = $e->getMessage();
} else {
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = __(
'Sorry, something went wrong during product attributes update. Please see log for details.'
);
}
} catch (NoSuchEntityException $e) {
$this->logger->critical($e->getMessage());
$status = ($e instanceof TemporaryStateExceptionInterface)
? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
: OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = $e->getMessage();
} catch (LocalizedException $e) {
$this->logger->critical($e->getMessage());
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = $e->getMessage();
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
}

$operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
->setErrorCode($errorCode ?? null)
->setResultMessage($message ?? null);

$this->entityManager->save($operation);
}

/**
* Execute
*
* @param array $data
*
* @return void
*/
private function execute($data): void
{
$this->productAction->updateAttributes($data['product_ids'], $data['attributes'], $data['store_id']);
if ($this->catalogProduct->isDataForPriceIndexerWasChanged($data['attributes'])) {
$this->productPriceIndexerProcessor->reindexList($data['product_ids']);
}

$this->productFlatIndexerProcessor->reindexList($data['product_ids']);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Catalog\Model\Attribute\Backend;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\TemporaryStateExceptionInterface;
use Magento\Framework\Bulk\OperationInterface;
use Magento\Framework\EntityManager\EntityManager;

/**
* Consumer for export message.
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ConsumerWebsiteAssign
{
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;

/**
* @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor
*/
private $productFlatIndexerProcessor;

/**
* @var \Magento\Catalog\Model\Product\Action
*/
private $productAction;

/**
* @var \Magento\Framework\Serialize\SerializerInterface
*/
private $serializer;

/**
* @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
*/
private $productPriceIndexerProcessor;

/**
* @var EntityManager
*/
private $entityManager;

/**
* @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor
* @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor
* @param \Magento\Catalog\Model\Product\Action $action
* @param \Psr\Log\LoggerInterface $logger
* @param \Magento\Framework\Serialize\SerializerInterface $serializer
* @param EntityManager $entityManager
*/
public function __construct(
\Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
\Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
\Magento\Catalog\Model\Product\Action $action,
\Psr\Log\LoggerInterface $logger,
\Magento\Framework\Serialize\SerializerInterface $serializer,
EntityManager $entityManager
) {
$this->productFlatIndexerProcessor = $productFlatIndexerProcessor;
$this->productAction = $action;
$this->logger = $logger;
$this->serializer = $serializer;
$this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
$this->entityManager = $entityManager;
}

/**
* Process
*
* @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
* @throws \Exception
*
* @return void
*/
public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
{
try {
$serializedData = $operation->getSerializedData();
$data = $this->serializer->unserialize($serializedData);
$this->execute($data);
} catch (\Zend_Db_Adapter_Exception $e) {
$this->logger->critical($e->getMessage());
if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
|| $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
|| $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
) {
$status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = __($e->getMessage());
} else {
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = __(
'Sorry, something went wrong during product attributes update. Please see log for details.'
);
}
} catch (NoSuchEntityException $e) {
$this->logger->critical($e->getMessage());
$status = ($e instanceof TemporaryStateExceptionInterface)
? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
: OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = $e->getMessage();
} catch (LocalizedException $e) {
$this->logger->critical($e->getMessage());
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = $e->getMessage();
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
$errorCode = $e->getCode();
$message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
}

$operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
->setErrorCode($errorCode ?? null)
->setResultMessage($message ?? null);

$this->entityManager->save($operation);
}

/**
* Update website in products
*
* @param array $productIds
* @param array $websiteRemoveData
* @param array $websiteAddData
*
* @return void
*/
private function updateWebsiteInProducts($productIds, $websiteRemoveData, $websiteAddData): void
{
if ($websiteRemoveData) {
$this->productAction->updateWebsites($productIds, $websiteRemoveData, 'remove');
}
if ($websiteAddData) {
$this->productAction->updateWebsites($productIds, $websiteAddData, 'add');
}
}

/**
* Execute
*
* @param array $data
*
* @return void
*/
private function execute($data): void
{
$this->updateWebsiteInProducts(
$data['product_ids'],
$data['attributes']['website_detach'],
$data['attributes']['website_assign']
);
$this->productPriceIndexerProcessor->reindexList($data['product_ids']);
$this->productFlatIndexerProcessor->reindexList($data['product_ids']);
}
}
2 changes: 2 additions & 0 deletions app/code/Magento/Catalog/Model/Product/Action.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,7 @@ public function updateWebsites($productIds, $websiteIds, $type)
if (!$categoryIndexer->isScheduled()) {
$categoryIndexer->reindexList(array_unique($productIds));
}

$this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@
<fillField stepKey="fillPrice" selector="{{AdminEditProductAttributesSection.AttributePrice}}" userInput="90.99"/>
<click stepKey="clickOnSaveButton" selector="{{AdminEditProductAttributesSection.Save}}"/>
<waitForPageLoad stepKey="waitForUpdatedProductToSave" />
<see selector="{{AdminProductMessagesSection.successMessage}}" userInput="A total of 2 record(s) were updated." stepKey="seeAttributeUpateSuccessMsg"/>
<see selector="{{AdminProductMessagesSection.successMessage}}" userInput="Message is added to queue" stepKey="seeAttributeUpateSuccessMsg"/>

<!-- Run cron twice -->
<magentoCLI command="cron:run" stepKey="runCron1"/>
<magentoCLI command="cron:run" stepKey="runCron2"/>
<reloadPage stepKey="refreshPage"/>
<waitForPageLoad stepKey="waitFormToReload1"/>

<!--Verify product name, sku and updated price-->
<click stepKey="openFirstProduct" selector="{{AdminProductGridSection.productRowBySku($$simpleProduct1.sku$$)}}"/>
Expand Down
Loading

0 comments on commit 2883b82

Please sign in to comment.