O WordPress ao longo do tempo se tornou mais do que uma ferramenta para criar simples blogs e acabou virando uma plataforma de CMS completa. Muitos sites começaram a adotá-lo, incluindo sites com grande acesso e interação. Estes grandes volumes, por sua vez, ocasionaram algumas deficiências em termos de desempenho do site em algumas partes da infraestrutura, principalmente nos servidores web e bancos de dados.
Para ajudar no desempenho do código PHP do WordPress e dos servidores web, podemos utilizar o Varnish que cuida da camada de cache. Plugins como o W3 Total Cache também ajudam no cache de objetos e consultas dos bancos de dados, assim como a opção de query cache do próprio MySQL.
Crescer horizontalmente
Mas quando uma instalação cresce demais, o banco pode se tornar um problema. Pense comigo: imagine um site em WordPress com milhares de posts e milhões de comentários? Algumas funcionalidades nativas do WordPress utilizam muitas consultas de banco de dados para fazer pesquisas e listagens (a busca é uma dessas funcionalidades). Quando o site tem milhões de linhas no banco de dados para sair pesquisando sem o uso otimizado de índices, a coisa pode complicar e o banco de dados pode ficar sobrecarregado. Grande parte dos responsáveis por sites assim resolvem aumentar verticalmente os servidores, ou seja, colocando servidores melhores, com mais processadores e memória. Mas por que não utilizar um modelo mais distribuído e crescer horizontalmente?
Um exemplo popular, o twitter. O twitter tem milhões e milhões de contas e usuários. Algumas contas são muito mais acessadas e utilizadas que outras, como disseram uma vez sobre a conta de algumas celebridades. Para essas contas, os desenvolvedores do twitter fazem com que as consultas aos bancos de dados sejam feitas em bancos de dados especiais ao invés do geral. Nesse modelo distribuído de banco de dados, a mesma aplicação pode ter vários bancos de dados, cada um para uma parte do sistema. Desse jeito, ao invés de ficar colocando mais processadores e mais memória (que uma hora param de ser suficientes), é só ir colocando mais servidores.
Outro exemplo para melhorar o desempenho das consultas é montar um modelo de replicação MySQL Master-Slave e sair dividindo as consultas de leitura entre os vários servidores slave.
Conheça o HyperDB
O HyperDB é um plugin de WordPress feito pelo pessoal da Automattic (sim, os mesmos criadores do próprio WordPress) e é utilizado como base para o funcionamento do WordPress.com, que contém centenas de milhares de blogs funcionando. Só por esses fatos a gente já começa a considerar um plugin sério e útil.
O HyperDB basicamente substitui as funções principais de banco de dados do WordPress (wpdb), colocando as suas funcionalidades no lugar. Com isso, você consegue:
- Servidores separados de escrita e leitura
- Prioridades sobre onde escrever/ler mais e menos
- Diferentes tabelas para diferentes bancos de dados e domínios
- Usar um outro banco de dados quando um falha (failover)
- Estatísticas de uso
E com todas essas funcionalidades, vem a pergunta: Devo usar o HyperDB?
Se você está lendo isso, provavelmente é porque se interessa pelo assunto e está procurando otimizar o seu WordPress. O uso do HyperDB vai adicionar um pouco de complexidade à sua instalação, mas vale muito à pena! Como esse é um plugin bem voltado ao backend e que substitui as funções normais de banco de dados transparentemente, você provavelmente não vai ter problemas com outros plugins também. Nada te impede usá-lo para instalações simples.
Mas se tens alguns desses problemas seguintes, comece a usar o mais rápido possível:
- Espaço do banco de dados muito grande (comum em instalações multi-site/network);
- Carga do banco de dados alta, inclusive com entradas frequentes de slow queries;
- Servidores espalhados em diferentes regiões;
- Necessidade de site sempre no ar, mesmo quando o banco de dados cai.
Instalação
Como o plugin mexe muito com o backend, sua instalação é um pouco diferente da maioria dos plugins do WordPress, mas é fácil. A diferença é que você vai ter que configurar o plugin primeiro, para depois instalá-lo.
- Após baixar o plugin (neste tutorial, usei a versão 1.1), descompacte-o em qualquer lugar da sua máquina ou servidor;
- Configure o arquivo db-config.php (à frente iremos entrar em detalhes).
- Quando a configuração estiver pronta, coloque na raíz do WordPress (junto com o wp-config.php). Você pode também colocar em qualquer lugar no servidor, se configurar a constante DB_CONFIG_FILE no wp-config.php apontando para a localização. Pra simplificar nossa vida, na raiz está ótimo :)
- Coloque o arquivo db.php no diretório wp-content. Só de colocar o arquivo lá, o plugin é ativado e vai ler as configurações do db-config.php.
No passado, o WordPress suportava o uso de múltiplos bancos de dados através da constante WP_USE_MULTIPLE_DB. Com o HyperDB essa variável não funciona mais e será completamente ignorada.
Configuração
E vamos lá pra configuração do db.php! O arquivo já vem configurado para usar os mesmos bancos de dados do wp-config.php, e também tem algumas variáveis já definidas. No próprio arquivo há muitas instruções, documentação e exemplos de uso (em inglês).
Vamos começar com o padrão, que de cara já adiciona o recurso de reconectar ao banco de dados caso a conexão falhe. Olhe o código:
/** * This is the most basic way to add a server to HyperDB using only the * required parameters: host, user, password, name. * This adds the DB defined in wp-config.php as a read/write server for * the 'global' dataset. (Every table is in 'global' by default.) */ $wpdb->add_database(array( 'host' => DB_HOST, // If port is other than 3306, use host:port. 'user' => DB_USER, 'password' => DB_PASSWORD, 'name' => DB_NAME, )); /** * This adds the same server again, only this time it is configured as a slave. * The last three parameters are set to the defaults but are shown for clarity. */ $wpdb->add_database(array( 'host' => DB_HOST, // If port is other than 3306, use host:port. 'user' => DB_USER, 'password' => DB_PASSWORD, 'name' => DB_NAME, 'write' => 0, 'read' => 1, 'dataset' => 'global', 'timeout' => 0.2, ));
Como podemos ver, o HyperDB extendeu a classe WPDB e adicionou métodos novos, um deles sendo o add_database. Esse método adiciona um servidor de banco de dados para o plugin usar. Nessa configuração padrão temos:
- As linhas 7-12 definem o servidor de banco principal, usando as mesmas configurações do wp-config.php. Só de ter essas linhas, o WordPress já começa a funcionar com o HyperDB.
- As linhas 18-27 adicionam o mesmo servidor, só que agora como slave (apenas leitura). Note que o atributo write está 0 e isso quer dizer que para este servidor, nenhum comando de escrita/atualização/remoção vai ser mandado para esse servidor de banco de dados.
A configuração padrão configura o mesmo servidor duas vezes justamente para usar suas funcionalidades de distribuição. Não há problemas nisso.
Toda vez que você salvar o arquivo, todas as configurações já estão feitas e funcionando, não precisa recarregar nada.
Configuração Master-Slave
Considerando que:
- O banco de dados master é db-master.devin.com.br
- Há um banco de dados slave em db-slave.devin.com.br
Podemos usar o mesmo exemplo de antes, assim:
$wpdb->add_database(array( 'host' => 'db-master.devin.com.br', 'user' => 'usuario_master', 'password' => 'senha_master', 'name' => 'wordpress', )); $wpdb->add_database(array( 'host' => 'db-slave.devin.com.br', 'user' => 'usuario_slave', 'password' => 'senha_slave', 'name' => 'wordpress', 'write' => 0, ));
Certifique-se de que o usuário usuario_slave só tenha permissões somente-leitura no banco de dados. Isso pode ser feito assim no MySQL do db-slave, ao criar o usuário:
GRANT SELECT ON wordpress.* TO usuario_slave@'%' IDENTIFIED BY 'minhasenhasecreta';
Pronto! Suas consultas SQL agora estão sendo balanceadas e a carga do banco de dados está dividida! :)
Configuração Master-Slave com prioridades
Agora vamos supor que eu adicione mais 3 servidores slaves, sendo que 2 deles estão em outro datacenter/país e por isso eu quero usar eles menos que os principais. Para isso eu posso diminuir a prioridade de leitura alterando o atributo read, assim ele só vai tentar usar os servidores externos caso os principais falhem. Queremos também que o master seja usado apenas para escrever.
Considerando que:
- O banco de dados master é db-master.devin.com.br
- Há um banco de dados slave em db-slave-local1.devin.com.br
- Há outro slave local em db-slave-local2.devin.com.br
- Há outro slave em um outro país: db-slave-ext1.devin.com.br
- Há outro slave em um outro país: db-slave-ext2.devin.com.br
A configuração ficaria:
// master apenas para escrever $wpdb->add_database(array( 'host' => 'db-master.devin.com.br', 'user' => 'usuario_master', 'password' => 'senha_master', 'name' => 'wordpress', 'write' => 1, 'read' => 0, )); // primeiro slave local $wpdb->add_database(array( 'host' => 'db-slave-local1.devin.com.br', 'user' => 'usuario_slave', 'password' => 'senha_slave', 'name' => 'wordpress', 'write' => 0, 'read' => 1, )); // segundo slave local $wpdb->add_database(array( 'host' => 'db-slave-local1.devin.com.br', 'user' => 'usuario_slave', 'password' => 'senha_slave', 'name' => 'wordpress', 'write' => 0, 'read' => 1, )); // primeiro slave externo, so usamos caso os locais falhem $wpdb->add_database(array( 'host' => 'db-slave-ext1.devin.com.br', 'user' => 'usuario_slave', 'password' => 'senha_slave', 'name' => 'wordpress', 'write' => 0, 'read' => 2, )); // segundo slave externo, so usamos caso os locais falhem $wpdb->add_database(array( 'host' => 'db-slave-ext2.devin.com.br', 'user' => 'usuario_slave', 'password' => 'senha_slave', 'name' => 'wordpress', 'write' => 0, 'read' => 2, ));
Podemos ver que os atributos read e write mudam de um servidor para o outro. Quando o atributo é 0 (zero), é porque não haverá o tipo de ação. De 1 em diante, quanto maior for o número, menor vai ser a prioridade. Nesse nosso caso:
- Servidor master só vai ter escrita e nada mais. (write=1, read=0);
- Servidor slave local 1 não vai ter escrita, mas vai ter leitura com prioridade máxima (write=0, read=1);
- O outro servidor, slave local 2, não vai ter escrita e a leitura vai ser balanceada com o o slave local 1, pois também está com prioridade máxima (write=0, read=1);
- O servidor slave externo 1 não vai ter escrita. Sua prioridade é menor que os locais, então só será usado se eles falharem (write=0, read=2);
- O outro servidor, slave externo 2, não vai ter escrita e a leitura vai ser balanceada com o slave externo 1, caso os locais falhem (write=0,read=2).
Eu poderia colocar o servidor master para ler com prioridade 3, assim se todos os outros servidores slave falharem, as leituras são feitas também do master.
E com essa funcionalidade você pode pensar na sua estrutura de rede e servidores e definir sua topologia e alta-disponibilidade. Legal né? Como o código é PHP, você pode usar sua imaginação e programação para definir as prioridades. Por exemplo, de acordo com o IP da máquina, ele pode usar um servidor ou outro (assim você mantém a prioridade local sempre maior).
Particionamento
Podemos também separar as tabelas em diversos bancos de dados. Isso é extremamente útil quando temos uma instalação multisite/network.
Para fazer isso a gente separa cada instância de banco de dados do hyperdb (adiconado via método add_database) e dá um nome, que é o atributo dataset. Então fazemos uma função callback que retorna qual dataset usar de acordo com qualquer coisa que você programar em PHP.
O arquivo db-config.php já vem com um exemplo muito legal: em uma instalação multisite/network, separar as tabelas globais em um banco de dados, e as tabelas dos sites em outro banco de dados. Isso quer dizer:
- Toda tabela que o nome começar com wp_NUMERO_ (o wp_ sendo o prefixo escolhido da sua instalação), ele manda pra um banco de dados.
- O resto das tabelas que não começarem com wp_NUMERO_, ele manda para o banco de dados global.
Eis o exemplo:
$wpdb->add_database(array( 'host' => 'db-global.devin.com.br', 'user' => 'wordpress', 'password' => 'senha_wordpress', 'name' => 'wordpress', )); $wpdb->add_database(array( 'host' => 'db-sites.devin.com.br', 'user' => 'wordpress-sites', 'password' => 'senha_wordpress', 'name' => 'wordpress-sites', 'dataset' => 'blog', )); $wpdb->add_callback('my_db_callback'); function my_db_callback($query, $wpdb) { // Multisite blog tables are "{$base_prefix}{$blog_id}_*" if ( preg_match("/^{$wpdb->base_prefix}\d+_/i", $wpdb->table) ) return 'blog'; }
- As linhas 1-14 adicionam os dois servidores de banco de dados normalmente. O segundo banco de dados tem o dataset blog (ao invés do padrão chamado global). Usaremos essa nomeação de dataset depois.
- A linha 16 adiciona a função de callback my_db_callback, que significa que toda vez que o sistema for usar o banco de dados, executará essa função.
- As linhas 17-21 testam via expressão regular se a tabela tem o prefixo wp_NUMERO_ e caso tenham, retorna a string blog. O HyperDB recebe então esse retorno e sabe que deve usar a conexão com dataset ‘blog’, que no exemplo é o segundo servidor: db-sites.devin.com.br.
Nessa função de callback, você pode fazer o que quiser para escolher qual banco de dados usar. O exemplo citado anteriormente, de separar um banco de dados específico para um site de grande movimento, pode ser feito facilmente. Ficaria assim:
$wpdb->add_database(array( 'host' => 'db-global.devin.com.br', 'user' => 'wordpress', 'password' => 'senha_wordpress', 'name' => 'wordpress', )); $wpdb->add_database(array( 'host' => 'db-dedicado1.devin.com.br', 'user' => 'ironmaiden', 'password' => 'flyoficarus', 'name' => 'wordpress', 'dataset' => 'ironmaiden', )); $wpdb->add_database(array( 'host' => 'db-dedicado2.devin.com.br', 'user' => 'blindguardian', 'password' => 'nightfall', 'name' => 'wordpress', 'dataset' => 'blindguardian', )); $wpdb->add_callback('separacao_db'); function separacao_db($query, $wpdb) { if ( preg_match("/^{$wpdb->base_prefix}666_/i", $wpdb->table) ) return 'ironmaiden' if ( preg_match("/^{$wpdb->base_prefix}42+_/i", $wpdb->table) ) return 'blindaguardian'; }
No exemplo, se o ID do site for 666, e por isso as tabelas tiverem o prefixo wp_666_, ele vai usar sempre o banco de dados do Iron Maiden (dataset=ironmaiden). Quando o ID do site for 42, e por isso as tabelas tiverem o prefixo wp_42_, ele usará o banco de dados do Blind Guardian (dataset=blindguardian). Pra todo o resto ele usa o banco de dados global, o db-global.devin.com.br. Simples né?
Outros exemplos que você pode fazer:
- Separar de acordo com o nome do blog
- Separar se o blog tiver o ID par ou ímpar :P
- Separar por URL ou qualquer informação que do servidor Web
- E o que você puder pensar…
Tradução das Perguntas Frequentemente Feitas (FAQ) do plugin
O que posso fazer com o HyperDB que não posso com o WPDB?
O WordPress.com, a instalação mais complexa de HyperDB, gerencia milhões de tabelas espalhadas em milhares de bancos de dados. Uma lógica dinâmica de configuração permite ao HyperDB computar a localização de tabelas perguntando à um banco de dados central. Scripts personalizados constantemente balanceiam os recursos dos bancos de dados migrando tabelas e atualizando suas localizações no servidor de banco de dados central.
Abuse de sua imaginação. Você pode criar configurações dinâmicas usando cache persistente para obter inteligência sobre o estado de sua rede. A única constante é o nome do arquivo de configuração. O resto, como costumam dizer, é PHP.
Como o HyperDB suporta replicação?
O HyperDB não fornece serviços de replicação. Isto é feito ao configurar os servidores MySQL para replicação. O HyperDB pode ser configurado para usar estes servidores apropriadamente, como por exemplo, conectando em servidores masters para fazer consultas de escrita.
Nota do tradutor: temos um artigo sobre como fazer replicação MySQL aqui no site.
Como o HyperDB suporta balanceamento de carga (load balancing)?
O HyperDB seleciona aleatoriamente conexões de banco de dados de grupos de prioridade que você configurar. As conexões com mais prioridade são feitas primeiro. Assim você consegue otimizar a configuração de acordo com sua topologia, capacidade de hardware e qualquer outro esquema que você inventar.
Como o HyperDB suporta failover?
O Failover é descrito em como o HyperDB lida com falhas de conexão. Quando o HyperDB falha ao tentar se conectar com um banco de dados, ele tenta se conectar em outro banco de dados que tem os mesmos dados. Se uma replicação não foi configurada, o HyperDB tenta reconectar-se algumas vezes antes de desistir.
Como o HyperDB suporta particionamento?
O HyperDB permite que tabelas sejam colocadas em bancos de dados arbitrários. Ele pode usar chamadas de retorno que você escrever para computar um banco de dados apropriado para uma consulta específica. Assim você pode particionar os dados de seu site de acordo com seu próprio esquema e configurar o HyperDB de acordo.
Há alguma vantagem em usar o HyperDB com apenas um servidor de banco de dados?
Não há nenhuma vantagem mensurada. O HyperDB ao menos tenta se conectar novamente antes de desistir, então isto pode ajudar em casos onde o servidor Web momentãneamente não consiga se conectar ao servidor de banco de dados.
Uma coisa que o HyperDB difere do WPDB é que o HyperDB não tenta se conectar ao banco de dados até que uma consulta é feita. Assim um site com um suficientemente agressivo cache persistente pode continuar como somente-leitura mesmo quando o banco de dados se torna indisponível.
E se todos os servidores de banco de dados de um conjunto de dados ficar fora?
Como o HyperDB tenta se conectar apenas quando uma consulta é feita, sua instalação de WordPress não vai matar todo o site com um erro de banco de dados. Ela vai permitir que o código decida o que fazer em seguida, logo após a consulta falhar. Se você quer fazer algo diferente, como configurar uma página de erro personalizada, você pode definir a chamda de retorno db_connection_error no seu db-config.php.
Referências
- Página de Download do plugin
- Comentários do arquivo db-config.php