Skip to content

Commit c76df20

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-4303' into PR_2025_11_07_muntianu
2 parents ba8475c + 2dfacb3 commit c76df20

19 files changed

+3274
-88
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogRule\Model\Indexer;
9+
10+
use Magento\Framework\DB\Adapter\AdapterInterface;
11+
use Magento\Framework\Indexer\BatchSizeManagementInterface;
12+
13+
/**
14+
* Calculate and validate batch size for catalogrule insert operations
15+
*/
16+
class CatalogRuleInsertBatchSizeCalculator
17+
{
18+
/**
19+
* Default batch size for insert operations
20+
*/
21+
private const DEFAULT_BATCH_SIZE = 5000;
22+
23+
/**
24+
* @var BatchSizeManagementInterface
25+
*/
26+
private $batchSizeManagement;
27+
28+
/**
29+
* @var int
30+
*/
31+
private $defaultBatchSize;
32+
33+
/**
34+
* @param BatchSizeManagementInterface $batchSizeManagement
35+
* @param int $defaultBatchSize
36+
*/
37+
public function __construct(
38+
BatchSizeManagementInterface $batchSizeManagement,
39+
int $defaultBatchSize = self::DEFAULT_BATCH_SIZE
40+
) {
41+
$this->batchSizeManagement = $batchSizeManagement;
42+
$this->defaultBatchSize = $defaultBatchSize;
43+
}
44+
45+
/**
46+
* Retrieve validated batch size for insert operations
47+
*
48+
* @param AdapterInterface $connection
49+
* @return int
50+
*/
51+
public function getInsertBatchSize(AdapterInterface $connection): int
52+
{
53+
$batchSize = $this->defaultBatchSize;
54+
55+
$this->batchSizeManagement->ensureBatchSize($connection, $batchSize);
56+
57+
return (int)$batchSize;
58+
}
59+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogRule\Model\Indexer;
9+
10+
use Magento\Framework\App\ResourceConnection;
11+
use Magento\Framework\Indexer\IndexTableRowSizeEstimatorInterface;
12+
use Magento\Customer\Model\ResourceModel\Group\CollectionFactory as CustomerGroupCollectionFactory;
13+
use Magento\Store\Model\StoreManagerInterface;
14+
15+
/**
16+
* Estimator of the catalogrule_product_price index table row size.
17+
*
18+
* @see \Magento\Framework\Indexer\BatchSizeManagement
19+
*/
20+
class CatalogRuleProductPriceRowSizeEstimator implements IndexTableRowSizeEstimatorInterface
21+
{
22+
/**
23+
* Approximate size of catalogrule_product_price row in bytes
24+
* Based on table structure:
25+
* - rule_product_price_id: 4 bytes (int)
26+
* - rule_date: 3 bytes (date)
27+
* - customer_group_id: 4 bytes (int)
28+
* - product_id: 4 bytes (int)
29+
* - rule_price: 8 bytes (decimal)
30+
* - website_id: 2 bytes (smallint)
31+
* - latest_start_date: 3 bytes (date)
32+
* - earliest_end_date: 3 bytes (date)
33+
* Plus index overhead (~30%)
34+
*/
35+
private const APPROXIMATE_ROW_SIZE_BYTES = 150;
36+
37+
/**
38+
* @var ResourceConnection
39+
*/
40+
private $resourceConnection;
41+
42+
/**
43+
* @var CustomerGroupCollectionFactory
44+
*/
45+
private $customerGroupCollectionFactory;
46+
47+
/**
48+
* @var StoreManagerInterface
49+
*/
50+
private $storeManager;
51+
52+
/**
53+
* @param ResourceConnection $resourceConnection
54+
* @param CustomerGroupCollectionFactory $customerGroupCollectionFactory
55+
* @param StoreManagerInterface $storeManager
56+
*/
57+
public function __construct(
58+
ResourceConnection $resourceConnection,
59+
CustomerGroupCollectionFactory $customerGroupCollectionFactory,
60+
StoreManagerInterface $storeManager
61+
) {
62+
$this->resourceConnection = $resourceConnection;
63+
$this->customerGroupCollectionFactory = $customerGroupCollectionFactory;
64+
$this->storeManager = $storeManager;
65+
}
66+
67+
/**
68+
* @inheritdoc
69+
*/
70+
public function estimateRowSize()
71+
{
72+
$customerGroupCount = $this->customerGroupCollectionFactory->create()->getSize();
73+
74+
$websiteCount = count($this->storeManager->getWebsites());
75+
76+
$estimatedRowsPerProduct = $customerGroupCount * $websiteCount * 2;
77+
78+
$memoryPerProduct = $estimatedRowsPerProduct * self::APPROXIMATE_ROW_SIZE_BYTES;
79+
80+
return (int)ceil($memoryPerProduct);
81+
}
82+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogRule\Model\Indexer;
9+
10+
/**
11+
* Calculates optimal batch sizes for PHP memory-bound operations
12+
*/
13+
class DynamicBatchSizeCalculator
14+
{
15+
/**
16+
* Percentage of memory limit to use for attribute caching
17+
*/
18+
private const ATTRIBUTE_CACHE_MEMORY_PERCENTAGE = 0.40;
19+
20+
/**
21+
* Estimated memory per product for attribute data (bytes)
22+
*/
23+
private const MEMORY_PER_PRODUCT_ATTRIBUTE = 2048;
24+
25+
/**
26+
* Minimum batch size
27+
*/
28+
private const MIN_BATCH_SIZE = 500;
29+
30+
/**
31+
* Maximum batch size
32+
*/
33+
private const MAX_BATCH_SIZE = 50000;
34+
35+
/**
36+
* Minimum batches in memory
37+
*/
38+
private const MIN_BATCHES_IN_MEMORY = 2;
39+
40+
/**
41+
* Maximum batches in memory
42+
*/
43+
private const MAX_BATCHES_IN_MEMORY = 100;
44+
45+
/**
46+
* @var int|null
47+
*/
48+
private $memoryLimit;
49+
50+
/**
51+
* @var array
52+
*/
53+
private $calculatedSizes = [];
54+
55+
/**
56+
* Get memory limit in bytes
57+
*
58+
* @return int
59+
*/
60+
private function getMemoryLimit(): int
61+
{
62+
if ($this->memoryLimit === null) {
63+
$memoryLimit = ini_get('memory_limit');
64+
65+
if ($memoryLimit === '-1') {
66+
$this->memoryLimit = 2 * 1024 * 1024 * 1024;
67+
} else {
68+
$this->memoryLimit = $this->convertToBytes($memoryLimit);
69+
}
70+
}
71+
72+
return $this->memoryLimit;
73+
}
74+
75+
/**
76+
* Convert PHP memory limit notation to bytes
77+
*
78+
* @param string $value
79+
* @return int
80+
*/
81+
private function convertToBytes(string $value): int
82+
{
83+
$value = trim($value);
84+
$unit = strtolower(substr($value, -1));
85+
$number = (int)substr($value, 0, -1);
86+
87+
switch ($unit) {
88+
case 'g':
89+
return $number * 1024 * 1024 * 1024;
90+
case 'm':
91+
return $number * 1024 * 1024;
92+
case 'k':
93+
return $number * 1024;
94+
default:
95+
return (int)$value;
96+
}
97+
}
98+
99+
/**
100+
* Get available memory for operations (excluding Magento base usage)
101+
*
102+
* @return int
103+
*/
104+
private function getAvailableMemory(): int
105+
{
106+
$totalMemory = $this->getMemoryLimit();
107+
$currentUsage = memory_get_usage(true);
108+
$magentoBaseOverhead = 400 * 1024 * 1024;
109+
110+
$available = $totalMemory - $currentUsage - $magentoBaseOverhead;
111+
112+
return max($available, 100 * 1024 * 1024);
113+
}
114+
115+
/**
116+
* Calculate optimal batch size for attribute loading
117+
*
118+
* @return int
119+
*/
120+
public function getAttributeBatchSize(): int
121+
{
122+
if (isset($this->calculatedSizes['attribute_batch_size'])) {
123+
return $this->calculatedSizes['attribute_batch_size'];
124+
}
125+
126+
$availableMemory = $this->getAvailableMemory();
127+
$memoryForAttributes = $availableMemory * self::ATTRIBUTE_CACHE_MEMORY_PERCENTAGE;
128+
129+
$maxBatchesInMemory = $this->getMaxBatchesInMemory();
130+
$memoryPerBatch = $memoryForAttributes / $maxBatchesInMemory;
131+
132+
$batchSize = (int)($memoryPerBatch / self::MEMORY_PER_PRODUCT_ATTRIBUTE);
133+
134+
$batchSize = max(self::MIN_BATCH_SIZE, min(self::MAX_BATCH_SIZE, $batchSize));
135+
136+
$this->calculatedSizes['attribute_batch_size'] = $batchSize;
137+
138+
return $batchSize;
139+
}
140+
141+
/**
142+
* Calculate maximum number of batches to keep in memory
143+
*
144+
* @return int
145+
*/
146+
public function getMaxBatchesInMemory(): int
147+
{
148+
if (isset($this->calculatedSizes['max_batches'])) {
149+
return $this->calculatedSizes['max_batches'];
150+
}
151+
152+
$availableMemory = $this->getAvailableMemory();
153+
$memoryForAttributes = $availableMemory * self::ATTRIBUTE_CACHE_MEMORY_PERCENTAGE;
154+
155+
$estimatedBatchMemory = self::MIN_BATCH_SIZE * self::MEMORY_PER_PRODUCT_ATTRIBUTE;
156+
$maxBatches = (int)($memoryForAttributes / $estimatedBatchMemory);
157+
158+
$maxBatches = max(self::MIN_BATCHES_IN_MEMORY, min(self::MAX_BATCHES_IN_MEMORY, $maxBatches));
159+
160+
$this->calculatedSizes['max_batches'] = $maxBatches;
161+
162+
return $maxBatches;
163+
}
164+
}

0 commit comments

Comments
 (0)