A query de ranking leva cinco segundos. O reflexo é Cache::remember. Trinta segundos depois, a mesma request volta em dezenas de milissegundos. O gráfico bonito esconde a pergunta que vai doer: quando esse valor deixa de ser verdade — e quem avisa o cache?
Cache é cópia. Cópia fica velha. O difícil nunca foi guardar. Foi decidir o que é copiável e como a cópia morre.
O que vale a pena guardar
Guarde o que é caro de obter e estável o bastante para o usuário aceitar um atraso de atualização. Agregado de avaliação, HTML de página pública, resposta de API de terceiro com cota, lista de categorias, configuração que muda uma vez por semana. O ganho é não repetir o custo.
Não guarde o que já é barato. Cache na frente de um SELECT por chave primária, com índice, em disco quente, adiciona Redis, serialização e um modo novo de estar errado. Otimize a query. Cache não é desculpa para N+1.
Com vários servidores, cache em memória do processo (APCu, array estático) mente: o nó A invalida, o B serve o velho. Cache distribuído — Redis, Memcached — é o que torna a cópia um componente, não um acidente de processo. Você paga RAM rápida e mais um ponto para operar. Em troca, todos os nós veem o mesmo valor, ou pelo menos o mesmo TTL.
TTL (tempo de vida) é o palpite honesto: “este ranking pode ter até 30 s de atraso”. Palpite bom vem do negócio, não do default do framework. Página de produto que muda preço na hora não é ranking de especialistas da semana.
flowchart TD
A[Dado pedido] --> B{É caro e estável?}
B -->|Não| C[Buscar na origem]
B -->|Sim| D{Pode atrasar?}
D -->|Não| C
D -->|Sim| E{Como morre a cópia?}
E -->|TTL basta| F[Cache com expiração]
E -->|Precisa do evento| G[Invalidar no write]
E -->|Não sei invalidar| C
classDef ouro fill:#FBF5E4,stroke:#C9A227,color:#0B1F18,stroke-width:1.4px
classDef verde fill:#F3FAF7,stroke:#1E7A57,color:#0B1F18,stroke-width:1.4px
classDef fim fill:#14513B,stroke:#0F3328,color:#FBFAF6,stroke-width:1.4px
class A,B,D ouro
class E,F,G verde
class C fim
Invalidação é o trabalho de verdade
Há três famílias, e misturá-las sem regra é incidente.
TTL só. Simples. Eventualmente consistente. Serve para dado que pode atrasar. Não serve para “o usuário acabou de publicar e não se vê”.
Invalidar no write. No mesmo fluxo que grava a origem, você apaga (ou atualiza) a chave. Correto quando o write é o único caminho. Falso quando existe admin, job, SQL manual, sync de ERP. O cache vira verdade paralela.
Versionar chave. produto:42:v7. O write incrementa versão. Leitores pegam a nova. Evita alguns races de delete/set. Exige disciplina de chave. Não apaga o problema de “quem incrementa”.
Stampede (mil requests recalculando o miss ao mesmo tempo) é o primo do TTL curto em dado caro. Lock de recálculo, grace period, prefill em job: ferramentas. Se o recálculo leva cinco segundos, não deixe o miss da request do usuário ser o recálculo. Prefira fila que aquece a chave.
A pergunta de desenho é: quem é o dono da verdade, e em que instante a cópia pode divergir? Se você não responde, o cache é um bug com SLA de performance.
O que nunca (ou quase nunca) cachear
Autorização. “Pode este usuário ver este recurso?” muda com papel, time, recurso revogado, sessão encerrada. Servir 200 de um cache antigo é vazamento. 403 antigo é suporte. Permissão pertence ao request atual, talvez com um lookup rápido e indexado, não com uma página HTML pública reutilizada entre usuários. Cuidado especial com cache de proxy (CDN, Nginx) em página autenticada: um Cache-Control errado publica o painel do cliente A no cliente B.
Dinheiro em movimento. Saldo, limite, posição de estoque no checkout, status de pagamento. Atraso aqui não é “ranking um pouco velho”. É oversell, double spend, comprovante mentiroso. Leia a origem transacional. Se precisar de velocidade, o caminho é modelo de dado e índice, não TTL de 10 s no saldo.
Relatório único e “em tempo real”. Compliance, auditoria, extrato, relatório que o usuário gera para enviar a um órgão. O requisito costuma ser “os dados mais recentes” ou “exatamente esta fotografia com carimbo”. Cache mente nos dois. Relatório pesado vai para job, não para Redis com a query de cinco segundos.
Há zona cinzenta: catálogo de produto cujo preço muda de hora em hora pode ter cache curto se o checkout relê o preço na origem. A página pode atrasar. A cobrança não.
| Tipo de dado | Cache? | Por quê |
|---|---|---|
| Ranking público, agregação cara | Sim, com TTL explícito | Atraso aceitável |
| HTML anônimo | Sim, CDN + TTL | Alta leitura, pouca variação |
| Permissão / papel | Não (ou TTL minúsculo com medo) | Revogação tem que valer agora |
| Saldo, pagamento, estoque de checkout | Não | Cópia vira dinheiro errado |
| Extrato, relatório oficial | Não | Fotografia tem que ser da origem |
| Config rara | Sim, invalidar no write | Write é caminho único |
Chave, hit e honestidade
Chave ruim é cache inútil: esquecer o limit, o idioma, o usuário, a versão do schema. Hit alto em chave errada é pior do que miss. Inclua na chave tudo que muda o resultado, e nada que não mude. highest_rated:10 não é highest_rated:50.
Meça hit ratio, mas não idolatre. 99% de hit em dado que não deveria estar no cache é 99% de mentira eficiente. Olhe também idade da chave, taxa de invalidação, tempo de recálculo, erros de desserialização.
Opcache e HTTP cache são primos, não o mesmo bicho. Bytecode no processo não substitui Redis de ranking. CDN não substitui autorização. Cada camada copia uma coisa. Nomeie a camada quando for discutir “vamos cachear”.
Uma regra para a próxima chave
- Qual o custo da origem e o atraso máximo que o negócio aceita?
- Qual evento torna a cópia falsa?
- Esse evento passa pelo mesmo código que eu controlo?
- Se a resposta a (3) for não, TTL conservador ou não cacheie.
- Se for dinheiro, permissão ou relatório único, pare no passo (1) e não crie a chave.
Cache bom é decisão de produto disfarçada de infraestrutura. TTL é o acordo de atraso. Invalidação é o acordo de verdade. Sem os dois, você só comprou RAM para servir o passado com confiança.
Fila, cache e API no mesmo projeto aparecem no PHP One volume 2, quando a Casa Amarela deixa de caber no request síncrono.
Leia também: Mensageria: por que filas melhoram a resposta e Load balancer, Nginx e vários servidores.