Skip to content

Commit a01560a

Browse files
Indrani SonawaneIndrani Sonawane
authored andcommitted
Merge remote-tracking branch '37889/feature/catalogrule_product_indexer_performance' into 247beta3_jan
2 parents a257bff + 028bd11 commit a01560a

8 files changed

+599
-154
lines changed

app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Magento\CatalogRule\Model\Indexer;
88

9+
use Exception;
910
use Magento\Catalog\Model\Product;
1011
use Magento\Catalog\Model\ProductFactory;
1112
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
@@ -28,6 +29,7 @@
2829
use Magento\Store\Model\ScopeInterface;
2930
use Magento\Store\Model\StoreManagerInterface;
3031
use Psr\Log\LoggerInterface;
32+
use Zend_Db_Statement_Exception;
3133

3234
/**
3335
* Catalog rule index builder
@@ -181,6 +183,16 @@ class IndexBuilder
181183
*/
182184
private $productCollectionFactory;
183185

186+
/**
187+
* @var ReindexRuleProductsPrice
188+
*/
189+
private $reindexRuleProductsPrice;
190+
191+
/**
192+
* @var int
193+
*/
194+
private $productBatchSize;
195+
184196
/**
185197
* @param RuleCollectionFactory $ruleCollectionFactory
186198
* @param PriceCurrencyInterface $priceCurrency
@@ -204,6 +216,8 @@ class IndexBuilder
204216
* @param TimezoneInterface|null $localeDate
205217
* @param ProductCollectionFactory|null $productCollectionFactory
206218
* @param IndexerRegistry|null $indexerRegistry
219+
* @param ReindexRuleProductsPrice|null $reindexRuleProductsPrice
220+
* @param int $productBatchSize
207221
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
208222
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
209223
*/
@@ -229,7 +243,9 @@ public function __construct(
229243
TableSwapper $tableSwapper = null,
230244
TimezoneInterface $localeDate = null,
231245
ProductCollectionFactory $productCollectionFactory = null,
232-
IndexerRegistry $indexerRegistry = null
246+
IndexerRegistry $indexerRegistry = null,
247+
ReindexRuleProductsPrice $reindexRuleProductsPrice = null,
248+
int $productBatchSize = 1000
233249
) {
234250
$this->resource = $resource;
235251
$this->connection = $resource->getConnection();
@@ -242,6 +258,7 @@ public function __construct(
242258
$this->dateTime = $dateTime;
243259
$this->productFactory = $productFactory;
244260
$this->batchCount = $batchCount;
261+
$this->productBatchSize = $productBatchSize;
245262

246263
$this->productPriceCalculator = $productPriceCalculator ?? ObjectManager::getInstance()->get(
247264
ProductPriceCalculator::class
@@ -275,6 +292,8 @@ public function __construct(
275292
ObjectManager::getInstance()->get(IndexerRegistry::class);
276293
$this->productCollectionFactory = $productCollectionFactory ??
277294
ObjectManager::getInstance()->get(ProductCollectionFactory::class);
295+
$this->reindexRuleProductsPrice = $reindexRuleProductsPrice ??
296+
ObjectManager::getInstance()->get(ReindexRuleProductsPrice::class);
278297
}
279298

280299
/**
@@ -296,7 +315,7 @@ public function reindexById($id)
296315
}
297316

298317
$this->reindexRuleGroupWebsite->execute();
299-
} catch (\Exception $e) {
318+
} catch (Exception $e) {
300319
$this->critical($e);
301320
throw new LocalizedException(
302321
__('Catalog rule indexing failed. See details in exception log.')
@@ -315,7 +334,7 @@ public function reindexByIds(array $ids)
315334
{
316335
try {
317336
$this->doReindexByIds($ids);
318-
} catch (\Exception $e) {
337+
} catch (Exception $e) {
319338
$this->critical($e);
320339
throw new LocalizedException(
321340
__("Catalog rule indexing failed. See details in exception log.")
@@ -328,6 +347,8 @@ public function reindexByIds(array $ids)
328347
*
329348
* @param array $ids
330349
* @return void
350+
* @throws LocalizedException
351+
* @throws Zend_Db_Statement_Exception
331352
*/
332353
protected function doReindexByIds($ids)
333354
{
@@ -340,9 +361,10 @@ protected function doReindexByIds($ids)
340361
$this->reindexRuleProduct->execute($rule, $this->batchCount);
341362
}
342363

343-
foreach ($ids as $productId) {
344-
$this->cleanProductPriceIndex([$productId]);
345-
$this->reindexRuleProductPrice->execute($this->batchCount, $productId);
364+
// batch products together, using configurable batch size parameter
365+
foreach (array_chunk($ids, $this->productBatchSize) as $productIds) {
366+
$this->cleanProductPriceIndex($productIds);
367+
$this->reindexRuleProductsPrice->execute($this->batchCount, $productIds);
346368
}
347369

348370
//the case was not handled via indexer dependency decorator or via mview configuration
@@ -367,7 +389,7 @@ public function reindexFull()
367389
{
368390
try {
369391
$this->doReindexFull();
370-
} catch (\Exception $e) {
392+
} catch (Exception $e) {
371393
$this->critical($e);
372394
throw new LocalizedException(
373395
__("Catalog rule indexing failed. See details in exception log.")
@@ -441,6 +463,7 @@ protected function cleanByIds($productIds)
441463
* @param int $productEntityId
442464
* @param array $websiteIds
443465
* @return void
466+
* @throws Exception
444467
*/
445468
private function assignProductToRule(Rule $rule, int $productEntityId, array $websiteIds): void
446469
{
@@ -502,7 +525,7 @@ private function assignProductToRule(Rule $rule, int $productEntityId, array $we
502525
* @param Rule $rule
503526
* @param Product $product
504527
* @return $this
505-
* @throws \Exception
528+
* @throws Exception
506529
* @deprecated 101.1.5
507530
* @see ReindexRuleProduct::execute
508531
* @SuppressWarnings(PHPMD.NPathComplexity)
@@ -525,6 +548,7 @@ protected function applyRule(Rule $rule, $product)
525548
* @param RuleCollection $ruleCollection
526549
* @param Product $product
527550
* @return void
551+
* @throws LocalizedException
528552
*/
529553
private function applyRules(RuleCollection $ruleCollection, Product $product): void
530554
{
@@ -590,7 +614,7 @@ protected function updateRuleProductData(Rule $rule)
590614
* Apply all rules
591615
*
592616
* @param Product|null $product
593-
* @throws \Exception
617+
* @throws Exception
594618
* @return $this
595619
* @deprecated 101.0.0
596620
* @see ReindexRuleProductPrice::execute
@@ -661,7 +685,7 @@ protected function getRuleProductsStmt($websiteId, Product $product = null)
661685
*
662686
* @param array $arrData
663687
* @return $this
664-
* @throws \Exception
688+
* @throws Exception
665689
* @deprecated 101.0.0
666690
* @see RuleProductPricesPersistor::execute
667691
*/
@@ -708,7 +732,7 @@ protected function getProduct($productId)
708732
/**
709733
* Log critical exception
710734
*
711-
* @param \Exception $e
735+
* @param Exception $e
712736
* @return void
713737
*/
714738
protected function critical($e)

app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php

Lines changed: 27 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66

77
namespace Magento\CatalogRule\Model\Indexer;
88

9+
use Magento\Framework\App\ObjectManager;
10+
use Magento\Framework\Exception\LocalizedException;
911
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
10-
use Magento\Store\Model\Store;
1112
use Magento\Store\Model\StoreManagerInterface;
13+
use Zend_Db_Statement_Exception;
1214

1315
/**
1416
* Reindex product prices according rule settings.
@@ -45,28 +47,37 @@ class ReindexRuleProductPrice
4547
*/
4648
private $useWebsiteTimezone;
4749

50+
/**
51+
* @var ReindexRuleProductsPriceProcessor
52+
*/
53+
private $reindexRuleProductsPriceProcessor;
54+
4855
/**
4956
* @param StoreManagerInterface $storeManager
5057
* @param RuleProductsSelectBuilder $ruleProductsSelectBuilder
5158
* @param ProductPriceCalculator $productPriceCalculator
5259
* @param TimezoneInterface $localeDate
5360
* @param RuleProductPricesPersistor $pricesPersistor
5461
* @param bool $useWebsiteTimezone
62+
* @param ReindexRuleProductsPriceProcessor|null $reindexRuleProductsPriceProcessor
5563
*/
5664
public function __construct(
5765
StoreManagerInterface $storeManager,
5866
RuleProductsSelectBuilder $ruleProductsSelectBuilder,
5967
ProductPriceCalculator $productPriceCalculator,
6068
TimezoneInterface $localeDate,
6169
RuleProductPricesPersistor $pricesPersistor,
62-
bool $useWebsiteTimezone = true
70+
bool $useWebsiteTimezone = true,
71+
ReindexRuleProductsPriceProcessor $reindexRuleProductsPriceProcessor = null
6372
) {
6473
$this->storeManager = $storeManager;
6574
$this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder;
6675
$this->productPriceCalculator = $productPriceCalculator;
6776
$this->localeDate = $localeDate;
6877
$this->pricesPersistor = $pricesPersistor;
6978
$this->useWebsiteTimezone = $useWebsiteTimezone;
79+
$this->reindexRuleProductsPriceProcessor = $reindexRuleProductsPriceProcessor ??
80+
ObjectManager::getInstance()->get(ReindexRuleProductsPriceProcessor::class);
7081
}
7182

7283
/**
@@ -76,6 +87,8 @@ public function __construct(
7687
* @param int|null $productId
7788
* @param bool $useAdditionalTable
7889
* @return bool
90+
* @throws LocalizedException
91+
* @throws Zend_Db_Statement_Exception
7992
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
8093
*/
8194
public function execute(int $batchCount, ?int $productId = null, bool $useAdditionalTable = false)
@@ -85,100 +98,20 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
8598
* because for each website date in website's timezone should be used
8699
*/
87100
foreach ($this->storeManager->getWebsites() as $website) {
88-
$productsStmt = $this->ruleProductsSelectBuilder->build($website->getId(), $productId, $useAdditionalTable);
89-
$dayPrices = [];
90-
$stopFlags = [];
91-
$prevKey = null;
92-
93-
$storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId());
94-
$dateInterval = $this->useWebsiteTimezone
95-
? $this->getDateInterval((int)$storeGroup->getDefaultStoreId())
96-
: $this->getDateInterval(Store::DEFAULT_STORE_ID);
97-
98-
while ($ruleData = $productsStmt->fetch()) {
99-
$ruleProductId = $ruleData['product_id'];
100-
$productKey = $ruleProductId .
101-
'_' .
102-
$ruleData['website_id'] .
103-
'_' .
104-
$ruleData['customer_group_id'];
105-
106-
if ($prevKey && $prevKey != $productKey) {
107-
$stopFlags = [];
108-
if (count($dayPrices) > $batchCount) {
109-
$this->pricesPersistor->execute($dayPrices, $useAdditionalTable);
110-
$dayPrices = [];
111-
}
112-
}
113-
114-
/**
115-
* Build prices for each day
116-
*/
117-
foreach ($dateInterval as $date) {
118-
$time = $date->getTimestamp();
119-
if (($ruleData['from_time'] == 0 ||
120-
$time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 ||
121-
$time <= $ruleData['to_time'])
122-
) {
123-
$priceKey = $time . '_' . $productKey;
124-
125-
if (isset($stopFlags[$priceKey])) {
126-
continue;
127-
}
128-
129-
if (!isset($dayPrices[$priceKey])) {
130-
$dayPrices[$priceKey] = [
131-
'rule_date' => $date,
132-
'website_id' => $ruleData['website_id'],
133-
'customer_group_id' => $ruleData['customer_group_id'],
134-
'product_id' => $ruleProductId,
135-
'rule_price' => $this->productPriceCalculator->calculate($ruleData),
136-
'latest_start_date' => $ruleData['from_time'],
137-
'earliest_end_date' => $ruleData['to_time'],
138-
];
139-
} else {
140-
$dayPrices[$priceKey]['rule_price'] = $this->productPriceCalculator->calculate(
141-
$ruleData,
142-
$dayPrices[$priceKey]
143-
);
144-
$dayPrices[$priceKey]['latest_start_date'] = max(
145-
$dayPrices[$priceKey]['latest_start_date'],
146-
$ruleData['from_time']
147-
);
148-
$dayPrices[$priceKey]['earliest_end_date'] = min(
149-
$dayPrices[$priceKey]['earliest_end_date'],
150-
$ruleData['to_time']
151-
);
152-
}
153-
154-
if ($ruleData['action_stop']) {
155-
$stopFlags[$priceKey] = true;
156-
}
157-
}
158-
}
159-
160-
$prevKey = $productKey;
161-
}
162-
$this->pricesPersistor->execute($dayPrices, $useAdditionalTable);
101+
$productsStmt = $this->ruleProductsSelectBuilder->build(
102+
(int)$website->getId(),
103+
$productId,
104+
$useAdditionalTable
105+
);
106+
$this->reindexRuleProductsPriceProcessor->execute(
107+
$productsStmt,
108+
$website,
109+
$batchCount,
110+
$useAdditionalTable,
111+
$this->useWebsiteTimezone
112+
);
163113
}
164114

165115
return true;
166116
}
167-
168-
/**
169-
* Retrieve date sequence in store time zone
170-
*
171-
* @param int $storeId
172-
* @return \DateTime[]
173-
*/
174-
private function getDateInterval(int $storeId): array
175-
{
176-
$currentDate = $this->localeDate->scopeDate($storeId, null, true);
177-
$previousDate = (clone $currentDate)->modify('-1 day');
178-
$previousDate->setTime(23, 59, 59);
179-
$nextDate = (clone $currentDate)->modify('+1 day');
180-
$nextDate->setTime(0, 0, 0);
181-
182-
return [$previousDate, $currentDate, $nextDate];
183-
}
184117
}

0 commit comments

Comments
 (0)