Batch product_count resolution against the category product index (#41214) - #41220
Open
lbajsarowicz wants to merge 1 commit into
Open
Batch product_count resolution against the category product index (#41214)#41220lbajsarowicz wants to merge 1 commit into
lbajsarowicz wants to merge 1 commit into
Conversation
…gento#41214) product_count built a full product collection per field instance and called getSize(), so one COUNT(DISTINCT e.entity_id) with five joins ran for every category in the response. Resolve it with a BatchResolverInterface that counts all requested categories with one GROUP BY category_id query per store against catalog_category_product_index_store{N}, which already materializes enabled status, website membership and per-store visibility. The is_parent predicate for non-anchor categories and the stock join for "Display Out of Stock Products" are preserved; admin scope still goes through the collection path.
Contributor
Author
|
@magento run all tests |
|
Hi @lbajsarowicz. Thank you for your contribution!
Allowed build names are:
You can find more information about the builds here For more details, review the Code Contributions documentation. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description (*)
Magento\CatalogGraphQl\Model\Resolver\Category\ProductsCountresolvesproduct_countone category at a time: it builds a full product collection for the category, runs the collection processors and callsgetSize(). Everyproduct_countfield in a response therefore costs oneCOUNT(DISTINCT e.entity_id)with joins on the category index, the stock status index, twocatalog_product_entity_intattribute tables andcatalog_product_website. On a store with ~60k products this query averaged 0.6 s and ran ~98,000 times in a week from a single PWA product-detail operation.The category product index
catalog_category_product_index_store{N}already materializes enabled status, website membership and per-store visibility, so the joins on the attribute and website tables add nothing to the count. This PR adds:Model\Category\ProductsCountProvider, which counts all requested categories of one store with oneGROUP BY category_idquery on the index table (resolved throughTableMaintainer, never a hardcoded suffix). Theis_parent = 1predicate for non-anchor categories and thecataloginventory_stock_statusjoin when "Display Out of Stock Products" is disabled are kept, so the numbers match the old count.Model\Resolver\Category\BatchProductsCount, aBatchResolverInterfacethat groups the requests of a response by store and anchor flag and calls the provider once per group. Requests for the admin store (store_id = 0) are delegated to the existingProductsCountresolver, because that path uses a different table and filter set.schema.graphqls:CategoryInterface.product_countnow points at the batch resolver.ProductsCountis unchanged and still wired for the admin scope.Performance impact
Measured on a vanilla 2.4-develop install (performance toolkit
smallprofile: 1200 products, 33 categories). Wall time is the median of 7 fresh requests without the profiler; call counts and memory are from one PHP SPX run of the same request. Response bodies are byte-identical before and after.Query 1, three levels of
categoryListwithproduct_count(31 fields):{ categoryList(filters: {ids: {eq: "2"}}) { children { id product_count children { id product_count children { id product_count } } } } }product_countqueriesCOUNT(DISTINCT e.entity_id)GROUP BY category_idQuery 2, product listing with categories (24
product_countfields over 12 items):{ products(filter: {category_id: {eq: "3"}}, pageSize: 12) { items { sku categories { id product_count } } } }product_countqueriesCOUNT(DISTINCT e.entity_id)GROUP BY category_idThe remaining per-item queries in query 2 come from the
categoriesresolver itself and are outside this change. On a small catalog the count query is cheap, so the wall-time gain is modest here; the production numbers in the issue show what the per-category query costs on a large catalog.Fixed Issues (if relevant)
Manual testing scenarios (*)
GROUP BY cat_index.category_idqueries oncatalog_category_product_index_store1and noCOUNT(DISTINCT e.entity_id).product_countvalues with the ones returned before the change for an anchor category, a non-anchor category, a category with out-of-stock products and a category with no products: identical (0 for the empty category).Questions or comments
Gates run locally: unit (CatalogGraphQl suite, 70 tests), integration (CatalogGraphQl suite), PHPCS, PHPStan and the Static Tests
LiveCodeTeston the changed files are clean. The API-functional tests that pin the count semantics (CategoryProductsCountTest,CategoryAnchorTest,CategoryListTest,CategoryTreeTest) need the WebAPI build.Batching happens per GraphQL execution level: a three-level
categoryListproduces one query per level, a flat product listing one query per response.The Semantic Version Checker will report two new classes and the changed
@resolverclass onCategoryInterface.product_count. Plugins onProductsCount::resolve()no longer run for storefront requests, only for the admin-scope fallback.Contribution checklist (*)