Skip to content

Reduce per-product work in listing swatch renderer (#39073) - #41228

Open
lbajsarowicz wants to merge 1 commit into
magento:2.4-developfrom
lbajsarowicz:fix/39073-swatch-listing-child-load
Open

Reduce per-product work in listing swatch renderer (#39073)#41228
lbajsarowicz wants to merge 1 commit into
magento:2.4-developfrom
lbajsarowicz:fix/39073-swatch-listing-child-load

Conversation

@lbajsarowicz

Copy link
Copy Markdown
Contributor

Description (*)

Two contained reductions of per-configurable work in the category listing swatch renderer. They do not remove the nine per-parent SQL statements the issue measures (each is intrinsically parent-scoped, and everything parent-independent is already memoized in EAV Source\Table, Swatches\Helper\Data and SwatchAttributeCodes), so this is a partial improvement on the axis the reporter is on, variations per configurable:

  • Renderer\Configurable::getConfigurableOptionsIds() called helper->getAllowAttributes($product) inside the per-child loop, resolving the parent's configurable attributes once per variation. With 250 variations and 12 listed products that is 3000 resolutions per page. It is now resolved once per parent; the option id set is unchanged.
  • Helper\Data::loadVariationByFallback() loaded the whole matching child collection and used getFirstItem(). It now sets setPageSize(1). This path runs per parent from Listing\Configurable::_getAdditionalConfig() whenever a swatch attribute is in the layered navigation query.

Also fixes the pre-existing Static Tests warnings in the touched block (public const, @see on deprecated members, explicit return null).

Fixed Issues (if relevant)

  1. Partially addresses Slow category loading due to configurable product swatches  #39073

Manual testing scenarios (*)

  1. Category page with configurable products and swatches enabled: swatches, prices and images render as before.
  2. Filter that category by a swatch attribute (layered navigation): the preselected variation image is unchanged.
  3. Profile the page: Swatches\Helper\Data::getAllowAttributes call count drops from children x parents to parents.

Contribution checklist (*)

  • Pull request has a meaningful description of its purpose
  • All commits are accompanied by meaningful commit messages
  • All new or changed code is covered with unit/integration tests (if applicable)
  • README.md files for modified modules are updated and included in the pull request if any README.md predefined sections require an update
  • All automated tests passed successfully (all builds are green)

Resolve the configurable attribute list once per parent in
getConfigurableOptionsIds() instead of once per child variation, and
limit the fallback variation collection to a single row since only the
first item is used. Both scale with the number of variations per
configurable, which is where listings with large configurables spend
their time.
@m2-assistant

m2-assistant Bot commented Sep 5, 2026

Copy link
Copy Markdown

Hi @lbajsarowicz. Thank you for your contribution!
Here are some useful tips on how you can test your changes using Magento test environment.
❗ Automated tests can be triggered manually with an appropriate comment:

  • @magento run all tests - run or re-run all required tests against the PR changes
  • @magento run <test-build(s)> - run or re-run specific test build(s)
    For example: @magento run Unit Tests

<test-build(s)> is a comma-separated list of build names.

Allowed build names are:
  1. Database Compare
  2. Functional Tests CE
  3. Functional Tests EE
  4. Functional Tests B2B
  5. Integration Tests
  6. Magento Health Index
  7. Sample Data Tests CE
  8. Sample Data Tests EE
  9. Sample Data Tests B2B
  10. Static Tests
  11. Unit Tests
  12. WebAPI Tests
  13. Semantic Version Checker

You can find more information about the builds here
ℹ️ Run only required test builds during development. Run all test builds before sending your pull request for review.


For more details, review the Code Contributions documentation.
Join Magento Community Engineering Slack and ask your questions in #github channel.

@lbajsarowicz

Copy link
Copy Markdown
Contributor Author

@magento run all tests

foreach ($attributeCodes as $attributeCode) {
$attributeValue = $product->getData($attributeCode);
if ($attributeValue !== null) {
$ids[$attributeValue] = 1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If only keys needed from $ids - the subsequent array_keys fcall can be also eliminated after changing $ids[$attributeValue] = 1; to $ids[] = $attributeValue;.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this against the data flow before changing anything, and I am going to keep array_keys($ids).

$attributeValue does repeat across the loop. It is nested per allowed product and per swatch attribute code, and sibling variations routinely share a value — ten children in "Red", five in size "L". $ids[$attributeValue] = 1 is a deliberate dedup, so array_keys($ids) returns one entry per distinct option, not one per product.

That dedup is essential downstream. The result goes to SwatchHelper::getSwatchesByOptionsId(), which decides whether the cache already holds everything with:

$swatches = $this->getCachedSwatches($optionIds);
if (count($swatches) !== count($optionIds)) {

and getCachedSwatches() is array_intersect_key($this->swatchesCache, array_combine($optionIds, $optionIds)). Both array_combine and array_intersect_key key by option id, so count($swatches) can never exceed the number of distinct ids. Feed duplicates in and count($optionIds) is permanently larger, the check never passes even with a fully warm cache, and every call falls through to addFilterByOptionsIds() and a fresh collection query.

That would defeat swatch caching on any listing with repeated swatch values, which is the normal case, and it works against exactly what this PR is for. No test catches it today because the unit fixtures use small already-distinct value sets.

Separately, on the red Semantic Version Checker: it reports one MAJOR, M121 [protected] Method return typing changed on getSwatchProductImage. The method is protected, has no declared return type, and could already return null — the pre-patch code simply fell off the end when neither branch matched while the docblock claimed @return string. This PR adds the explicit return null; and corrects the docblock to @return string|null. Runtime behaviour is unchanged; SVC is flagging a documentation correction. I would rather leave the docblock accurate than restore a wrong one to silence the check, but say the word if you want it reverted.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re right deduplication needs to be preserved, so $ids[] = $attributeValue isn’t equivalent.
It can instead use $ids[$attributeValue] = $attributeValue and return $ids directly.
The downstream code consumes the values and does not require sequential keys.
The gain is very small, though, so either implementation is fine.

Comment on lines +401 to +402

return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lbajsarowicz, Do you think you could avoid changing the return type of the protected method so that the Semantic Version Check passes without errors? This would allow to include these changes in a minor release without requiring approval from the Architect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: P3 May be fixed according to the position in the backlog. Progress: pending review

Projects

Status: Pending Review

Development

Successfully merging this pull request may close these issues.

4 participants