← Voltar ao blog

Performance de produtos configuráveis no Magento 2: o que corrigir

Rodrigo Biazzi

Produtos configuráveis são onde decisões de design do catálogo viram problemas de performance. Um parent com vinte filhos é barato. Um parent com oitocentos filhos, renderizado com swatches em uma página de categoria, não é. Este artigo mostra para onde vai o custo no Magento Open Source e no Adobe Commerce 2.4.8 e o que eu mudo primeiro quando os configuráveis são o motivo de as páginas estarem lentas.

Como um produto configurável é armazenado

Um produto configurável é um parent sem preço nem estoque próprios. Os filhos são produtos simples, geralmente Not Visible Individually. A relação mora em catalog_product_super_link, os atributos configuráveis em catalog_product_super_attribute e a relação genérica de parent e filho em catalog_product_relation, que os indexers usam.

Tudo que a storefront mostra do parent é derivado dos filhos. O preço é o menor preço entre os filhos vendáveis. O status de estoque depende de algum filho estar vendável. Os swatches e a lista de opções vêm dos valores de atributo dos filhos. Então todo lugar que renderiza um configurável precisa olhar para os filhos, e o custo cresce com o número de filhos.

Para onde vai o custo

Página de produto

Na página de produto, o Magento\ConfigurableProduct\Block\Product\View\Type\Configurable monta um JSON de config com todos os filhos: IDs de opção, preços de cada filho, imagens e estoque. Com swatches, o block do Magento_Swatches adiciona os dados de swatch por cima. Para um parent com centenas de filhos, esse JSON pode chegar a centenas de kilobytes no HTML. O tempo de servidor vai para carregar os filhos com seus preços e imagens, e depois o navegador precisa fazer o parse do JSON e rodar o widget de swatch nele.

O Magento faz cache da lista de used products por meio do Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsCache, o que ajuda em loads repetidos. Mas não diminui o JSON.

Páginas de categoria com swatches

Quando Show Swatches in Product List está habilitado, cada configurável na listagem recebe seu próprio swatch renderer e seu próprio JSON de config. Vinte e quatro configuráveis com muitos filhos cada podem deixar a página de listagem muito mais pesada do que a de produto. As configurações estão em Stores, Configuration, Catalog, Catalog, Storefront:

bin/magento config:show catalog/frontend/show_swatches_in_product_list
bin/magento config:show catalog/frontend/swatches_per_product

Swatches per Product limita quantos swatches são renderizados por item. Reduz o HTML, mas os dados dos filhos ainda precisam ser carregados. Se a listagem não precisa de swatches, desligá-los é o maior ganho isolado.

Save de produto no admin

Salvar um parent no admin salva as relações e, com indexers em Update on Save, reindexa o parent e seus filhos. O formulário do produto também carrega o grid de configurações com todos os filhos. Parents grandes deixam a página de edição lenta para abrir e lenta para salvar. Colocar os indexers em schedule tira o custo de indexação da request, veja modos de indexer: save vs schedule.

Indexers

O price indexer calcula os preços dos configuráveis a partir dos filhos para cada website e customer group. O stock indexer calcula o estoque do parent a partir dos filhos. O search indexer adiciona valores de atributo dos filhos ao documento do parent, para que buscar a cor de um filho encontre o parent. Os três crescem com o número de filhos vezes websites e customer groups.

Corrija primeiro a modelagem dos dados

A maioria dos configuráveis lentos que vejo tem um problema de modelagem, não de código.

  • Atributos configuráveis demais. Cor vezes tamanho vezes comprimento vezes material produz centenas de combinações, muitas das quais nunca entram em estoque. Se uma opção muda só um detalhe pequeno, considere uma custom option, ou divida o produto.
  • Filhos que nunca vendem. Filhos criados para toda combinação "só por garantia" continuam sendo indexados e renderizados. Apague combinações que não existem no mundo real.
  • Cor como produto separado. Se cada cor tem suas próprias fotos, sua própria descrição e deveria ranquear sozinha nos buscadores, muitas vezes é melhor ela ser um configurável próprio de tamanho, ligado aos outros. Isso também reduz o número de filhos por parent.
  • Atributos só no parent. Valores iguais para todos os filhos, como marca ou material, pertencem ao parent e não precisam ser repetidos como dados variáveis.

Não há um limite rígido de filhos, mas na minha experiência parents com algumas dezenas de filhos se comportam bem, e parents com várias centenas pedem atenção em toda página que os renderiza.

Mudanças de configuração

Price indexer dimensions

No Magento Open Source o price index pode ser dividido em tabelas por dimensão, por website e customer group. Isso deixa cada tabela menor e torna o reindex parcial mais barato em lojas com vários websites ou muitos customer groups.

