WordPress, Object Cache e Redis
Tags: wordpress, redis, performance, braziliandevs
Published: 2022-08-15 21:55:04

_Originalmente publicado no [meu blog](https://felipeelia.com.br/wordpress-object-cache-e-redis/)._

<!-- wp:paragraph -->
<p>Quer um jeito fácil de deixar o seu <strong>site WordPress mais rápido</strong> e ainda por cima dar um <strong><em>up </em>no seu nível de programação com WP</strong>? O Object Cache pode ser a resposta para as duas coisas!</p>
<!-- /wp:paragraph -->

## Índice
* [Antes de Object Cache… O que é cache?](#antes-de-object-cache-o-que-cache)
* [O Object Cache do WordPress](#o-object-cache-do-wordpress)
* [Como “gravar” coisas na memória? Redis ou Memcached](#como-gravar-coisas-na-memria-redis-ou-memcached)
	* [O que eu preciso para usar Object Cache com Redis no WordPress?](#o-que-eu-preciso-para-usar-object-cache-com-redis-no-wordpress)
* [O plugin Redis Object Cache](#o-plugin-redis-object-cache)
	* [Mesmo Redis com diferentes sites no mesmo servidor](#mesmo-redis-com-diferentes-sites-no-mesmo-servidor)
* [Como o plugin de Object Cache funciona](#como-o-plugin-de-object-cache-funciona)
* [Dando uma olhada em código](#dando-uma-olhada-em-cdigo)
* [As funções de cache disponíveis no WordPress](#as-funes-de-cache-disponveis-no-wordpress)
	* [Chave, valor, grupo e expiração](#chave-valor-grupo-e-expirao)
	* [Expiração](#expirao)
		* [Baseado em um evento](#baseado-em-um-evento)
		* [Baseado em tempo](#baseado-em-tempo)
* [Transients API e Object Cache](#transients-api-e-object-cache)

<!-- wp:paragraph -->
<p>No post sobre <a href="https://felipeelia.com.br/o-que-e-uma-api/">o que é uma API</a>, comentei brevemente sobre o cache de objetos do WordPress e como eu uso o Redis aqui no blog.  Nesse post vamos dar uma olhada melhor nessas duas coisas.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="antes-de-object-cache-o-que-e-cache">Antes de <em>Object Cache</em>... O que é cache?</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Antes de falarmos sobre Redis, precisamos falar sobre Object Cache. E antes de falarmos sobre Object Cache, você adivinhou, vamos falar rapidamente sobre cache em geral.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Procurando por definições sobre o que é cache na internet, o senso comum parece ser algo como o seguinte:</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>Cache é um lugar de <strong>armazenamento intermediário e acesso rápido</strong>, localizado entre o consumidor e o armazenamento principal, potencialmente economizando uma viagem mais demorada.</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>Veja o exemplo abaixo. Na primeira vez a requisição vai até o servidor final, mas o servidor de cache guarda uma cópia. <strong>Na segunda vez é servida a cópia, economizando a viagem até o servidor.</strong></p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":2110,"width":768,"height":384,"sizeSlug":"large","linkDestination":"media"} -->
<figure class="wp-block-image aligncenter size-large is-resized"><a href="https://felipeelia.com.br/wp-content/uploads/2022/08/cache-definition-lg2x.webp"><img src="https://felipeelia.com.br/wp-content/uploads/2022/08/cache-definition-lg2x-1024x512.webp" alt="Diagrama com duas linhas: na primeira a requisição sai do client, passa pelo servidor de cache e chega até o servidor de origem. Na segunda, o servidor de cache já responde a requisição." class="wp-image-2110" width="768" height="384"/></a><figcaption class="wp-element-caption">Peguei a imagem lá no <a href="https://www.keycdn.com/support/cache-definition-explanation">blog do KeyCDN</a>.</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Com isso em mente, podemos pensar nas várias viagens necessárias para abrir um site e nos vários intermediários que podemos inserir para economizar viagens:</p>
<!-- /wp:paragraph -->

<!-- wp:table {"className":"is-style-stripes"} -->
<figure class="wp-block-table is-style-stripes"><table><tbody><tr><td><strong>Browser cache</strong></td><td>Entre o seu navegador e o servidor do site, se houver uma <strong>cópia válida do site no seu computador</strong>, economizamos uma viagem. Na verdade, a viagem inteira nesse caso.</td></tr><tr><td><strong>CDN</strong></td><td>Se seu site passar por um CDN como Cloudflare e <strong>se o conteúdo gravado no CDN ainda for válido</strong>, não há porque solicitar uma nova cópia, basta enviar a antiga e economizamos uma viagem.</td></tr><tr><td><strong>Cache&nbsp;do&nbsp;WP</strong></td><td>No seu site WordPress, se um post já foi trazido do banco de dados uma vez, se a <strong>informação que temos na memória ainda é valida</strong>, por que ir até o banco de dados novamente?</td></tr></tbody></table></figure>
<!-- /wp:table -->

<!-- wp:paragraph -->
<p>Existem muitas outras viagens em que podemos adicionar um lugar intermediário para salvar uma versão da informação esses são só alguns exemplos. Ah, e <strong>a pronúncia é a mesma que CASH (dinheiro)</strong>, não cachê ou qualquer outra coisa.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="o-object-cache-do-wordpress">O <em>Object Cache</em> do WordPress</h2>
<!-- /wp:heading -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>O <em>Object Cache</em> do WordPress é simples e entender como ele funciona pode ser um diferencial na sua próxima entrevista de emprego ;-)</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>Como falei no post sobre <a href="https://felipeelia.com.br/o-que-e-uma-api/">o que é uma API</a>, acesso a memória é sempre mais rápido que acesso a disco. Com isso em mente, se no início do processamento de uma página <strong>já trouxemos um post para memória, não há necessidade de irmos no banco novamente para buscá-lo</strong>.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":2119,"sizeSlug":"full","linkDestination":"media"} -->
<figure class="wp-block-image aligncenter size-full"><a href="https://felipeelia.com.br/wp-content/uploads/2022/08/memory-hierarchy.png"><img src="https://felipeelia.com.br/wp-content/uploads/2022/08/memory-hierarchy.png" alt="Hierarquia de memória estendida. Da base para o topo: armazenamento offline (mais lento), disco rígido, memória RAM, cache do processador e CPU (mais rápido). Quando mais rápido, mais caro." class="wp-image-2119"/></a><figcaption class="wp-element-caption">A qualidade não é das melhores, mas a imagem é ótima para entender a relação entre armazenamento e velocidade.</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>O problema é que, na implementação padrão do WordPress, <strong>ele só ficará na memória até o fim do processamento daquela página</strong>. Precisa dele novamente se o usuário recarregar a página? Vamos ter que ir no banco de dados novamente.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>O MySQL é inteligente o suficiente para notar se uma informação já foi solicitada recentemente e deixá-la disponível mais rapidamente, mas mesmo assim não é tão rápido. <strong>E, no fim das contas, se estamos indo até o MySQL, estamos fazendo uma viagem maior de qualquer forma.</strong></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Por padrão, esse post solicitado no começo do processo (ou qualquer outra informação que quisermos) é <strong>gravado usando uma classe chamada <a href="https://developer.wordpress.org/reference/classes/wp_object_cache">WP_Object_Cache</a></strong>. Seu objetivo principal é economizar viagens ao banco de dados e ela funciona basicamente como um par chave-valor. O problema, como eu disse, é que isso tudo só dura uma requisição. Para a próxima, fazemos tudo de novo.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="como-gravar-coisas-na-memoria-redis-ou-memcached">Como "gravar" coisas na memória? Redis ou Memcached</h2>
<!-- /wp:heading -->

<div class="wp-block-column" style="flex-basis:66.66%"><!-- wp:paragraph -->
<p><strong>Programas como <a href="https://redis.io/">Redis</a> ou <a href="https://memcached.org/">Memcached</a> são bancos de dados que armazenam informações em memória.</strong> Eles podem fazer mais do que isso, principalmente o Redis, mas para o nosso problema com o WordPress, o armazenamento em memória de pares chave-valor do Redis é tudo o que realmente precisamos.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Ao longo do post vou usar o Redis como exemplo, mas a instalação e utilização com memcached é bem parecida.</p>
<!-- /wp:paragraph --></div>

<!-- wp:heading {"level":3} -->
<h3 id="o-que-eu-preciso-para-usar-object-cache-com-redis-no-wordpress">O que eu preciso para usar Object Cache com Redis no WordPress?</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>A lista de pré-requisitos para usar o Object Cache do WordPress com Redis é bem simples:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>Um site WordPress</li><li>Redis funcionando na sua infraestrutura</li><li>Um plugin WordPress para conectar o WP ao Redis</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>Para o segundo item, o mais rápido é entrar em contato com sua empresa de hospedagem. Na Cloudways, por exemplo, você precisa <a href="https://www.cloudways.com/blog/install-redis-cache-wordpress/#activating-redis-on-the-cloudways-platform">apertar um botão no Painel para instalar o Redis</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":1963,"width":512,"height":239,"sizeSlug":"full","linkDestination":"media"} -->
<figure class="wp-block-image aligncenter size-full is-resized"><a href="https://felipeelia.com.br/wp-content/uploads/2021/08/como-habilitar-redis-na-cloudways.jpg"><img src="https://felipeelia.com.br/wp-content/uploads/2021/08/como-habilitar-redis-na-cloudways.jpg" alt="Como habilitar o Redis na Cloudways: Server Management -&gt; Settings &amp; Packages -&gt; Packages -&gt; Redis (Install)" class="wp-image-1963" width="512" height="239"/></a></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 id="o-plugin-redis-object-cache">O plugin <em>Redis Object Cache</em></h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Como eu disse no outro post, o plugin que eu uso é o <a href="https://br.wordpress.org/plugins/redis-cache/">Redis Object Cache</a>. Instalá-lo e configurá-lo é relativamente simples. Por padrão, ele tentará se conectar a uma instância do Redis localizada no próprio servidor (<code>127.0.0.1</code>) na porta <code>6379</code> usando o banco de dados Redis de número <code>0</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":2048,"width":480,"height":216,"sizeSlug":"full","linkDestination":"custom"} -->
<figure class="wp-block-image aligncenter size-full is-resized"><a href="https://br.wordpress.org/plugins/redis-cache/"><img src="https://felipeelia.com.br/wp-content/uploads/2022/06/redis-object-cache-plugin.png" alt="Captura de tela do cabeçalho do plugin WordPress &quot;Redis Object Cache&quot; no repositório de plugins." class="wp-image-2048" width="480" height="216"/></a></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Para que o plugin funcione, ele precisa copiar o arquivo <code>object-cache.php</code> de dentro de sua própria pasta <em>includes</em> para a pasta <em>wp-content</em> da sua instalação WordPress. Se por qualquer motivo, como permissão de diretórios, não for possível copiar o arquivo automaticamente, será necessário copiá-lo manualmente.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 id="mesmo-redis-com-diferentes-sites-no-mesmo-servidor">Mesmo Redis com diferentes sites no mesmo servidor</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>O meu outro blog, o <a href="https://melancianacabeca.com.br/">Melancia na Cabeça</a>, também é armazenado neste servidor e compartilha a mesma instalação do Redis. Para evitar que os valores armazenados pelos dois sites entrem em conflito, eu precisei definir <code>define( 'WP_REDIS_DATABASE', 0 );</code> em um e <code>define( 'WP_REDIS_DATABASE', 1 );</code> no outro.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Os parâmetros de configuração diferentes do padrão precisam ser adicionados no arquivo <em>wp-config.php</em> do seu site. A lista completa de parâmetros está na <a href="https://github.com/rhubarbgroup/redis-cache/wiki/Connection-Parameters">wiki do plugin</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="como-o-plugin-de-object-cache-funciona">Como o plugin de <em>Object Cache </em>funciona</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Na função <a href="https://developer.wordpress.org/reference/functions/wp_start_object_cache/">wp_start_object_cache()</a> (trechos abaixo), executada no início do fluxo de carregamento do WordPress, ele detecta a existência ou não do drop-in <code>object-cache.php</code>. Se disponível, o arquivo é carregado, caso contrário a implementação padrão entra em ação. Então, o cache é instanciado.</p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"php"} -->
<pre class="wp-block-code php"><code>function wp_start_object_cache() {
	...
		if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
			<strong>require_once WP_CONTENT_DIR . '/object-cache.php';</strong>
			...
		}
	...
 

	// Se não houver object cache externo, usa o padrão do WP.
	if ( ! wp_using_ext_object_cache() ) {
		require_once ABSPATH . WPINC . '/cache.php';
 	}
 
	require_once ABSPATH . WPINC . '/cache-compat.php';
 
	...
	wp_cache_init();
	...
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>O conteúdo do drop-in <code>object-cache.php</code> do <em>Redis Object Cache</em> pode ser visto <a href="https://plugins.trac.wordpress.org/browser/redis-cache/trunk/includes/object-cache.php">aqui</a>. Ele é formado por uma implementação da classe <code>WP_Object_Cache</code> (mais pro final do arquivo) e das várias funções que a utilizam como <code>wp_cache_add</code>, <code>wp_cache_get</code> e <code>wp_cache_delete</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":2102,"width":360,"height":270,"sizeSlug":"full","linkDestination":"media"} -->
<figure class="wp-block-image aligncenter size-full is-resized"><a href="https://felipeelia.com.br/wp-content/uploads/2022/08/doc-back-to-the-future-ready.webp"><img src="https://felipeelia.com.br/wp-content/uploads/2022/08/doc-back-to-the-future-ready.webp" alt="Doutor Brown do filme &quot;De volta para o futuro&quot; segurando cabos elétricos e falando &quot;Ready!&quot;" class="wp-image-2102" width="360" height="270"/></a></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 id="dando-uma-olhada-em-codigo">Dando uma olhada em código</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Se você precisa de código para entender as coisas melhor, eu entendo. Também sou assim. Aqui está uma parte do caminho executado quando você chama <code>get_post( 123 );</code> no WordPress 6.0.1.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>O código começa em <a href="https://github.com/WordPress/WordPress/blob/6.0.1/wp-includes/post.php#L990">wp-includes/post.php</a>, onde get_post chama <code>WP_Post::get_instance( $post )</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>E então em <a href="https://github.com/WordPress/WordPress/blob/6.0.1/wp-includes/class-wp-post.php#L231">wp-includes/class-wp-post.php</a>:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>final class WP_Post {
	...
	public static function get_instance( $post_id ) {
		<strong>$_post = wp_cache_get( $post_id, 'posts' );</strong>

		if ( ! $_post ) {
			...
			<strong>wp_cache_add( $_post-&gt;ID, $_post, 'posts' );</strong>
		} 
		...
	}
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Primeiro se verifica se o cache existe (<code>wp_cache_get</code>). Se existir é usado, senão o post é buscado e salvo no cache (<code>wp_cache_add</code>).</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>E só para dar uma olhada no conteúdo da função <code>wp_cache_add</code>, veja como ela nada mais é que um envelope para o método <code>add</code> da instância da classe <code>WP_Object_Cache</code> armazenada na variável global <code>$wp_object_cache</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
    global $wp_object_cache;
 
    return $wp_object_cache-&gt;add( $key, $data, $group, (int) $expire );
}</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 id="as-funcoes-de-cache-disponiveis-no-wordpress">As funções de cache disponíveis no WordPress</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>A <a href="https://developer.wordpress.org/reference/classes/wp_object_cache/#wp_cache-functions">documentação oficial lista algumas das funções de cache mais importantes</a> e estou transcrevendo-as aqui. O próprio core faz uso delas em vários lugares, mas você também pode chamá-las em seu tema ou plugin.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>wp_cache_add( $key, $data, $group = '', $expire = 0 ) // Se existe, não sobrescreve
wp_cache_set( $key, $data, $group = '', $expire = 0 ) // Se existe, sobrescreve
wp_cache_replace( $key, $data, $group, $expire ) // Se não existe, não faz nada
wp_cache_get( $key, $group = '', $force = false, $found = null )
wp_cache_delete( $key, $group = '' )
wp_cache_flush()</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3 id="chave-valor-grupo-e-expiracao">Chave, valor, grupo e expiração</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>A maioria das funções <code>wp_cache_*</code> usam quatro parâmetros:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><strong>Chave:</strong> o identificador do objeto armazenado em cache. O ID do post, por exemplo</li><li><strong>Valor:</strong> o objeto armazenado propriamente dito. O próprio post.</li><li><strong>Grupo (opcional):</strong> o grupo serve para -- você adivinhou -- agrupar diferentes chaves. Antes servia mais para organização, mas no WordPress 6.1, será possível chamar <a href="https://core.trac.wordpress.org/ticket/4476">a nova função <code>wp_cache_flush_group</code></a> para apagar todos os objetos de um determinado grupo, como por exemplo, tudo armazenado em cache de um determinado plugin.</li><li><strong>Expiração (opcional):</strong> por quanto tempo o cache é considerado válido. Vamos dar uma olhada mais a fundo nisso.</li></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":3} -->
<h3 id="expiracao">Expiração</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Quando falamos de cache, um conceito importantíssimo é tempo de expiração. <strong>A informação que temos ainda é válida?</strong> Essa é uma parte chave de qualquer implementação de cache, incluindo o Object Cache do WordPress.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Então, como decidimos por quanto tempo um objeto em cache deve ser considerado válido?</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4 id="baseado-em-um-evento">Baseado em um evento</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Vamos supor que você está armazenado em cache a lista de posts com maior conteúdo. Você pega todos os posts, passa o filtro <code>the_content</code> em todos eles e mede quantas letras cada um tem. Bem intenso, certo? Isso merece ser armazenado em cache.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>O que pode fazer o resultado deste processo mudar?</strong> Só um post novo ser criado ou algum existente ser editado, certo?</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Neste caso, nosso cache não precisa expirar. <strong>Basta invalidar o resultado que temos usando um <a href="https://felipeelia.com.br/o-que-sao-hooks/">hook</a> relacionado</strong>, nesse caso, a criação de um novo post ou edição de um post que já existe.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4 id="baseado-em-tempo">Baseado em tempo</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Se o que você está armazenando vem, por exemplo, de um ambiente externo como uma API, o evento que invalida o cache não está sob nosso controle. Neste caso, precisamos <strong>verificar o resultado de tempos em tempos</strong> na API externa e atualizar o valor que temos armazenado.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="transients-api-e-object-cache">Transients API e Object Cache</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Se você leu o post ou assistiu ao vídeo sobre a <a href="https://felipeelia.com.br/cache-facil-no-wordpress-transients-api/">Transients API do WordPress</a>, a ideia principal aqui não é novidade: <strong>um par de chave-valor que evita um processo demorado é exatamente a ideia por trás das duas coisas</strong>. De fato, em um certo aspecto elas são a mesma coisa. Veja a implementação da função <code>get_transient</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>function get_transient( $transient ) {
    if ( wp_using_ext_object_cache() || wp_installing() ) {
        $value = <strong>wp_cache_get( $transient, 'transient' )</strong>;
    } else {
		...
    }
	...
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Basicamente, se você está usando um mecanismo externo para Object Cache, o transiente será gravado lá. Caso contrário, o banco de dados normal será usado. Interessante, não é? Dá uma olhada lá!</p>
<!-- /wp:paragraph -->