Acelere o carregamento de páginas: técnicas incomuns de aumento de velocidade de sites – Parte 02
Artigo traduzido com permissão do autor, Manuel Lemos: Accelerate Page Accesses Throttling Background Tasks: Unusual Site Speedup Techniques – Part 2
***
Introdução
Mais do que geralmente, a velocidade de umsite é o principal fator que determina o seu sucesso. Quando eu falo de
velocidade de um site, estou falando sobre a percepção do usuário em determinar
se o carregamento das páginas do site está sendo rápido ou não.
No artigo anterior, falei sobre um fatorimportante que muitas vezes afeta bastante os efeitos da percepção do usuário
sobre a velocidade de um site, que é a presença de conteúdo de sites externos
que geram lentidão no carregamento das páginas, como publicidade e widgets.
No artigo em questão, apresentei umatécnica que estou usando para que o conteúdo externo não tenha efeito sobre a
percepção do usuário na velocidade do site. Neste artigo, irei falar sobre
outro fator que também pode afetar a percepção da velocidade do site, mas desta
vez relacionado a aspectos no ambiente do lado do servidor.
Processos background do lado do servidor
Ser capaz de servir disponibilizar web rápido osuficiente não depende somente do servidor web e do código da linguagem que
gera as páginas. Em um servidor, existem muitos processos sendo
executados em paralelo. Às vezes, esses processos não têm nada a ver com entregar páginas web. Vamos chamar esses processos de processos background.
Muitas vezes, esses processos backgroundexcedem o número de processos do servidor web. Às vezes, processos background consomem mais CPU e outros recursos da máquina do que os processos
do servidor web.
Assim, esses processos podem executartarefas que são importantes para o site, mas que não influenciam na velocidade
da entrega das páginas web. Exemplos dessas tarefas são: entrega de
newsletters, processos de back up, percorrer as páginas do site para atualizar
o mecanismo de busca, e outras tarefas de manutenção que devem ser executadas
regularmente no site.
O problema é que todos esses processos estãocompetindo pelos núcleos disponíveis da CPU e muitas vezes afetam a velocidade da
geração e de entrega das páginas web. Portanto,
é muito importante garantir que esses outros processos não afetem
significativamente a performance dos principais processos do servidor. Caso
contrário, a percepção do usuário da velocidade será amplamente afetada.
Limitação da CPU (CPU Throttling) em
processos background
Uma solução óbvia para evitar que osprocessos background afetem os processos do servidor web é comprar um ou
mais servidores e dividir essas tarefas pesadas em máquinas diferentes. Isso
poderia resolver o problema, mas ficaria mais dispendioso manter o site.
Na verdade, mover os processos backgroundpesados em máquinas diferentes é um dos caminhos a ser seguido se esses processos background estiverem consumindo
muita CPU e outros recursos durante todo o dia. No entanto, esse geralmente não
é o caso.
Por exemplo, se você tiver que entregar umanewsletter para os usuários registrados em seu site, a entrega para todos eles
pode levar de minutos a horas. O script que executa a entrega da newsletter
pode requerer muito trabalho da CPU, até que seja finalizado. Por outro lado, a
entrega da newsletter pode não ser urgente. Portanto, se a atrasarmos de vez em
quando, isso não será um problema. Na pior das hipóteses, os usuários irão
receber a newsletter um pouco depois.
A técnica para controlar a velocidade de umprograma ao desacelerá-lo quando necessário é chamada de Limitação de CPU (CPU throttling).
Limitação de CPU no PHP
Uma maneira pela qual podemos atrasar aentrega de newsletter ou de qualquer outro processo repetidamente executado no
background é inserir código nos loops repetitivos que pausem o script por um tempo.
No PHP, isso pode ser feito, por exemplo, com a função sleep (). Essa função
simplesmente espera por alguns segundos, pausando efetivamente o script PHP.
Durante esse tempo, o SO irá dar para a CPU outros processos.
Agora que sabemos como pausar um script PHP,a única questão que falta respondermos é quando devemos fazer isso de modo que
os scripts dos processos background não consumam tanta CPU a ponto de afetar
os principais processos do servidor web. Se você não sabe a resposta, esse é o
segredo que este artigo irá revelar.
Primeiramente, você precisa compreender muitobem como os sistemas operacionais funcionam. A maioria dos sistemas
operacionais modernos implementa o que é chamado de mutitarefas. Isso
significa que quando existe um certo número de processos sendo executados no
sistema, todos eles precisam da CPU para executar seu código, e então todos
devem esperar em fila.
Mesmo se a sua máquina tiver CPUs múltilpasou múltiplos núcleos de CPU, geralmente existem mais processos esperando a CPU
para serem executados do que CPUs disponíveis. Portanto, o SO tem que
rotacionar os núcleos disponíveis da CPU entre todos os processos que estão sendo
executados. Um SO multitarefas faz isso alocando um período de tempo para cada
processo. Quando esse período termina, o SO suspende aquele processo e aloca
outro período de tempo para o próximo processo que precisa da CPU.
Em sistemas operacionais como o Linux, existeuma maneira simples de determinar o número de processos esperando pela CPU. O
Linux disponibiliza as estatísticas dos processos de carregamento através do
arquivo /proc/loadavg. Na verdade, esse não é um arquivo real armazenado no disco. É
apenas uma variável do sistema no sistema de arquivos virtuais /proc.
Você consegue acessá-lo usando chamados regulares de acesso a arquivos. No PHP,
você pode utilizar open() e fread() ou chamadas file_get_contents() para acessá-lo.
A classe system monitor
Então a solução para implementar a limitaçãode CPU nos scripts PHP no Linux é bastante simples. Nós apenas lemos o arquivo loadavg
regularmente. Se ele disser que o carregamento atual está muito alto, nós
apenas chamamos a função sleep () para esperar um tempo. Então checamos o
arquivo de carregamento mais uma vez. Se o carregamento da CPU ainda estiver
muito alto, esperamos mais um tempo até que o carregamento esteja baixo o
suficiente.
Para simplificar esseprocesso, eu desenvolvi há muitos anos uma classe chamada system monitor que
faz toda a checagem do carregamento da CPU para você. Eu publiquei a
classe system monitor no site PHPClasses, para
que todos possam se beneficiar dela.
Como de praxe, ela está disponível através dalicença BSD. Então você pode usá-la, modificá-la, desde que seus direitos
originais de copyright sejam mantidos. Peço apenas que preserve quaisquer URLs
que você encontre no arquivo da classe, de modo que outras pessoas consigam
saber onde encontrar as últimas atualizações da mesma.
A maneira de usar essa classe para limitar osseus scripts é muito simples. Existe uma function call chamada Throttle. Essa
função retorna um valor que diz se o sistema está sob um alto carregamento. Ela
considera um limite de carregamento que você pode definir ao configurar a
variável da classe cpu_load_limit.
Se a função Throttle disserque o carregamento da CPU está muito alto, podemos chamar a função sleep () e
repetir o processo até que o carregamento da CPU se torne baixo novamente.
Portanto, o único fatorque devemos decidir agora é o limite de carregamento que será a referência para
que o carregamento da CPU seja considerado alto e baixo o suficiente.
Eu uso o seguintecritério: pego o número de núcleos da CPU disponível na máquina e o multiplico
por 2. Portanto, se o seu servidor tiver 1 CPU com 2 núcleos, o limite de
carregamento da CPU deve ser definido por 2 x 2 = 4. Isso significa que se houver 2 ou
mais processos esperando por um núcleo da CPU, os processos background devem
ficar em espera por um tempo.
Outros fatores que você deve configurar sãoos períodos que determinam por quanto tempo seus processos devem esperar quando
o carregamento está muito ato e com qual frequência você deve checar o
carregamento da CPU para determinar se o sistema está muito pesado ou leve.
O arquivo loadavg fornece 3 médias deestatísticas de CPUs: uma para o último minuto e outras duas para os últimos 5
e 15 minutos. Por padrão, a classe system monitor usa aquela para o último minuto,
mas você pode mudar isso.
Com a média docarregamento da CPU no último minuto, parece razoável checar a variável de
carregamento da CPU pelo menos uma vez a cada 60 segundos. Você pode checá-lo
mais de uma vez se quiser reagir mais rapidamente quando o carregamento do
sistema surgir. Também parece razoável esperar por 60 segundos quando o
carregamento do sistema estiver muito alto, mas você também pode esperar menos
tempo se não quiser atrasar muito os processos background.
O loop das suas tarefasintensivas na CPU deve se parecer com isto:
$monitor = new system_monitor_class;
$monitor->cpu_load_limit = 4;
/* Work loop starts here */
for($i = $start; $i < $end; ++$i)
{
/* do some heavy work here, say
deliver a bunch of newsletters messages */
for(;;)
{
if(!$monitor->Throttle($cause))
die('Fatal error: '.$monitor->error);
if($cause == THROTTLE_CAUSE_NONE)
break;
sleep(60);
}
}
Limitando programas externos
Limitar os processos de scripts PHP se tornoufácil com as instruções acima. No entanto, você só é capaz de aplicar esse
método para limitar scripts sobre os quais você tem o controle do código. Se você
confiar em um programa externo que executa uma tarefa pesada mas você não
controla esse código, muitas vezes porque ele foi escrito em outra linguagem,
essa solução não irá limitar esse programa externo.
O site do PHPClasses começou com esse problema,porque o mecanismo de busca interno se apoiava em um programa externo chamado
htDig. É um programa open source bastante antigo, escrito em C++. Ele é
usado para indexar o site todo. Esse processo demora muito, porque existem mais
de 50 mil páginas para analisar.
Talvez seria melhor desenvolver um mecanismode busca para substituí-lo em PHP, pois então seria mais fácil limitar o código
a ser analisado. Infelizmente, essa não é uma solução viável no momento, pois
iria demorar muito para refazer a integração do htDig com os recursos de busca relacionados ao
PHPClasses.
Felizmente, eu consegui elaborar uma abordagemdiferente. No Linux, existe uma maneira muito fácil de colocar programas em
execução em espera por um tempo, desde que você utilize o mesmo SO que você
iniciou ou programa ou que “você” seja o usuário raiz.
A ideia consiste em enviar um sinal para oprocesso background em execução chamado SIGSTOP. Isso faz com que os
processos fiquem em espera até que eles recebam outro sinal chamado SIGCONT. No PHP,
você pode enviar sinais para processos em execução usando a função
proc_terminate(). Apesar do nome da função, ela não necessariamente finaliza o
programa. Apenas especifique o sinal certo a ser enviado. Se você quiser
começar e controlar a execução de um processo em PHP e deixar o programa
rodando no background, você pode usar a função proc_open().
Eu melhorei a classesystem monitor para usar essas funções e para automatizar todo o processo de
execução de um programa externo no PHP e limitar sua execução. Tudo que você
precisa é chamar a função ThrottleExecute passando a linha de comando do
programa externo que você quer executar. Você pode passar parâmetros adicionais
para controlar outros aspectos como capturar o output do comando.
O script de limitação para seu programa externo devese parecer com isto:
$monitor = new system_monitor_class;
$monitor->cpu_load_limit = 4;
$command = 'path_to_external_command command parameters';
$parameters = array(
'Command' => $command,
'CaptureOutput' => 'string',
);
if(!$monitor->ThrottleExecute($parameters))
die('Fatal error: '.$monitor->error);
if($parameters['Status'] != 0)
die('the command failed for some reason');
do_something_with_output($parameters['Output']);
Outros problemas de carregamento
excessivo do servidor
Se você acessou o site do PHPClasses no período em que a autenticação OpenID foi implementada, vocêdeve ter experenciado dificuldade para acessá-lo em alguns períodos do dia. O carregamento
excessivo foi uma das consequências de se ter muitos processos background
rodando ao mesmo tempo sem serem limitados.
Felizmente, a classe system monitor foiutilizada para resolver a maioria dos problemas, mas isso levou tempo, pois não
é fácil saber quais partes dos scripts rodando no background devem ser
limitadas. As coisas estão muito melhores agora, mas pode haver partes do dia
em que o carregamento do servidor fique muito alto.
Outro problema que estava causando o excessode carregamento do servidor, mas que não estava relacionado ao uso excessivo da
CPU, foi um erro de configuração. Eu gostaria de comentar sobre ele porque ele
foi tão sutil que eu precisei de semanas para descobrir o que estava errado.
Então isso pode acontecer com você também.
Tudo começou quando eu abri o site da autenticação OpenID e então o site JSClasses. Então eu pensei queseria uma boa ideia, por motivos de segurança, usar diferentes usuários de
bancos de dados para cada um dos sites, apesar de que todos estavam rodando no
mesmo servidor.
Eu pensei que se algum cracker invadisse umsite e manipulasse seu banco de dados, ele não seria capaz de acessar os bancos
de dados do outro site, pois ele não iria permitir que o mesmo usuário do banco
de dados o acessasse. A ideia parece inteligente em termos de segurança, mas as
consequências foram catastróficas em termos de uso de recursos do servidor.
O problema é que esses sites usam conexõespersistentes de bancos de dados para acessar o servidor do banco de dados.
Conexões persistentes são boas para evitar um grande tempo de conexão. No
entanto, se você utilizar usuários diferentes para acessar o mesmo servidor de
banco de dados, o PHP estabelece diferentes conexões para cada usuário do banco.
Portanto, isso fez com que o servidor web executasseo PHP para estabelecer 3 servidores de bancos de dados a mais do que o
necessário se ele estivesse usando o mesmo usuário do banco de dados. A
consequência foi que todas as conexões do banco de dados estavam derrubando o servidor
de banco de dados durante as horas de maior acesso do site. O servidor de banco
de dados só se recuperava depois de alguns minutos.
Eu estava notando muitas conexões de bancosde dados, mas não conseguia entender por quê, uma vez que o servidor web só
tinha 1/3 dos processos sendo executados. Infelizmente, somente depois de
algumas semanas eu percebi que cada processo do servidor web estava
estabelecendo diferentes conexões para cada usuário do banco de dados.
Eu consertei o problema rapidamente e deixeitodos os sites utilizarem o mesmo usuário de banco de dados, mas foi muito
frustrante perceber o problema somente várias semanas depois que ele tinha
começado. Uma consequência ruim para o conteúdo do site, além da frustração do
usuário, é que o número de pacotes inseridos caiu drasticamente. Os
contribuidores do site estavam desistindo de inserir seus pacotes no site.
Agora a taxa de contribuição está quase de volta nos seus níveis anteriores,
mas essa foi definitivamente uma lição aprendida da maneira mais difícil.
Acredito que a maioria das empresas dehospedagem PHP tem problemas similares, porque consumidores utilizando o mesmo
servidor web têm que usar usuários de bancos de dados diferentes. Nesse caso,
eles não podem permitir que seus consumidores usem o mesmo usuário de banco de
dados, pois dessa maneira eles seriam capazes de roubar os dados de cada banco
de dados.
Normalmente eles incentivam seus clientes afazer o upgrade de seus planos para VPS ou servidor dedicado quando eles
começam a utilizar muitos recursos do servidor, mas eu me pergunto se uma
solução melhor não existiria nos níveis de configuração do PHP ou do servidor. Talvez encerrando automaticamente
conexões de bancos de dados que não foram usadas depois de um tempo, mesmo se
elas tiverem sido iniciadas como conexões persistentes. Eu simplesmente não
consegui descobrir como fazer isso para MySQL. Mas acredito que existe algo
parecido para Oracle na configuração PHP.
Conclusão
Bom, esse foi mais um artigo sobre técnicasnão usuais para aceleração de sites. Se você estava tendo problemas parecidos
de carregamento nos seus servidores web,
espero que a solução de limitação te ajude a balancear e colocar mais processos
trabalhando nas suas máquinas. Ela certamente ajudou o site PHPClasses.
A solução de checagem do carregamentofunciona bem no Linux, porque é o que o PHPClasses e muitos outros sites usam
nos seus servidores. Portanto, não me dei ao trabalho de encontrar outra solução
que também funcione no Windows ou em outro sistema operacional. Se você souber como checar a média de carregamento da CPU ou suspender e
resumir os processos em outros sistemas usando PHP, me informe. Talvez eu possa
adaptar a classe system monitor para deixá-la útil para mais desenvolvedores
PHP.
?
Texto original eminglês de Manuel Lemos, disponível em http://www.phpclasses.org/blog/post/132-Accelerate-Page-Accesses-Throttling-Background-Tasks-Unusual-Site-Speedup-Techniques-Part-2.html







