← Voltar ao blog

Full page cache com Varnish no Magento 2: setup que realmente dá hit

Rodrigo Biazzi

Colocar o Varnish na frente do Magento 2 é fácil. Fazer ele servir a maioria das requisições do cache é outro trabalho. Muitas lojas rodam Varnish com um hit rate tão baixo que quase todo visitante ainda espera pelo PHP. Este artigo cobre como o full page cache do Magento funciona com o Varnish na 2.4.x, como configurar a partir do VCL gerado, como medir o hit rate e os motivos de sempre para ele ficar baixo.

Como funciona o full page cache do Magento

O Magento decide quais páginas são cacheáveis. O Varnish as guarda e as serve. Os dois conversam por headers HTTP:

  • O Cache-Control na resposta diz se a página pode ser cacheada e por quanto tempo. Páginas que não podem ser cacheadas são enviadas com valores private ou no-store.
  • O X-Magento-Tags lista as cache tags de tudo que está na página, como cat_p_123 para um produto ou cat_c_45 para uma categoria. O Varnish guarda o header junto com o objeto e o remove antes de enviar a resposta ao navegador.
  • O cookie X-Magento-Vary guarda um hash do contexto que muda a página: grupo do cliente, moeda, store e valores parecidos. Ele faz parte da chave de cache, então um cliente de atacado logado não recebe a página do varejo.

Quando uma entidade muda, o Magento envia uma requisição PURGE ao Varnish com um header X-Magento-Tags-Pattern. O VCL transforma isso num ban em todos os objetos cujas tags casam. É assim que o save de um produto invalida a página do produto, as categorias dele e qualquer página que o liste, sem limpar o resto.

O conteúdo específico do cliente, como o minicart, a mensagem de boas-vindas e o contador da wishlist, nunca faz parte do HTML cacheado. Ele é carregado por JavaScript a partir de customer/section/load e guardado no navegador. Esse é o mecanismo de private content, e é a razão de um full page cache conseguir funcionar numa loja com login e carrinho.

Configurando o Varnish a partir do VCL gerado

Não escreva um VCL do zero e não copie um de um blog antigo. Gere a partir da sua versão do Magento, porque o VCL do core muda entre releases.

bin/magento config:set system/full_page_cache/caching_application 2
bin/magento config:set system/full_page_cache/ttl 86400

bin/magento varnish:vcl:generate \
    --export-version=6 \
    --backend-host=127.0.0.1 \
    --backend-port=8080 \
    --access-list=127.0.0.1 \
    --output-file=/etc/varnish/default.vcl

bin/magento setup:config:set --http-cache-hosts=127.0.0.1:6081
bin/magento cache:flush
  • caching_application igual a 2 troca o full page cache do cache interno da aplicação para o Varnish.
  • --export-version precisa bater com a versão major do Varnish que você roda. Veja nos requisitos de sistema quais versões a sua release do Magento suporta.
  • --access-list lista os hosts autorizados a enviar PURGE. Deixe só os seus servidores de aplicação.
  • --http-cache-hosts no app/etc/env.php diz ao Magento para onde enviar as requisições de purge. Com vários nós de Varnish, liste todos separados por vírgula.

O que conferir no arquivo gerado

  • A probe do health check aponta para /pub/health_check.php. Se o document root do seu web server já é pub/, como deveria ser, troque a URL da probe para /health_check.php. Senão o Varnish marca o backend como doente.
  • Se o TLS termina na frente do Varnish, garanta que o terminador envie X-Forwarded-Proto. O VCL gerado usa isso no hash para que as versões HTTP e HTTPS não se misturem.
  • Páginas de categoria grandes produzem headers X-Magento-Tags longos. Se passarem dos limites do Varnish, o fetch no backend falha com 503. A documentação da Adobe recomenda aumentar os parâmetros http_resp_hdr_len e http_resp_size por esse motivo.

Mantenha suas próprias alterações numa seção pequena e comentada e regenere o arquivo base quando atualizar o Magento. Um diff entre o arquivo gerado antigo e o novo mostra o que o core mudou.

Medindo o hit rate

Você não conserta o que não mede. O Varnish dá os números direto:

varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss -f MAIN.cache_hitpass -f MAIN.s_pass

varnishncsa -F '%U%q %{Varnish:hitmiss}x %{Varnish:handling}x'

O primeiro comando dá os totais desde que o Varnish iniciou. O segundo transmite cada requisição indicando se foi hit, miss ou pass. Deixe rodando alguns minutos com tráfego normal e ordene os misses e passes por URL. Os padrões aparecem rápido: a mesma URL com query strings diferentes, um tipo de página inteiro que sempre dá pass, ou requisições POST que você não esperava.

Em developer mode o Magento adiciona um header X-Magento-Cache-Debug com HIT ou MISS, o que ajuda no staging. Em produção use o varnishlog para uma URL só:

varnishlog -g request -q 'ReqURL ~ "^/women/tops.html"'

Olhe as linhas VCL_call e VCL_return para ver se a requisição foi para hash, pass ou pipe, e os headers da resposta do backend para ver o que o Magento enviou.

Por que o hit rate fica baixo

cacheable="false" no layout

Essa é a armadilha clássica. Um único block marcado com cacheable="false" num arquivo de layout torna a página inteira não cacheável, não só aquele block. Se ele está no handle default, a storefront inteira fica não cacheável.

<referenceContainer name="sidebar.additional">
    <block class="Acme\Promo\Block\Countdown" name="acme.promo.countdown"
           template="Acme_Promo::countdown.phtml" cacheable="false"/>
