Skip to content

Commit 2883b82

Browse files
authored
Merge pull request #3918 from magento-performance/MC-13613-PR
[Performance] Execution of heavy admin operations in asynchronous flow Tasks: - MC-13864 Consumer always read config from memory - MC-13613 Product mass update
2 parents d976a2b + 37b5352 commit 2883b82

File tree

53 files changed

+1296
-1307
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1296
-1307
lines changed

app/code/Magento/AsynchronousOperations/Model/MassConsumer.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use Magento\Framework\MessageQueue\MessageLockException;
1515
use Magento\Framework\MessageQueue\ConnectionLostException;
1616
use Magento\Framework\Exception\NotFoundException;
17-
use Magento\Framework\MessageQueue\CallbackInvoker;
17+
use Magento\Framework\MessageQueue\CallbackInvokerInterface;
1818
use Magento\Framework\MessageQueue\ConsumerConfigurationInterface;
1919
use Magento\Framework\MessageQueue\EnvelopeInterface;
2020
use Magento\Framework\MessageQueue\QueueInterface;
@@ -30,7 +30,7 @@
3030
class MassConsumer implements ConsumerInterface
3131
{
3232
/**
33-
* @var \Magento\Framework\MessageQueue\CallbackInvoker
33+
* @var CallbackInvokerInterface
3434
*/
3535
private $invoker;
3636

@@ -67,7 +67,7 @@ class MassConsumer implements ConsumerInterface
6767
/**
6868
* Initialize dependencies.
6969
*
70-
* @param CallbackInvoker $invoker
70+
* @param CallbackInvokerInterface $invoker
7171
* @param ResourceConnection $resource
7272
* @param MessageController $messageController
7373
* @param ConsumerConfigurationInterface $configuration
@@ -76,7 +76,7 @@ class MassConsumer implements ConsumerInterface
7676
* @param Registry $registry
7777
*/
7878
public function __construct(
79-
CallbackInvoker $invoker,
79+
CallbackInvokerInterface $invoker,
8080
ResourceConnection $resource,
8181
MessageController $messageController,
8282
ConsumerConfigurationInterface $configuration,

app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php

Lines changed: 194 additions & 143 deletions
Large diffs are not rendered by default.
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Attribute\Backend;
9+
10+
use Magento\Framework\Exception\LocalizedException;
11+
use Magento\Framework\EntityManager\EntityManager;
12+
use Magento\Framework\Exception\NoSuchEntityException;
13+
use Magento\Framework\Exception\TemporaryStateExceptionInterface;
14+
use Magento\Framework\Bulk\OperationInterface;
15+
16+
/**
17+
* Consumer for export message.
18+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
19+
*/
20+
class Consumer
21+
{
22+
/**
23+
* @var \Psr\Log\LoggerInterface
24+
*/
25+
private $logger;
26+
27+
/**
28+
* @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor
29+
*/
30+
private $productFlatIndexerProcessor;
31+
32+
/**
33+
* @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
34+
*/
35+
private $productPriceIndexerProcessor;
36+
37+
/**
38+
* @var \Magento\Catalog\Helper\Product
39+
*/
40+
private $catalogProduct;
41+
42+
/**
43+
* @var \Magento\Catalog\Model\Product\Action
44+
*/
45+
private $productAction;
46+
47+
/**
48+
* @var \Magento\Framework\Serialize\SerializerInterface
49+
*/
50+
private $serializer;
51+
52+
/**
53+
* @var \Magento\Framework\Bulk\OperationManagementInterface
54+
*/
55+
private $operationManagement;
56+
/**
57+
* @var EntityManager
58+
*/
59+
private $entityManager;
60+
61+
/**
62+
* @param \Magento\Catalog\Helper\Product $catalogProduct
63+
* @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor
64+
* @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor
65+
* @param \Magento\Framework\Bulk\OperationManagementInterface $operationManagement
66+
* @param \Magento\Catalog\Model\Product\Action $action
67+
* @param \Psr\Log\LoggerInterface $logger
68+
* @param \Magento\Framework\Serialize\SerializerInterface $serializer
69+
* @param EntityManager $entityManager
70+
*/
71+
public function __construct(
72+
\Magento\Catalog\Helper\Product $catalogProduct,
73+
\Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
74+
\Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
75+
\Magento\Framework\Bulk\OperationManagementInterface $operationManagement,
76+
\Magento\Catalog\Model\Product\Action $action,
77+
\Psr\Log\LoggerInterface $logger,
78+
\Magento\Framework\Serialize\SerializerInterface $serializer,
79+
EntityManager $entityManager
80+
) {
81+
$this->catalogProduct = $catalogProduct;
82+
$this->productFlatIndexerProcessor = $productFlatIndexerProcessor;
83+
$this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
84+
$this->productAction = $action;
85+
$this->logger = $logger;
86+
$this->serializer = $serializer;
87+
$this->operationManagement = $operationManagement;
88+
$this->entityManager = $entityManager;
89+
}
90+
91+
/**
92+
* Process
93+
*
94+
* @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
95+
* @throws \Exception
96+
*
97+
* @return void
98+
*/
99+
public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
100+
{
101+
try {
102+
$serializedData = $operation->getSerializedData();
103+
$data = $this->serializer->unserialize($serializedData);
104+
$this->execute($data);
105+
} catch (\Zend_Db_Adapter_Exception $e) {
106+
$this->logger->critical($e->getMessage());
107+
if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
108+
|| $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
109+
|| $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
110+
) {
111+
$status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
112+
$errorCode = $e->getCode();
113+
$message = $e->getMessage();
114+
} else {
115+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
116+
$errorCode = $e->getCode();
117+
$message = __(
118+
'Sorry, something went wrong during product attributes update. Please see log for details.'
119+
);
120+
}
121+
} catch (NoSuchEntityException $e) {
122+
$this->logger->critical($e->getMessage());
123+
$status = ($e instanceof TemporaryStateExceptionInterface)
124+
? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
125+
: OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
126+
$errorCode = $e->getCode();
127+
$message = $e->getMessage();
128+
} catch (LocalizedException $e) {
129+
$this->logger->critical($e->getMessage());
130+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
131+
$errorCode = $e->getCode();
132+
$message = $e->getMessage();
133+
} catch (\Exception $e) {
134+
$this->logger->critical($e->getMessage());
135+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
136+
$errorCode = $e->getCode();
137+
$message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
138+
}
139+
140+
$operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
141+
->setErrorCode($errorCode ?? null)
142+
->setResultMessage($message ?? null);
143+
144+
$this->entityManager->save($operation);
145+
}
146+
147+
/**
148+
* Execute
149+
*
150+
* @param array $data
151+
*
152+
* @return void
153+
*/
154+
private function execute($data): void
155+
{
156+
$this->productAction->updateAttributes($data['product_ids'], $data['attributes'], $data['store_id']);
157+
if ($this->catalogProduct->isDataForPriceIndexerWasChanged($data['attributes'])) {
158+
$this->productPriceIndexerProcessor->reindexList($data['product_ids']);
159+
}
160+
161+
$this->productFlatIndexerProcessor->reindexList($data['product_ids']);
162+
}
163+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Attribute\Backend;
9+
10+
use Magento\Framework\Exception\LocalizedException;
11+
use Magento\Framework\Exception\NoSuchEntityException;
12+
use Magento\Framework\Exception\TemporaryStateExceptionInterface;
13+
use Magento\Framework\Bulk\OperationInterface;
14+
use Magento\Framework\EntityManager\EntityManager;
15+
16+
/**
17+
* Consumer for export message.
18+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
19+
*/
20+
class ConsumerWebsiteAssign
21+
{
22+
/**
23+
* @var \Psr\Log\LoggerInterface
24+
*/
25+
private $logger;
26+
27+
/**
28+
* @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor
29+
*/
30+
private $productFlatIndexerProcessor;
31+
32+
/**
33+
* @var \Magento\Catalog\Model\Product\Action
34+
*/
35+
private $productAction;
36+
37+
/**
38+
* @var \Magento\Framework\Serialize\SerializerInterface
39+
*/
40+
private $serializer;
41+
42+
/**
43+
* @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
44+
*/
45+
private $productPriceIndexerProcessor;
46+
47+
/**
48+
* @var EntityManager
49+
*/
50+
private $entityManager;
51+
52+
/**
53+
* @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor
54+
* @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor
55+
* @param \Magento\Catalog\Model\Product\Action $action
56+
* @param \Psr\Log\LoggerInterface $logger
57+
* @param \Magento\Framework\Serialize\SerializerInterface $serializer
58+
* @param EntityManager $entityManager
59+
*/
60+
public function __construct(
61+
\Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
62+
\Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
63+
\Magento\Catalog\Model\Product\Action $action,
64+
\Psr\Log\LoggerInterface $logger,
65+
\Magento\Framework\Serialize\SerializerInterface $serializer,
66+
EntityManager $entityManager
67+
) {
68+
$this->productFlatIndexerProcessor = $productFlatIndexerProcessor;
69+
$this->productAction = $action;
70+
$this->logger = $logger;
71+
$this->serializer = $serializer;
72+
$this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
73+
$this->entityManager = $entityManager;
74+
}
75+
76+
/**
77+
* Process
78+
*
79+
* @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
80+
* @throws \Exception
81+
*
82+
* @return void
83+
*/
84+
public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
85+
{
86+
try {
87+
$serializedData = $operation->getSerializedData();
88+
$data = $this->serializer->unserialize($serializedData);
89+
$this->execute($data);
90+
} catch (\Zend_Db_Adapter_Exception $e) {
91+
$this->logger->critical($e->getMessage());
92+
if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
93+
|| $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
94+
|| $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
95+
) {
96+
$status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
97+
$errorCode = $e->getCode();
98+
$message = __($e->getMessage());
99+
} else {
100+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
101+
$errorCode = $e->getCode();
102+
$message = __(
103+
'Sorry, something went wrong during product attributes update. Please see log for details.'
104+
);
105+
}
106+
} catch (NoSuchEntityException $e) {
107+
$this->logger->critical($e->getMessage());
108+
$status = ($e instanceof TemporaryStateExceptionInterface)
109+
? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
110+
: OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
111+
$errorCode = $e->getCode();
112+
$message = $e->getMessage();
113+
} catch (LocalizedException $e) {
114+
$this->logger->critical($e->getMessage());
115+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
116+
$errorCode = $e->getCode();
117+
$message = $e->getMessage();
118+
} catch (\Exception $e) {
119+
$this->logger->critical($e->getMessage());
120+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
121+
$errorCode = $e->getCode();
122+
$message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
123+
}
124+
125+
$operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
126+
->setErrorCode($errorCode ?? null)
127+
->setResultMessage($message ?? null);
128+
129+
$this->entityManager->save($operation);
130+
}
131+
132+
/**
133+
* Update website in products
134+
*
135+
* @param array $productIds
136+
* @param array $websiteRemoveData
137+
* @param array $websiteAddData
138+
*
139+
* @return void
140+
*/
141+
private function updateWebsiteInProducts($productIds, $websiteRemoveData, $websiteAddData): void
142+
{
143+
if ($websiteRemoveData) {
144+
$this->productAction->updateWebsites($productIds, $websiteRemoveData, 'remove');
145+
}
146+
if ($websiteAddData) {
147+
$this->productAction->updateWebsites($productIds, $websiteAddData, 'add');
148+
}
149+
}
150+
151+
/**
152+
* Execute
153+
*
154+
* @param array $data
155+
*
156+
* @return void
157+
*/
158+
private function execute($data): void
159+
{
160+
$this->updateWebsiteInProducts(
161+
$data['product_ids'],
162+
$data['attributes']['website_detach'],
163+
$data['attributes']['website_assign']
164+
);
165+
$this->productPriceIndexerProcessor->reindexList($data['product_ids']);
166+
$this->productFlatIndexerProcessor->reindexList($data['product_ids']);
167+
}
168+
}

app/code/Magento/Catalog/Model/Product/Action.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,7 @@ public function updateWebsites($productIds, $websiteIds, $type)
168168
if (!$categoryIndexer->isScheduled()) {
169169
$categoryIndexer->reindexList(array_unique($productIds));
170170
}
171+
172+
$this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
171173
}
172174
}

app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@
5454
<fillField stepKey="fillPrice" selector="{{AdminEditProductAttributesSection.AttributePrice}}" userInput="90.99"/>
5555
<click stepKey="clickOnSaveButton" selector="{{AdminEditProductAttributesSection.Save}}"/>
5656
<waitForPageLoad stepKey="waitForUpdatedProductToSave" />
57-
<see selector="{{AdminProductMessagesSection.successMessage}}" userInput="A total of 2 record(s) were updated." stepKey="seeAttributeUpateSuccessMsg"/>
57+
<see selector="{{AdminProductMessagesSection.successMessage}}" userInput="Message is added to queue" stepKey="seeAttributeUpateSuccessMsg"/>
58+
59+
<!-- Run cron twice -->
60+
<magentoCLI command="cron:run" stepKey="runCron1"/>
61+
<magentoCLI command="cron:run" stepKey="runCron2"/>
62+
<reloadPage stepKey="refreshPage"/>
63+
<waitForPageLoad stepKey="waitFormToReload1"/>
5864

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

0 commit comments

Comments
 (0)