Open ioweb-gr opened 1 year ago
Hi @ioweb-gr. Thank you for your report. To speed up processing of this issue, make sure that the issue is reproducible on the vanilla Magento instance following Steps to reproduce. To deploy vanilla Magento instance on our environment, Add a comment to the issue:
@magento give me 2.4-develop instance
- upcoming 2.4.x release@magento I am working on this
Join Magento Community Engineering Slack and ask your questions in #github channel. :warning: According to the Magento Contribution requirements, all issues must go through the Community Contributions Triage process. Community Contributions Triage is a public meeting. :clock10: You can find the schedule on the Magento Community Calendar page. :telephone_receiver: The triage of issues happens in the queue order. If you want to speed up the delivery of your contribution, join the Community Contributions Triage session to discuss the appropriate ticket.
@engcom-Bravo To give you more context, this is affecting all product export plugins I've seen so far. The consecutive loading of product data to export even with paginating over Collections or searchResults leads to this when configurable products are involved.
I've run a php-memprof profile on this where it stopped while out of memory at 10G !!!
I'd like to bring your attention to this kcachegrind view
*sorted by self
Out of a total memorysize cost 10460310036
6220963464 were spent in PDOStatement::fetchAll
and 2280736792 in Magento\Framework\Model\AbstractModel::setData
I've tried to dig into each function caller and it seems I keep going back to magento core functions that take up all the memory without ever freeing it.
By double clicking on kcachegrind I reach this point If I understand correctly using relative mode 14.37 out of the 21.80 total is spent on tier prices calculation
And 7.17 out of 21.80 is spent on _loadAttributes
We have addmediaGalleryData and _loadAttributes
And by tracing the code execution graph everything points out to the getUsedProducts function and the aroundPlugin for it.
The weird thing in my case is there is a point where after 14-15k configurable products are processed there is sudden increase in memory usage which I can't quite pinpoint.
For example in my case at around 13k products I have used only 1272.5MB memory which rarely increases
And then at around 14.5k products I suddenly experience constant increasing of memory
But if I process that batch of products only e.g. from 14000 to 15000 the memory is not increasing like this. So it's not product specific.
Moreover there was one case which I forgot to screenshot but it show 80% of memory cost assigned to json_decode from the function
\Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsCache::aroundGetUsedProducts
I'd appreciate any help in deducing where the memory leak is
Here's an example profile where json_decode
seems to be utilizing most of the memory usage constantly and the list of callers in sequence. That's in a smaller subset sample of course but the idea is the same
-
-
-
Disabling the caching plugin saved 100MBs of memory only though in a subset of 2000 products out of 25000
Do you have some progress or a workaround for this? We have a lot of configurable, and looks like because of this bug site is really slow.
Nope unfortunately, there are memory leaks on all magento loops through Collections or ListInterfaces and this one is just the most extreme of them. I think that the Zend::PDO abstraction layer is just not good enough causing the memory leaks when fetching the data. I've never faced something similar with Doctrine for example.
For me it affects category page if swatches are enabled there. I see that problem is in salable check in plugin afterGetUsedProducts, page with 32 products and 170 swatches on them takes 54 sec to load. This https://github.com/magento/magento2/issues/26850#issuecomment-1311122767 helps a lot but issue is still there (30 sec to load). There is no performance issue at all if salable check is skipped (6.7sec to load): public function afterGetUsedProducts(Configurable $subject, array $products): array { // here was salable check return $products; } So problem for me is definitely in this plugin.
Looks like problem is in $this->productsSalableStatuses variable, I added logger to see how many values are stored there, and with each product object is growing, on first there are 370 values, on 40 there are 3603 values, possibly this is the memory leak we are looking for. Currently, I'm testing this as a replacement:
public function afterGetUsedProducts(Configurable $subject, array $products): array
{
$skusToCheck = [];
$salableResults = [];
$website = $this->storeManager->getWebsite();
$stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode());
foreach ($products as $product) {
$sku = $product->getSku();
$skusToCheck[] = $sku;
}
if (!empty($skusToCheck)) {
$salableResults = $this->areProductsSalable->execute($skusToCheck, $stock->getStockId());
}
foreach ($products as $key => $product) {
$sku = $product->getSku();
$isSalableResult = $salableResults[$sku] ?? null;
if ($isSalableResult && !$isSalableResult->isSalable()) {
$product->setIsSalable(false);
if (!$this->stockConfiguration->isShowOutOfStock()) {
unset($products[$key]);
}
}
}
return $products;
}
Here is patch:
--- Plugin/Model/Product/Type/Configurable/IsSalableOptionPlugin.php
+++ Plugin/Model/Product/Type/Configurable/IsSalableOptionPlugin.php
@@ -73,29 +73,34 @@
*/
public function afterGetUsedProducts(Configurable $subject, array $products): array
{
- $skus = [];
- foreach ($products as $product) {
- foreach ($this->productsSalableStatuses as $isProductSalableResult) {
- if ($isProductSalableResult->getSku() === $product->getSku()) {
- continue 2;
- }
- }
- $skus[] = $product->getSku();
- }
-
- if (empty($skus)) {
- $this->filterProducts($products, $this->productsSalableStatuses);
- return $products;
- }
+ $skusToCheck = [];
+ $salableResults = [];
$website = $this->storeManager->getWebsite();
$stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode());
- $this->productsSalableStatuses = array_merge(
- $this->productsSalableStatuses,
- $this->areProductsSalable->execute($skus, $stock->getStockId())
- );
- $this->filterProducts($products, $this->productsSalableStatuses);
+ foreach ($products as $product) {
+ $sku = $product->getSku();
+ $skusToCheck[] = $sku;
+ }
+
+ if (!empty($skusToCheck)) {
+ $salableResults = $this->areProductsSalable->execute($skusToCheck, $stock->getStockId());
+ }
+
+ foreach ($products as $key => $product) {
+ $sku = $product->getSku();
+ $isSalableResult = $salableResults[$sku] ?? null;
+
+ if ($isSalableResult && !$isSalableResult->isSalable()) {
+ $product->setIsSalable(false);
+
+ if (!$this->stockConfiguration->isShowOutOfStock()) {
+ unset($products[$key]);
+ }
+ }
+ }
+
return $products;
}
I think this is a serious issue for stores with configurables that have a lot of variants, and I'm surprised that there is no answer from Magento team yet. It would be really great if someone could check solution I found.
Hi @engcom-November. Thank you for working on this issue. In order to make sure that issue has enough information and ready for development, please read and check the following instruction: :point_down:
Area: XXXXX
label to the ticket, indicating the functional areas it may be related to.2.4-develop
branch@magento give me 2.4-develop instance
to deploy test instance on Magento infrastructure. 2.4-develop
branch, please, add the label Reproduced on 2.4.x
.Issue: Confirmed
once verification is complete. Hello @ioweb-gr,
Thank you for the report and collaboration! We tried to reproduce this on 2.4-develop, but we didn't see any major increase in time between the iterations. For profiling we have used SPX tool, please take a look at the screenshot: First iteration took 82ms
Last iteration took 75ms
We have used 50 configurable products with 80 variations, still we got the expected result. Please find the attached module used to reproduce this issue and let us know if we are missing anything. ProductVendor.zip
Thank you.
@engcom-November with 50 products you won't notice the issue. PHP buffer won't fill up with such a small dataset. Try with 50.000 configurables with 200 variations each (color / size)
The problem at some point is that because the memory is not properly released somewhere, PHP tries to find available object slot to clear and put the new one taking a tremendous increasing time to do so. With each pass, this slows down futher and further and further.
Your dataset is just too small.
My use case was with a site with 2.000.000 products of which around 50000 are configurables.
Check this video for example and notice the progressive increase of memory at each loop of configurable product batches
Hi @engcom-Hotel. Thank you for working on this issue. In order to make sure that issue has enough information and ready for development, please read and check the following instruction: :point_down:
Area: XXXXX
label to the ticket, indicating the functional areas it may be related to.2.4-develop
branch@magento give me 2.4-develop instance
to deploy test instance on Magento infrastructure. 2.4-develop
branch, please, add the label Reproduced on 2.4.x
.Issue: Confirmed
once verification is complete. Hello @ioweb-gr,
Thanks for the contribution and collaboration!
We have tried to reproduce the issue and it seems the issue is reproducible in the Magento 2.4-develop branch. We have used memprof for issue reproduction.
Please find below the screenshot of qcachegrind UI for the reference:
Hence confirming the issue.
Thanks
:white_check_mark: Jira issue https://jira.corp.adobe.com/browse/AC-10942 is successfully created for this GitHub issue.
:white_check_mark: Confirmed by @engcom-Hotel. Thank you for verifying the issue.
Issue Available: @engcom-Hotel, You will be automatically unassigned. Contributors/Maintainers can claim this issue to continue. To reclaim and continue work, reassign the ticket to yourself.
Looks like problem is in $this->productsSalableStatuses variable, I added logger to see how many values are stored there, and with each product object is growing, on first there are 370 values, on 40 there are 3603 values, possibly this is the memory leak we are looking for. Currently, I'm testing this as a replacement:
public function afterGetUsedProducts(Configurable $subject, array $products): array { $skusToCheck = []; $salableResults = []; $website = $this->storeManager->getWebsite(); $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()); foreach ($products as $product) { $sku = $product->getSku(); $skusToCheck[] = $sku; } if (!empty($skusToCheck)) { $salableResults = $this->areProductsSalable->execute($skusToCheck, $stock->getStockId()); } foreach ($products as $key => $product) { $sku = $product->getSku(); $isSalableResult = $salableResults[$sku] ?? null; if ($isSalableResult && !$isSalableResult->isSalable()) { $product->setIsSalable(false); if (!$this->stockConfiguration->isShowOutOfStock()) { unset($products[$key]); } } } return $products; }
Here is patch:
--- Plugin/Model/Product/Type/Configurable/IsSalableOptionPlugin.php +++ Plugin/Model/Product/Type/Configurable/IsSalableOptionPlugin.php @@ -73,29 +73,34 @@ */ public function afterGetUsedProducts(Configurable $subject, array $products): array { - $skus = []; - foreach ($products as $product) { - foreach ($this->productsSalableStatuses as $isProductSalableResult) { - if ($isProductSalableResult->getSku() === $product->getSku()) { - continue 2; - } - } - $skus[] = $product->getSku(); - } - - if (empty($skus)) { - $this->filterProducts($products, $this->productsSalableStatuses); - return $products; - } + $skusToCheck = []; + $salableResults = []; $website = $this->storeManager->getWebsite(); $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()); - $this->productsSalableStatuses = array_merge( - $this->productsSalableStatuses, - $this->areProductsSalable->execute($skus, $stock->getStockId()) - ); - $this->filterProducts($products, $this->productsSalableStatuses); + foreach ($products as $product) { + $sku = $product->getSku(); + $skusToCheck[] = $sku; + } + + if (!empty($skusToCheck)) { + $salableResults = $this->areProductsSalable->execute($skusToCheck, $stock->getStockId()); + } + + foreach ($products as $key => $product) { + $sku = $product->getSku(); + $isSalableResult = $salableResults[$sku] ?? null; + + if ($isSalableResult && !$isSalableResult->isSalable()) { + $product->setIsSalable(false); + + if (!$this->stockConfiguration->isShowOutOfStock()) { + unset($products[$key]); + } + } + } + return $products; }
I think this is a serious issue for stores with configurables that have a lot of variants, and I'm surprised that there is no answer from Magento team yet. It would be really great if someone could check solution I found.
@MarksAfanasevics - Did you make much progress on this. I think it works well apart from back orders. But I'll dig further
Preconditions and environment
Steps to reproduce
Create a lot of configurable products with at least 80 variations by color and size
Load the products via productRepository using searchCriteria
Then in a foreach loop
Try to use the function
$this->configurableType->getUsedProducts($product)
You will end up seeing a blackfire profile like this where each execution of the function runs more slowly over time.
This is an example of 50 products in my case,
The first execution of the loop took 300ms,
the final one took 5+sec
Example code
Expected result
Memory and time spent to fetch the products doesn't increase per iteration.
Actual result
Time execution increases steadily and so does memory consumption
Additional information
I'm developing an XML export plugin for Magento that aims to export a vast catalog of products. The catalog comprises approximately 1 million products, with 28,000 of them being configurable products with multiple variations. However, I am encountering challenges due to the sheer volume of products, leading to memory exhaustion and a significant increase in processing time due to the magento core functions.
Unfortunately as it is it's impossible to process the catalog properly.
My example was a limited example of 50 products. Imagine what happens with 28.000
I've provided the blackfire report to showcase that only magento core functions are taking up the majority of the slowness.
Release note
No response
Triage and priority