As Zonas Mortas do Cache: Como um Plugin de Backup Corrompeu 1.400 Sites WP e Ninguém Percebeu

Em um servidor silencioso de Houston, às 3:17 da madrugada, 1.400 sites WordPress pararam de servir páginas. Não havia pânico. Nenhum alerta no New Relic. O health check do cPanel ainda mostrava ‘verde’. Mas eu vi. Eu vi porque era o engenheiro de plantão que herdara aquele cluster legado — e porque, durante 17 horas seguidas, eu vivi dentro de um loop de SELECT ... FOR UPDATE que não fazia sentido.

O Fenômeno Invisível: Cache Invalidation por Stale Locks

Você já reparou como quase ninguém discute a física do cache em WordPress? Falam de Redis, de Varnish, de Cloudflare — mas ninguém menciona a bomba-relógio silenciosa que é a invalidação de cache em sites com plugins de backup concorrentes. O padrão é clássico: um plugin de backup (vou chamá-lo de BackupBuddyUltra, nome fictício) é configurado para fazer snapshots incrementais a cada 6 horas. Paralelamente, outro plugin gerencia o cache de página inteira. Ambos prometem ‘compatibilidade total’. Mas não há compatibilidade na física de transações MySQL.

Aqui está o furo: quando o backup lê uma tabela wp_options com mais de 50 transientes expirados, ele gera uma leitura pesada. O cache, ao invalidar aquelas chaves, tenta um DELETE em lote. O MySQL então escalona o lock — e aí o inferno começa. O loop de deadlock não derruba o site. Ele apenas o congela parcialmente. As páginas são servidas do cache estale por horas. Os administradores acham que o site está lento por causa de plugins pesados. Ninguém culpa o backup.

Cenário Real: 1.400 Sites, Mesmo Comportamento

O cluster hospedava sites de imobiliárias, pequenos e-commerces e blogs de nicho. Todos usando o mesmo tema base e o mesmo trio de plugins: Yoast, BackupBuddyUltra e W3 Total Cache. O padrão era idêntico: uma queda de 30% no Core Web Vitals (LCP subindo de 1,8s para 4,2s) por volta das 4h da manhã, e uma normalização ‘misteriosa’ após 2 horas. O suporte dos plugins apontava para ‘picos de tráfego de bots’. Mas não havia tráfego. O que havia era um deadlock silencioso entre a thread de backup e a thread de invalidação de cache.

-- Transação A (Backup):
SELECT * FROM wp_options WHERE option_name LIKE '_transient_%' FOR UPDATE;

-- Transação B (Cache Invalidation):
DELETE FROM wp_options WHERE option_name = '_transient_timeout_w3tc_123';

-- Resultado: InnoDB detecta deadlock. Rollback da transação B.
-- Cache não é invalidado. Página velha fica servida por até 2 horas.

O rolo compressor: como o backup e a invalidação rodavam em horários muito próximos (definidos aleatoriamente pelo cron do WordPress), o deadlock acontecia em 30% das execuções. E como o W3 Total Cache não reportava falha de invalidação, ninguém sabia. O cache simplesmente expirava pelo TTL normal.

Diagnóstico com WP-CLI Forensics

Levei 12 horas para entender o padrão. O truque? Um script de wp db query executado a cada minuto, capturando a saída de SHOW ENGINE INNODB STATUS durante a janela do backup:

#!/bin/bash
while true; do
  timestamp=$(date +%s)
  wp db query "SHOW ENGINE INNODB STATUS" --skip-column-names --single-transaction \
    | grep -A 10 "LATEST DETECTED DEADLOCK" > /tmp/deadlock_$timestamp.log
  sleep 60
done

Em 3 madrugadas, coletei 47 deadlocks. Todos envolvendo wp_options e as mesmas duas transações. O plugin de backup lia um range de linhas com FOR UPDATE, e o cache tentava deletar uma linha específica com lock implícito. O InnoDB escolhia a transação de cache como vítima — porque o backup já estava segurando mais locks.

A Correção que Ninguém Queria Fazer

Eu sabia que a causa raiz era a leitura com FOR UPDATE desnecessária no backup. Mas o plugin era proprietário e fechado. Sugeri ao time de infra: desativar o backup incremental automático e rodar backups manuais fora do horário de pico. ‘Perderemos a janela de SLA’, disseram. ‘Então mudem o cron do cache para invalidar antes do backup começar’. ‘Muito complexo’.

A solução de contorno que implementei: um script WP-CLI que, 5 minutos antes do backup, forçava a invalidação de todo o cache de página (wp cache flush) e matava todos os transientes expirados:

wp transient delete --expired
wp cache flush

Os deadlocks caíram para zero. Mas a verdadeira lição é mais profunda.

Por que o Mercado Ignora Isso?

Vivemos em uma bolha de ‘best practices’ superficiais. Tutoriais de performance focam em configurações de Redis, em plugins de cache, em CDN. Mas a termodinâmica de um sistema WordPress é ignorada. Ninguém estuda os estados de lock do MySQL durante operações concorrentes. Ninguém testa o comportamento de plugins de backup com cache ativo — porque os testes unitários não cobrem concorrência real. É o equivalente a projetar uma ponte sem considerar a dilatação térmica. Funciona… até não funcionar.

A Física do Gargalo Oculto

Os transientes do WordPress são salvos na tabela wp_options, que é MyISAM por padrão em muitos hosts antigos. Mas mesmo em InnoDB, o FOR UPDATE em um range lock pode travar toda a tabela se não houver índice adequado. E adivinhe? A coluna option_name tem índice, mas o backup muitas vezes usa LIKE '_transient_%' — que não é sargable. Full table scan. Lock de tabela inteira.

O resultado é uma zona morta: entre 2 e 5 da manhã, dezenas de sites ficam em um estado de limbo. Páginas servidas do cache não são atualizadas, formulários falham (porque nonces expirados não são renovados), e transações financeiras ficam inconsistentes. Ninguém percebe porque o site ‘funciona’ — até que um cliente reclama que o pedido sumiu.

Manifesto: A Web Precisa de Engenheiros, Não de Apertadores de Parafusos

A indústria de WordPress está doente. Celebridades dev vendem cursos sobre ‘como montar sites rápidos’ sem nunca terem debugado um deadlock. Plugins são feitos para funcionar isoladamente, não em ecossistemas. E a comunidade aceita isso como normal.

Eu não vou vender solução. Não vou falar de migrar para headless ou para outro CMS. Isso seria covarde. O que proponho é uma mudança cultural: todo site WordPress que sirva conteúdo crítico deveria passar por um teste de estresse de concorrência nas primeiras horas da manhã. Um simples script que simula backup + invalidação de cache + renovação de transientes. Se o site travar, o setup está errado.

Naquela noite em Houston, aprendi que o WordPress não é frágil. Nós é que o tornamos frágil. O sistema de cache é incrível — desde que você entenda que ele não é mágica. É um atleta de alto rendimento que precisa de aquecimento e resfriamento adequados. Ignorar a física dos locks é como correr uma maratona com tênis de salto.

Você pode continuar achando que o problema é sempre o plugin de cache. Mas, quando o cache falha silenciosamente às 3 da manhã, lembre-se: o culpado pode estar em um lugar que ninguém olha. E agora você já sabe onde procurar.

Rolar para cima