bin/magento indexer:show-dimensions-mode catalog_product_price
bin/magento indexer:set-dimensions-mode catalog_product_price website_and_customer_group
bin/magento indexer:reindex catalog_product_price

Rode primeiro em staging e meça o tempo do reindex antes e depois. Em um único website com os grupos padrão o ganho é pequeno.

Filhos sem estoque

Com Display Out of Stock Products em No, os filhos sem estoque ficam fora do JSON de config e da lista de opções, o que o deixa menor. Se isso é aceitável é uma decisão de negócio, porque os clientes deixam de ver tamanhos que voltarão ao estoque.

Configurações de imagem do produto

Cada imagem de um filho adiciona entradas aos dados da galeria do parent. Se os filhos compartilham as imagens do parent, não envie cópias em cada filho. Enviar a mesma foto para cinquenta filhos multiplica armazenamento, trabalho de resize e tamanho do JSON. O tratamento de imagens está em otimização de imagens de produto.

Meça o custo real

Antes de mudar qualquer coisa, descubra qual parte está lenta. Um jeito rápido de ver o tamanho do JSON em uma página de produto:

curl -s https://example.test/some-configurable.html \
  | grep -o '"jsonConfig":.*' | wc -c

Para o tempo de servidor, faça o profiling de uma página de produto e de uma de categoria sem cache com um profiler como Blackfire, Tideways ou New Relic, e olhe o tempo gasto nos blocks de renderer do configurável e dos swatches e o número de queries SQL. No banco, esta query lista os parents com mais filhos:

SELECT p.sku, COUNT(l.product_id) AS children
FROM catalog_product_super_link l
JOIN catalog_product_entity p ON p.entity_id = l.parent_id
GROUP BY l.parent_id, p.sku
ORDER BY children DESC
LIMIT 20;

No Adobe Commerce, a chave de join em catalog_product_entity é row_id em vez de entity_id por causa do content staging, então ajuste a query lá.

Correções em nível de código

Quando a modelagem de dados está boa e a loja ainda precisa de parents grandes, algumas mudanças de código ajudam.

  • Carregue swatches de forma lazy nas listagens. Renderize uma lista simples de cores na listagem e carregue a config completa dos swatches só quando o cliente interagir. Isso exige um renderer customizado e é específico do tema, mas remove a maior parte do custo da listagem.
  • Confira plugins de terceiros. Módulos que se enganchem em getUsedProducts() ou no JSON de config, por exemplo para adicionar dados extras dos filhos, muitas vezes carregam models completos dos filhos em um loop. Faça o profiling com e sem eles.
  • Evite carregar filhos em templates. Templates de listagem customizados que chamam getTypeInstance()->getUsedProducts() por produto criam um problema de N+1. Pegue os dados da collection indexada.
  • Use cache de forma agressiva. Páginas de produto e de categoria devem ser servidas do full page cache. A maior parte do custo acima é paga em cache miss, então uma taxa de hit alta esconde muita coisa. O setup do Varnish está em full page cache com Varnish.

Hyva e storefronts headless

Um frontend diferente não remove o custo de backend de carregar filhos, mas muda o lado do navegador. O Hyva troca o widget de swatch em Knockout e jQuery por componentes Alpine.js mais leves. Uma storefront headless busca as variantes por GraphQL, onde você controla quais campos são pedidos. Nos dois casos o conselho de modelagem de dados acima continua valendo.

FAQ

Quantos filhos um produto configurável do Magento pode ter?

Não há um limite fixo no código. A performance degrada conforme o número cresce. Algumas dezenas de filhos funcionam bem. Várias centenas pedem profiling e geralmente uma mudança na modelagem, nas configurações de swatch ou na renderização.

Por que o preço do produto configurável fica errado depois de uma mudança?

O preço do parent vem do price index, calculado a partir dos filhos vendáveis. Se os indexers estão em schedule e o cron não está rodando, ou se um filho ficou sem estoque sem um reindex, o parent mostra o valor antigo.

Swatches deixam as páginas de categoria mais lentas?

Deixam, quando Show Swatches in Product List está habilitado, porque cada configurável na listagem carrega dados dos filhos e renderiza seu próprio JSON de config. Desabilitá-los ou limitar os swatches por produto reduz o custo.

Devo usar configurável ou produtos simples com custom options?

Use configurável quando cada variação tem seu próprio SKU, estoque ou preço. Use custom options para personalização que não tem estoque próprio. Misturar os dois é uma causa comum de catálogos inchados.

Atendo como freelancer em Magento e Adobe Commerce, incluindo revisões de performance de catálogo. Se precisar de ajuda com isso, fale comigo.

Artigos relacionados