</referenceContainer>

Autores de extensões adicionam isso quando um block mostra dados dinâmicos ou do cliente, porque deixa o block correto nos testes. Ache todas as ocorrências:

grep -rn 'cacheable="false"' app/code app/design vendor --include=*.xml | grep -v "/adminhtml/"

Páginas como carrinho, checkout e conta do cliente são feitas para não serem cacheáveis. Qualquer coisa em default, catalog_category_view, catalog_product_view ou cms_index_index merece um olhar mais atento. A correção é renderizar a parte dinâmica por private content ou por uma chamada AJAX e remover a flag.

Query strings que fragmentam o cache

Cada URL única é um objeto de cache separado. Links de marketing adicionam parâmetros como utm_source ou gclid, e sem tratamento cada clique de campanha é um miss. O VCL gerado já remove uma lista de parâmetros comuns de marketing antes de fazer o hash. Confira essa lista contra os parâmetros que o seu tráfego realmente usa e amplie se precisar. Parâmetros que mudam a página, como filtros de layered navigation, ordenação e paginação, precisam ficar.

Contextos demais

Tudo que entra no X-Magento-Vary multiplica o número de versões de cada página. Muitos grupos de clientes, várias moedas e valores de contexto customizados adicionados por extensões via Magento\Framework\App\Http\Context podem espalhar o tráfego por tantas variantes que poucas chegam a ficar quentes. Revise o que é adicionado ao contexto HTTP e mantenha só o que realmente muda o HTML.

Cookies e sessões em páginas cacheáveis

Se um módulo customizado define um cookie ou grava na sessão enquanto renderiza uma página cacheável, ou ele quebra o cache ou, pior, vaza o estado de um visitante para o HTML cacheado. Dados de sessão não têm lugar num block que renderiza numa página em cache.

Purges amplos demais

Um purge total frequente esvazia o cache toda hora. As causas comuns são um cron job ou importação que chama cache:flush ou limpa o tipo full_page, uma sincronização com o ERP que salva todo produto a cada execução mesmo sem mudança, e atualizações de estoque de produtos exibidos em toda página. Registre as requisições de purge no Varnish por um dia e veja o que as dispara.

varnishlog -g request -q 'ReqMethod eq "PURGE"' -i ReqHeader

Private content do jeito certo

Quando um block precisa mostrar algo específico do cliente numa página em cache, use customer data sections em vez de desligar o cache. Uma section é uma classe PHP que implementa Magento\Customer\CustomerData\SectionSourceInterface e retorna um array. Ela é registrada no di.xml e lida em JavaScript pelo módulo Magento_Customer/js/customer-data no Luma, ou pelo evento equivalente no Hyvä.

Para atualizar uma section depois de uma ação, declare no etc/frontend/sections.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd">
    <action name="acme_promo/coupon/apply">
        <section name="cart"/>
        <section name="acme-promo"/>
    </action>
</config>

Depois de um POST para essa action, o navegador recarrega essas sections. O HTML continua em cache e compartilhado, e só uma pequena resposta JSON é pessoal.

Hole punching com ESI

Alguns conteúdos são iguais para todos, mas mudam com mais frequência que a página ao redor. Para isso o Magento suporta Edge Side Includes. Um block com atributo ttl no layout é renderizado como um include ESI quando o Varnish é a cache application, e o Varnish o busca e o cacheia separadamente, com sua própria duração.

<block class="Acme\Promo\Block\Banner" name="acme.promo.banner"
       template="Acme_Promo::banner.phtml" ttl="600"/>

Use com moderação. Cada block ESI é outra requisição ao backend quando expira, e o ESI é a ferramenta errada para conteúdo específico do cliente, que pertence ao private content.

Grace e warm up

O período de grace deixa o Varnish servir um objeto um pouco velho enquanto busca um novo em segundo plano, e mantém o site no ar se o backend ficar doente por pouco tempo. O VCL gerado suporta isso e a opção --grace-period define o valor. Depois de um deploy ou de um flush total o cache está vazio, então os primeiros visitantes pagam pela renderização sem cache. Um warm up simples que requisita a home, as principais categorias e os produtos mais vendidos a partir do sitemap reduz esse impacto. Mantenha lento o bastante para não sobrecarregar o backend que você acabou de publicar.

FAQ

Um block com cacheable=false desabilita o cache da página inteira?

Sim. Se qualquer block no layout da página está marcado com cacheable=false, o Magento envia a página inteira como não cacheável. Coloque no handle default e nenhuma página da storefront fica em cache.

Como vejo se uma página do Magento foi hit no Varnish?

Em developer mode o Magento adiciona um header X-Magento-Cache-Debug com HIT ou MISS. Em produção use o varnishlog para a URL ou o varnishncsa com o campo Varnish:hitmiss para ver como cada requisição foi tratada.

Como o Magento faz purge no Varnish quando um produto muda?

O Magento envia uma requisição PURGE com um header X-Magento-Tags-Pattern listando as cache tags afetadas. O VCL gerado transforma isso num ban em todos os objetos em cache que carregam essas tags no X-Magento-Tags.

Posso cachear páginas para clientes logados?

Sim. Clientes logados recebem páginas em cache por contexto, principalmente grupo do cliente, e as partes pessoais, como carrinho e nome, vêm de customer data sections carregadas por JavaScript.

Atendo como freelancer em Magento e Adobe Commerce, de desenvolvimento de módulos e auditorias de performance a migrações e projetos em Hyvä. Se isso fizer sentido para você, fale comigo.

Artigos relacionados